UNPKG

web3d

Version:

MetaEditor — ReactJS Pixel Streaming library, helps integrate Unreal Engine v.5 in the browser. Allows you to send commands and get callbacks from the stream server with launched Unreal Engine.

1 lines 11.7 kB
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=webRtcPlayer,require("webrtc-adapter");const{MetaMsg}=require("./middleware/");function webRtcPlayer(a){function b(a){MetaMsg.info("Signaling state change. |",a.srcElement.signalingState,"|")}function c(a){MetaMsg.info("Browser ICE connection |",a.srcElement.iceConnectionState,"|")}function d(a){MetaMsg.info("Browser ICE gathering |",a.srcElement.iceGatheringState,"|")}function e(a){if(a.track&&MetaMsg.log("Got track. | Kind="+a.track.kind+" | Id="+a.track.id+" | readyState="+a.track.readyState+" |"),"audio"==a.track.kind)return void f(a.streams[0]);"video"==a.track.kind;{for(const b of a.streams)p.availableVideoStreams.has(b.id)||p.availableVideoStreams.set(b.id,b);p.video.srcObject=a.streams[0],a.track.onunmute=()=>{p.video.srcObject=a.streams[0];try{p.onNewVideoTrack(a.streams)}catch(a){MetaMsg.error(a)}}}}function f(a){if(p.video.srcObject!=a&&p.video.srcObject&&p.video.srcObject!==a){let b=document.createElement("Audio");if(b.srcObject=a,!p.autoPlayAudio){let a=function(){b.play(),p.video.removeEventListener("click",a)};p.video.addEventListener("click",a)}else b.play();MetaMsg.log("Created new audio element to play seperate audio stream.")}}function g(a){MetaMsg.log("Data channel created for us by browser as we are a receiving peer."),p.dcClient=a.channel,i(p.dcClient)}function h(a,b,c){let d=a.createDataChannel(b,c);return MetaMsg.log(`Created datachannel (${b})`),i(d),d}function i(a){try{return a.binaryType="arraybuffer",a.onopen=function(){MetaMsg.log("Data channel connected"),p.onDataChannelConnected&&p.onDataChannelConnected()},a.onclose=function(a){MetaMsg.log("Data channel connected",a)},a.onmessage=function(a){p.onDataChannelMessage&&p.onDataChannelMessage(a.data)},a.onerror=function(a){MetaMsg.error("Data channel error",a)},a}catch(a){return MetaMsg.warn("No data channel",a),null}}function j(a){let b=a.candidate;b&&b.candidate&&(MetaMsg.log("%c[Browser ICE candidate]","background: violet; color: black","| Type=",b.type,"| Protocol=",b.protocol,"| Address=",b.address,"| Port=",b.port,"|"),p.onWebRtcCandidate(b))}function k(a){a.createOffer(p.sdpConstraints).then(function(b){l(b),a.setLocalDescription(b),p.onWebRtcOffer&&p.onWebRtcOffer(b)},function(){MetaMsg.warn("Couldn't create offer")})}function l(a){a.sdp=a.sdp.replace("useinbandfec=1","useinbandfec=1;stereo=1;sprop-maxcapturerate=48000")}function m(a){a.onsignalingstatechange=b,a.oniceconnectionstatechange=c,a.onicegatheringstatechange=d,a.ontrack=e,a.onicecandidate=j,a.ondatachannel=g}function n(){return p.aggregatedStats||(p.aggregatedStats={}),function(a){let b={};a.forEach(a=>{"inbound-rtp"==a.type&&!a.isRemote&&("video"==a.mediaType||a.id.toLowerCase().includes("video"))&&(b.timestamp=a.timestamp,b.bytesReceived=a.bytesReceived,b.framesDecoded=a.framesDecoded,b.packetsLost=a.packetsLost,b.bytesReceivedStart=p.aggregatedStats&&p.aggregatedStats.bytesReceivedStart?p.aggregatedStats.bytesReceivedStart:a.bytesReceived,b.framesDecodedStart=p.aggregatedStats&&p.aggregatedStats.framesDecodedStart?p.aggregatedStats.framesDecodedStart:a.framesDecoded,b.timestampStart=p.aggregatedStats&&p.aggregatedStats.timestampStart?p.aggregatedStats.timestampStart:a.timestamp,p.aggregatedStats&&p.aggregatedStats.timestamp&&(p.aggregatedStats.bytesReceived&&(b.bitrate=8*(b.bytesReceived-p.aggregatedStats.bytesReceived)/(b.timestamp-p.aggregatedStats.timestamp),b.bitrate=Math.floor(b.bitrate),b.lowBitrate=p.aggregatedStats.lowBitrate&&p.aggregatedStats.lowBitrate<b.bitrate?p.aggregatedStats.lowBitrate:b.bitrate,b.highBitrate=p.aggregatedStats.highBitrate&&p.aggregatedStats.highBitrate>b.bitrate?p.aggregatedStats.highBitrate:b.bitrate),p.aggregatedStats.bytesReceivedStart&&(b.avgBitrate=8*(b.bytesReceived-p.aggregatedStats.bytesReceivedStart)/(b.timestamp-p.aggregatedStats.timestampStart),b.avgBitrate=Math.floor(b.avgBitrate)),p.aggregatedStats.framesDecoded&&(b.framerate=(b.framesDecoded-p.aggregatedStats.framesDecoded)/((b.timestamp-p.aggregatedStats.timestamp)/1e3),b.framerate=Math.floor(b.framerate),b.lowFramerate=p.aggregatedStats.lowFramerate&&p.aggregatedStats.lowFramerate<b.framerate?p.aggregatedStats.lowFramerate:b.framerate,b.highFramerate=p.aggregatedStats.highFramerate&&p.aggregatedStats.highFramerate>b.framerate?p.aggregatedStats.highFramerate:b.framerate),p.aggregatedStats.framesDecodedStart&&(b.avgframerate=(b.framesDecoded-p.aggregatedStats.framesDecodedStart)/((b.timestamp-p.aggregatedStats.timestampStart)/1e3),b.avgframerate=Math.floor(b.avgframerate)))),"track"==a.type&&("video_label"==a.trackIdentifier||"video"==a.kind)&&(b.framesDropped=a.framesDropped,b.framesReceived=a.framesReceived,b.framesDroppedPercentage=100*(a.framesDropped/a.framesReceived),b.frameHeight=a.frameHeight,b.frameWidth=a.frameWidth,b.frameHeightStart=p.aggregatedStats&&p.aggregatedStats.frameHeightStart?p.aggregatedStats.frameHeightStart:a.frameHeight,b.frameWidthStart=p.aggregatedStats&&p.aggregatedStats.frameWidthStart?p.aggregatedStats.frameWidthStart:a.frameWidth),"candidate-pair"==a.type&&a.hasOwnProperty("currentRoundTripTime")&&0!=a.currentRoundTripTime&&(b.currentRoundTripTime=a.currentRoundTripTime)}),p.aggregatedStats.receiveToCompositeMs&&(b.receiveToCompositeMs=p.aggregatedStats.receiveToCompositeMs,p.latencyTestTimings.SetFrameDisplayDeltaTime(p.aggregatedStats.receiveToCompositeMs)),p.aggregatedStats=b,p.onAggregatedStats&&p.onAggregatedStats(b)}}async function o(a){let b=0<a.getTransceivers().length;if(a.addTransceiver("video",{direction:"recvonly"}),!p.useMic)a.addTransceiver("audio",{direction:"recvonly"});else{let c=!!p.useMic&&{autoGainControl:!1,channelCount:1,echoCancellation:!1,latency:0,noiseSuppression:!1,sampleRate:48e3,volume:1};const d=await navigator.mediaDevices.getUserMedia({video:!1,audio:c});if(!d)a.addTransceiver("audio",{direction:"recvonly"});else if(b){for(let b of a.getTransceivers())if(b&&b.receiver&&b.receiver.track&&"audio"===b.receiver.track.kind)for(const a of d.getTracks())a.kind&&"audio"==a.kind&&(b.sender.replaceTrack(a),b.direction="sendrecv");}else for(const b of d.getTracks())b.kind&&"audio"==b.kind&&a.addTransceiver(b,{direction:"sendrecv"})}}a="undefined"==typeof a?{}:a;var p=this;const q=new URLSearchParams(window.location.search);this.cfg="undefined"==typeof a.peerConnectionOptions?{}:a.peerConnectionOptions,this.cfg.sdpSemantics="unified-plan",this.cfg.offerExtmapAllowMixed=!1,this.forceTURN=q.has("ForceTURN"),this.forceTURN&&(MetaMsg.log("Forcing TURN usage by setting ICE Transport Policy in peer connection config."),this.cfg.iceTransportPolicy="relay"),this.cfg.bundlePolicy="balanced",this.forceMaxBundle=q.has("ForceMaxBundle"),this.forceMaxBundle&&(this.cfg.bundlePolicy="max-bundle"),this.pcClient=null,this.dcClient=null,this.tnClient=null,this.sdpConstraints={offerToReceiveAudio:1,offerToReceiveVideo:1,voiceActivityDetection:!1},this.dataChannelOptions={ordered:!0},this.startVideoMuted="undefined"!=typeof a.startVideoMuted&&a.startVideoMuted,this.autoPlayAudio=!("undefined"!=typeof a.autoPlayAudio)||a.autoPlayAudio,this.useMic=q.has("useMic"),this.useMic||MetaMsg.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");let r="localhost"===location.hostname||"127.0.0.1"===location.hostname,s="https:"===location.protocol;!this.useMic||r||s||(this.useMic=!1,MetaMsg.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access."),MetaMsg.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'")),this.preferSFU=q.has("preferSFU"),MetaMsg.log(this.preferSFU?"The browser will signal it would prefer an SFU connection. Remove ?preferSFU from the url to signal for P2P usage.":"The browser will signal for a P2P connection. Pass ?preferSFU in the url to signal for SFU usage."),this.latencyTestTimings={TestStartTimeMs:null,UEReceiptTimeMs:null,UEEncodeMs:null,UECaptureToSendMs:null,UETransmissionTimeMs:null,BrowserReceiptTimeMs:null,FrameDisplayDeltaTimeMs:null,Reset:function(){this.TestStartTimeMs=null,this.UEReceiptTimeMs=null,this.UEEncodeMs=null,this.UECaptureToSendMs=null,this.UETransmissionTimeMs=null,this.BrowserReceiptTimeMs=null,this.FrameDisplayDeltaTimeMs=null},SetUETimings:function(a){this.UEReceiptTimeMs=a.ReceiptTimeMs,this.UEEncodeMs=a.EncodeMs,this.UECaptureToSendMs=a.CaptureToSendMs,this.UETransmissionTimeMs=a.TransmissionTimeMs,this.BrowserReceiptTimeMs=Date.now(),this.OnAllLatencyTimingsReady(this)},SetFrameDisplayDeltaTime:function(a){null==this.FrameDisplayDeltaTimeMs&&(this.FrameDisplayDeltaTimeMs=Math.round(a),this.OnAllLatencyTimingsReady(this))},OnAllLatencyTimingsReady:function(){}},this.createWebRtcVideo=function(){var a=document.createElement("video");if(a.id="streamingVideo",a.playsInline=!0,a.disablepictureinpicture=!0,a.muted=p.startVideoMuted,(a.addEventListener("loadedmetadata",function(){p.onVideoInitialised&&p.onVideoInitialised()},!0),"requestVideoFrameCallback"in HTMLVideoElement.prototype)){const b=(c,d)=>{if(d.receiveTime&&d.expectedDisplayTime){const a=d.presentationTime-d.receiveTime;p.aggregatedStats.receiveToCompositeMs=a}a.requestVideoFrameCallback(b)};a.requestVideoFrameCallback(b)}return a},this.video=this.createWebRtcVideo(),this.availableVideoStreams=new Map;this.setVideoEnabled=function(a){p.video.srcObject.getTracks().forEach(b=>b.enabled=a)},this.startLatencyTest=function(a){p.video&&(p.latencyTestTimings.Reset(),p.latencyTestTimings.TestStartTimeMs=Date.now(),a(p.latencyTestTimings.TestStartTimeMs))},this.handleCandidateFromServer=function(a){let b=new RTCIceCandidate(a);return MetaMsg.log("%c[Unreal ICE candidate]","background: pink; color: black","| Type=",b.type,"| Protocol=",b.protocol,"| Address=",b.address,"| Port=",b.port,"|"),p.forceTURN&&0>b.candidate.indexOf("relay")?void MetaMsg.warn("Dropping candidate because it was not TURN relay.","| Type=",b.type,"| Protocol=",b.protocol,"| Address=",b.address,"| Port=",b.port,"|"):void p.pcClient.addIceCandidate(b).catch(function(a){MetaMsg.error("Failed to add ICE candidate",a)})},this.createOffer=function(){p.pcClient&&(MetaMsg.log("Closing existing PeerConnection"),p.pcClient.close(),p.pcClient=null),p.pcClient=new RTCPeerConnection(p.cfg),m(p.pcClient),o(p.pcClient).finally(function(){p.dcClient=h(p.pcClient,"cirrus",p.dataChannelOptions),k(p.pcClient)})},this.receiveOffer=function(a){var b=new RTCSessionDescription(a);p.pcClient||(MetaMsg.log("Creating a new PeerConnection in the browser."),p.pcClient=new RTCPeerConnection(p.cfg),m(p.pcClient),p.pcClient.setRemoteDescription(b).then(()=>{o(p.pcClient).finally(function(){p.pcClient.createAnswer().then(a=>p.pcClient.setLocalDescription(a)).then(()=>{p.onWebRtcAnswer&&p.onWebRtcAnswer(p.pcClient.currentLocalDescription)}).then(()=>{let a=p.pcClient.getReceivers();for(let b of a)b.playoutDelayHint=0}).catch(a=>MetaMsg.error("createAnswer() failed:",a))})}))},this.receiveAnswer=function(a){var b=new RTCSessionDescription(a);p.pcClient.setRemoteDescription(b);let c=p.pcClient.getReceivers();for(let b of c)b.playoutDelayHint=0},this.close=function(){p.pcClient&&(MetaMsg.log("Closing existing peerClient"),p.pcClient.close(),p.pcClient=null),p.aggregateStatsIntervalId&&clearInterval(p.aggregateStatsIntervalId)},this.send=function(a){p.dcClient&&"open"==p.dcClient.readyState&&p.dcClient.send(a)},this.getStats=function(a){p.pcClient&&a&&p.pcClient.getStats(null).then(b=>{a(b)})},this.aggregateStats=function(a){let b=n();p.aggregateStatsIntervalId=setInterval(()=>{p.getStats(b)},a)}}