import type { LocalStream, } from './LocalStream.js' import attachDND from './dnd.js' const SS_START_TS_KEY = '__openreplay_assist_call_start_ts' export default class CallWindow { private iframe: HTMLIFrameElement private vRemote: HTMLVideoElement | null = null 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 videoContainer: HTMLElement | null = null private vPlaceholder: HTMLElement | null = null private tsInterval: ReturnType private load: Promise constructor() { const iframe = this.iframe = document.createElement('iframe') Object.assign(iframe.style, { position: 'fixed', zIndex: 2147483647 - 1, border: 'none', bottom: '10px', right: '10px', height: '200px', width: '200px', }) // TODO: find the best attribute name for the ignoring iframes iframe.setAttribute('data-openreplay-obscured', '') iframe.setAttribute('data-openreplay-hidden', '') iframe.setAttribute('data-openreplay-ignore', '') document.body.appendChild(iframe) const doc = iframe.contentDocument if (!doc) { console.error('OpenReplay: CallWindow iframe document is not reachable.') return } //const baseHref = "https://static.openreplay.com/tracker-assist/test" const baseHref = 'https://static.openreplay.com/tracker-assist/3.4.4' this.load = fetch(baseHref + '/index.html') .then(r => r.text()) .then((text) => { iframe.onload = () => { const assistSection = doc.getElementById('or-assist') assistSection?.classList.remove('status-connecting') //iframe.style.height = doc.body.scrollHeight + 'px'; //iframe.style.width = doc.body.scrollWidth + 'px'; this.adjustIframeSize() iframe.onload = null } // ? text = text.replace(/href="css/g, `href="${baseHref}/css`) doc.open() doc.write(text) doc.close() this.vLocal = doc.getElementById('video-local') as (HTMLVideoElement | null) this.vRemote = doc.getElementById('video-remote') as (HTMLVideoElement | null) this.videoContainer = doc.getElementById('video-container') this.audioBtn = doc.getElementById('audio-btn') if (this.audioBtn) { this.audioBtn.onclick = () => this.toggleAudio() } this.videoBtn = doc.getElementById('video-btn') if (this.videoBtn) { this.videoBtn.onclick = () => this.toggleVideo() } this.endCallBtn = doc.getElementById('end-call-btn') this.agentNameElem = doc.getElementById('agent-name') this.vPlaceholder = doc.querySelector('#remote-stream p') const tsElem = doc.getElementById('duration') if (tsElem) { const startTs = Number(sessionStorage.getItem(SS_START_TS_KEY)) || Date.now() sessionStorage.setItem(SS_START_TS_KEY, startTs.toString()) this.tsInterval = setInterval(() => { const ellapsed = Date.now() - startTs const secsFull = ~~(ellapsed / 1000) const mins = ~~(secsFull / 60) const secs = secsFull - mins * 60 tsElem.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}` }, 500) } const dragArea = doc.querySelector('.drag-area') if (dragArea) { // TODO: save coordinates on the new page attachDND(iframe, dragArea, doc.documentElement) } }) //this.toggleVideoUI(false) //this.toggleRemoteVideoUI(false) } private adjustIframeSize() { const doc = this.iframe.contentDocument if (!doc) { return } this.iframe.style.height = doc.body.scrollHeight + 'px' this.iframe.style.width = doc.body.scrollWidth + 'px' } setCallEndAction(endCall: () => void) { this.load.then(() => { if (this.endCallBtn) { this.endCallBtn.onclick = endCall } }) } private aRemote: HTMLAudioElement | null = null; private checkRemoteVideoInterval: ReturnType setRemoteStream(rStream: MediaStream) { this.load.then(() => { if (this.vRemote && !this.vRemote.srcObject) { this.vRemote.srcObject = rStream if (this.vPlaceholder) { this.vPlaceholder.innerText = 'Video has been paused. Click anywhere to resume.' } // Hack for audio. Doesen't work inside the iframe because of some magical reasons (check if it is connected to autoplay?) this.aRemote = document.createElement('audio') this.aRemote.autoplay = true this.aRemote.style.display = 'none' this.aRemote.srcObject = rStream document.body.appendChild(this.aRemote) } // Hack to determine if the remote video is enabled if (this.checkRemoteVideoInterval) { clearInterval(this.checkRemoteVideoInterval) } // just in case let enabled = false this.checkRemoteVideoInterval = setInterval(() => { const settings = rStream.getVideoTracks()[0]?.getSettings() //console.log(settings) const isDummyVideoTrack = !!settings && (settings.width === 2 || settings.frameRate === 0) const shouldBeEnabled = !isDummyVideoTrack if (enabled !== shouldBeEnabled) { this.toggleRemoteVideoUI(enabled=shouldBeEnabled) } }, 1000) }) } toggleRemoteVideoUI(enable: boolean) { this.load.then(() => { if (this.videoContainer) { if (enable) { this.videoContainer.classList.add('remote') } else { this.videoContainer.classList.remove('remote') } this.adjustIframeSize() } }) } private localStream: LocalStream | null = null; // TODO: on construction? setLocalStream(lStream: LocalStream) { this.localStream = lStream } playRemote() { this.vRemote && this.vRemote.play() } setAssistentName(name: string) { this.load.then(() => { if (this.agentNameElem) { this.agentNameElem.innerText = name } }) } private toggleAudioUI(enabled: boolean) { if (!this.audioBtn) { return } if (enabled) { this.audioBtn.classList.remove('muted') } else { this.audioBtn.classList.add('muted') } } private toggleAudio() { const enabled = this.localStream?.toggleAudio() || false this.toggleAudioUI(enabled) } private toggleVideoUI(enabled: boolean) { if (!this.videoBtn || !this.videoContainer) { return } if (enabled) { this.videoContainer.classList.add('local') this.videoBtn.classList.remove('off') } else { this.videoContainer.classList.remove('local') this.videoBtn.classList.add('off') } this.adjustIframeSize() } private videoRequested = false private toggleVideo() { this.localStream?.toggleVideo() .then(enabled => { this.toggleVideoUI(enabled) this.load.then(() => { if (this.vLocal && this.localStream && !this.vLocal.srcObject) { this.vLocal.srcObject = this.localStream.stream } }) }) } remove() { this.localStream?.stop() clearInterval(this.tsInterval) clearInterval(this.checkRemoteVideoInterval) if (this.iframe.parentElement) { document.body.removeChild(this.iframe) } if (this.aRemote && this.aRemote.parentElement) { document.body.removeChild(this.aRemote) } sessionStorage.removeItem(SS_START_TS_KEY) } }