// WebRTC Sdp Player export default class sdpPlayer { private pc: any; public player: any; private statsTimeoutHandler: any; private statsFun: Function = new Function(); private startFun: Function = new Function(); private errorFun: Function = (e)=>{ clearInterval(this.statsTimeoutHandler); }; public setView(id: string): void { this.player = document.getElementById(id); } /** * video play * @param url .sdp video url */ public start(url: string): void { if (url === '' && !this.player) { cc.error("need url and set player ID"); this.errorFun(`need url and set player ID`); // cahnge to error Fun return; } // cc.log('Start play'); const offerSdpOption = { offerToReceiveAudio: true, offerToReceiveVideo: true }; this.pc = new RTCPeerConnection(); this.pc.addTransceiver("audio", { direction: "recvonly" }); this.pc.addTransceiver("video", { direction: "recvonly" }); this.pc.oniceconnectionstatechange = this.onIceStateChange(this.pc); this.pc.stream = new MediaStream(); this.pc.ontrack = this.gotRemoteStream.bind(this); this.pc.createOffer(offerSdpOption).then((offer) => { this.pc.setLocalDescription(offer); const xmlhttp = new XMLHttpRequest(); // new HttpRequest instance xmlhttp.timeout = 6000; xmlhttp.onerror = this.errorFun.bind(this); // xmlerror to error Fun xmlhttp.ontimeout = this.errorFun.bind(this); // timeout to error Fun xmlhttp.onload = () => { if (xmlhttp.status == 200) { const data = JSON.parse(xmlhttp.responseText); const remoteSdp = data.remoteSdp; this.pc.setRemoteDescription( new RTCSessionDescription(remoteSdp), () => { cc.log("setRemoteDescription success!"); }, (e) => { stop(); this.errorFun(e.message); // cahnge to error Fun cc.log("setRemoteDescription failed, message:" + e.message); } ); this.startFun(); this.onStatus(); } else { this.errorFun(`xml status: ${xmlhttp.status}`); // cahnge to error Fun } }; xmlhttp.open("POST", url); xmlhttp.send(JSON.stringify({ localSdp: offer })); }).catch((reason) => { this.errorFun(reason); // cahnge to error Fun }); } private onIceStateChange(pc): void { if (pc) { if (pc.iceConnectionState == 'disconnected') stop(); cc.log('ICE state: ' + pc.iceConnectionState); } } private gotRemoteStream(m): void { cc.log("ontrack, kind:" + m.track.kind); this.pc.stream.addTrack(m.track); this.player.srcObject = this.pc.stream; } // video status event timer private onStatus(): void { var checkInterval = 1000; // check every 1s (do not use lower values) this.statsTimeoutHandler = setInterval(this.statsFun, checkInterval) } /** * video event listener * @param event start,stats,error * @param fn listener callback function */ public on(event: "start" | "stats" | "error", fn: Function): void { this[event + "Fun"] = (event === "error") ? (e) => { clearInterval(this.statsTimeoutHandler); fn(e); } : fn; } /** video voice (support max and min voice only) */ public setVolume(num: Number): void { if (this.player) { if (num == 0) this.player.muted = true; else this.player.muted = false; } } /** video stop */ public stop(): void { clearInterval(this.statsTimeoutHandler); try { if (this.player) { const tracks = this.player.srcObject?.getTracks(); this.player.load(); if (tracks) { tracks.forEach(track => track.stop()); this.pc?.removeStream(this.player.srcObject); } } } catch(ex) { cc.log(ex); } this.pc?.close(); this.pc = null; } /** auto play active (need user touch active) */ public autoPlayActive(): void { if (this.player) { this.player.muted = false; this.player.controls = false; this.player.autoplay = true; this.player.playsinline = true; } } }