So, I have an adaptation of https://github.com/Dirvann/mediasoup-sfu-webrtc-video-rooms working in vanilla JS that I am attempting to adapt to use React. Instead of every user being a broadcaster, in my version, only the room creator is the broadcaster.
I've hit an issue. In the React version, when a viewer navigates to the room, they are not receiving the stream! I have no idea why since they use the same RoomClient class: https://github.com/Dirvann/mediasoup-sfu-webrtc-video-rooms/blob/master/public/RoomClient.js
This line const consumer = await this.consumerTransport.consume({ id, producerId, kind, rtpParameters, codecOptions, }); seems to be causing the problem, since the log following it doesn't get printed. Inside the consume function, my log that says 'hi' is executed, but 'blah' is not.
Here is a screenshot of the client console:
The most important functions are found below. For the entire class, please click the github link above.
async consume(producer_id) {
//let info = await roomInfo()
console.log('consume ', producer_id);
console.log('dddddddddddd', await this.getConsumeStream(producer_id));
this.getConsumeStream(producer_id).then(
function ({ consumer, stream, kind }) {
console.log('blah');
this.consumers.set(consumer.id, consumer);
let elem;
console.log('clg kind === ', kind);
if (kind === 'video') {
console.log('cons vid');
elem = document.createElement('video');
elem.srcObject = stream;
elem.id = consumer.id;
elem.playsinline = false;
elem.autoplay = true;
elem.className = 'vid';
this.remoteVideoEl.appendChild(elem);
} else {
elem = document.createElement('audio');
elem.srcObject = stream;
elem.id = consumer.id;
elem.playsinline = false;
elem.autoplay = true;
this.remoteAudioEl.appendChild(elem);
}
consumer.on(
'trackended',
function () {
this.removeConsumer(consumer.id);
}.bind(this)
);
consumer.on(
'transportclose',
function () {
this.removeConsumer(consumer.id);
}.bind(this)
);
}.bind(this)
);
}
async getConsumeStream(producerId) {
const { rtpCapabilities } = this.device;
console.log('rtpcaps ', rtpCapabilities);
const data = await this.socketRequest('consume', {
rtpCapabilities,
consumerTransportId: this.consumerTransport.id, // might be
producerId,
}).then((data) => {
console.log('daaatttaaa', data);
return data;
});
const { id, kind, rtpParameters } = data;
console.log('data === ', data);
let codecOptions = {};
console.log('aaaaaaaaaaaaaa', this.consumerTransport.consume);
const consumer = await this.consumerTransport
.consume({
id,
producerId,
kind,
rtpParameters,
codecOptions,
})
.then((result) => {
console.log('bbbbbbb', result);
return result;
});
console.log('consumer === ', consumer);
const stream = new MediaStream();
console.log('stream === ', stream);
stream.addTrack(consumer.track);
console.log('kind ', kind);
return {
consumer,
stream,
kind,
};
}
Many thanks for your time
---update---
This line never resolves: const consumer = await this.consumerTransport.consume({ id, producerId, kind, rtpParameters, codecOptions, })
It ends up executing some complex-looking functions from packages. In fact, It seems to get stuck in an infinite loop in the package called sdp-transform, after executing a few lines in mediasoup-client.
I don't even understand how chrome debugger's working. Because if I put a breakpoint on that line where ...consume(... is called, and click 'step', it parses this line: let answer = await this._pc.createAnswer() ... Which is part of a function called receive, which wasn't called by the breakpoint line... Please, somebody help.
You said something interesting there. 'hi' gets logged and 'blah' not. There isn't much code in between them. This should make us wonder how it is even possible.
Maybe create the same just without any distractions around.
Promise.resolve()
.then(console.log('hi'))
.then(function() {
console.log('blah');
})
What would that do? and why?
.then is a function that expects to be given a function (to be called when the promise resolves...)
What does console.log('hi') return? And when is it executed?
You did not want to execute the log when creating the promise chain and you also want to maybe return the result again since you want to work with the value.
So my guess would be. Changing it to:
.then(result => {
console.log('hi')
return result
})
brings you at least a step closer.
Related
I am trying to create a website that scrapes a news website and reads it.
For some reason, whenever I am trying to read the actual info of the article, it reads part of it and stops after a few seconds.
important to point out :
I am using chromium.
The text I'm inserting doesn't reach speechSynthesis.speak()
text limit.
My function :
export async function textToSpeech(text) {
new Promise((resolve) => {
let msg = new SpeechSynthesisUtterance();
msg.voice = voices[6];
msg.lang = "en";
msg.text = text;
speechSynthesis.speak(msg);
msg.addEventListener("end", () => {
resolve();
});
});
}
calling the function :
ReadNews(data) {
textToSpeech(data.Title)
.then(textToSpeech(data.Info))
.then(textToSpeech("Would You like me to continue?"))
.then(
ContinueCON.addEventListener("click", () => {
textToSpeech(data.Content);
})
);
};
The first 3 instances (data.title, data.info, and the default message) are all spoken as expected. But on the 4th instance, stops after a few seconds.
Several things I have tried:
I used window.speechSynthesis.speaking right after the sound stopped working, and it printed true(which is very bizarre)
1st Edit (Yet to be solved)
Changed the code by the comments below
export function textToSpeech(text) {
return new Promise((resolve) => {
let msg = new SpeechSynthesisUtterance();
msg.voice = voices[6];
msg.lang = "en";
msg.text = text;
speechSynthesis.cancel(msg);
speechSynthesis.speak(msg);
msg.addEventListener("end", () => {
resolve();
});
});
}
Async was unnecessary and also "clean" the "Speak" queue (by using cancel)
ReadNews(data) {
textToSpeech(data.Title)
.then(() => textToSpeech(data.Info))
.then(() => textToSpeech("Would You like me to continue?"))
.then(() =>
ContinueCON.addEventListener("click", () => {
textToSpeech(data.Content);
})
);
},
};
I invoked the return value immediately instead of waiting for a resolve and then running the next line of text.
Problem is yet to be solved.
I see a couple issues with your code, which are causing the speech to all get queued up right away, without waiting. But speechSynthesis is supposed to handle that automatically, so it's not clear to me how they would cause what you're seeing. Still, i'll point them out in case they're causing your problem in a subtle way that i can't identify.
The first issue is that your textToSpeech function doesn't return the promise it creates. A promise is implicitly returned since it's an async function, but that promise will not wait for the speech to finish. The fix for this is to add a return in (and you can also remove async if you wish, since you're not awaiting anything)
export function textToSpeech(text) {
return new Promise((resolve) => {
let msg = new SpeechSynthesisUtterance();
msg.voice = voices[6];
msg.lang = "en";
msg.text = text;
speechSynthesis.speak(msg);
msg.addEventListener("end", () => {
resolve();
});
});
}
Secondly, ReadNews is not waiting for the promises to finish. .then(textToSpeech(data.Info)) means "immediately call textToSpeech, passing in data.Info, and then whatever it returns pass that into .then". Instead, you want to pass a function into .then, so that the function will be called once the previous promise has resolved:
ReadNews(data) {
textToSpeech(data.Title)
.then(() => textToSpeech(data.Info))
.then(() => textToSpeech("Would You like me to continue?"))
.then(
() => ContinueCON.addEventListener("click", () => {
textToSpeech(data.Content);
})
);
}
Or with async/await:
async ReadNews(data) {
await textToSpeech(data.Title);
await textToSpeech(data.Info);
await textToSpeech("Would you like me to continue?");
ContinueCON.addEventListener("click", () => {
textToSpeech(data.Content);
})
}
I'm creating a YouTube upload notification bot for a Discord Server I am in using the YouTube RSS Feed and am having problems with it. I have issues with the bot sending the same video twice even though I've tried everything to fix it. The bot cycles through different users in a for loop and checks the user's latest video's ID with one stored in a JSON file. If they do not match, it sends a message and updates the JSON. Here is my current code:
function update(videoId, n) {
var u = JSON.parse(fs.readFileSync("./jsons/uploads.json"))
u[n].id = videoId
fs.writeFile("./jsons/uploads.json", JSON.stringify(u, null, 2), (err) => {
if (err) throw err;
// client.channels.cache.get("776895633033396284").send()
console.log('Hey, Listen! ' + n + ' just released a new video! Go watch it: https://youtu.be/' + videoId + "\n\n")
});
}
async function uploadHandler() {
try {
var u = require('./jsons/uploads.json');
var users = require('./jsons/users.json');
for (i = 0; i < Object.keys(users).length; i++) {
// sleep(1000)
setTimeout(function(i) {
var username = Object.keys(users)[i]
let xml = f("https://www.youtube.com/feeds/videos.xml?channel_id=" + users[username]).text()
parseString(xml, function(err, result) {
if (err) {} else {
let videoId = result.feed.entry[0]["yt:videoId"][0]
let isMatch = u[username].id == videoId ? true : false
if (isMatch) {} else {
if (!isMatch) {
u[username] = videoId
update(videoId, username)
}
}
}
});
}, i * 1000, i)
}
} catch (e) {
console.log(e)
}
}
My code is rather simple but I've had the same issue with other codes that use this method; therefore what would be the best way to accomplish this? Any advice is appreciated
There are a few issues with your code that I would call out right off the bat:
Empty blocks. You use this especially with your if statements, e.g. if (condition) {} else { // Do the thing }. Instead, you should negate the condition, e.g. if (!condition) { // Do the thing }.
You declare the function uploadHandler as async, but you never declare that you're doing anything asynchronously. I'm suspecting that f is your asynchronous Promise that you're trying to handle.
You've linked the duration of the timeout to your incrementing variable, so in the first run of your for block, the timeout will wait zero seconds (i is 0, times 1000), then one second, then two seconds, then three...
Here's a swag at a refactor with some notes that I hope are helpful in there:
// Only require these values once
const u = require('./jsons/uploads.json');
const users = require('./jsons/users.json');
// This just makes the code a little more readable, I think
const URL_BASE = 'https://www.youtube.com/feeds/videos.xml?channel_id=';
function uploadHandler() {
Object.keys(users).forEach(username => {
// We will run this code once for each username that we find in users
// I am assuming `f` is a Promise. When it resolves, we'll have xml available to us in the .then method
f(`${URL_BASE}${username}`).then(xml => {
parseString(xml, (err, result) => {
if (!err) {
const [videoId] = result.feed.entry[0]['yt:videoId']; // We can use destructuring to get element 0 from this nested value
if (videoId !== u[username].id) {
// Update the in-memory value for this user's most recent video
u[username].id = videoId;
// Console.log the update
console.log(`Hey listen! ${username} just released a new video! Go watch it: https://youtu.be/${videoId}\n\n`);
// Attempt to update the json file; this won't affect the u object in memory, but will keep your app up to date
// when you restart it in the future.
fs.writeFile('./jsons/uploads.json', JSON.stringify(u, null, 2), err => {
if (err) {
console.err(`There was a problem updating uploads.json with the new videoId ${videoId} for user ${username}`);
}
});
}
}
});
})
// This .catch method will run if the call made by `f` fails for any reason
.catch(err => console.error(err));
});
}
// I am assuming that what you want is to check for updates once every second.
setInterval(uploadHandler, 1000);
This perplexes me. I'm six months into a firebase project and have been using Javascript for firebase-functions. I've learned a lot along the way by adding transactions, promises, batch writes and neat tricks. However, it seems like complete luck for a function to execute correctly. More often than not, the functions do execute correctly, but there are strange periods when bursts of consecutive function calls where functions half complete with no errors in the logs.
For example. I have a function for when a new user joins my app. It does a little bit of server data construction and also notifies the two admins that a new user has joined. Last night I did a test run with two new users and got no notification, but their user profiles constructed correctly on the server database. I checked the function logs and there were no errors.
Am I not handling Promises in the correct way? If a firebase function hangs, does it mess up the next few function calls?
exports.onNewUser = functions.firestore
.document('/users/{userId}')
.onCreate(async (snapshot, context) => {
user = snapshot.data().username;
//Notification payload
const payload = {
notification: {
title: `New user!`,
body: `${user} has joined [AppName]`
}
};
var promises = [];
//Check if usename unique
var passed = true;
promises.push(db.runTransaction(async t => {
const docRef = db.collection('users').doc('index');
const doc = await t.get(docRef);
var newIndex = doc.data().usernames;
if (newIndex[user.toUpperCase()] == true) {
t.delete(snapshot.ref);
passed = false;
return null;
} else {
newIndex[user.toUpperCase()] = true;
t.set(docRef, { 'usernames': newIndex });
}
}));
if (!passed) return Promise.all(promises);
//add new user to Algolia database
const algoliasearch = require('algoliasearch');
const algoliaClient = algoliasearch(functions.config().algolia.appid, functions.config().algolia.apikey);
const collectionIndex = algoliaClient.initIndex(collectionIndexName);
await saveDocumentInAlgolia(snapshot, collectionIndex);
//Notify Admins
db.collection('notificationTokens')
.doc(admin1)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
db.collection('notificationTokens')
.doc(admin2)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
return Promise.all(promises);
});
Just change
return Promise.all(promises);
to
return await Promise.all(promises);
You have to wait till the promises resolve before you return the function, as that would stop the instance of the cloud function.
Example code:
Hub.listen('auth', event => {
const { event: type, data } = event.payload;
if (type === 'signIn') {
const session = data.signInUserSession;
console.log('SESSION', data.signInUserSession);
setTimeout(() => {
console.log('SESSION', data.signInUserSession);
}, 100);
}
});
When using oath, after the provider redirects to my app, the Hub fires a signIn event. However, the signInUserSession property is null when the event is fired, but gets a value some time later (within 100 ms). This does not seem to occur when using Auth.signIn(email, password) directly; signInUserSession is populated when the event is fired.
What is happening here, and how can I get around it? Currently, I have an explicit delay in the code, which is a terrible hack.
Perhaps the old way of JavaScript for waiting for value to be populated is useful to ensure that code does not fail even if the it takes longer than expected in populating the value.
Here is a sample code that I normally use when no other options are available.
waitForValue(){
if(myVar!= null && typeof myVar !== "undefined"){
//value exists, do what you want
console.log(myVar)
}
else{
setTimeout(() => {this.waitForValue()}, 100);
}
}
You can refactor this sample code as per your need.
Alternatively, AWS Amplify also have other ways to get current logged in user session. e.g. Auth.currentAuthenticatedUser() and Auth.currentSession() return promise. They can be used like this
private async getUser(){
let user = null;
try {
user = await Auth.currentAuthenticatedUser();
//console.log(user);
} catch (err) {
//console.log(err);
}
//return user;
}
i am not used to aws amplify - just read some github and so far i can see we will need info about your userPool implementation - i guess some weird callback issue
But for a workaround you can proxy the reference:
const event = {type: "signIn", data: {signInProperty: "null"}}
setTimeout(()=>event.data.signInProperty = "{Stack: Overflow}", 1000)
// mock events
function emit(type, args){
console.log(type, args)
}
//initialize
let watchedValue = event.data.signInProperty
document.getElementById("app").innerHTML = event.data.signInProperty
// protect reference
Object.defineProperty(event.data, "signInProperty", {
set(newValue){
watchedValue = newValue
document.getElementById("app").innerHTML = newValue
emit("event:signInCompleted", event.data)
},
get(){
return watchedValue
}
})
<div id="app"></div>
What is the correct way to implement a retry on error/condition without using any third party modules in nodejs, please?
I'm not sure how to call the same function on the error and how to then pass the original callback/data to the newly called function?
Do I need to destroy/end the sockets?
I've tried looking for examples but have only found reference to third party modules and http.get samples which don't seem to work. How does one test this?
I have attempted the below without success:
async pingApi(cb) {
let options = {
"method":"post",
"path": `/API/pingAPI?${this.auth}`, /ect do I reference this path?
}
};
let request = await http.request(options, (response) => {
let body = new Buffer(0);
response.on('data', (chunk) => {
body = Buffer.concat([body, chunk]);
});
response.on('end', function () {
if (this.complete) {
let decoded = new Buffer(body, 'base64').toString('utf8')
let json = JSON.parse(decoded);
if (json.result != 'OK') {
setTimeout(pingApi, 1000); //cant pass callback
} else {
cb(null, json.result) //works
}
}
});
})
request.end(); //does the socket need to be closed if retry is this function?
}
Any help, pointing in the right direction or criticism will be greatly appreciated as I think this is a very important learning curve for me.
Thank you in advance,
I'm not sure how to call the same function on the error and how to then pass the original callback/data to the newly called function?
I don't know for sure that everything else in your function is correct, but you can fix the recursion that you're asking about by changing this:
setTimeout(pingApi, 1000); //cant pass callback
to this:
setTimeout(() => {
this.pingApi(cb);
}, 1000);
You aren't showing the whole context here, but if pingApi() is a method, then you also need to keep track of the this value to you can call this.pingApi(db). You can preserve the value of this by using arrow function callbacks like this:
response.on('end', () => { ... });
Other things I notice that look off here:
There's no reason to use await http.request(). http.request() does not return a promise so using await with it does not do anything useful.
Without the await, there's then no reason for your function to be declared async since nobody is using a returned promise from it.
It's not clear what if (this.complete) is meant to do. Since this is inside a regular function callback, the value of this won't be your pingApi object. You should either save this higher in the scope typically with const self = this or all callbacks internally need to be arrow functions so the value of this is preserved.
You should probably put try/catch around JSON.parse() because it can throw if the input is not perfect JSON.
You should probably not retry forever. Servers really hate clients that retry forever because if something goes wrong, the client may just be bashing the server every second indefinitely. I'd suggest a certain number of max retries and then give up with an error.
Do I need to destroy/end the sockets?
No, that will happen automatically after the request ends.
How does one test this?
You have to create a test route in your server that returns the error condition for the first few requests and then returns a successful response and see if your code works with that.
Here's an attempt at a code fixup (untested):
const maxRetries = 10;
pingApi(cb, cnt = 0) {
let options = {
"method":"post",
"path": `/API/pingAPI?${this.auth}`, // ect do I reference this path?
};
let request = http.request(options, (response) => {
let body = new Buffer(0);
response.on('data', (chunk) => {
body = Buffer.concat([body, chunk]);
});
response.on('end', () => {
if (this.complete) {
let decoded = new Buffer(body, 'base64').toString('utf8')
try {
let json = JSON.parse(decoded);
if (json.result != 'OK') {
if (cnt < maxRetries)
setTimeout(() => {
this.pingApi(cb, ++cnt);
}, 1000);
} else {
cb(new Error("Exceeded maxRetries with error on pingApi()"));
}
} else {
cb(null, json.result) //works
}
} catch(e) {
// illegal JSON encountered
cb(e);
}
}
});
})
request.end();
}
Remaining open questions about this code:
What is this.complete doing and what this should it be referencing?
Why is there no request.write() to send the body of the POST request?
I know you ask for no external modules, but my preferred way of doing this would be to use promises and to use the request-promise wrapper around http.request() because it handles a lot of this code for you (checks response.status for you, parses JSON for you, uses promise interface, etc...). You can see how much cleaner the code is:
const rp = require('request-promise');
const maxRetries = 5;
pingApi(cnt = 0) {
let options = {
method: "post",
url: `http://somedomain.com/API/pingAPI?${this.auth}`,
json: true
};
return rp(options).then(result => {
if (result.result === "OK") {
return result;
} else {
throw "try again"; // advance to .catch handler
}
}).catch(err => {
if (cnt < maxRetries) {
return pingApi(++cnt);
} else {
throw new Error("pingApi failed after maxRetries")
}
});
}
And, then sample usage:
pingApi().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
your use of async/await with core node server intrigued me and I've tried to use much as possible of this new async features.
This is what I end up with: https://runkit.com/marzelin/pified-ping
const pify = require("util").promisify;
const http = require("http");
const hostname = "jsonplaceholder.typicode.com";
const failEndpoint = "/todos/2";
const goodEndpoint = "/todos/4";
let options = {
method: "get",
path: `${failEndpoint}`,
hostname
};
async function ping(tries = 0) {
return new Promise((res) => {
const req = http.request(options, async (response) => {
let body = new Buffer(0);
response.on("data", (chunk) => {
body = Buffer.concat([body, chunk]);
})
const on = pify(response.on.bind(response));
await on("end");
let decoded = new Buffer(body, 'base64').toString('utf8')
let json = JSON.parse(decoded);
if (json.completed) {
return res("all good");
}
if (tries < 3) {
console.log(`retrying ${tries + 1} time`);
return res(ping(tries + 1));
}
return res("failed");
})
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
// write data to request body
req.end();
})
}
const status = await ping();
"status: " + status