export default class CallWindow { private iframe: HTMLIFrameElement; private vRemote: HTMLVideoElement | null = null; private vLocal: HTMLVideoElement | null = null; private audioBtn: HTMLAnchorElement | null = null; private videoBtn: HTMLAnchorElement | null = null; private userNameSpan: HTMLSpanElement | null = null; private vPlaceholder: HTMLParagraphElement | null = null; private tsInterval: ReturnType; constructor(endCall: () => void) { const iframe = this.iframe = document.createElement('iframe'); Object.assign(iframe.style, { position: "fixed", zIndex: 2147483647 - 1, //borderRadius: ".25em .25em .4em .4em", //border: "4px rgba(0, 0, 0, .7)", border: "none", bottom: "10px", right: "10px", background: "white", height: "200px", width: "200px", }); document.body.appendChild(iframe); const doc = iframe.contentDocument; if (!doc) { console.error("OpenReplay: CallWindow iframe document is not reachable.") return; } fetch("https://static.openreplay.com/tracker-assist/index.html") //fetch("file:///Users/shikhu/work/asayer-tester/dist/assist/index.html") .then(r => r.text()) .then((text) => { iframe.onload = () => { doc.body.removeChild(doc.body.children[0]); //?!!>R# const assistSection = doc.getElementById("or-assist") assistSection && assistSection.removeAttribute("style"); iframe.style.height = doc.body.scrollHeight + 'px'; iframe.style.width = doc.body.scrollWidth + 'px'; iframe.onload = null; } text = text.replace(/href="css/g, "href=\"https://static.openreplay.com/tracker-assist/css") doc.open(); doc.write(text); doc.close(); this.vLocal = doc.getElementById("video-local") as HTMLVideoElement; this.vRemote = doc.getElementById("video-remote") as HTMLVideoElement; // this.vLocal.parentElement && this.vLocal.parentElement.classList.add("d-none"); this.audioBtn = doc.getElementById("audio-btn") as HTMLAnchorElement; this.audioBtn.onclick = () => this.toggleAudio(); this.videoBtn = doc.getElementById("video-btn") as HTMLAnchorElement; this.videoBtn.onclick = () => this.toggleVideo(); this.userNameSpan = doc.getElementById("username") as HTMLSpanElement; this.vPlaceholder = doc.querySelector("#remote-stream p") this._trySetAssistentName(); this._trySetStreams(); const endCallBtn = doc.getElementById("end-call-btn") as HTMLAnchorElement; endCallBtn.onclick = endCall; const tsText = doc.getElementById("time-stamp"); const startTs = Date.now(); if (tsText) { this.tsInterval = setInterval(() => { const ellapsed = Date.now() - startTs; const secsFull = ~~(ellapsed / 1000); const mins = ~~(secsFull / 60); const secs = secsFull - mins * 60 tsText.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}`; }, 500); } // TODO: better D'n'D doc.body.setAttribute("draggable", "true"); doc.body.ondragstart = (e) => { if (!e.dataTransfer || !e.target) { return; } //@ts-ignore if (!e.target.classList || !e.target.classList.contains("card-header")) { return; } e.dataTransfer.setDragImage(doc.body, e.clientX, e.clientY); }; doc.body.ondragend = e => { Object.assign(iframe.style, { left: `${e.clientX}px`, // TODO: fix in case e is inside the iframe top: `${e.clientY}px`, bottom: 'auto', right: 'auto', }) } }); } // TODO: load(): Promise private aRemote: HTMLAudioElement | null = null; private localStream: MediaStream | null = null; private remoteStream: MediaStream | null = null; private setLocalVideoStream: (MediaStream) => void = () => {}; private videoRequested: boolean = true; // TODO: green camera light private _trySetStreams() { if (this.vRemote && !this.vRemote.srcObject && this.remoteStream) { this.vRemote.srcObject = this.remoteStream; if (this.vPlaceholder) { this.vPlaceholder.innerText = "Video has been paused. Click anywhere to resume."; } // Hack for audio (doesen't work in 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 = this.remoteStream; document.body.appendChild(this.aRemote) } if (this.vLocal && !this.vLocal.srcObject && this.localStream) { this.vLocal.srcObject = this.localStream; } } setRemoteStream(rStream: MediaStream) { this.remoteStream = rStream; this._trySetStreams(); } setLocalStream(lStream: MediaStream, setLocalVideoStream: (MediaStream) => void) { lStream.getVideoTracks().forEach(track => { track.enabled = false; }); this.localStream = lStream; this.setLocalVideoStream = setLocalVideoStream; this._trySetStreams(); } playRemote() { this.vRemote && this.vRemote.play() } // TODO: determined workflow _trySetAssistentName() { if (this.userNameSpan && this.assistentName) { this.userNameSpan.innerText = this.assistentName; } } private assistentName: string = ""; setAssistentName(name: string) { this.assistentName = name; this._trySetAssistentName(); } toggleAudio() { let enabled = true; this.localStream?.getAudioTracks().forEach(track => { enabled = enabled && !track.enabled; track.enabled = enabled; }); const cList = this.audioBtn?.classList; if (!this.audioBtn) { return; } if (enabled) { this.audioBtn.classList.remove("muted"); this.audioBtn.childNodes[1].textContent = "Mute"; } else { this.audioBtn.classList.add("muted"); this.audioBtn.childNodes[1].textContent = "Unmute"; } } private _toggleVideoUI(enabled) { if (!this.videoBtn || !this.vLocal || !this.vLocal.parentElement) { return; } if (enabled) { this.vLocal.parentElement.classList.remove("d-none"); this.videoBtn.classList.remove("off"); this.videoBtn.childNodes[1].textContent = "Stop Video"; } else { this.vLocal.parentElement.classList.add("d-none"); this.videoBtn.classList.add("off"); this.videoBtn.childNodes[1].textContent = "Start Video"; } } toggleVideo() { if (!this.videoRequested) { navigator.mediaDevices.getUserMedia({video:true, audio:false}).then(vd => { this.videoRequested = true; this.setLocalVideoStream(vd); this._toggleVideoUI(true); this.localStream?.getVideoTracks().forEach(track => { track.enabled = true; }) }); return; } let enabled = true; this.localStream?.getVideoTracks().forEach(track => { enabled = enabled && !track.enabled; track.enabled = enabled; }); this._toggleVideoUI(enabled); } remove() { clearInterval(this.tsInterval); if (this.iframe.parentElement) { document.body.removeChild(this.iframe); } if (this.aRemote && this.aRemote.parentElement) { document.body.removeChild(this.aRemote); } } }