222 lines
7.6 KiB
JavaScript
222 lines
7.6 KiB
JavaScript
import { AzureCommunicationTokenCredential } from '@azure/communication-common';
|
|
import { CallClient, LocalVideoStream, VideoStreamRenderer } from '@azure/communication-calling';
|
|
|
|
class VideoCall {
|
|
constructor() {
|
|
this.callClient = null;
|
|
this.callAgent = null;
|
|
this.deviceManager = null;
|
|
this.localVideoStream = null;
|
|
this.call = null;
|
|
this.videoElement = document.createElement('video');
|
|
this.videoElement.setAttribute('autoplay', '');
|
|
this.videoElement.setAttribute('muted', '');
|
|
this.stateChangedCallback = null;
|
|
this.remoteParticipantsUpdated = null;
|
|
this.isLocalVideoStartedChanged = null;
|
|
this.localVideoStreamsUpdated = null;
|
|
this.idChanged = null;
|
|
this.onCreateLocalVideoStream = null;
|
|
this.remoteParticipantStateChanged = null;
|
|
this.remoteVideoIsAvailableChanged = null;
|
|
this.onGetServerCallID = null;
|
|
}
|
|
|
|
async init(userAccessToken, options) {
|
|
this.stateChangedCallback = options.stateChangedCallback;
|
|
this.remoteParticipantsUpdated = options.remoteParticipantsUpdated;
|
|
this.isLocalVideoStartedChanged = options.isLocalVideoStartedChanged;
|
|
this.localVideoStreamsUpdated = options.localVideoStreamsUpdated;
|
|
this.idChanged = options.idChanged;
|
|
this.onCreateLocalVideoStream = options.onCreateLocalVideoStream;
|
|
this.remoteParticipantStateChanged = options.remoteParticipantStateChanged;
|
|
this.remoteVideoIsAvailableChanged = options.remoteVideoIsAvailableChanged;
|
|
this.onGetServerCallID = options.onGetServerCallID;
|
|
|
|
const tokenCredential = new AzureCommunicationTokenCredential(userAccessToken);
|
|
|
|
this.callClient = new CallClient();
|
|
this.callAgent = await this.callClient.createCallAgent(tokenCredential);
|
|
|
|
this.deviceManager = await this.callClient.getDeviceManager();
|
|
await this.deviceManager.askDevicePermission({ audio: true, video: true });
|
|
const cameras = await this.deviceManager.getCameras();
|
|
this.localVideoStream = new LocalVideoStream(cameras[0]);
|
|
}
|
|
|
|
stopLocalVideo() {
|
|
if (this.call) {
|
|
this.call.stopVideo(this.localVideoStream);
|
|
}
|
|
}
|
|
|
|
async joinRoom(roomId) {
|
|
// Assuming you have a room ID and call the appropriate ACS API to join a room
|
|
// This is just a placeholder, replace with actual logic
|
|
console.log('Joining room:', roomId);
|
|
if (this.callAgent) {
|
|
try {
|
|
const localVideoStream = await this.createLocalVideoStream();
|
|
const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
|
|
|
|
this.callAgent.on('callsUpdated', e => {
|
|
e.added.forEach((addedCall) => {
|
|
addedCall.on('stateChanged', () => {
|
|
if (addedCall.state === 'Connected') {
|
|
addedCall.info.getServerCallId().then(result => {
|
|
this.onGetServerCallID?.(result);
|
|
}).catch(err => {
|
|
console.log(err);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
const roomCallLocator = { roomId: roomId };
|
|
let call = this.callAgent.join(roomCallLocator, { videoOptions });
|
|
this.subscribeToCall(call);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async subscribeToCall(call) {
|
|
try {
|
|
call.on('idChanged', () => {
|
|
this.idChanged?.(call.id);
|
|
});
|
|
call.on('stateChanged', async () => {
|
|
this.stateChangedCallback?.(call.state);
|
|
});
|
|
call.on('isLocalVideoStartedChanged', () => {
|
|
this.isLocalVideoStartedChanged?.(call.isLocalVideoStarted);
|
|
});
|
|
call.on('localVideoStreamsUpdated', e => {
|
|
this.localVideoStreamsUpdated?.(e);
|
|
});
|
|
|
|
// Subscribe to the call's 'remoteParticipantsUpdated' event to be
|
|
// notified when new participants are added to the call or removed from the call.
|
|
call.on('remoteParticipantsUpdated', e => {
|
|
this.remoteParticipantsUpdated?.(e);
|
|
|
|
e.added.forEach(remoteParticipant => {
|
|
this.subscribeToRemoteParticipant(remoteParticipant)
|
|
});
|
|
// Unsubscribe from participants that are removed from the call
|
|
e.removed.forEach(remoteParticipant => {
|
|
console.log('Remote participant removed from the call.');
|
|
});
|
|
});
|
|
|
|
call.localVideoStreams.forEach(async (lvs) => {
|
|
this.localVideoStream = lvs;
|
|
await this.displayLocalVideoStream(lvs);
|
|
});
|
|
|
|
// Inspect the call's current remote participants and subscribe to them.
|
|
call.remoteParticipants.forEach(remoteParticipant => {
|
|
this.subscribeToRemoteParticipant(remoteParticipant);
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
subscribeToRemoteParticipant(remoteParticipant) {
|
|
try {
|
|
// Inspect the initial remoteParticipant.state value.
|
|
console.log(`Remote participant state: ${remoteParticipant.state}`);
|
|
// Subscribe to remoteParticipant's 'stateChanged' event for value changes.
|
|
remoteParticipant.on('stateChanged', () => {
|
|
console.log(`Remote participant state changed: ${remoteParticipant.state}`, JSON.stringify(remoteParticipant));
|
|
this.remoteParticipantStateChanged?.(remoteParticipant.state);
|
|
});
|
|
|
|
// Inspect the remoteParticipants's current videoStreams and subscribe to them.
|
|
remoteParticipant.videoStreams.forEach(remoteVideoStream => {
|
|
this.subscribeToRemoteVideoStream(remoteVideoStream);
|
|
});
|
|
// Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
|
|
// notified when the remoteParticipant adds new videoStreams and removes video streams.
|
|
remoteParticipant.on('videoStreamsUpdated', e => {
|
|
// Subscribe to new remote participant's video streams that were added.
|
|
e.added.forEach(remoteVideoStream => {
|
|
this.subscribeToRemoteVideoStream(remoteVideoStream);
|
|
});
|
|
// Unsubscribe from remote participant's video streams that were removed.
|
|
e.removed.forEach(remoteVideoStream => {
|
|
console.log('Remote participant video stream was removed.');
|
|
})
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
async subscribeToRemoteVideoStream(remoteVideoStream) {
|
|
let renderer = new VideoStreamRenderer(remoteVideoStream);
|
|
let view;
|
|
|
|
const createView = async () => {
|
|
// Create a renderer view for the remote video stream.
|
|
view = await renderer.createView();
|
|
this.remoteVideoIsAvailableChanged?.({
|
|
isAvailable: remoteVideoStream.isAvailable,
|
|
participantId: remoteVideoStream.tsParticipantId,
|
|
el: view.target
|
|
});
|
|
}
|
|
|
|
// Remote participant has switched video on/off
|
|
remoteVideoStream.on('isAvailableChanged', async () => {
|
|
try {
|
|
if (remoteVideoStream.isAvailable) {
|
|
await createView();
|
|
} else {
|
|
view?.dispose();
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
});
|
|
|
|
// Remote participant has video on initially.
|
|
if (remoteVideoStream.isAvailable) {
|
|
try {
|
|
await createView();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
async createLocalVideoStream() {
|
|
const camera = (await this.deviceManager.getCameras())[0];
|
|
if (camera) {
|
|
return new LocalVideoStream(camera);
|
|
} else {
|
|
console.error(`No camera device found on the system`);
|
|
}
|
|
}
|
|
|
|
leaveRoom() {
|
|
if (this.call) {
|
|
this.call.hangUp();
|
|
}
|
|
}
|
|
|
|
async displayLocalVideoStream(lvs) {
|
|
try {
|
|
let localVideoStreamRenderer = new VideoStreamRenderer(lvs);
|
|
const view = await localVideoStreamRenderer.createView();
|
|
this.onCreateLocalVideoStream?.(view.target);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default VideoCall; |