How could I change the audio output device of node module "say" - node.js

I'm making a program that will output text to speech, the user will need to be able to change the output device (for example to virtual audio cable). At the moment I'm using https://www.npmjs.com/package/say to produce the speech
e.g.
const say = require("say");
say.speak("Hello World");
but I've no clue on how I could go about choosing the audio output.
What I've found so far has been pretty useless, I'm guessing largely because I don't know the proper terminology to search for answers, regardless this is it:
I first found out about navigator.MediaDevices, then I found how I could make an audio element and change the audio device of that element via setSinkId, then I realized these things are probably(?) irrelevant since the say module seems to play sounds using powershell commands. I've even gone as far as trying to change powershell's output device in app volume device preferences(img), but that seems to do nothing.
I'm pretty much stumped right now, so I'd appreciate any help please. I'm not set on using Say as a module, it just seemed easy to use at first.
Edit:
A workaround I've settled with is making my own TTS class and using SpVoice.
I have something similar to this:
const childProcess = require('child_process');
class TTS {
constructor(channel, speed) {
this.speed = speed;
this.channel = channel;
this.baseCommand = "$speak = New-Object -ComObject SAPI.SPVoice;"
}
speak(text){
var command = this.baseCommand +
`$speak.AudioOutput = foreach ($o in $speak.GetAudioOutputs()) {if ($o.getDescription() -eq '${this.channel}') {$o; break;}}; `
+ "$speak.Speak([Console]::In.ReadToEnd());"
this.child = childProcess.spawn('powershell', [command], {shell: true})
this.child.stdin.setEncoding('ascii')
this.child.stdin.end(text);
this.child.addListener('exit', (code, signal) => {
if (code === null || signal !== null) {
console.log(new Error(`error [code: ${code}] [signal: ${signal}]`))
}
this.child = null
})
}
}
Then I can pass in an audio channel like
tts = new TTS("CABLE Input (VB-Audio Virtual Cable)", 0);
tts.speak("Hello there");
and it will output TTS in my desired channel

Some of the browsers support the built in speechSynthesis API.
Save the following code in 'test.html' file and open the file in chrome web browser for testing the speech api.
<script>
//---------------- SpeechAPI Start ------------------------
function speak(message) {
try {
var utterance= new SpeechSynthesisUtterance("");
utterance.lang='en-GB';
utterance.text=message;
window.speechSynthesis.speak(utterance);
}catch(e){
console.log('Exception in speak : ' + e)
}
}
//---------------- SpeechAPI End ------------------------
</script>
<button onclick="speak('Hello, how are you doing?');">Press to speak</button>
Is this what you are looking for ?

Related

How to manually stop getDisplayMedia stream to end screen capture?

I'm interested in getting a screenshot from the user and I'm using the getDisplayMedia API to capture the user's screen:
const constraints = { video: true, audio: false };
if (navigator.mediaDevices["getDisplayMedia"]) {
navigator.mediaDevices["getDisplayMedia"](constraints).then(startStream).catch(error);
} else {
navigator.getDisplayMedia(constraints).then(startStream).catch(error);
}
When executed, the browser prompts the user if they want to share their display. After the user accepts the prompt, the provided callback receives a MediaStream. For visualization, I'm binding it directly to a element:
const startStream = (stream: MediaStream) => {
this.video.nativeElement.srcObject = stream;
};
This is simple and very effective so far. Nevertheless, I'm only interested in a single frame and I'd therefore like to manually stop the stream as soon as I've processed it.
What I tried is to remove the video element from the DOM, but Chrome keeps displaying a message that the screen is currently captured. So this only affected the video element but not the stream itself:
I've looked at the Screen Capture API article on MDN but couldn't find any hints on how to stop the stream.
How do I end the stream properly so that the prompt stops as well?
Rather than stopping the stream itself, you can stop its tracks.
Iterate over the tracks using the getTracks method and call stop() on each of them:
stream.getTracks()
.forEach(track => track.stop())
As soon as all tracks are stopped, Chrome's capturing prompt disappears as well.
start screen capture sample code:
async function startCapture() {
logElem.innerHTML = "";
try {
videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
dumpOptionsInfo();
} catch(err) {
console.error("Error: " + err);
}
}
stop screen capture sample code:
function stopCapture(evt) {
let tracks = videoElem.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoElem.srcObject = null;
}
more info: MDN - Stopping display capture

VIDEOJS: playing a pre-roll add before each playlist item

