Add suport for Safari 11 and Microsoft Edge
parent
103be0ece3
commit
b86b6e6957
|
|
@ -133,7 +133,9 @@ export default class Client extends events.EventEmitter
|
|||
}
|
||||
|
||||
videoTrack.stop();
|
||||
stream.removeTrack(videoTrack);
|
||||
|
||||
// New API.
|
||||
if (this._peerconnection.removeTrack)
|
||||
{
|
||||
let sender;
|
||||
|
|
@ -146,9 +148,11 @@ export default class Client extends events.EventEmitter
|
|||
|
||||
this._peerconnection.removeTrack(sender);
|
||||
}
|
||||
// Old API.
|
||||
else
|
||||
{
|
||||
stream.removeTrack(videoTrack);
|
||||
this._peerconnection.removeStream(stream);
|
||||
this._peerconnection.addStream(stream);
|
||||
}
|
||||
|
||||
if (!dontNegotiate)
|
||||
|
|
@ -187,19 +191,33 @@ export default class Client extends events.EventEmitter
|
|||
|
||||
if (stream)
|
||||
{
|
||||
// Fucking hack for adapter.js in Chrome.
|
||||
if (this._peerconnection.removeStream)
|
||||
this._peerconnection.removeStream(stream);
|
||||
|
||||
stream.addTrack(newVideoTrack);
|
||||
|
||||
// New API.
|
||||
if (this._peerconnection.addTrack)
|
||||
{
|
||||
this._peerconnection.addTrack(newVideoTrack, stream);
|
||||
}
|
||||
// Old API.
|
||||
else
|
||||
{
|
||||
this._peerconnection.addStream(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this._localStream = newStream;
|
||||
this._peerconnection.addStream(newStream);
|
||||
|
||||
// New API.
|
||||
if (this._peerconnection.addTrack)
|
||||
{
|
||||
this._peerconnection.addTrack(newVideoTrack, stream);
|
||||
}
|
||||
// Old API.
|
||||
else
|
||||
{
|
||||
this._peerconnection.addStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('localstream', this._localStream, videoResolution);
|
||||
|
|
@ -803,6 +821,9 @@ export default class Client extends events.EventEmitter
|
|||
{
|
||||
let state = this._peerconnection.iceConnectionState;
|
||||
|
||||
if (state === 'failed')
|
||||
logger.warn('peerconnection "iceconnectionstatechange" event [state:failed]');
|
||||
else
|
||||
logger.debug('peerconnection "iceconnectionstatechange" event [state:%s]', state);
|
||||
|
||||
this.emit('connectionstate', state);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default class RemoteVideo extends React.Component
|
|||
let videoTrack = this.props.stream.getVideoTracks()[0];
|
||||
let videoEnabled = videoTrack && videoTrack.enabled;
|
||||
let stream = this.props.stream;
|
||||
let msid = stream.id;
|
||||
let msid = stream.jitsiRemoteId || stream.id;
|
||||
|
||||
if (videoEnabled)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ export default class Room extends React.Component
|
|||
this._client = null;
|
||||
// Timer to retrieve RTC stats.
|
||||
this._statsTimer = null;
|
||||
|
||||
// TODO: TMP
|
||||
global.ROOM = this;
|
||||
}
|
||||
|
||||
render()
|
||||
|
|
@ -221,6 +224,13 @@ export default class Room extends React.Component
|
|||
{
|
||||
logger.debug('handleLocalResolutionChange()');
|
||||
|
||||
if (!utils.canChangeResolution())
|
||||
{
|
||||
logger.warn('changing local resolution not implemented for this browser');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._client.changeVideoResolution();
|
||||
}
|
||||
|
||||
|
|
@ -395,8 +405,23 @@ export default class Room extends React.Component
|
|||
|
||||
let peers = this.state.peers;
|
||||
|
||||
peer = peers[peer.id];
|
||||
if (!peer)
|
||||
return;
|
||||
|
||||
delete peers[peer.id];
|
||||
this.setState({ peers });
|
||||
|
||||
// NOTE: This shouldn't be needed but Safari 11 does not fire pc "removestream"
|
||||
// nor stream "removetrack" nor track "ended", so we need to cleanup remote
|
||||
// streams when a peer leaves.
|
||||
let remoteStreams = this.state.remoteStreams;
|
||||
|
||||
for (let msid of peer.msids)
|
||||
{
|
||||
delete remoteStreams[msid];
|
||||
}
|
||||
|
||||
this.setState({ peers, remoteStreams });
|
||||
});
|
||||
|
||||
this._client.on('connectionstate', (state) =>
|
||||
|
|
@ -407,16 +432,18 @@ export default class Room extends React.Component
|
|||
this._client.on('addstream', (stream) =>
|
||||
{
|
||||
let remoteStreams = this.state.remoteStreams;
|
||||
let streamId = stream.jitsiRemoteId || stream.id;
|
||||
|
||||
remoteStreams[stream.id] = stream;
|
||||
remoteStreams[streamId] = stream;
|
||||
this.setState({ remoteStreams });
|
||||
});
|
||||
|
||||
this._client.on('removestream', (stream) =>
|
||||
{
|
||||
let remoteStreams = this.state.remoteStreams;
|
||||
let streamId = stream.jitsiRemoteId || stream.id;
|
||||
|
||||
delete remoteStreams[stream.id];
|
||||
delete remoteStreams[streamId];
|
||||
this.setState({ remoteStreams });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ export default class Stats extends React.Component
|
|||
|
||||
_processStats(stats)
|
||||
{
|
||||
// global.STATS = stats; // TODO: REMOVE
|
||||
|
||||
if (browser.check({ chrome: '58' }, true))
|
||||
{
|
||||
this._processStatsChrome58(stats);
|
||||
|
|
@ -108,6 +110,10 @@ export default class Stats extends React.Component
|
|||
{
|
||||
this._processStatsFirefox(stats);
|
||||
}
|
||||
else if (browser.check({ safari: '11' }, true))
|
||||
{
|
||||
this._processStatsSafari11(stats);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn('_processStats() | unsupported browser [name:"%s", version:%s]',
|
||||
|
|
@ -388,9 +394,6 @@ export default class Stats extends React.Component
|
|||
|
||||
for (let group of stats.values())
|
||||
{
|
||||
// TODO: REMOVE
|
||||
global.STATS = stats;
|
||||
|
||||
switch (group.type)
|
||||
{
|
||||
case 'candidate-pair':
|
||||
|
|
@ -489,6 +492,87 @@ export default class Stats extends React.Component
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
_processStatsSafari11(stats)
|
||||
{
|
||||
let transport = {};
|
||||
let audio = {};
|
||||
let video = {};
|
||||
|
||||
for (let group of stats.values())
|
||||
{
|
||||
switch (group.type)
|
||||
{
|
||||
case 'candidate-pair':
|
||||
{
|
||||
if (!group.writable)
|
||||
break;
|
||||
|
||||
transport['bytes sent'] = group.bytesSent;
|
||||
transport['bytes received'] = group.bytesReceived;
|
||||
transport['available bitrate'] =
|
||||
Math.round(group.availableOutgoingBitrate / 1000) + ' kbps';
|
||||
transport['current RTT'] =
|
||||
Math.round(group.currentRoundTripTime * 1000) + ' ms';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'outbound-rtp':
|
||||
{
|
||||
if (group.isRemote)
|
||||
break;
|
||||
|
||||
let block;
|
||||
|
||||
switch (group.mediaType)
|
||||
{
|
||||
case 'audio':
|
||||
block = audio;
|
||||
break;
|
||||
case 'video':
|
||||
block = video;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!block)
|
||||
break;
|
||||
|
||||
block['ssrc'] = group.ssrc;
|
||||
block['bytes sent'] = group.bytesSent;
|
||||
block['packets sent'] = group.packetsSent;
|
||||
|
||||
if (block === video)
|
||||
block['frames encoded'] = group.framesEncoded;
|
||||
|
||||
block['NACK count'] = group.nackCount;
|
||||
block['PLI count'] = group.pliCount;
|
||||
block['FIR count'] = group.firCount;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post checks.
|
||||
|
||||
if (!video.ssrc)
|
||||
video = {};
|
||||
|
||||
if (!audio.ssrc)
|
||||
audio = {};
|
||||
|
||||
// Set state.
|
||||
this.setState(
|
||||
{
|
||||
stats :
|
||||
{
|
||||
transport,
|
||||
audio,
|
||||
video
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Stats.propTypes =
|
||||
|
|
|
|||
|
|
@ -161,7 +161,9 @@ export default class Video extends React.Component
|
|||
return stream.getTracks()
|
||||
.map((track) =>
|
||||
{
|
||||
return track.id;
|
||||
let trackId = track.jitsiRemoteId || track.id;
|
||||
|
||||
return trackId;
|
||||
})
|
||||
.join('|');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,6 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
constructor(pcConfig) {
|
||||
super();
|
||||
|
||||
// TODO: TMP
|
||||
window.PC = this;
|
||||
|
||||
logger.debug('constructor() pcConfig:', pcConfig);
|
||||
|
||||
// Buffered local ICE candidates (in WebRTC format).
|
||||
|
|
@ -115,6 +112,7 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
// - trackId: MediaStreamTrack.jitsiRemoteId
|
||||
// - cname: CNAME
|
||||
// - stream: MediaStream
|
||||
// - track: MediaStreamTrack
|
||||
// - rtpReceiver: Associated RTCRtpReceiver instance
|
||||
// @type {map<Number, Object>}
|
||||
this._remoteTrackInfos = new Map();
|
||||
|
|
@ -686,9 +684,6 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
const localCapabilities = this._localCapabilities;
|
||||
const localTrackInfos = this._localTrackInfos;
|
||||
|
||||
// TODO: TMP
|
||||
logger.warn(`_createLocalDescription() ICE local [username:${localIceParameters.usernameFragment}, password:${localIceParameters.password}`);
|
||||
|
||||
// Increase SDP version if an offer.
|
||||
if (type === 'offer') {
|
||||
this._sdpGlobalFields.version++;
|
||||
|
|
@ -1901,6 +1896,17 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
cname
|
||||
});
|
||||
|
||||
// Store the track into the info object.
|
||||
// NOTE: This should not be needed, but Edge has a bug:
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12399497/
|
||||
info.track = rtpReceiver.track;
|
||||
|
||||
// Set error handler.
|
||||
rtpReceiver.onerror = ev => {
|
||||
logger.error('rtpReceiver "error" event, event:');
|
||||
logger.error(ev);
|
||||
};
|
||||
|
||||
// Fill the info with the stream and rtpReceiver.
|
||||
info.stream = stream;
|
||||
info.rtpReceiver = rtpReceiver;
|
||||
|
|
@ -1913,11 +1919,14 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
rtpReceiver.receive(parameters);
|
||||
|
||||
// Get the associated MediaStreamTrack.
|
||||
const track = rtpReceiver.track;
|
||||
const track = info.track;
|
||||
|
||||
// Set custom property with the remote id.
|
||||
track.jitsiRemoteId = trackRemoteId;
|
||||
|
||||
// TODO: TMP
|
||||
logger.warn(`new remote track [stream.jitsiRemoteId:${stream.jitsiRemoteId}, track.jitsiRemoteId:${track.jitsiRemoteId}, track.id:${track.id}]`);
|
||||
|
||||
// Add the track to the stream.
|
||||
stream.addTrack(track);
|
||||
|
||||
|
|
@ -1940,8 +1949,11 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
|
||||
const info = this._remoteTrackInfos.get(ssrc);
|
||||
const stream = info.stream;
|
||||
const track = info.track;
|
||||
const rtpReceiver = info.rtpReceiver;
|
||||
const track = rtpReceiver.track;
|
||||
|
||||
// TODO: TMP
|
||||
logger.warn(`remote track removed [track.jitsiRemoteId:${track.jitsiRemoteId}, track.id:${track.id}]`);
|
||||
|
||||
try {
|
||||
rtpReceiver.stop();
|
||||
|
|
@ -1965,6 +1977,9 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
|
||||
// Emit MediaStream 'removetrack' for removed tracks.
|
||||
for (const [ track, stream ] of removedRemoteTracks) {
|
||||
// TODO: TMP
|
||||
logger.warn(`emit "removetrack" [stream.jitsiRemoteId:${stream.jitsiRemoteId}, track.jitsiRemoteId:${track.jitsiRemoteId}, track.id:${track.id}]`);
|
||||
|
||||
const event = new Event('removetrack');
|
||||
|
||||
event.track = track;
|
||||
|
|
@ -1987,6 +2002,9 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
|
||||
// Emit RTCPeerConnection 'removestream' for removed remote streams.
|
||||
for (const [ streamRemoteId, stream ] of this._remoteStreams) {
|
||||
// TODO: TMP
|
||||
logger.warn(`remote stream [streamRemoteId:${streamRemoteId}, jitsiRemoteId:${stream.jitsiRemoteId}, tracks:${stream.getTracks().length}]`);
|
||||
|
||||
if (stream.getTracks().length > 0) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
|
|
@ -2202,11 +2220,6 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
logger.debug(
|
||||
'nominated candidate pair:',
|
||||
iceTransport.getNominatedCandidatePair());
|
||||
|
||||
// TODO: TMP
|
||||
logger.warn(
|
||||
'nominated candidate pair:',
|
||||
iceTransport.getNominatedCandidatePair());
|
||||
}
|
||||
|
||||
this._emitIceConnectionStateChange();
|
||||
|
|
@ -2390,11 +2403,6 @@ export default class ortcRTCPeerConnection extends yaeti.EventTarget {
|
|||
const remoteDtlsParameters
|
||||
= ortcUtils.extractDtlsParameters(sdpObject);
|
||||
|
||||
// TODO: TMP
|
||||
logger.warn(`_startIceAndDtls() ICE remote [username:${remoteIceParameters.usernameFragment}, password:${remoteIceParameters.password}`);
|
||||
logger.warn('_startIceAndDtls() remoteIceParameters:');
|
||||
console.warn(remoteIceParameters);
|
||||
|
||||
// Start the RTCIceTransport.
|
||||
switch (desc.type) {
|
||||
case 'offer':
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import browser from 'bowser';
|
||||
import webrtc from 'webrtc-adapter'; // eslint-disable-line no-unused-vars
|
||||
import domready from 'domready';
|
||||
import UrlParse from 'url-parse';
|
||||
import React from 'react';
|
||||
|
|
@ -14,20 +13,29 @@ import edgeRTCPeerConnection from './edge/RTCPeerConnection';
|
|||
import edgeRTCSessionDescription from './edge/RTCSessionDescription';
|
||||
import App from './components/App';
|
||||
|
||||
const REGEXP_FRAGMENT_ROOM_ID = new RegExp('^#room-id=([0-9a-zA-Z_\-]+)$');
|
||||
const REGEXP_FRAGMENT_ROOM_ID = new RegExp('^#room-id=([0-9a-zA-Z_-]+)$');
|
||||
const logger = new Logger();
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
logger.debug('detected browser [name:"%s", version:%s]', browser.name, browser.version);
|
||||
|
||||
// If Edge, use the Jitsi RTCPeerConnection shim.
|
||||
if (browser.msedge)
|
||||
{
|
||||
logger.debug('EDGE detected, overriding WebRTC global classes');
|
||||
logger.debug('Edge detected, overriding RTCPeerConnection and RTCSessionDescription');
|
||||
|
||||
window.RTCPeerConnection = edgeRTCPeerConnection;
|
||||
window.RTCSessionDescription = edgeRTCSessionDescription;
|
||||
}
|
||||
// Otherwise, do almost anything.
|
||||
else
|
||||
{
|
||||
window.RTCPeerConnection =
|
||||
window.webkitRTCPeerConnection ||
|
||||
window.mozRTCPeerConnection ||
|
||||
window.RTCPeerConnection;
|
||||
}
|
||||
|
||||
domready(() =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import browser from 'bowser';
|
|||
import randomNumberLib from 'random-number';
|
||||
import Logger from './Logger';
|
||||
|
||||
global.BROWSER = browser;
|
||||
|
||||
const logger = new Logger('utils');
|
||||
const randomNumberGenerator = randomNumberLib.generator(
|
||||
{
|
||||
|
|
@ -42,6 +44,18 @@ export function isPlanB()
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately Edge produces rtpSender.send() to fail when receiving media
|
||||
* from others and removing/adding a local track.
|
||||
*/
|
||||
export function canChangeResolution()
|
||||
{
|
||||
if (browser.msedge)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function randomNumber()
|
||||
{
|
||||
return randomNumberGenerator();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mediasoup-demo-app",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "mediasoup demo app",
|
||||
"author": "Iñaki Baz Castillo <ibc@aliax.net>",
|
||||
|
|
@ -8,25 +8,24 @@
|
|||
"main": "lib/index.jsx",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.23.0",
|
||||
"bowser": "^1.6.1",
|
||||
"bowser": "^1.7.0",
|
||||
"classnames": "^2.2.5",
|
||||
"debug": "^2.6.4",
|
||||
"debug": "^2.6.8",
|
||||
"domready": "^1.0.8",
|
||||
"hark": "ibc/hark#main-with-raf",
|
||||
"material-ui": "^0.18.2",
|
||||
"material-ui": "^0.18.3",
|
||||
"prop-types": "^15.5.10",
|
||||
"protoo-client": "^1.1.4",
|
||||
"random-number": "0.0.7",
|
||||
"random-string": "^0.2.0",
|
||||
"react": "^15.5.4",
|
||||
"react-clipboard.js": "^1.0.1",
|
||||
"react-dom": "^15.5.4",
|
||||
"react": "^15.6.1",
|
||||
"react-clipboard.js": "^1.1.2",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-notification-system": "ibc/react-notification-system#master",
|
||||
"react-tap-event-plugin": "^2.0.1",
|
||||
"react-transition-group": "^1.1.3",
|
||||
"react-transition-group": "^1.2.0",
|
||||
"sdp-transform": "^2.3.0",
|
||||
"url-parse": "^1.1.8",
|
||||
"webrtc-adapter": "^4.0.0",
|
||||
"url-parse": "^1.1.9",
|
||||
"yaeti": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -35,16 +34,16 @@
|
|||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browser-sync": "^2.18.8",
|
||||
"browserify": "^14.3.0",
|
||||
"del": "^2.2.2",
|
||||
"browser-sync": "^2.18.12",
|
||||
"browserify": "^14.4.0",
|
||||
"del": "^3.0.0",
|
||||
"envify": "^4.0.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-react": "^7.0.1",
|
||||
"eslint": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.3.0",
|
||||
"eslint-plugin-react": "^7.1.0",
|
||||
"gulp": "git://github.com/gulpjs/gulp.git#4.0",
|
||||
"gulp-css-base64": "^1.3.4",
|
||||
"gulp-eslint": "^3.0.1",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"gulp-header": "^1.8.8",
|
||||
"gulp-if": "^2.0.2",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ class Room extends EventEmitter
|
|||
});
|
||||
});
|
||||
|
||||
this._mediaRoom.on('audiolevels', (entries) =>
|
||||
// TODO: FIX
|
||||
this._mediaRoom.on('____audiolevels', (entries) =>
|
||||
{
|
||||
logger.debug('room "audiolevels" event');
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"colors": "^1.1.2",
|
||||
"debug": "^2.6.4",
|
||||
"express": "^4.15.2",
|
||||
"mediasoup": "^1.2.3",
|
||||
"debug": "^2.6.8",
|
||||
"express": "^4.15.3",
|
||||
"mediasoup": "^1.2.5",
|
||||
"protoo-server": "^1.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"gulp": "git://github.com/gulpjs/gulp.git#4.0",
|
||||
"gulp-eslint": "^3.0.1",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"gulp-plumber": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue