Add suport for Safari 11 and Microsoft Edge

master
Iñaki Baz Castillo 2017-06-21 17:09:50 +02:00
parent 103be0ece3
commit b86b6e6957
11 changed files with 2651 additions and 2487 deletions

View File

@ -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);

View File

@ -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)
{

View File

@ -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 });
});

View File

@ -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 =

View File

@ -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('|');
}

View File

@ -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':

View File

@ -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(() =>
{

View File

@ -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();

View File

@ -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",

View File

@ -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');

View File

@ -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"
}
}