diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 6c4c23ed9..bd31a3983 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -146,7 +146,7 @@ function AssistActions({ setIncomeStream([]); }; - function call(additionalAgentIds?: string[]) { + function call() { RequestLocalStream() .then((lStream) => { setLocalStream(lStream); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx index 2d3e3fc9a..afde85b68 100644 --- a/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerBlockHeader.tsx @@ -87,7 +87,7 @@ function LivePlayerBlockHeader({ )} - + diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index 6b5e1782d..4197f6e9f 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -39,6 +39,7 @@ export default class WebLivePlayer extends WebPlayer { config, wpState, (id) => this.messageManager.getNode(id), + agentId, uiErrorHandler ); this.assistManager.connect(session.agentToken!, agentId, projectId); diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index be1732ec7..b97a46512 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -10,7 +10,6 @@ import RemoteControl, { RemoteControlStatus } from './RemoteControl'; import ScreenRecording, { SessionRecordingStatus } from './ScreenRecording'; import CanvasReceiver from 'Player/web/assist/CanvasReceiver'; import { gunzipSync } from 'fflate'; -import logger from '@/logger'; export { RemoteControlStatus, SessionRecordingStatus, CallingState }; @@ -68,6 +67,7 @@ export default class AssistManager { ...RemoteControl.INITIAL_STATE, ...ScreenRecording.INITIAL_STATE, }; + private agentIds: string[] = []; // TODO: Session type constructor( @@ -78,9 +78,10 @@ export default class AssistManager { private config: RTCIceServer[] | null, private store: Store, private getNode: MessageManager['getNode'], + public readonly agentId: number, public readonly uiErrorHandler?: { error: (msg: string) => void; - } + }, ) {} public getAssistVersion = () => this.assistVersion; @@ -194,9 +195,9 @@ export default class AssistManager { }, })); - socket.onAny((event, ...args) => { - logger.log(`📩 Socket: ${event}`, args); - }); + // socket.onAny((event, ...args) => { + // logger.log(`📩 Socket: ${event}`, args); + // }); socket.on('connect', () => { @@ -275,6 +276,10 @@ export default class AssistManager { } } } + if (data.agentIds) { + const filteredAgentIds = this.agentIds.filter((id: string) => id.split('-')[3] !== agentId.toString()); + this.agentIds = filteredAgentIds; + } }); socket.on('SESSION_DISCONNECTED', (e) => { waitingForMessages = true; @@ -299,7 +304,8 @@ export default class AssistManager { { ...this.session.agentInfo, id: agentId, - } + }, + this.agentIds, ); this.remoteControl = new RemoteControl( this.store, diff --git a/frontend/app/player/web/assist/Call.ts b/frontend/app/player/web/assist/Call.ts index 00e58d5fe..5880fa6f5 100644 --- a/frontend/app/player/web/assist/Call.ts +++ b/frontend/app/player/web/assist/Call.ts @@ -17,6 +17,12 @@ export interface State { currentTab?: string; } +const WEBRTC_CALL_AGENT_EVENT_TYPES = { + OFFER: 'offer', + ANSWER: 'answer', + ICE_CANDIDATE: 'ice-candidate', +} + export default class Call { private assistVersion = 1; static readonly INITIAL_STATE: Readonly = { @@ -26,6 +32,8 @@ export default class Call { private connections: Record = {}; private connectAttempts = 0; private videoStreams: Record = {}; + private callID: string; + private agentInCallIds: string[] = []; constructor( private store: Store }>, @@ -34,36 +42,29 @@ export default class Call { private peerID: string, private getAssistVersion: () => number, private agent: Record, - private agentInCallIds: string[] = [], - private callId: string, + private agentIds: string[], ) { socket.on('WEBRTC_AGENT_CALL', (data) => { - console.log("!WEBRTC AGENT CALL RECEIVED", data); + console.log("!WEBRTC AGENT CALL RECEIVED", data.type, Object.keys(this.connections)); + switch (data.type) { + case WEBRTC_CALL_AGENT_EVENT_TYPES.OFFER: + this.handleOffer(data, true); + break; + case WEBRTC_CALL_AGENT_EVENT_TYPES.ICE_CANDIDATE: + console.log("#### RECEIVED ICE CANDIDATE", data); + this.handleIceCandidate(data); + break; + case WEBRTC_CALL_AGENT_EVENT_TYPES.ANSWER: + this.handleAnswer(data, true); + default: + break; + } }) socket.on('UPDATE_SESSION', (data: { data: { agentIds: string[] }}) => { - const { agentIds } = data.data; - console.log("AGENT IDS", agentIds); - if (agentIds) { - const filteredAgentIds = agentIds.filter((id: string) => id.split('_')[3] !== this.agent.id.toString()); - console.log("!!! FILTERED IDS", filteredAgentIds); - const newIds = filteredAgentIds.filter((id: string) => !this.agentInCallIds.includes(id)); - console.log("!!! NEW IDS", newIds); - const removedIds = this.agentInCallIds.filter((id: string) => !filteredAgentIds.includes(id)); - console.log("!!! REMOVED IDS", removedIds); - removedIds.forEach((id: string) => this.agentDisconnected(id)); - if (store.get().calling !== CallingState.OnCall) { - newIds.forEach((id: string) => { - console.log("CALL3 for", id); - const socketId = getSocketIdByCallId(id); - console.log("FOUND SOCKET ID", socketId); - this._peerConnection(id, true, socketId); - }); - } - - this.agentInCallIds = filteredAgentIds; - } + console.log("UPDATE SESSION", data.data.agentIds); + this.callAgentsInSession({ agentIds: data.data.agentIds }); }); socket.on('call_end', () => { @@ -108,6 +109,7 @@ export default class Call { }); socket.on('webrtc_call_answer', (data: { data: { from: string, answer: RTCSessionDescriptionInit } }) => { + console.log("ПРИШЕЛ оБЫЧНЫЙ сОКЕТ ANSWER", data.data); this.handleAnswer(data.data); }); socket.on('webrtc_call_ice_candidate', (data: { data: { from: string, candidate: RTCIceCandidateInit } }) => { @@ -118,12 +120,17 @@ export default class Call { } // CREATE A LOCAL PEER - private async createPeerConnection(callId: string): Promise { + private async createPeerConnection({ remotePeerId, localPeerId, isAgent }: { remotePeerId: string, isAgent?: boolean, localPeerId?: string }): Promise { // create pc with ice config + const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }], }); + if (isAgent) { + console.log('!!! CREATED PC FOR AGENT', remotePeerId); + } + // If there is a local stream, add its tracks to the connection if (this.callArgs && this.callArgs.localStream && this.callArgs.localStream.stream) { this.callArgs.localStream.stream.getTracks().forEach((track) => { @@ -134,7 +141,12 @@ export default class Call { // when ice is ready we send it pc.onicecandidate = (event) => { if (event.candidate) { - this.socket.emit('webrtc_call_ice_candidate', { from: callId, candidate: event.candidate }); + if (isAgent) { + console.log("!!! SEND ICE CANDIDATE for", getSocketIdByCallId(remotePeerId)); + this.socket.emit('WEBRTC_AGENT_CALL', { from: localPeerId, candidate: event.candidate, toAgentId: getSocketIdByCallId(remotePeerId), type: WEBRTC_CALL_AGENT_EVENT_TYPES.ICE_CANDIDATE }); + } else { + this.socket.emit('webrtc_call_ice_candidate', { from: remotePeerId, candidate: event.candidate }); + } } else { logger.log("ICE candidate gathering complete"); } @@ -143,13 +155,15 @@ export default class Call { // when we receive a remote track, we write it to videoStreams[peerId] pc.ontrack = (event) => { const stream = event.streams[0]; - if (stream) { - this.videoStreams[callId] = stream.getVideoTracks()[0]; + if (stream && !this.videoStreams[remotePeerId]) { + const clonnedStream = stream.clone(); + console.log('SETTING VIDEOTRACKS TO', remotePeerId); + this.videoStreams[remotePeerId] = clonnedStream.getVideoTracks()[0]; if (this.store.get().calling !== CallingState.OnCall) { this.store.update({ calling: CallingState.OnCall }); } if (this.callArgs) { - this.callArgs.onStream(stream, isAgentId(callId)); + this.callArgs.onStream(stream, remotePeerId !== this.callID && isAgentId(remotePeerId)); } } }; @@ -177,21 +191,22 @@ export default class Call { } // ESTABLISHING A CONNECTION - private async _peerConnection(remotePeerId: string, isAgent?: boolean, socketId?: string) { + private async _peerConnection({ remotePeerId, isAgent, socketId, localPeerId }: { remotePeerId: string, isAgent?: boolean, socketId?: string, localPeerId?: string }) { console.log("_ PEER CONNECTION", remotePeerId); try { - // Create RTCPeerConnection - const pc = await this.createPeerConnection(remotePeerId); + // Create RTCPeerConnection with client + const pc = await this.createPeerConnection({ remotePeerId, localPeerId, isAgent }); this.connections[remotePeerId] = pc; // Create an SDP offer const offer = await pc.createOffer(); await pc.setLocalDescription(offer); + console.log("1 !!! CREATED PC WITH OFFER FOR AN AGENT", pc, offer); // Sending offer if (isAgent) { - console.log("CALL6", remotePeerId, socketId); - this.socket.emit('WEBRTC_AGENT_CALL', { from: remotePeerId, offer, toAgentId: socketId }); + console.log("2 !!! SENDING OFFER TO AGENT", socketId); + this.socket.emit('WEBRTC_AGENT_CALL', { from: localPeerId, offer, toAgentId: socketId, type: WEBRTC_CALL_AGENT_EVENT_TYPES.OFFER }); } else { this.socket.emit('webrtc_call_offer', { from: remotePeerId, offer }); } @@ -204,8 +219,7 @@ export default class Call { this.connectAttempts++; logger.log('reconnecting', this.connectAttempts); await new Promise((resolve) => setTimeout(resolve, 250)); - console.log("CALL2") - await this._peerConnection(remotePeerId); + await this._peerConnection({ remotePeerId }); } else { logger.log('error', this.connectAttempts); this.callArgs?.onError?.('Could not establish a connection with the peer after 5 attempts'); @@ -216,21 +230,34 @@ export default class Call { } // Process the received offer to answer - private async handleOffer(data: { from: string, offer: RTCSessionDescriptionInit }) { + private async handleOffer(data: { from: string, offer: RTCSessionDescriptionInit }, isAgent?: boolean) { // set to remotePeerId data.from - const callId = data.from; - const pc = this.connections[callId]; + const fromCallId = data.from; + let pc = this.connections[fromCallId]; + console.log("3 !!! HANDLE OFFER", isAgent, pc); if (!pc) { - logger.error("No connection found for remote peer", callId); - return; + if (isAgent) { + this.connections[fromCallId] = await this.createPeerConnection({ remotePeerId: fromCallId, isAgent, localPeerId: this.callID }); + pc = this.connections[fromCallId]; + console.log("4 CREATED NEW PC FOR INCOMING OFFER", pc); + } else { + logger.error("No connection found for remote peer", fromCallId); + return; + } } try { // if the connection is not established yet, then set remoteDescription to peer - if (pc.signalingState !== "stable") { + if (!pc.localDescription) { await pc.setRemoteDescription(new RTCSessionDescription(data.offer)); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); - this.socket.emit('webrtc_call_answer', { from: callId, answer: pc.localDescription }); + if (isAgent) { + console.log("4 !!! SENDING ANSWER TO AGENT", getSocketIdByCallId(fromCallId)); + this.socket.emit('WEBRTC_AGENT_CALL', { from: this.callID, answer, toAgentId: getSocketIdByCallId(fromCallId), type: WEBRTC_CALL_AGENT_EVENT_TYPES.ANSWER }); + } else { + console.log("4.1 !!! SENDING PLAIN ANSWER SOCKET TO AGENT", getSocketIdByCallId(fromCallId)); + this.socket.emit('webrtc_call_answer', { from: fromCallId, answer }); + } } else { logger.warn("Skipping setRemoteDescription: Already in stable state"); } @@ -241,15 +268,16 @@ export default class Call { } // Process the received answer to offer - private async handleAnswer(data: { from: string, answer: RTCSessionDescriptionInit }) { + private async handleAnswer(data: { from: string, answer: RTCSessionDescriptionInit }, isAgent?: boolean) { // set to remotePeerId data.from - if (this.agentInCallIds.includes(data.from)) { + console.log("5 !!! HANDLE ANSWER", this.connections, data.from, this.connections[data.from]); + if (this.agentInCallIds.includes(data.from) && !isAgent) { return; } const callId = data.from; const pc = this.connections[callId]; if (!pc) { - logger.error("No connection found for remote peer", callId); + logger.error("No connection found for remote peer", callId, this.connections); return; } try { @@ -267,6 +295,7 @@ export default class Call { // process the received iceCandidate private async handleIceCandidate(data: { from: string, candidate: RTCIceCandidateInit }) { + console.log("### GET ICE CANDIDATE", data); const callId = data.from; const pc = this.connections[callId]; if (!pc) return; @@ -317,7 +346,7 @@ export default class Call { // Ends the call and sends the call_end signal initiateCallEnd = async () => { - this.emitData('call_end', this.callId); + this.emitData('call_end', this.callID); this.handleCallEnd(); }; @@ -339,7 +368,7 @@ export default class Call { setCallArgs( localStream: LocalStream, - onStream: (s: MediaStream) => void, + onStream: (s: MediaStream, isAgent: boolean) => void, onCallEnd: () => void, onReject: () => void, onError?: (e?: any) => void @@ -354,12 +383,10 @@ export default class Call { } // Initiates a call - call(thirdPartyPeers?: string[]): { end: () => void } { - if (thirdPartyPeers && thirdPartyPeers.length > 0) { - this.addPeerCall(thirdPartyPeers); - } else { - this._callSessionPeer(); - } + call(): { end: () => void } { + console.log("INTIATE CALL", this.agentIds); + this._callSessionPeer(); + // this.callAgentsInSession({ agentIds: this.agentInCallIds }); return { end: this.initiateCallEnd, }; @@ -367,7 +394,7 @@ export default class Call { // Notify peers of local video state change toggleVideoLocalStream(enabled: boolean) { - this.emitData('videofeed', { streamId: this.peerID, enabled }); + this.emitData('videofeed', { streamId: this.callID, enabled }); } // Connect with other agents @@ -378,7 +405,6 @@ export default class Call { // Calls the method to create a connection with a peer private _callSessionPeer() { - console.log("CALL1") if (![CallingState.NoCall, CallingState.Reconnecting].includes(this.store.get().calling)) { return; } @@ -389,17 +415,42 @@ export default class Call { } // Generate a peer identifier depending on the assist version - const peerId = - this.getAssistVersion() === 1 - ? this.peerID - : `${this.peerID}_${tab || Array.from(this.store.get().tabs)[0]}_${this.agent.id}_${this.socket.id}_agent`; + this.callID = this.getCallId(); - console.log("PEER IDDDD", peerId); - this.callId = peerId; - const userName = userStore.account.name; this.emitData('_agent_name', userName); - void this._peerConnection(peerId); + void this._peerConnection({ remotePeerId: this.callID }); + } + + private callAgentsInSession({ agentIds }: { agentIds: string[] }) { + if (agentIds) { + const filteredAgentIds = agentIds.filter((id: string) => id.split('-')[3] !== this.agent.id.toString()); + console.log("!!! FILTERED AGENT IDS", filteredAgentIds, this.agentInCallIds); + const newIds = filteredAgentIds.filter((id: string) => !this.agentInCallIds.includes(id)); + console.log("!!! NEW AGENT IDS", newIds); + const removedIds = this.agentInCallIds.filter((id: string) => !filteredAgentIds.includes(id)); + console.log("!!! REMOVED AGENT IDS", removedIds); + removedIds.forEach((id: string) => this.agentDisconnected(id)); + if (this.store.get().calling === CallingState.OnCall) { + newIds.forEach((id: string) => { + const socketId = getSocketIdByCallId(id); + console.log("!!! INITIALISING CALL WITH AgENT", id); + this._peerConnection({ remotePeerId: id, isAgent: true, socketId, localPeerId: this.callID }); + }); + } + + this.agentInCallIds = filteredAgentIds; + } + } + + private getCallId() { + const tab = this.store.get().currentTab; + if (!tab) { + logger.warn('No tab data to connect to peer'); + } + + // Generate a peer identifier depending on the assist version + return `${this.peerID}-${tab || Array.from(this.store.get().tabs)[0]}-${this.agent.id}-${this.socket.id}-agent`; } agentDisconnected(agentId: string) { @@ -420,6 +471,10 @@ function isAgentId(id: string): boolean { return id.endsWith('_agent'); } -function getSocketIdByCallId(callId: string): string | undefined { - return callId.split('_')[3]; -} \ No newline at end of file +function getSocketIdByCallId(callId?: string): string | undefined { + const socketIdRegex = /-\d{2}-(.*?)\-agent/; + const match = callId?.match(socketIdRegex); + if (match) { + return match[1]; + } +} diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index e9455f788..e8616e44a 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -426,7 +426,6 @@ export default class Assist { if (app.getTabId() !== info.meta.tabId) return const name = info.data callingAgents.set(id, name) - console.log('CALLING AGENTS', callingAgents) updateCallerNames() }) @@ -477,7 +476,6 @@ export default class Assist { }) socket.on('webrtc_call_offer', async (_, data: { from: string, offer: RTCSessionDescriptionInit }) => { - console.log("OFFER FROM", data.from) if (!this.calls.has(data.from)) { await handleIncomingCallOffer(data.from, data.offer); } @@ -517,7 +515,6 @@ export default class Assist { } const handleCallEndWithAgent = (id: string) => { - console.log("!!!!", this.calls.get(id)) this.calls.get(id)?.close() this.calls.delete(id) } @@ -621,6 +618,7 @@ export default class Assist { // get all local tracks and add them to RTCPeerConnection lStreams[from].stream.getTracks().forEach(track => { + console.log('GETTING TRACKS FROM', from); pc.addTrack(track, lStreams[from].stream); }); @@ -635,10 +633,10 @@ export default class Assist { pc.ontrack = (event) => { const rStream = event.streams[0]; if (rStream && callUI) { - + console.log('2 GETTING TRACKS FROM', from); callUI.addRemoteStream(rStream, from); const onInteraction = () => { - callUI?.playRemote(); + callUI?.playRemote(from); document.removeEventListener('click', onInteraction); }; document.addEventListener('click', onInteraction); diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index 0d90ca5f7..60d2dbee2 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -3,15 +3,252 @@ import attachDND from './dnd.js' const SS_START_TS_KEY = '__openreplay_assist_call_start_ts' +const text = ` + + + + + + + + OpenReplay | Assist + + + + + + + + + + +
+
+
Connecting...
+
+
+ +
+ + 00:00 +
+
+
+
+ + +
+ +
+
+
+ + +
+
+
This tab has remote control access
+ +
+
+
+ + + +` + export default class CallWindow { - private remoteVideoId: string private readonly iframe: HTMLIFrameElement - private vRemote: HTMLVideoElement | null = null + private vRemote: Map = new Map() private vLocal: HTMLVideoElement | null = null private audioBtn: HTMLElement | null = null private videoBtn: HTMLElement | null = null private endCallBtn: HTMLElement | null = null private agentNameElem: HTMLElement | null = null + private remoteStreamVideoContainerSample: HTMLElement | null = null private videoContainer: HTMLElement | null = null private vPlaceholder: HTMLElement | null = null private remoteControlContainer: HTMLElement | null = null @@ -19,7 +256,8 @@ export default class CallWindow { private controlsContainer: HTMLElement | null = null private onToggleVideo: (args: any) => void private tsInterval: ReturnType - private remoteVideo: MediaStreamTrack + private remoteVideos: Map = new Map() + private vContainer: HTMLDivElement | null = null private readonly load: Promise @@ -51,7 +289,7 @@ export default class CallWindow { // this.load = fetch(this.callUITemplate || baseHref + '/index2.html') this.load = fetch(this.callUITemplate || baseHref + '/index.html') .then((r) => r.text()) - .then((text) => { + .then(() => { iframe.onload = () => { const assistSection = doc.getElementById('or-assist') setTimeout(() => { @@ -62,17 +300,19 @@ export default class CallWindow { this.adjustIframeSize() iframe.onload = null } - // ? - text = text.replace(/href="css/g, `href="${baseHref}/css`) + const newText = text.replace(/href="css/g, `href="${baseHref}/css`) doc.open() - doc.write(text) + doc.write(newText) doc.close() this.vLocal = doc.getElementById('video-local') as HTMLVideoElement | null - this.vRemote = doc.getElementById('video-remote') as HTMLVideoElement | null + // this.vRemote = doc.getElementById('video-remote') as HTMLVideoElement | null + this.remoteStreamVideoContainerSample = doc.querySelector('[data-attr="remote-stream"]'); + this.remoteStreamVideoContainerSample?.remove(); + this.videoContainer = doc.getElementById('video-container') - + this.audioBtn = doc.getElementById('audio-btn') if (this.audioBtn) { this.audioBtn.onclick = () => this.toggleAudio() @@ -138,30 +378,52 @@ export default class CallWindow { this.load .then(() => { // Video - if (this.vRemote && !this.vRemote.srcObject) { - this.vRemote.srcObject = rStream - this.remoteVideo = rStream.getVideoTracks()[0] - this.remoteVideoId = peerId - if (this.vPlaceholder) { - this.vPlaceholder.innerText = - 'Video has been paused. Click anywhere to resume.' - } - // Hack to determine if the remote video is enabled - // TODO: pass this info through socket - if (this.checkRemoteVideoInterval) { - clearInterval(this.checkRemoteVideoInterval) - } // just in case - let enabled = false - this.checkRemoteVideoInterval = setInterval(() => { - const settings = this.remoteVideo?.getSettings() - const isDummyVideoTrack = !this.remoteVideo.enabled || (!!settings && (settings.width === 2 || settings.frameRate === 0)) - const shouldBeEnabled = !isDummyVideoTrack - if (enabled !== shouldBeEnabled) { - this.toggleRemoteVideoUI((enabled = shouldBeEnabled)) + console.log('VREMOTE', this.vRemote); + if (!this.vRemote.has(peerId)) { + if (this.remoteStreamVideoContainerSample && this.videoContainer) { + const newRemoteStreamVideoContainer = this.remoteStreamVideoContainerSample.cloneNode(true) as HTMLDivElement; + newRemoteStreamVideoContainer.setAttribute("data-peer-id", peerId); + + const videoElement = document.createElement("video"); + videoElement.autoplay = true; + newRemoteStreamVideoContainer.appendChild(videoElement); + const clonedStream = rStream.clone() + videoElement.srcObject = clonedStream + + const videoElementTest = document.createElement("video"); + videoElementTest.autoplay = true; + videoElementTest.srcObject = clonedStream; + videoElementTest.setAttribute("data-peer-id", peerId); + document.body.appendChild(videoElementTest); + + this.remoteVideos.set(peerId, clonedStream.getVideoTracks()[0]) + console.log("ADD REMOTE STREAM", clonedStream.getVideoTracks()[0]); + if (this.vPlaceholder) { + this.vPlaceholder.innerText = + 'Video has been paused. Click anywhere to resume.' } - }, 1000) + this.vRemote.set(peerId, videoElement) + this.videoContainer.appendChild(newRemoteStreamVideoContainer); + console.log('ДОБАВИЛИ ВИДЕО В ДОМ для', peerId); + } } + // Hack to determine if the remote video is enabled + // TODO: pass this info through socket + if (this.checkRemoteVideoInterval) { + clearInterval(this.checkRemoteVideoInterval) + } // just in case + let enabled = false + this.checkRemoteVideoInterval = setInterval(() => { + const settings = this.remoteVideos.get(peerId)?.getSettings() + const isDummyVideoTrack = !this.remoteVideos.get(peerId)?.enabled || (!!settings && (settings.width === 2 || settings.frameRate === 0)) + const shouldBeEnabled = !isDummyVideoTrack + if (enabled !== shouldBeEnabled) { + this.toggleRemoteVideoUI((enabled = shouldBeEnabled)) + } + }, 1000) + + // Audio if (!this.audioContainer) { this.audioContainer = document.createElement('div') @@ -199,8 +461,13 @@ export default class CallWindow { this.localStreams = streams } - playRemote() { - this.vRemote && this.vRemote.play() + playRemote(peerId: string) { + if (this.vRemote.has(peerId)) { + const vRemote = this.vRemote.get(peerId) + if (vRemote) { + vRemote.play() + } + } } setAssistentName(callingAgents: Map) { @@ -329,9 +596,16 @@ export default class CallWindow { } toggleVideoStream({ streamId, enabled, }: { streamId: string, enabled: boolean }) { - if (this.remoteVideoId === streamId) { - this.remoteVideo.enabled = enabled - this.toggleRemoteVideoUI(enabled) + if (this.remoteVideos.has(streamId)) { + console.log("TRYING TO ENABLE", this.remoteVideos.get[streamId]) + const track = this.remoteVideos.get(streamId) + if (track) { + console.log('ENABLING TRACK', track); + track.enabled = enabled + } + if (this.vRemote.size <= 1) { + this.toggleRemoteVideoUI(enabled) + } } } }