PeerConnection create answer - javascript

I would like create WebRTC connection on 2 devices.
On my first device (Initiator), i create my Offer with this method :
createOffer() {
const { pc } = this;
pc.onnegotiationneeded = () => {
pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => {
this.setState({
offer: JSON.stringify(pc.localDescription)
});
});
};
}
And on my second device (Receiver), i create the answer :
createAnswer() {
const { pc } = this;
const { dataOffer } = this.state;
if (dataOffer) {
const sd = new RTCSessionDescription(JSON.parse(dataOffer));
pc.setRemoteDescription(sd)
.then(() => pc.createAnswer())
.then(answer => {
this.setState({
offer: JSON.stringify(answer)
});
return pc.setLocalDescription(answer);
});
}
}
But, after send Answer on first device, i've this error : PeerConnection cannot create an answer in a state other than have-remote-offer or have-local-pranswer.
When Initiator receive the answer, i run createAnswer method with answer data, That may be the problem ?
I don't really understand what method/event i need use after receive the answer :/
EDIT with new method for Initiator device :
receiveAnswer() {
const { pc } = this;
const { dataOffer } = this.state;
const sd = new RTCSessionDescription(JSON.parse(dataOffer));
pc.setRemoteDescription(sd);
}
But the connection state stay on checking :(
You can see my componentDidMount :
componentDidMount() {
const { pc } = this;
pc.oniceconnectionstatechange = () => {
this.setState({
connectionState: pc.iceConnectionState
});
};
pc.onaddstream = ({ stream }) => {
if (stream) {
this.setState({
receiverVideoURL: stream.toURL()
});
}
};
}

Related

The video stops and does not resume after track.stop()

I'm creating simple video-chat one-to-one using PeerJS and React. Everything is working fine except the camera muting. When participant A muting camera, it's muting on both clients but when participant A unmutes it, participant B can see only the static image instead of the opponent video.
I have a global state webcamState which changed when the corresponding button was clicked:
const videoRef = useRef<any>(null);
const webcamState = useSelector(getWebcamState);
const [stream, setStream] = useState<MediaStream | null>(null);
const [isWebcamLoading, setIsWebcamLoading] = useState(true);
const loadUserMedia = () => {
setIsWebcamLoading(true);
getUserMedia(
{ video: webcamState, audio: micState },
(newStream: MediaStream) => {
videoRef.current.srcObject = newStream;
setStream(newStream);
setIsWebcamLoading(false);
},
(err: any) => {
setIsWebcamLoading(false);
},
);
};
useEffect(() => {
if (videoRef?.current?.srcObject) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = webcamState));
if (!webcamState) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop());
videoRef.current.pause();
} else {
loadUserMedia();
}
}
}, [webcamState]);
Then this stream exporting from this hook and passed into another to initialize Peer call (and peer answer as well):
export interface IPeerOptions {
opponentVideoRef: React.MutableRefObject<null>;
currentVideoRef: React.MutableRefObject<null>;
currentStream: MediaStream;
userId: string;
}
export const initializePeerCall = (options: IPeerOptions): Peer => {
const call = options.peer.call(options.opponentId, options.currentStream);
call.on('stream', stream => {
options.opponentVideoRef = setVideo(options.opponentVideoRef, stream);
});
call.on('error', err => {
console.log(err);
});
call.on('close', () => {
console.log('Connection closed');
});
return options.peer;
};
No errors appear in the console
But if I will remove the following line: videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop()); everything will work fine as expected
Maybe anyone faced something similar?
UPD: I tried this but the result was the same:
useEffect(() => {
if (videoRef?.current?.srcObject) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = webcamState));
if (!webcamState) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop());
loadUserMedia();
} else {
loadUserMedia();
}
}
}, [webcamState]);
Notification:
const onOpponentVideoStatusChanged = (newStatus: boolean) => {
setOpponentState(prev => {
return { microphoneState: !!prev?.microphoneState, webcamState: newStatus };
});
options.opponentVideoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = newStatus));
};
As I understand after long investigation, user B still getting the same stream after user A created a new one. How can I fix it?
If you turn track off using track.stop(), you can not resume it.
I did get new stream when I have to resume it.
React.useEffect(()=>{
if(mediaStream) { // mediaStream could be state locally or globally.
const videoTrack = mediaStream.getVideoTracks();
videoTrack.forEach((t) => {
t.enabled = false;
t.stop();
});
}
// get MediaStream again with contraints
}, [isAudioOnly]);

