@ -5,7 +5,7 @@ export class WebRtcPlayer
pcClient : any ;
pcClient : any ;
dcClient : any ;
dcClient : any ;
tnClient : any ;
tnClient : any ;
sdpConstraints : { offerToReceiveAudio : number ; offerToReceiveVideo : number ; } ;
sdpConstraints : { offerToReceiveAudio : number ; offerToReceiveVideo : number ; voiceActivityDetection : boolean ; } ;
dataChannelOptions : { ordered : boolean ; } ;
dataChannelOptions : { ordered : boolean ; } ;
onVideoInitialised : any ;
onVideoInitialised : any ;
video : HTMLVideoElement ;
video : HTMLVideoElement ;
@ -23,144 +23,398 @@ export class WebRtcPlayer
send : ( data : any ) = > void ;
send : ( data : any ) = > void ;
getStats : ( onStats : any ) = > void ;
getStats : ( onStats : any ) = > void ;
aggregateStats : ( checkInterval : any ) = > void ;
aggregateStats : ( checkInterval : any ) = > void ;
forceTURN : boolean ;
constructor ( parOptions ? : { peerConnectionOptions ? : { iceServers? : string [ ] ; } ; } )
forceMaxBundle : boolean ;
startVideoMuted : any ;
autoPlayAudio : any ;
useMic : boolean ;
preferSFU : boolean ;
latencyTestTimings : { TestStartTimeMs : any ; UEReceiptTimeMs : any ; UEEncodeMs : any ; UECaptureToSendMs : any ; UETransmissionTimeMs : any ; BrowserReceiptTimeMs : any ; FrameDisplayDeltaTimeMs : any ; Reset : ( ) = > void ; SetUETimings : ( UETimings : any ) = > void ; SetFrameDisplayDeltaTime : ( DeltaTimeMs : any ) = > void ; OnAllLatencyTimingsReady : ( Timings : any ) = > void ; } ;
createWebRtcVideo : ( ) = > HTMLVideoElement ;
availableVideoStreams : Map < any , any > ;
setVideoEnabled : ( enabled : any ) = > void ;
startLatencyTest : ( onTestStarted : any ) = > void ;
receiveOffer : ( offer : any ) = > void ;
onWebRtcAnswer : any ;
constructor ( parOptions ? : {
startVideoMuted? : boolean ,
autoPlayAudio? : boolean ,
peerConnectionOptions ? : {
iceServers? : any [ ] ,
sdpSemantics : string ,
offerExtmapAllowMixed : boolean ,
bundlePolicy : string ,
} ,
type : string ,
} )
{
{
parOptions = parOptions || { } ;
parOptions = parOptions || {
"type" : "config" ,
"peerConnectionOptions" : {
"iceServers" : [
{
"urls" : [
"stun:stun.l.google.com:19302"
]
}
] ,
"sdpSemantics" : "unified-plan" ,
"offerExtmapAllowMixed" : false ,
"bundlePolicy" : "balanced"
}
} ;
const self = this ;
const self = this ;
const urlParams = new URLSearchParams ( window . location . search ) ;
//**********************
//**********************
//Config setup
//Config setup
//**********************;
//**********************;
this . cfg = parOptions . peerConnectionOptions || { } ;
this . cfg = parOptions . peerConnectionOptions || { } ;
this . cfg . sdpSemantics = 'unified-plan' ;
this . cfg . sdpSemantics = 'unified-plan' ;
// this.cfg.rtcAudioJitterBufferMaxPackets = 10;
// this.cfg.rtcAudioJitterBufferFastAccelerate = true;
// this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
// If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
// However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
// tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
this . cfg . offerExtmapAllowMixed = false ;
this . forceTURN = urlParams . has ( 'ForceTURN' ) ;
if ( this . forceTURN )
{
console . log ( "Forcing TURN usage by setting ICE Transport Policy in peer connection config." ) ;
this . cfg . iceTransportPolicy = "relay" ;
}
this . cfg . bundlePolicy = "balanced" ;
this . forceMaxBundle = urlParams . has ( 'ForceMaxBundle' ) ;
if ( this . forceMaxBundle )
{
this . cfg . bundlePolicy = "max-bundle" ;
}
//**********************
//Variables
//**********************
this . pcClient = null ;
this . pcClient = null ;
this . dcClient = null ;
this . dcClient = null ;
this . tnClient = null ;
this . tnClient = null ;
this . sdpConstraints = {
this . sdpConstraints = {
offerToReceiveAudio : 1 ,
offerToReceiveAudio : 1 , //Note: if you don't need audio you can get improved latency by turning this off.
offerToReceiveVideo : 1
offerToReceiveVideo : 1 ,
voiceActivityDetection : false
} ;
} ;
// See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values
// See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
this . dataChannelOptions = { ordered : true } ;
this . dataChannelOptions = { ordered : true } ;
// This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
this . startVideoMuted = typeof parOptions . startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false ;
this . autoPlayAudio = typeof parOptions . autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true ;
// To enable mic in browser use SSL/localhost and have ?useMic in the query string.
this . useMic = urlParams . has ( 'useMic' ) ;
if ( ! this . useMic )
{
console . log ( "Microphone access is not enabled. Pass ?useMic in the url to enable it." ) ;
}
// When ?useMic check for SSL or localhost
let isLocalhostConnection = location . hostname === "localhost" || location . hostname === "127.0.0.1" ;
let isHttpsConnection = location . protocol === 'https:' ;
if ( this . useMic && ! isLocalhostConnection && ! isHttpsConnection )
{
this . useMic = false ;
console . error ( "Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access." ) ;
console . error ( "For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'" ) ;
}
// Prefer SFU or P2P connection
this . preferSFU = urlParams . has ( 'preferSFU' ) ;
console . 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." ) ;
// Latency tester
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 ( UETimings )
{
this . UEReceiptTimeMs = UETimings . ReceiptTimeMs ;
this . UEEncodeMs = UETimings . EncodeMs ,
this . UECaptureToSendMs = UETimings . CaptureToSendMs ,
this . UETransmissionTimeMs = UETimings . TransmissionTimeMs ;
this . BrowserReceiptTimeMs = Date . now ( ) ;
this . OnAllLatencyTimingsReady ( this ) ;
} ,
SetFrameDisplayDeltaTime : function ( DeltaTimeMs )
{
if ( this . FrameDisplayDeltaTimeMs == null )
{
this . FrameDisplayDeltaTimeMs = Math . round ( DeltaTimeMs ) ;
this . OnAllLatencyTimingsReady ( this ) ;
}
} ,
OnAllLatencyTimingsReady : function ( Timings ) { }
} ;
//**********************
//**********************
//Functions
//Functions
//**********************
//**********************
//Create Video element and expose that as a parameter
//Create Video element and expose that as a parameter
function createWebRtcVideo ( )
this . createWebRtcVideo = function ( )
{
{
const video = document . createElement ( 'video' ) ;
var video = document . createElement ( 'video' ) ;
video . id = "streamingVideo" ;
video . id = "streamingVideo" ;
video . playsInline = true ;
video . playsInline = true ;
video . addEventListener ( 'loadedmetadata' , e = >
//@ts-ignore
video . disablepictureinpicture = true ;
video . muted = self . startVideoMuted ; ;
video . addEventListener ( 'loadedmetadata' , function ( e )
{
{
if ( self . onVideoInitialised )
if ( self . onVideoInitialised )
{
self . onVideoInitialised ( ) ;
self . onVideoInitialised ( ) ;
}
} , true ) ;
} , true ) ;
// Check if request video frame callback is supported
if ( 'requestVideoFrameCallback' in HTMLVideoElement . prototype )
{
// The API is supported!
const onVideoFrameReady = ( now , metadata ) = >
{
if ( metadata . receiveTime && metadata . expectedDisplayTime )
{
const receiveToCompositeMs = metadata . presentationTime - metadata . receiveTime ;
self . aggregatedStats . receiveToCompositeMs = receiveToCompositeMs ;
}
// Re-register the callback to be notified about the next frame.
//@ts-ignore
video . requestVideoFrameCallback ( onVideoFrameReady ) ;
} ;
// Initially register the callback to be notified about the first frame.
//@ts-ignore
video . requestVideoFrameCallback ( onVideoFrameReady ) ;
}
return video ;
return video ;
}
} ;
this . video = createWebRtcVideo ( ) ;
this . video = this . createWebRtcVideo ( ) ;
this . availableVideoStreams = new Map ( ) ;
const onsignalingstatechange = state = >
const onsignalingstatechange = function ( state )
{
{
// console.info('signaling state change:', state);
console . info ( 'Signaling state change. |' , state . srcElement . signalingState , "|" ) ;
} ;
} ;
const oniceconnectionstatechange = state = >
const oniceconnectionstatechange = function ( state )
{
{
// console.info('ice connection state change:', state);
console . info ( 'Browser ICE connection |' , state . srcElement . iceConnectionState , '|' ) ;
} ;
} ;
const onicegatheringstatechange = state = >
const onicegatheringstatechange = function ( state )
{
{
// console.info('ice gathering state change:', state);
console . info ( 'Browser ICE gathering |' , state . srcElement . iceGatheringState , '|' ) ;
} ;
} ;
const handleOnTrack = e = >
const handleOnTrack = function ( e )
{
{
// console.log('handleOnTrack', e.streams);
if ( e . track )
if ( self . video . srcObject !== e . streams [ 0 ] )
{
console . log ( 'Got track. | Kind=' + e . track . kind + ' | Id=' + e . track . id + ' | readyState=' + e . track . readyState + ' |' ) ;
}
if ( e . track . kind == "audio" )
{
handleOnAudioTrack ( e . streams [ 0 ] ) ;
return ;
}
else ( e . track . kind == "video" ) ;
{
{
// console.log('setting video stream from ontrack');
for ( const s of e . streams )
{
if ( ! self . availableVideoStreams . has ( s . id ) )
{
self . availableVideoStreams . set ( s . id , s ) ;
}
}
self . video . srcObject = e . streams [ 0 ] ;
self . video . srcObject = e . streams [ 0 ] ;
// All tracks are added "muted" by WebRTC/browser and become unmuted when media is being sent
e . track . onunmute = ( ) = >
{
if ( self . video . srcObject !== e . streams [ 0 ] ) //直接设置导致了错误???
self . video . srcObject = e . streams [ 0 ] ;
} ;
}
} ;
const handleOnAudioTrack = function ( audioMediaStream )
{
// do nothing the video has the same media stream as the audio track we have here (they are linked)
if ( self . video . srcObject === audioMediaStream )
{
return ;
}
// video element has some other media stream that is not associated with this audio track
else if ( self . video . srcObject && self . video . srcObject !== audioMediaStream )
{
// create a new audio element
let audioElem = document . createElement ( "Audio" ) as HTMLAudioElement ;
//@ts-ignore
audioElem . srcObject = audioMediaStream ;
// there is no way to autoplay audio (even muted), so we defer audio until first click
if ( ! self . autoPlayAudio )
{
let clickToPlayAudio = function ( )
{
audioElem . play ( ) ;
self . video . removeEventListener ( "click" , clickToPlayAudio ) ;
} ;
self . video . addEventListener ( "click" , clickToPlayAudio ) ;
}
// we assume the user has clicked somewhere on the page and autoplaying audio will work
else
audioElem . play ( ) ;
console . log ( "%c[OK]" , "background: green; color: black" , 'Created new audio element to play seperate audio stream.' ) ;
}
}
} ;
const onDataChannel = function ( dataChannelEvent )
{
// This is the primary data channel code path when we are "receiving"
console . log ( "Data channel created for us by browser as we are a receiving peer." ) ;
self . dcClient = dataChannelEvent . channel ;
setupDataChannelCallbacks ( self . dcClient ) ;
} ;
} ;
const setupDataChannel = ( pc , label , options ) = >
const createDataChannel = function ( pc , label , options )
{
// This is the primary data channel code path when we are "offering"
let datachannel = pc . createDataChannel ( label , options ) ;
console . log ( ` Created datachannel ( ${ label } ) ` ) ;
setupDataChannelCallbacks ( datachannel ) ;
return datachannel ;
} ;
const setupDataChannelCallbacks = function ( datachannel )
{
{
try
try
{
{
const datachannel = pc . createDataChannel ( label , options ) ;
// Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
// console.log(`Created datachannel (${label})`);
datachannel . binaryType = "arraybuffer" ;
datachannel . onopen = e = >
datachannel . onopen = function ( e )
{
{
// console.log(`data channel (${label}) connect`);
console . log ( "Data channel onopen" ) ;
if ( self . onDataChannelConnected )
if ( self . onDataChannelConnected )
{
{
self . onDataChannelConnected ( ) ;
self . onDataChannelConnected ( ) ;
}
}
} ;
} ;
datachannel . onclose = e = >
datachannel . onclose = function ( e )
{
{
// console.log(`data channel (${label}) closed`);
console . log ( "Data channel onclose" , e ) ;
} ;
} ;
datachannel . onmessage = e = >
datachannel . onmessage = function ( e )
{
{
// console.log(`Got message (${label})`, e.data); rtc状态 输出质量
if ( self . onDataChannelMessage )
if ( self . onDataChannelMessage )
{
self . onDataChannelMessage ( e . data ) ;
self . onDataChannelMessage ( e . data ) ;
}
} ;
datachannel . onerror = function ( e )
{
console . error ( "Data channel error" , e ) ;
} ;
} ;
return datachannel ;
return datachannel ;
} catch ( e )
} catch ( e )
{
{
// console.warn('No data channel', e);
console . warn ( 'No data channel' , e ) ;
return null ;
return null ;
}
}
} ;
} ;
const onicecandidate = e = >
const onicecandidate = function ( e )
{
{
// console.log('ICE candidate', e);
let candidate = e . candidate ;
if ( e. candidate && e . candidate . candidate )
if ( candidate && candidate . candidate )
{
{
self . onWebRtcCandidate ( e . candidate ) ;
console . log ( "%c[Browser ICE candidate]" , "background: violet; color: black" , "| Type=" , candidate . type , "| Protocol=" , candidate . protocol , "| Address=" , candidate . address , "| Port=" , candidate . port , "|" ) ;
self . onWebRtcCandidate ( candidate ) ;
}
}
} ;
} ;
const handleCreateOffer = pc = >
const handleCreateOffer = function ( pc )
{
{
pc . createOffer ( self . sdpConstraints ) . then ( offer = >
pc . createOffer ( self . sdpConstraints ) . then ( function ( offer )
{
{
offer . sdp = offer . sdp . replace ( "useinbandfec=1" , "useinbandfec=1;stereo=1;maxaveragebitrate=128000" ) ;
// Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
mungeSDPOffer ( offer ) ;
// Set our munged SDP on the local peer connection so it is "set" and will be send across
pc . setLocalDescription ( offer ) ;
pc . setLocalDescription ( offer ) ;
if ( self . onWebRtcOffer )
if ( self . onWebRtcOffer )
{
{
// (andriy): increase start bitrate from 300 kbps to 20 mbps and max bitrate from 2.5 mbps to 100 mbps
// (100 mbps means we don't restrict encoder at all)
// after we `setLocalDescription` because other browsers are not c happy to see google-specific config
offer . sdp = offer . sdp . replace ( /(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm , "$1;x-google-start-bitrate=10000;x-google-max-bitrate=20000\r\n" ) ;
self . onWebRtcOffer ( offer ) ;
self . onWebRtcOffer ( offer ) ;
}
}
} ,
} ,
( ) = > { console . warn ( "Couldn't create offer" ) ; } ) ;
function ( ) { console . warn ( "Couldn't create offer" ) ; } ) ;
} ;
} ;
const setupPeerConnection = pc = >
const mungeSDPOffer = function ( offer )
{
{
// if (pc.SetBitrate)
// console.log("Hurray! there's RTCPeerConnection.SetBitrate function");
// turn off video-timing sdp sent from browser
//offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
// this indicate we support stereo (Chrome needs this)
offer . sdp = offer . sdp . replace ( 'useinbandfec=1' , 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000' ) ;
} ;
const setupPeerConnection = function ( pc )
{
//Setup peerConnection events
//Setup peerConnection events
pc . onsignalingstatechange = onsignalingstatechange ;
pc . onsignalingstatechange = onsignalingstatechange ;
pc . oniceconnectionstatechange = oniceconnectionstatechange ;
pc . oniceconnectionstatechange = oniceconnectionstatechange ;
@ -168,19 +422,20 @@ export class WebRtcPlayer
pc . ontrack = handleOnTrack ;
pc . ontrack = handleOnTrack ;
pc . onicecandidate = onicecandidate ;
pc . onicecandidate = onicecandidate ;
pc . ondatachannel = onDataChannel ;
} ;
} ;
const generateAggregatedStatsFunction = ( ) = >
const generateAggregatedStatsFunction = function ( )
{
{
if ( ! self . aggregatedStats )
if ( ! self . aggregatedStats )
self . aggregatedStats = { } ;
self . aggregatedStats = { } ;
return stats = >
return function ( stats )
{
{
//console.log('Printing Stats');
//console.log('Printing Stats');
let newStat = { } as any ;
let newStat = { } as any ;
// console.log('----------------------------- Stats start -----------------------------');
stats . forEach ( stat = >
stats . forEach ( stat = >
{
{
// console.log(JSON.stringify(stat, undefined, 4));
// console.log(JSON.stringify(stat, undefined, 4));
@ -250,7 +505,13 @@ export class WebRtcPlayer
}
}
} ) ;
} ) ;
//console.log(JSON.stringify(newStat));
if ( self . aggregatedStats . receiveToCompositeMs )
{
newStat . receiveToCompositeMs = self . aggregatedStats . receiveToCompositeMs ;
self . latencyTestTimings . SetFrameDisplayDeltaTime ( self . aggregatedStats . receiveToCompositeMs ) ;
}
self . aggregatedStats = newStat ;
self . aggregatedStats = newStat ;
if ( self . onAggregatedStats )
if ( self . onAggregatedStats )
@ -258,51 +519,197 @@ export class WebRtcPlayer
} ;
} ;
} ;
} ;
const setupTransceiversAsync = async function ( pc )
{
let hasTransceivers = pc . getTransceivers ( ) . length > 0 ;
// Setup a transceiver for getting UE video
pc . addTransceiver ( "video" , { direction : "recvonly" } ) ;
// Setup a transceiver for sending mic audio to UE and receiving audio from UE
if ( ! self . useMic )
{
pc . addTransceiver ( "audio" , { direction : "recvonly" } ) ;
}
else
{
let audioSendOptions = self . useMic ?
{
autoGainControl : false ,
channelCount : 1 ,
echoCancellation : false ,
latency : 0 ,
noiseSuppression : false ,
sampleRate : 48000 ,
volume : 1.0
} : false ;
// Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
const stream = await navigator . mediaDevices . getUserMedia ( { video : false , audio : audioSendOptions } ) ;
if ( stream )
{
if ( hasTransceivers )
{
for ( let transceiver of pc . getTransceivers ( ) )
{
if ( transceiver && transceiver . receiver && transceiver . receiver . track && transceiver . receiver . track . kind === "audio" )
{
for ( const track of stream . getTracks ( ) )
{
if ( track . kind && track . kind == "audio" )
{
transceiver . sender . replaceTrack ( track ) ;
transceiver . direction = "sendrecv" ;
}
}
}
}
}
else
{
for ( const track of stream . getTracks ( ) )
{
if ( track . kind && track . kind == "audio" )
{
pc . addTransceiver ( track , { direction : "sendrecv" } ) ;
}
}
}
}
else
{
pc . addTransceiver ( "audio" , { direction : "recvonly" } ) ;
}
}
} ;
//**********************
//**********************
//Public functions
//Public functions
//**********************
//**********************
this . setVideoEnabled = function ( enabled )
{
//@ts-ignore
self . video . srcObject . getTracks ( ) . forEach ( track = > track . enabled = enabled ) ;
} ;
this . startLatencyTest = function ( onTestStarted )
{
// Can't start latency test without a video element
if ( ! self . video )
{
return ;
}
self . latencyTestTimings . Reset ( ) ;
self . latencyTestTimings . TestStartTimeMs = Date . now ( ) ;
onTestStarted ( self . latencyTestTimings . TestStartTimeMs ) ;
} ;
//This is called when revceiving new ice candidates individually instead of part of the offer
//This is called when revceiving new ice candidates individually instead of part of the offer
//This is currently not used but would be called externally from this class
this . handleCandidateFromServer = function ( iceCandidate )
this . handleCandidateFromServer = iceCandidate = >
{
{
// console.log("ICE candidate: ", iceCandidate);
let candidate = new RTCIceCandidate ( iceCandidate ) ;
let candidate = new RTCIceCandidate ( iceCandidate ) ;
self . pcClient . addIceCandidate ( candidate ) . then ( _ = >
console . log ( "%c[Unreal ICE candidate]" , "background: pink; color: black" , "| Type=" , candidate . type , "| Protocol=" , candidate . protocol , "| Address=" , candidate . address , "| Port=" , candidate . port , "|" ) ;
// if forcing TURN, reject any candidates not relay
if ( self . forceTURN )
{
// check if no relay address is found, if so, we are assuming it means no TURN server
if ( candidate . candidate . indexOf ( "relay" ) < 0 )
{
console . warn ( "Dropping candidate because it was not TURN relay." , "| Type=" , candidate . type , "| Protocol=" , candidate . protocol , "| Address=" , candidate . address , "| Port=" , candidate . port , "|" ) ;
return ;
}
}
self . pcClient . addIceCandidate ( candidate ) . catch ( function ( e )
{
{
// console.log('ICE candidate successfully added');
console . error ( "Failed to add ICE candidate" , e ) ;
} ) ;
} ) ;
} ;
} ;
//Called externaly to create an offer for the server
//Called externaly to create an offer for the server
this . createOffer = ( ) = >
this . createOffer = function ( )
{
{
if ( self . pcClient )
if ( self . pcClient )
{
{
// console.log("Closing existing PeerConnection");
console . log ( "Closing existing PeerConnection" ) ;
self . pcClient . close ( ) ;
self . pcClient . close ( ) ;
self . pcClient = null ;
self . pcClient = null ;
}
}
self . cfg . offerExtmapAllowMixed = false ;
self . pcClient = new RTCPeerConnection ( self . cfg ) ;
self . pcClient = new RTCPeerConnection ( self . cfg ) ;
setupPeerConnection ( self . pcClient ) ;
setupPeerConnection ( self . pcClient ) ;
self . dcClient = setupDataChannel ( self . pcClient , 'cirrus' , self . dataChannelOptions ) ;
handleCreateOffer ( self . pcClient ) ;
setupTransceiversAsync ( self . pcClient ) . finally ( function ( )
{
self . dcClient = createDataChannel ( self . pcClient , 'cirrus' , self . dataChannelOptions ) ;
handleCreateOffer ( self . pcClient ) ;
} ) ;
} ;
//Called externaly when an offer is received from the server
this . receiveOffer = function ( offer )
{
var offerDesc = new RTCSessionDescription ( offer ) ;
if ( ! self . pcClient )
{
console . log ( "Creating a new PeerConnection in the browser." ) ;
self . pcClient = new RTCPeerConnection ( self . cfg ) ;
setupPeerConnection ( self . pcClient ) ;
// Put things here that happen post transceiver setup
self . pcClient . setRemoteDescription ( offerDesc )
. then ( ( ) = >
{
setupTransceiversAsync ( self . pcClient ) . finally ( function ( )
{
self . pcClient . createAnswer ( )
. then ( answer = > self . pcClient . setLocalDescription ( answer ) )
. then ( ( ) = >
{
if ( self . onWebRtcAnswer )
{
self . onWebRtcAnswer ( self . pcClient . currentLocalDescription ) ;
}
} )
. then ( ( ) = >
{
let receivers = self . pcClient . getReceivers ( ) ;
for ( let receiver of receivers )
{
receiver . playoutDelayHint = 0 ;
}
} )
. catch ( ( error ) = > console . error ( "createAnswer() failed:" , error ) ) ;
} ) ;
} ) ;
}
} ;
} ;
//Called externaly when an answer is received from the server
//Called externaly when an answer is received from the server
this . receiveAnswer = answer = >
this . receiveAnswer = function ( answer )
{
{
// console.log(`Received answer:\n${answer}`);
var answerDesc = new RTCSessionDescription ( answer ) ;
const answerDesc = new RTCSessionDescription ( answer ) ;
self . pcClient . setRemoteDescription ( answerDesc ) ;
self . pcClient . setRemoteDescription ( answerDesc ) ;
let receivers = self . pcClient . getReceivers ( ) ;
for ( let receiver of receivers )
{
receiver . playoutDelayHint = 0 ;
}
} ;
} ;
this . close = ( ) = >
this . close = function ( )
{
{
if ( self . pcClient )
if ( self . pcClient )
{
{
// console.log("Closing existing peerClient");
console . log ( "Closing existing peerClient" ) ;
self . pcClient . close ( ) ;
self . pcClient . close ( ) ;
self . pcClient = null ;
self . pcClient = null ;
}
}
@ -311,7 +718,7 @@ export class WebRtcPlayer
} ;
} ;
//Sends data across the datachannel
//Sends data across the datachannel
this . send = data = >
this . send = function ( data )
{
{
if ( self . dcClient && self . dcClient . readyState == 'open' )
if ( self . dcClient && self . dcClient . readyState == 'open' )
{
{
@ -320,7 +727,7 @@ export class WebRtcPlayer
}
}
} ;
} ;
this . getStats = onStats = >
this . getStats = function ( onStats )
{
{
if ( self . pcClient && onStats )
if ( self . pcClient && onStats )
{
{
@ -331,7 +738,7 @@ export class WebRtcPlayer
}
}
} ;
} ;
this . aggregateStats = checkInterval = >
this . aggregateStats = function ( checkInterval )
{
{
let calcAggregatedStats = generateAggregatedStatsFunction ( ) ;
let calcAggregatedStats = generateAggregatedStatsFunction ( ) ;
let printAggregatedStats = ( ) = > { self . getStats ( calcAggregatedStats ) ; } ;
let printAggregatedStats = ( ) = > { self . getStats ( calcAggregatedStats ) ; } ;