I'm using the videojs-playlist plugin along with Google's videojs-ima plugin. Everything works swimmingly except I am only getting a preload ad before the first video. I want one before each video in the playlist.
Basic setup is boilerplate, but for reference:
this.player = videojs('currentvideo', { autoplay : true, fluid : true });
this.player.playlist(this.playlist);
this.player.playlist.autoadvance(5);
const skippable_linear = {google's test ad};
const options = {
id: 'currentvideo',
adTagUrl: skippable_linear,
debug : true
};
this.player.ima(
options
);
this.player.ima.requestAds();
I have tried various ways of manually calling ads from inside an 'ended' event handler, such as calling requestAds again:
const _this = this;
this.player.on( 'ended', function(){
/* some other stuff */
_this.player.ima.requestAds();
});
This does play an ad where I want it, but
this breaks playlist's 'autoadvance' setting (next video doesn't start playing when the ad is finished), and
this puts the player into "ad display" mode (scrubber is unavailable, etc).
Is there a simple way to just say, "play an ad now" programmatically? I've tried, without joy, to use all of the seemingly applicable methods exposed by both the ima plugin and the contrib-ads plugin it relies on. I'll admit here that this is the first time I've ever had to deal with videos that run ads, so I'm kind of a noob.
I am trying to do the same thing. Just like you I failed when calling player.ima.requestAds() on events. I dug deeper and the best I could come up with is what I share bellow.
According to the videojs-ima API you have to use the setContentWithAdTag method instead of whatever you are using to switch the player content. In our case it is the player.playlist.next method.
I combined the code found in the videojs-ima examples with the original playlist.next to write my own next.
Then quite brutally I overrode the original plugin method.
Here's the code:
player.playlist(myPlayilst);
player.playlist.autoadvance(2);
player.playlistUi(); //videojs-playlist-ui
player.ima({
id: 'video5',
adTagUrl: 'thy adserver request'
});
//override playlist.next
player.playlist.next = function(){
var nextIndex = 0,
playlist = this.player_.playlist,
list = this.player_.playlist();
//everything below is copied directly from the original `next` (except for the "//load with ad")
// Repeat
if (playlist.repeat_) {
nextIndex = playlist.currentIndex_ + 1;
if (nextIndex > list.length - 1) {
nextIndex = 0;
}
} else {
// Don't go past the end of the playlist.
nextIndex = Math.min(playlist.currentIndex_ + 1, list.length - 1);
}
// Make the change
if (nextIndex !== playlist.currentIndex_) {
//load with ad
this.player_.playlist.currentItem(nextIndex);
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
/////
return list[playlist.currentItem()];
}
}
You will probably need to override other methods that change the current playback, like playlist.previous.
I use videojs-playlist-ui so in my case it was neccessary to change the onclick handler called switchPlaylistItem_. I used some good old brute force to do that like this:
videojs.getComponent('PlaylistMenuItem').prototype.switchPlaylistItem_ = function(e){
this.player_.playlist.currentItem(this.player_.playlist().indexOf(this.item));
this.player_.ima.setContentWithAdTag(
this.player_.playlist.currentItem(),
null,
true);
this.player_.ima.requestAds();
};
PlaylistMenuItem's prototype should be changed before initializing the player.
This solution works, but it feels hacky, so if anyone can come up with something cleaner, please share!
I ended up forking videojs-playlist, and adding the option to override the player.src method. Feel free to use it:
fw-videojs-playlist
Details on how to use it are all in the github readme (including an example with ima.setContentWithAdTag)

Why is phonegap app not making sound on Samsung with Android 4?

