Jump to content
  • 0

Issue with @antmedia/webrtc_adaptor JS SDK in a React Application


Albin
 Share

Question

Hi,

I'm trying to implement a conference call application on my own and I'm having issues with streams sometimes disappearing,

 

Example: Client 1 enters conference call and sits there for a while

Client 2 enters conference call and does not see or hear Client 1. However, Client 1 can see and hear Client 2.

Client 1 leaves and re-enters the conference call and now both see each other.

 

As you can see this gets exponentially bad the more people are in the call and people don't know who sees them and who they are missing. And eventually someone disappears etc.

 

I have looked at the code in ant-media/conference-call-application: Conference Call Application over Ant Media Server (github.com) and there is just so much code, which I feel is unnecessary to my use case, and also I would like to use my own UI library and CSS, and develop the entire application from scratch.

It would be nice if there was a barebones react application example for a stable conference call without any extra features.

I can provide some of the code for my application: (This is just relevant part of the code)
Also this is using TypeScript;

import React, { useEffect, useRef, useState } from 'react';
import { WebRTCAdaptor } from '@antmedia/webrtc_adaptor';
import useStateRef from 'react-usestateref';
import ReactPlayer from 'react-player';

type Stream = {
	id: string;
	src: string;
};

let webRTCAdaptor: WebRTCAdaptor | null = null;

type Props = {
};