Check two async boolean variables and call a method if both are satisfied

How could I improve this method of rendering only when both variables are met as true, to allow the renderFilters() method to be called:
These two variables are filled asynchronously through 2 API methods:
//getManager()
this.isLoadingManager = true;
//getPdiPOrganization()
this.isLoadingPdiOrganization = true;
promiseRender() {
let interval = setInterval(() => {
if (this.isLoadingManager && this.isLoadingPdiOrganization) {
clearInterval(interval);
this.renderFilters();
} else {
setTimeout(() => {
clearInterval(interval);
this.renderFilters();
}, 5000)
}
}, 500);
}
The problem is that it's very slow... it's calling long after the APIs are called...
Maybe some feature of angular itself, if anyone has a better solution...
const observable = forkJoin({
loading1:this.isLoadingManager,
loading2:this.isLoadingPdiOrganization
});
observable.subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
})
Or:
const myObservable = Observable.of(this.isLoadingManager && this.isLoadingPdiOrganization);
const myObserver = {
next: (result: Boolean) => this.renderFilters(),
};
myObserver.next(true);
myObservable.subscribe(myObserver);
Adding the methods:
getManager() {
if (this.fromAdminPage && localStorage.getItem('_receivers_pdi')) {
this.meetingService.getIsManager()
.subscribe(res => {
this.showPdiToastNotification = res;
this.isLoadingManager = true;
});
}
}
getPdiPOrganization() {
const url = this.publicEndpoint ? 'current/organization/pdi/configuration' : 'api/current/organization/pdi/configuration';
const requestOptions = {
params: new CustomHttpParams({ isPublicTokenUrl: this.publicEndpoint })
};
this.http.get<any>(url, requestOptions).subscribe(resp => {
this.isLoadingPdiOrganization = true;
this.pdiOrgConfig = resp || {};
this.updatePdiReferenceType(this.pdiOrgConfig);
});
}
You can use forkjoin to subscribe to two observables at the same time. I would stick with using RxJs operators for cases like these. You can read more about forkJoin here.
forkJoin([obs1, obs2]).subscribe({
next: (results) => {
const obs1Val = results[0];
const obs2Val = results[1];
if (obs1Val && obs2Val) {
this.renderFilters();
}
}
});

How to update RTK Query cache when Firebase RTDB change event fired (update, write, create, delete)