I have a phonegap app, which is now in alpha testing of PlayStore.
The sound works on most devices, but not on Samsung Devices with Android 4.x.
I didn't find an issue or similar questions. Did I do something wrong?
Code with https://github.com/goldfire/howler.js/tree/2.0
var sound = new Howl({
src: ['res/raw/warning.mp3']
}).play();
My code with Cordova-plugin-media:
var src = "res/raw/warning.mp3";
// HTML5 Audio
if (typeof Audio != "undefined") {
new Audio(src).play() ;
// Phonegap media
} else if (typeof device != "undefined") {
// Android needs the search path explicitly specified
if (device.platform == 'Android') {
src = '/android_asset/www/' + src;
}
var mediaRes = new Media(src,
function onSuccess() {
// release the media resource once finished playing
mediaRes.release();
},
function onError(e){
alert("error playing sound: " + JSON.stringify(e));
});
mediaRes.play();
} else {
alert("no sound API to play: " + src);
}
Anyone an idea what is the source of the problem?
There could be a couple of things here; ideally, you should wait for the sound be to loaded before trying to play it (Howler tries to handle this itself, but it doesn't always work)
From their examples
var sound = new Howl({
src: ['sound.webm', 'sound.mp3']
});
// Clear listener after first call.
sound.once('load', function(){
sound.play();
});
The second issue may be because it's an older android device, and thus WebAudio support may be sketchy. Try adding html5 into the options
var sound = new Howl({
src: ['sound.webm', 'sound.mp3', 'sound.wav'],
html5 : true
});
The last issue could be because a lot of mobile devices require a user interaction before they can play a sound (think of browser makers not wanting people to browse to websites and having music automatically start playing!). So once the sound has loaded, request the user to touch the screen once, and then play a sound on that touch event. This should 'unlock' the audio from now on. Again, Howler does try to do this, but it's something you may want to look at as well.

Reading data from StreamSocket only works on desktop, not on phone (Universal Windows App)

Having a weird problem with an UWP app I'm trying to create when creating a socket and reading back data from it. Here's the basic scenario:
_commandSocket = new StreamSocket();
await _commandSocket.ConnectAsync(new HostName("192.168.1.1"), "15740", SocketProtectionLevel.PlainSocket);
_commandOutput = _commandSocket.OutputStream;
_commandInput = _commandSocket.InputStream;
await _commandOutput.WriteAsync(initCommandBuffer);
var lengthBytes = new byte[4].AsBuffer();
await input.ReadAsync(lengthBytes, 4, InputStreamOptions.None);
etc..etc...etc...
I'm basically opening a socket and sending an initialization command to my device. The idea is that it'll reply by first sending me the length of the reply as an uint.
This all works perfectly well when I debug/run the app on my desktop, but if I try to run this on my Window 10 Mobile device (Nokia 930) it seems not a single byte is being read (I've tried reading 1 byte instead of 4). Since app development should be 'Universal' and it's all working perfectly on my desktop I have no idea where to start fixing this.
Opening the connection on the mobile device works, I'm also not getting any errors when sending the initialization command, it just keeps blocking on the await ReadAsync command (as if nothing is being sent back).
I don't have any influence on the device that I'm connecting to, but again since this is all working flawlessly on the desktop and the device doesn't know who's asking I'm pretty clueless.
Cannot confirm my answer for sure, becasue I only tested in on an emulator, but I used stream socket listener.
private async void StartServer()
{
int Port = 80;
try
{
//Bound to port 80
await listener.BindServiceNameAsync(Port.ToString());
}
catch (System.Runtime.InteropServices.COMException)
{
//Port 80 for communication is blocked.
}
listener.ConnectionReceived += async (s, e) =>
{
using (IInputStream input = e.Socket.InputStream)
{
var buffer = new Windows.Storage.Streams.Buffer(200);
await input.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.Partial);
//reads incoming stream
string data = Encoding.ASCII.GetString(buffer.ToArray(), 0, (int) buffer.Length);
}
using (IOutputStream output = e.Socket.OutputStream)
{
using (Stream response = output.AsStreamForWrite())
{
response.Write(Encoding.ASCII.GetBytes("Response."), 0, 1);
}
}
};
}

Default Audio Output - Getting Device Changed Notification? (CoreAudio, Mac OS X, AudioHardwareAddPropertyListener)

I am trying to write a listener using the CoreAudio API for when the default audio output is changed (e.g.: a headphone jack is plugged in). I found sample code, although a bit old and using deprecated functions (http://developer.apple.com/mac/library/samplecode/AudioDeviceNotify/Introduction/Intro.html, but it didn't work. Re-wrote the code in the 'correct' way using AudioHardwareAddPropertyListener method, but still it doesn't seem to work. When I plug in a headphone the function that I registered is not triggered. I'm a bit of a loss here... I suspect the problem may lay some where else, but I can't figure out where...
The Listener Registration Code:
OSStatus err = noErr;
AudioObjectPropertyAddress audioDevicesAddress = { kAudioHardwarePropertyDefaultOutputDevice, KAudioObjectPropertyScopeGlobal, KAudioObjectPropertyElementMaster };
err = AudioObjectAddPropertyListener ( KAudioObjectAudioSystemObject, &AudioDevicesAddress, coreaudio_property_listener, NULL);
if (err) trace ("error on AudioObjectAddPropertyListener");
After a search in sourceforge for projects that used the CoreAudio API, I found the rtaudio project, and more importantly these lines:
// This is a largely undocumented but absolutely necessary
// requirement starting with OS-X 10.6. If not called, queries and
// updates to various audio device properties are not handled
// correctly.
CFRunLoopRef theRunLoop = NULL;
AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
if ( result != noErr ) {
errorText_ = "RtApiCore::RtApiCore: error setting run loop property!";
error( RtError::WARNING );
}
After adding this code I didn't even need to register a listener myself.
Try CFRunLoopRun() - it has the same effect. i.e. making sure the event loop that is calling your listener is running.

Resources