export default function SessionReworked({}: Props) {
	let roomTimerId = React.useRef<NodeJS.Timeout | null>(null);

	// video stream related
	const webSocketUrl = '';
	const adaptorReadyRef = useRef<boolean>(false);
	const localVideoRef = useRef<HTMLVideoElement | null>(null);
	const [localMediaStream, setLocalMediaStream] = React.useState<MediaStream | null>(null);
	var [streamsList, setStreamsList, ref] = useStateRef<Stream[]>([]);
	var [localStreamId, setLocalStreamId, localStreamIdRef] = useStateRef<string>('');

	// Buttons and other UI/functionality related hooks
	const [hasJoined, setHasJoined] = useState<boolean>(false);
	const [isCameraOff, setIsCameraOff] = useState<boolean>(false);
	const [isMuted, setIsMuted] = useState<boolean>(false);	

	const getVideo = () => {
		if (localVideoRef.current) {
			navigator.mediaDevices
				.getUserMedia({ video: { width: 300 } })
				.then((stream) => {
					setLocalMediaStream(stream);
					let video = localVideoRef.current;
					console.log('video, stream', video, stream);
					video.srcObject = stream;
					video.play();
				})
				.catch((err) => {
					console.error('error:', err);
				});
		}
	};
	

	useEffect(() => {
		// get webcam video
		getVideo();
	}, [localVideoRef, hasJoined, isCameraOff]);

	useEffect(() => {
		webRTCAdaptor = new WebRTCAdaptor({
			websocket_url: webSocketUrl,
			mediaConstraints: {
				video: true,
				audio: true,
			},
			peerconnection_config: {
				iceServers: [{ urls: 'stun:stun1.l.google.com:19302' }],
			},
			sdp_constraints: {
				offerToReceiveAudio: false,
				offerToReceiveVideo: false,
			},
			localVideoId: 'localMediaStream', // <video id="id-of-video-element" autoplay muted></video>
			bandwidth: 'unlimited', // default is 900 kbps, string can be 'unlimited'
			dataChannelEnabled: true, // enable or disable data channel
			debug: true,
			callback: (info, obj) => {
				const room = obj?.ATTR_ROOM_NAME;
				switch (info) {
					case 'pong':
						// ping-pong is a mechanism to keep the websocket connection alive
						console.log('pong', info, obj, room);
						break;
					case 'joinedTheRoom':
						adaptorReadyRef.current = true;
						// called when You join the room
						console.log('joinedTheRoom', info, obj, room);
						webRTCAdaptor.publish(obj.streamId, room);
						setLocalStreamId(obj.streamId);
						obj.streams?.forEach((stream) => {
							webRTCAdaptor.play(stream, null, room);
						});
						addStreamToStreamsListNoDuplicates({ id: obj.streamId, src: obj.streamId });
						roomTimerId.current = setInterval(() => {
							webRTCAdaptor.getRoomInfo(roomId, obj.streamId);
						}, 1000);
						break;
					case 'roomInformation':
						// Used to get room information, will be called when you join the room and every 5 seconds with the interval
						console.log('roomInformation', info, obj, room);
						// Add new streams to the list if they don't already exist in the list
						for (let str of obj.streams) {
							webRTCAdaptor.play(str, null, room);
						}
						break;
					case 'newStreamAvailable':
						console.log('newStreamAvailable', info, obj, room);
						let newStream: Stream = { id: obj.streamId, src: obj.stream };
						addStreamToStreamsListNoDuplicates(newStream);
						webRTCAdaptor.play(obj.streamId, null, room);
						webRTCAdaptor.enableAudioLevel(obj.stream, obj.streamId);
						break;
					case 'leavedFromRoom':
						if (roomTimerId.current) {
							clearInterval(roomTimerId.current);
						}
						setStreamsList([]);
						break;
					case 'closed':
						console.log('closed', info, obj);
						break;
					case 'play_finished':
						console.log('play_finished', info, obj);
						webRTCAdaptor.stop(obj.streamId);
						removeStreamFromStreamsList(obj.streamId);
						break;
					default:
						console.log('default', info, obj);
						break;
				}
			}, // check info callbacks bellow
			callbackError: function (error, message) {
				console.log('errcallback error:', error);
				console.log('errcallback message:', message);
			}, // check error callbacks bellow
		});
		return () => {
			clearInterval(roomTimerId.current);
			if (webRTCAdaptor && adaptorReadyRef.current === true) {
				webRTCAdaptor.leaveFromRoom(roomId);
				webRTCAdaptor?.stop();
			}
		};
	}, []);

	const addStreamToStreamsListNoDuplicates = (stream: Stream) => {
		const streamId = stream.id;
		const streamSrc = stream.src;
		if (ref.current.some((s) => s.id === streamId)) {
			return;
		}
		setStreamsList((prev) => [...prev, { id: streamId, src: streamSrc }]);
	};

	const removeStreamFromStreamsList = (streamId: string) => {
		setStreamsList((prev) => prev.filter((s) => s.id !== streamId));
	};

	function _leaveRoom() {
		webRTCAdaptor.leaveFromRoom(roomId);
	}

	const _publishStream = () => {
		const generatedStreamId = `${roomId}-${activeAccount?.idTokenClaims?.oid || nanoid(5)}`;
		webRTCAdaptor.joinRoom(roomId, generatedStreamId);
	};

	const createRemoteVideoReact = (streamId, src, index) => {
		return (
			<>
				<ReactPlayer
					id={`remoteVideo${streamId}`}
					url={src || ''}
					playsinline
					playing={true}
					controls={true}
					height="100%"
					width="100%"
					style={{ height: '100%', width: '100%' }}
				/>
			</>
		);
	};

	const createLocalVideoPlayer = () => {
		return (
			<>
                <ReactPlayer
                    id={`localMediaStream`}
                    url={localMediaStream || ''}
                    playsinline
                    playing={true}
                    muted={true}
                    controls={true}
                    height="100%"
                    width="100%"
                    style={{ height: '100%', width: '100%' }}
                />
			</>
		);
	};
	
	// and just render the video elements.
	return (
	<div className={`grid grid--${streamsList.length}`}>
						{streamsList?.length > 0 &&
							streamsList.map((stream: Stream, index: number) => {
								console.log('StreamsList map', stream);
								if (stream.src && stream.id && stream.id !== localStreamId) {
									return (
										<div key={`video-${index}`} className={`video-wrapper video-#${index}`}>
											{createRemoteVideoReact(stream.id, stream.src, index)}
										</div>
									);
								}
							})}
							<div
								key={`video-${streamsList.length}`}
								className={`video-wrapper video-#${streamsList.length}`}
							>
								{createLocalVideoPlayer()}
							</div>
					</div>
	);
}

If anyone can spot what I'm doing wrong, or take my code and try to implement it themselves to see what I'm doing wrong.

I can also send the full code with UI components, and access to the deployed frontend, if someone wants to help me.

Or if there is already existing code I can use that would be helpful.

Thanks.

Link to comment
Share on other sites

  • Answers 3
  • Created
  • Last Reply

Top Posters For This Question

Popular Days

Top Posters For This Question

3 answers to this question

Recommended Posts

  • 0
55 minutes ago, mustafaboleken said:

Hi @Albin,

Can you please share full code with me. I can take a look quickly what is the problematic part.

Ok, I will share a codesandbox with you over PM 🙂

Thanks!

Link to comment
Share on other sites

  • 0
2 hours ago, mustafaboleken said:

Hi @Albin,

Can you please share full code with me. I can take a look quickly what is the problematic part.

Also feel free to modify the code however you want in the codesandbox i sent

Link to comment
Share on other sites

 Share


×
×
  • Create New...