I am using redux-tookit, rtk-query (for querying other api's and not just Firebase) and Firebase (for authentication and db).
The code below works just fine for retrieving and caching the data but I wish to take advantage of both rtk-query caching as well as Firebase event subscribing, so that when ever a change is made in the DB (from any source even directly in firebase console) the cache is updated.
I have tried both updateQueryCache and invalidateTags but so far I am not able to find an ideal approach that works.
Any assistance in pointing me in the right direction would be greatly appreciated.
// firebase.ts
export const onRead = (
collection: string,
callback: (snapshort: DataSnapshot) => void,
options: ListenOptions = { onlyOnce: false }
) => onValue(ref(db, collection), callback, options);
export async function getCollection<T>(
collection: string,
onlyOnce: boolean = false
): Promise<T> {
let timeout: NodeJS.Timeout;
return new Promise<T>((resolve, reject) => {
timeout = setTimeout(() => reject('Request timed out!'), ASYNC_TIMEOUT);
onRead(collection, (snapshot) => resolve(snapshot.val()), { onlyOnce });
}).finally(() => clearTimeout(timeout));
}
// awards.ts
const awards = dbApi
.enhanceEndpoints({ addTagTypes: ['Themes'] })
.injectEndpoints({
endpoints: (builder) => ({
getThemes: builder.query<ThemeData[], void>({
async queryFn(arg, api) {
try {
const { auth } = api.getState() as RootState;
const programme = auth.user?.unit.guidingProgramme!;
const path = `/themes/${programme}`;
const themes = await getCollection<ThemeData[]>(path, true);
return { data: themes };
} catch (error) {
return { error: error as FirebaseError };
}
},
providesTags: ['Themes'],
keepUnusedDataFor: 1000 * 60
}),
getTheme: builder.query<ThemeData, string | undefined>({
async queryFn(slug, api) {
try {
const initiate = awards.endpoints.getThemes.initiate;
const getThemes = api.dispatch(initiate());
const { data } = (await getThemes) as ApiResponse<ThemeData[]>;
const name = slug
?.split('-')
.map(
(value) =>
value.substring(0, 1).toUpperCase() +
value.substring(1).toLowerCase()
)
.join(' ');
return { data: data?.find((theme) => theme.name === name) };
} catch (error) {
return { error: error as FirebaseError };
}
},
keepUnusedDataFor: 0
})
})
});

WebSockets server receives data but doesn't send back response

WebSocket doesn't send back a response despite the payLoad of the response is correct. The connection between front end and back end seems fine too. The boolean toggling inside the object array also works fine and does it's job. Any ideas why it isnt sending the JSON back to front end?
--------------------Front-end--------------------
const clientChangeVote = (c) => {
const payLoad = {
method: "changeVote",
clientId: gameData.clients[c].id,
gameId: gameData.id,
};
// voteValue: gameData.clients[c].voteReady,
ws.send(JSON.stringify(payLoad));
};
-----------------Back-end------------------------
if (result.method === "changeVote") {
const gameId = result.gameId;
const clientId = result.clientId;
games[gameId].clients
.filter((x) => x.id === clientId)
.forEach((vote) => (vote.voteReady = !vote.voteReady));
const updatedData = games[gameId].clients;
const payLoad = {
method: "changeVote",
updatedData: updatedData,
};
const game = games[gameId];
console.log(games);
game.clients.forEach((c) => {
console.log(payLoad);
c.connection.send(JSON.stringify(payLoad, getCircularReplacer()));
});
}
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
-----------Inside the respone area. Im using React-----------
const [ws, setWs] = useState(new W3CWebSocket(URL));
useEffect(() => {
ws.onopen = () => {
console.log("Successful connection");
};
ws.onmessage = (message) => {
if (response.method === "changeVote") {
console.log("Vote received");
}
return () => {
ws.onclose = () => {
console.log("Connection closed");
setWs(new WebSocket(URL));
};
};
}, [ws.onmessage, ws.onopen, ws.onclose]);
Change ws.onmessage() :
ws.onmessage = (message) => {
let response = JSON.parse(message.data)
if (response.method === "changeVote") {
console.log("Vote received");
}
if not works add a comment.

Try download img from firebase storage which link is firebase database

I tried to download the image which is in firebase storage which link is store in database. When I tried to download the image, it takes more time to execute while for loop is completed.
Is there any process that somehow I download in time which doesn't make the function really slow? I already solve this issue using setTimeout but I hope there may be a better solution than mine. Help me! thank you!
export const shampooHandler = () => {
return (dispatch) => {
dispatch(shampooStart());
const data = [];
const imgList = [];
fire
.database()
.ref()
.child("Shampoo")
.once("value")
.then((response) => {
for (let i = 0; i < response.val().length; i++) {
fire.storage().refFromURL(response.val()[i].img).getDownloadURL().then((image) => {
imgList.push(image);
})
.catch((error) => {
dispatch(shampooError(error));
});
setTimeout(() => {
name = response.val()[i].name;
description = response.val()[i].description;
value = response.val()[i].value;
img = imgList[i];
data.push({ name, description, value, img });
if (i === (response.val().length - 1)) {
dispatch(shampooSuccess(data));
}
}, 3000);
}
})
.catch((error) => {
dispatch(shampooError(error));
});
};
};
I spend a day finding a right solution for it. It may help someone to find solution in future. Thanks guys for giving a thought and specially DougStevensen to tiggering me an idea
export const shampooHandler = () => {
return (dispatch) => {
dispatch(shampooStart());
const data = [];
const imglist = [];
fire.database().ref().child("Shampoo").once("value").then((response) => {
response.val().forEach(element => {
const promise = imageUrlHandler(element.img).then(url => {
return url;
}).catch(error =>{
dispatch(shampooError(error));
})
imglist.push(promise);
//all the promise call to download the images
Promise.all(imglist).then(items =>{
const dataCollection = {
name: element.name,
description: element.description,
value: element.value,
img: items[items.length - 1]
}
data.push(dataCollection);
if(data.length === response.val().length){
dispatch(shampooSuccess(data));
}
}).catch(err =>dispatch(shampooError(err)));
})
}).catch(error => {
dispatch(shampooError(error));
})
}
}
export const imageUrlHandler = (databaseUrl) => {
return new Promise((resolve,reject)=> {
fire.storage().refFromURL(databaseUrl).getDownloadURL().then((url) => {
resolve(url);
})
.catch((error) => {
reject(error)
});
})
}

Categories