910 lines
19 KiB
JavaScript
910 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
import events from 'events';
|
|
import browser from 'bowser';
|
|
import sdpTransform from 'sdp-transform';
|
|
import Logger from './Logger';
|
|
import protooClient from 'protoo-client';
|
|
import urlFactory from './urlFactory';
|
|
import utils from './utils';
|
|
|
|
const logger = new Logger('Client');
|
|
|
|
const DO_GETUSERMEDIA = true;
|
|
const ENABLE_SIMULCAST = false;
|
|
const VIDEO_CONSTRAINS =
|
|
{
|
|
qvga : { width: { ideal: 320 }, height: { ideal: 240 }},
|
|
vga : { width: { ideal: 640 }, height: { ideal: 480 }},
|
|
hd : { width: { ideal: 1280 }, height: { ideal: 720 }}
|
|
};
|
|
|
|
export default class Client extends events.EventEmitter
|
|
{
|
|
constructor(peerId, roomId)
|
|
{
|
|
logger.debug('constructor() [peerId:"%s", roomId:"%s"]', peerId, roomId);
|
|
|
|
super();
|
|
this.setMaxListeners(Infinity);
|
|
|
|
// TODO: TMP
|
|
global.CLIENT = this;
|
|
|
|
let url = urlFactory.getProtooUrl(peerId, roomId);
|
|
let transport = new protooClient.WebSocketTransport(url);
|
|
|
|
// protoo-client Peer instance.
|
|
this._protooPeer = new protooClient.Peer(transport);
|
|
|
|
// RTCPeerConnection instance.
|
|
this._peerconnection = null;
|
|
|
|
// Webcam map indexed by deviceId.
|
|
this._webcams = new Map();
|
|
|
|
// Local Webcam device.
|
|
this._webcam = null;
|
|
|
|
// Local MediaStream instance.
|
|
this._localStream = null;
|
|
|
|
// Closed flag.
|
|
this._closed = false;
|
|
|
|
// Local video resolution.
|
|
this._localVideoResolution = 'vga';
|
|
|
|
this._protooPeer.on('open', () =>
|
|
{
|
|
logger.debug('protoo Peer "open" event');
|
|
});
|
|
|
|
this._protooPeer.on('disconnected', () =>
|
|
{
|
|
logger.warn('protoo Peer "disconnected" event');
|
|
|
|
// Close RTCPeerConnection.
|
|
try
|
|
{
|
|
this._peerconnection.close();
|
|
}
|
|
catch (error) {}
|
|
|
|
// Close local MediaStream.
|
|
if (this._localStream)
|
|
utils.closeMediaStream(this._localStream);
|
|
|
|
this.emit('disconnected');
|
|
});
|
|
|
|
this._protooPeer.on('close', () =>
|
|
{
|
|
if (this._closed)
|
|
return;
|
|
|
|
logger.warn('protoo Peer "close" event');
|
|
|
|
this.close();
|
|
});
|
|
|
|
this._protooPeer.on('request', this._handleRequest.bind(this));
|
|
}
|
|
|
|
close()
|
|
{
|
|
if (this._closed)
|
|
return;
|
|
|
|
this._closed = true;
|
|
|
|
logger.debug('close()');
|
|
|
|
// Close protoo Peer.
|
|
this._protooPeer.close();
|
|
|
|
// Close RTCPeerConnection.
|
|
try
|
|
{
|
|
this._peerconnection.close();
|
|
}
|
|
catch (error) {}
|
|
|
|
// Close local MediaStream.
|
|
if (this._localStream)
|
|
utils.closeMediaStream(this._localStream);
|
|
|
|
// Emit 'close' event.
|
|
this.emit('close');
|
|
}
|
|
|
|
removeVideo(dontNegotiate)
|
|
{
|
|
logger.debug('removeVideo()');
|
|
|
|
let stream = this._localStream;
|
|
let videoTrack = stream.getVideoTracks()[0];
|
|
|
|
if (!videoTrack)
|
|
{
|
|
logger.warn('removeVideo() | no video track');
|
|
|
|
return Promise.reject(new Error('no video track'));
|
|
}
|
|
|
|
stream.removeTrack(videoTrack);
|
|
videoTrack.stop();
|
|
|
|
// NOTE: For Firefox (modern WenRTC API).
|
|
if (this._peerconnection.removeTrack)
|
|
{
|
|
let sender;
|
|
|
|
for (sender of this._peerconnection.getSenders())
|
|
{
|
|
if (sender.track === videoTrack)
|
|
break;
|
|
}
|
|
|
|
this._peerconnection.removeTrack(sender);
|
|
}
|
|
|
|
if (!dontNegotiate)
|
|
{
|
|
this.emit('localstream', stream, null);
|
|
|
|
return this._requestRenegotiation();
|
|
}
|
|
}
|
|
|
|
addVideo()
|
|
{
|
|
logger.debug('addVideo()');
|
|
|
|
let stream = this._localStream;
|
|
let videoTrack;
|
|
let videoResolution = this._localVideoResolution; // Keep previous resolution.
|
|
|
|
if (stream)
|
|
videoTrack = stream.getVideoTracks()[0];
|
|
|
|
if (videoTrack)
|
|
{
|
|
logger.warn('addVideo() | there is already a video track');
|
|
|
|
return Promise.reject(new Error('there is already a video track'));
|
|
}
|
|
|
|
return this._getLocalStream(
|
|
{
|
|
video : VIDEO_CONSTRAINS[videoResolution]
|
|
})
|
|
.then((newStream) =>
|
|
{
|
|
let newVideoTrack = newStream.getVideoTracks()[0];
|
|
|
|
if (stream)
|
|
{
|
|
stream.addTrack(newVideoTrack);
|
|
|
|
// NOTE: For Firefox (modern WenRTC API).
|
|
if (this._peerconnection.addTrack)
|
|
this._peerconnection.addTrack(newVideoTrack, this._localStream);
|
|
}
|
|
else
|
|
{
|
|
this._localStream = newStream;
|
|
this._peerconnection.addStream(newStream);
|
|
}
|
|
|
|
this.emit('localstream', this._localStream, videoResolution);
|
|
})
|
|
.then(() =>
|
|
{
|
|
return this._requestRenegotiation();
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('addVideo() failed: %o', error);
|
|
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
changeWebcam()
|
|
{
|
|
logger.debug('changeWebcam()');
|
|
|
|
return Promise.resolve()
|
|
.then(() =>
|
|
{
|
|
return this._updateWebcams();
|
|
})
|
|
.then(() =>
|
|
{
|
|
let array = Array.from(this._webcams.keys());
|
|
let len = array.length;
|
|
let deviceId = this._webcam ? this._webcam.deviceId : undefined;
|
|
let idx = array.indexOf(deviceId);
|
|
|
|
if (idx < len - 1)
|
|
idx++;
|
|
else
|
|
idx = 0;
|
|
|
|
this._webcam = this._webcams.get(array[idx]);
|
|
|
|
this._emitWebcamType();
|
|
|
|
if (len < 2)
|
|
return;
|
|
|
|
logger.debug(
|
|
'changeWebcam() | new selected webcam [deviceId:"%s"]',
|
|
this._webcam.deviceId);
|
|
|
|
// Reset video resolution to VGA.
|
|
this._localVideoResolution = 'vga';
|
|
|
|
// For Chrome (old WenRTC API).
|
|
// Replace the track (so new SSRC) and renegotiate.
|
|
if (!this._peerconnection.removeTrack)
|
|
{
|
|
this.removeVideo(true);
|
|
|
|
return this.addVideo();
|
|
}
|
|
// For Firefox (modern WebRTC API).
|
|
// Avoid renegotiation.
|
|
else
|
|
{
|
|
return this._getLocalStream(
|
|
{
|
|
video : VIDEO_CONSTRAINS[this._localVideoResolution]
|
|
})
|
|
.then((newStream) =>
|
|
{
|
|
let newVideoTrack = newStream.getVideoTracks()[0];
|
|
let stream = this._localStream;
|
|
let oldVideoTrack = stream.getVideoTracks()[0];
|
|
let sender;
|
|
|
|
for (sender of this._peerconnection.getSenders())
|
|
{
|
|
if (sender.track === oldVideoTrack)
|
|
break;
|
|
}
|
|
|
|
sender.replaceTrack(newVideoTrack);
|
|
stream.removeTrack(oldVideoTrack);
|
|
oldVideoTrack.stop();
|
|
stream.addTrack(newVideoTrack);
|
|
|
|
this.emit('localstream', stream, this._localVideoResolution);
|
|
});
|
|
}
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('changeWebcam() failed: %o', error);
|
|
});
|
|
}
|
|
|
|
changeVideoResolution()
|
|
{
|
|
logger.debug('changeVideoResolution()');
|
|
|
|
let newVideoResolution;
|
|
|
|
switch (this._localVideoResolution)
|
|
{
|
|
case 'qvga':
|
|
newVideoResolution = 'vga';
|
|
break;
|
|
case 'vga':
|
|
newVideoResolution = 'hd';
|
|
break;
|
|
case 'hd':
|
|
newVideoResolution = 'qvga';
|
|
break;
|
|
default:
|
|
throw new Error(`unknown resolution "${this._localVideoResolution}"`);
|
|
}
|
|
|
|
this._localVideoResolution = newVideoResolution;
|
|
|
|
// For Chrome (old WenRTC API).
|
|
// Replace the track (so new SSRC) and renegotiate.
|
|
if (!this._peerconnection.removeTrack)
|
|
{
|
|
this.removeVideo(true);
|
|
|
|
return this.addVideo();
|
|
}
|
|
// For Firefox (modern WebRTC API).
|
|
// Avoid renegotiation.
|
|
else
|
|
{
|
|
return this._getLocalStream(
|
|
{
|
|
video : VIDEO_CONSTRAINS[this._localVideoResolution]
|
|
})
|
|
.then((newStream) =>
|
|
{
|
|
let newVideoTrack = newStream.getVideoTracks()[0];
|
|
let stream = this._localStream;
|
|
let oldVideoTrack = stream.getVideoTracks()[0];
|
|
let sender;
|
|
|
|
for (sender of this._peerconnection.getSenders())
|
|
{
|
|
if (sender.track === oldVideoTrack)
|
|
break;
|
|
}
|
|
|
|
sender.replaceTrack(newVideoTrack);
|
|
stream.removeTrack(oldVideoTrack);
|
|
oldVideoTrack.stop();
|
|
stream.addTrack(newVideoTrack);
|
|
|
|
this.emit('localstream', stream, newVideoResolution);
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('changeVideoResolution() failed: %o', error);
|
|
});
|
|
}
|
|
}
|
|
|
|
getStats()
|
|
{
|
|
return this._peerconnection.getStats()
|
|
.catch((error) =>
|
|
{
|
|
logger.error('pc.getStats() failed: %o', error);
|
|
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
disableRemoteVideo(msid)
|
|
{
|
|
this._protooPeer.send('disableremotevideo', { msid, disable: true })
|
|
.catch((error) =>
|
|
{
|
|
logger.warn('disableRemoteVideo() failed: %o', error);
|
|
});
|
|
}
|
|
|
|
enableRemoteVideo(msid)
|
|
{
|
|
this._protooPeer.send('disableremotevideo', { msid, disable: false })
|
|
.catch((error) =>
|
|
{
|
|
logger.warn('enableRemoteVideo() failed: %o', error);
|
|
});
|
|
}
|
|
|
|
_handleRequest(request, accept, reject)
|
|
{
|
|
logger.debug('_handleRequest() [method:%s, data:%o]', request.method, request.data);
|
|
|
|
switch(request.method)
|
|
{
|
|
case 'joinme':
|
|
{
|
|
let videoResolution = this._localVideoResolution;
|
|
|
|
Promise.resolve()
|
|
.then(() =>
|
|
{
|
|
return this._updateWebcams();
|
|
})
|
|
.then(() =>
|
|
{
|
|
if (DO_GETUSERMEDIA)
|
|
{
|
|
return this._getLocalStream(
|
|
{
|
|
audio : true,
|
|
video : VIDEO_CONSTRAINS[videoResolution]
|
|
})
|
|
.then((stream) =>
|
|
{
|
|
logger.debug('got local stream [resolution:%s]', videoResolution);
|
|
|
|
// Close local MediaStream if any.
|
|
if (this._localStream)
|
|
utils.closeMediaStream(this._localStream);
|
|
|
|
this._localStream = stream;
|
|
|
|
// Emit 'localstream' event.
|
|
this.emit('localstream', stream, videoResolution);
|
|
});
|
|
}
|
|
})
|
|
.then(() =>
|
|
{
|
|
return this._createPeerConnection();
|
|
})
|
|
.then(() =>
|
|
{
|
|
return this._peerconnection.createOffer(
|
|
{
|
|
offerToReceiveAudio : 1,
|
|
offerToReceiveVideo : 1
|
|
});
|
|
})
|
|
.then((offer) =>
|
|
{
|
|
let capabilities = offer.sdp;
|
|
let parsedSdp = sdpTransform.parse(capabilities);
|
|
|
|
logger.debug('capabilities [parsed:%O, sdp:%s]', parsedSdp, capabilities);
|
|
|
|
// Accept the protoo request.
|
|
accept(
|
|
{
|
|
capabilities : capabilities,
|
|
usePlanB : utils.isPlanB()
|
|
});
|
|
})
|
|
.then(() =>
|
|
{
|
|
logger.debug('"joinme" request accepted');
|
|
|
|
// Emit 'join' event.
|
|
this.emit('join');
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('"joinme" request failed: %o', error);
|
|
|
|
reject(500, error.message);
|
|
throw error;
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'peers':
|
|
{
|
|
this.emit('peers', request.data.peers);
|
|
accept();
|
|
|
|
break;
|
|
}
|
|
|
|
case 'addpeer':
|
|
{
|
|
this.emit('addpeer', request.data.peer);
|
|
accept();
|
|
|
|
break;
|
|
}
|
|
|
|
case 'updatepeer':
|
|
{
|
|
this.emit('updatepeer', request.data.peer);
|
|
accept();
|
|
|
|
break;
|
|
}
|
|
|
|
case 'removepeer':
|
|
{
|
|
this.emit('removepeer', request.data.peer);
|
|
accept();
|
|
|
|
break;
|
|
}
|
|
|
|
case 'offer':
|
|
{
|
|
let offer = new RTCSessionDescription(request.data.offer);
|
|
let parsedSdp = sdpTransform.parse(offer.sdp);
|
|
|
|
logger.debug('received offer [parsed:%O, sdp:%s]', parsedSdp, offer.sdp);
|
|
|
|
Promise.resolve()
|
|
.then(() =>
|
|
{
|
|
return this._peerconnection.setRemoteDescription(offer);
|
|
})
|
|
.then(() =>
|
|
{
|
|
return this._peerconnection.createAnswer();
|
|
})
|
|
// Play with simulcast.
|
|
.then((answer) =>
|
|
{
|
|
if (!ENABLE_SIMULCAST)
|
|
return answer;
|
|
|
|
// Chrome Plan B simulcast.
|
|
if (utils.isPlanB())
|
|
{
|
|
// Just for the initial offer.
|
|
// NOTE: Otherwise Chrome crashes.
|
|
// TODO: This prevents simulcast to be applied to new tracks.
|
|
if (this._peerconnection.localDescription && this._peerconnection.localDescription.sdp)
|
|
return answer;
|
|
|
|
// TODO: Should be done just for VP8.
|
|
let parsedSdp = sdpTransform.parse(answer.sdp);
|
|
let videoMedia;
|
|
|
|
for (let m of parsedSdp.media)
|
|
{
|
|
if (m.type === 'video')
|
|
{
|
|
videoMedia = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!videoMedia || !videoMedia.ssrcs)
|
|
return answer;
|
|
|
|
logger.debug('setting video simulcast (PlanB)');
|
|
|
|
let ssrc1;
|
|
let ssrc2;
|
|
let ssrc3;
|
|
let cname;
|
|
let msid;
|
|
|
|
for (let ssrcObj of videoMedia.ssrcs)
|
|
{
|
|
// Chrome uses:
|
|
// a=ssrc:xxxx msid:yyyy zzzz
|
|
// a=ssrc:xxxx mslabel:yyyy
|
|
// a=ssrc:xxxx label:zzzz
|
|
// Where yyyy is the MediaStream.id and zzzz the MediaStreamTrack.id.
|
|
switch (ssrcObj.attribute)
|
|
{
|
|
case 'cname':
|
|
ssrc1 = ssrcObj.id;
|
|
cname = ssrcObj.value;
|
|
break;
|
|
|
|
case 'msid':
|
|
msid = ssrcObj.value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ssrc2 = ssrc1 + 1;
|
|
ssrc3 = ssrc1 + 2;
|
|
|
|
videoMedia.ssrcGroups =
|
|
[
|
|
{
|
|
semantics : 'SIM',
|
|
ssrcs : `${ssrc1} ${ssrc2} ${ssrc3}`
|
|
}
|
|
];
|
|
|
|
videoMedia.ssrcs =
|
|
[
|
|
{
|
|
id : ssrc1,
|
|
attribute : 'cname',
|
|
value : cname,
|
|
},
|
|
{
|
|
id : ssrc1,
|
|
attribute : 'msid',
|
|
value : msid,
|
|
},
|
|
{
|
|
id : ssrc2,
|
|
attribute : 'cname',
|
|
value : cname,
|
|
},
|
|
{
|
|
id : ssrc2,
|
|
attribute : 'msid',
|
|
value : msid,
|
|
},
|
|
{
|
|
id : ssrc3,
|
|
attribute : 'cname',
|
|
value : cname,
|
|
},
|
|
{
|
|
id : ssrc3,
|
|
attribute : 'msid',
|
|
value : msid,
|
|
}
|
|
];
|
|
|
|
let modifiedAnswer =
|
|
{
|
|
type : 'answer',
|
|
sdp : sdpTransform.write(parsedSdp)
|
|
};
|
|
|
|
return modifiedAnswer;
|
|
}
|
|
// Firefox way.
|
|
else
|
|
{
|
|
let parsedSdp = sdpTransform.parse(answer.sdp);
|
|
let videoMedia;
|
|
|
|
logger.debug('created answer [parsed:%O, sdp:%s]', parsedSdp, answer.sdp);
|
|
|
|
for (let m of parsedSdp.media)
|
|
{
|
|
if (m.type === 'video' && m.direction === 'sendonly')
|
|
{
|
|
videoMedia = m;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!videoMedia)
|
|
return answer;
|
|
|
|
logger.debug('setting video simulcast (Unified-Plan)');
|
|
|
|
videoMedia.simulcast_03 =
|
|
{
|
|
value : 'send rid=1,2'
|
|
};
|
|
|
|
videoMedia.rids =
|
|
[
|
|
{ id: '1', direction: 'send' },
|
|
{ id: '2', direction: 'send' }
|
|
];
|
|
|
|
let modifiedAnswer =
|
|
{
|
|
type : 'answer',
|
|
sdp : sdpTransform.write(parsedSdp)
|
|
};
|
|
|
|
return modifiedAnswer;
|
|
}
|
|
})
|
|
.then((answer) =>
|
|
{
|
|
return this._peerconnection.setLocalDescription(answer);
|
|
})
|
|
.then(() =>
|
|
{
|
|
let answer = this._peerconnection.localDescription;
|
|
let parsedSdp = sdpTransform.parse(answer.sdp);
|
|
|
|
logger.debug('sent answer [parsed:%O, sdp:%s]', parsedSdp, answer.sdp);
|
|
|
|
accept(
|
|
{
|
|
answer :
|
|
{
|
|
type : answer.type,
|
|
sdp : answer.sdp
|
|
}
|
|
});
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('"offer" request failed: %o', error);
|
|
|
|
reject(500, error.message);
|
|
throw error;
|
|
})
|
|
.then(() =>
|
|
{
|
|
// If Firefox trigger 'forcestreamsupdate' event due to bug:
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1347578
|
|
if (browser.firefox || browser.gecko)
|
|
{
|
|
// Not sure, but it thinks that the timeout does the trick.
|
|
setTimeout(() => this.emit('forcestreamsupdate'), 500);
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case 'activespeaker':
|
|
{
|
|
let data = request.data;
|
|
|
|
this.emit('activespeaker', data.peer, data.level);
|
|
accept();
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
logger.error('unknown method');
|
|
|
|
reject(404, 'unknown method');
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateWebcams()
|
|
{
|
|
logger.debug('_updateWebcams()');
|
|
|
|
// Reset the list.
|
|
this._webcams = new Map();
|
|
|
|
return Promise.resolve()
|
|
.then(() =>
|
|
{
|
|
return navigator.mediaDevices.enumerateDevices();
|
|
})
|
|
.then((devices) =>
|
|
{
|
|
for (let device of devices)
|
|
{
|
|
if (device.kind !== 'videoinput')
|
|
continue;
|
|
|
|
this._webcams.set(device.deviceId, device);
|
|
}
|
|
})
|
|
.then(() =>
|
|
{
|
|
let array = Array.from(this._webcams.values());
|
|
let len = array.length;
|
|
let currentWebcamId = this._webcam ? this._webcam.deviceId : undefined;
|
|
|
|
logger.debug('_updateWebcams() [webcams:%o]', array);
|
|
|
|
if (len === 0)
|
|
this._webcam = null;
|
|
else if (!this._webcams.has(currentWebcamId))
|
|
this._webcam = array[0];
|
|
|
|
this.emit('numwebcams', len);
|
|
|
|
this._emitWebcamType();
|
|
});
|
|
}
|
|
|
|
_getLocalStream(constraints)
|
|
{
|
|
logger.debug('_getLocalStream() [constraints:%o, webcam:%o]',
|
|
constraints, this._webcam);
|
|
|
|
if (this._webcam)
|
|
constraints.video.deviceId = { exact: this._webcam.deviceId };
|
|
|
|
return navigator.mediaDevices.getUserMedia(constraints);
|
|
}
|
|
|
|
_createPeerConnection()
|
|
{
|
|
logger.debug('_createPeerConnection()');
|
|
|
|
this._peerconnection = new RTCPeerConnection({ iceServers: [] });
|
|
|
|
// TODO: TMP
|
|
global.PC = this._peerconnection;
|
|
|
|
if (this._localStream)
|
|
this._peerconnection.addStream(this._localStream);
|
|
|
|
this._peerconnection.addEventListener('iceconnectionstatechange', () =>
|
|
{
|
|
let state = this._peerconnection.iceConnectionState;
|
|
|
|
logger.debug('peerconnection "iceconnectionstatechange" event [state:%s]', state);
|
|
|
|
this.emit('connectionstate', state);
|
|
});
|
|
|
|
this._peerconnection.addEventListener('addstream', (event) =>
|
|
{
|
|
let stream = event.stream;
|
|
|
|
logger.debug('peerconnection "addstream" event [stream:%o]', stream);
|
|
|
|
this.emit('addstream', stream);
|
|
|
|
// NOTE: For testing.
|
|
let interval = setInterval(() =>
|
|
{
|
|
if (!stream.active)
|
|
{
|
|
logger.warn('stream inactive [stream:%o]', stream);
|
|
|
|
clearInterval(interval);
|
|
}
|
|
}, 2000);
|
|
|
|
stream.addEventListener('addtrack', (event) =>
|
|
{
|
|
let track = event.track;
|
|
|
|
logger.debug('stream "addtrack" event [track:%o]', track);
|
|
|
|
this.emit('addtrack', track);
|
|
|
|
// Firefox does not implement 'stream.onremovetrack' so let's use 'track.ended'.
|
|
// But... track "ended" is neither fired.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1347578
|
|
track.addEventListener('ended', () =>
|
|
{
|
|
logger.debug('track "ended" event [track:%o]', track);
|
|
|
|
this.emit('removetrack', track);
|
|
});
|
|
});
|
|
|
|
// NOTE: Not implemented in Firefox.
|
|
stream.addEventListener('removetrack', (event) =>
|
|
{
|
|
let track = event.track;
|
|
|
|
logger.debug('stream "removetrack" event [track:%o]', track);
|
|
|
|
this.emit('removetrack', track);
|
|
});
|
|
});
|
|
|
|
this._peerconnection.addEventListener('removestream', (event) =>
|
|
{
|
|
let stream = event.stream;
|
|
|
|
logger.debug('peerconnection "removestream" event [stream:%o]', stream);
|
|
|
|
this.emit('removestream', stream);
|
|
});
|
|
}
|
|
|
|
_requestRenegotiation()
|
|
{
|
|
logger.debug('_requestRenegotiation()');
|
|
|
|
return this._protooPeer.send('reofferme');
|
|
}
|
|
|
|
_restartIce()
|
|
{
|
|
logger.debug('_restartIce()');
|
|
|
|
return this._protooPeer.send('restartice')
|
|
.then(() =>
|
|
{
|
|
logger.debug('_restartIce() succeded');
|
|
})
|
|
.catch((error) =>
|
|
{
|
|
logger.error('_restartIce() failed: %o', error);
|
|
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
_emitWebcamType()
|
|
{
|
|
let webcam = this._webcam;
|
|
|
|
if (!webcam)
|
|
return;
|
|
|
|
if (/(back|rear)/i.test(webcam.label))
|
|
{
|
|
logger.debug('_emitWebcamType() | it seems to be a back camera');
|
|
|
|
this.emit('webcamtype', 'back');
|
|
}
|
|
else
|
|
{
|
|
logger.debug('_emitWebcamType() | it seems to be a front camera');
|
|
|
|
this.emit('webcamtype', 'front');
|
|
}
|
|
}
|
|
}
|