Initial work on Edge
parent
2ea269ea9a
commit
0c63f4cd8c
|
|
@ -5,8 +5,8 @@ import browser from 'bowser';
|
||||||
import sdpTransform from 'sdp-transform';
|
import sdpTransform from 'sdp-transform';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
import protooClient from 'protoo-client';
|
import protooClient from 'protoo-client';
|
||||||
import urlFactory from './urlFactory';
|
import * as urlFactory from './urlFactory';
|
||||||
import utils from './utils';
|
import * as utils from './utils';
|
||||||
|
|
||||||
const logger = new Logger('Client');
|
const logger = new Logger('Client');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ClipboardButton from 'react-clipboard.js';
|
import ClipboardButton from 'react-clipboard.js';
|
||||||
|
import browser from 'bowser';
|
||||||
import TransitionAppear from './TransitionAppear';
|
import TransitionAppear from './TransitionAppear';
|
||||||
import LocalVideo from './LocalVideo';
|
import LocalVideo from './LocalVideo';
|
||||||
import RemoteVideo from './RemoteVideo';
|
import RemoteVideo from './RemoteVideo';
|
||||||
import Stats from './Stats';
|
import Stats from './Stats';
|
||||||
import Logger from '../Logger';
|
import Logger from '../Logger';
|
||||||
import utils from '../utils';
|
import * as utils from '../utils';
|
||||||
import Client from '../Client';
|
import Client from '../Client';
|
||||||
|
|
||||||
const logger = new Logger('Room');
|
const logger = new Logger('Room');
|
||||||
|
|
@ -285,8 +286,8 @@ export default class Room extends React.Component
|
||||||
imageHeight : 80
|
imageHeight : 80
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start retrieving WebRTC stats (unless mobile)
|
// Start retrieving WebRTC stats (unless mobile or Edge).
|
||||||
if (utils.isDesktop())
|
if (utils.isDesktop() && !browser.msedge)
|
||||||
{
|
{
|
||||||
this.setState({ showStats: true });
|
this.setState({ showStats: true });
|
||||||
|
|
||||||
|
|
@ -475,10 +476,10 @@ export default class Room extends React.Component
|
||||||
|
|
||||||
this.setState({ stats: null });
|
this.setState({ stats: null });
|
||||||
|
|
||||||
this._statsTimer = setTimeout(() =>
|
// this._statsTimer = setTimeout(() =>
|
||||||
{
|
// {
|
||||||
getStats.call(this);
|
// getStats.call(this);
|
||||||
}, STATS_INTERVAL);
|
// }, STATS_INTERVAL);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,105 @@
|
||||||
|
import sdpTransform from 'sdp-transform';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTCSessionDescription implementation.
|
||||||
|
*/
|
||||||
|
export default class RTCSessionDescription {
|
||||||
|
/**
|
||||||
|
* RTCSessionDescription constructor.
|
||||||
|
* @param {Object} [data]
|
||||||
|
* @param {String} [data.type] - 'offer' / 'answer'.
|
||||||
|
* @param {String} [data.sdp] - SDP string.
|
||||||
|
* @param {Object} [data._sdpObject] - SDP object generated by the
|
||||||
|
* sdp-transform library.
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
// @type {String}
|
||||||
|
this._sdp = null;
|
||||||
|
|
||||||
|
// @type {Object}
|
||||||
|
this._sdpObject = null;
|
||||||
|
|
||||||
|
// @type {String}
|
||||||
|
this._type = null;
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case 'offer':
|
||||||
|
break;
|
||||||
|
case 'answer':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError(`invalid type "${data.type}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._type = data.type;
|
||||||
|
|
||||||
|
if (typeof data.sdp === 'string') {
|
||||||
|
this._sdp = data.sdp;
|
||||||
|
try {
|
||||||
|
this._sdpObject = sdpTransform.parse(data.sdp);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`invalid sdp: ${error}`);
|
||||||
|
}
|
||||||
|
} else if (typeof data._sdpObject === 'object') {
|
||||||
|
this._sdpObject = data._sdpObject;
|
||||||
|
try {
|
||||||
|
this._sdp = sdpTransform.write(data._sdpObject);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`invalid sdp object: ${error}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new TypeError('invalid sdp or _sdpObject');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sdp field.
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
get sdp() {
|
||||||
|
return this._sdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sdp field.
|
||||||
|
* NOTE: This is not allowed per spec, but lib-jitsi-meet uses it.
|
||||||
|
* @param {String} sdp
|
||||||
|
*/
|
||||||
|
set sdp(sdp) {
|
||||||
|
try {
|
||||||
|
this._sdpObject = sdpTransform.parse(sdp);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`invalid sdp: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sdp = sdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the internal sdp object.
|
||||||
|
* @return {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
get sdpObject() {
|
||||||
|
return this._sdpObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get type field.
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
get type() {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object with type and sdp fields.
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
sdp: this._sdp,
|
||||||
|
type: this._type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Create a class inheriting from Error.
|
||||||
|
*/
|
||||||
|
function createErrorClass(name) {
|
||||||
|
const klass = class extends Error {
|
||||||
|
/**
|
||||||
|
* Custom error class constructor.
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
// Override `name` property value and make it non enumerable.
|
||||||
|
Object.defineProperty(this, 'name', { value: name });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return klass;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InvalidStateError = createErrorClass('InvalidStateError');
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
/* global RTCRtpReceiver */
|
||||||
|
|
||||||
|
import sdpTransform from 'sdp-transform';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract RTP capabilities from remote description.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {RTCRtpCapabilities}
|
||||||
|
*/
|
||||||
|
export function extractCapabilities(sdpObject) {
|
||||||
|
// Map of RtpCodecParameters indexed by payload type.
|
||||||
|
const codecsMap = new Map();
|
||||||
|
|
||||||
|
// Array of RtpHeaderExtensions.
|
||||||
|
const headerExtensions = [];
|
||||||
|
|
||||||
|
for (const m of sdpObject.media) {
|
||||||
|
// Media kind.
|
||||||
|
const kind = m.type;
|
||||||
|
|
||||||
|
if (kind !== 'audio' && kind !== 'video') {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get codecs.
|
||||||
|
for (const rtp of m.rtp) {
|
||||||
|
const codec = {
|
||||||
|
clockRate: rtp.rate,
|
||||||
|
kind,
|
||||||
|
mimeType: `${kind}/${rtp.codec}`,
|
||||||
|
name: rtp.codec,
|
||||||
|
numChannels: rtp.encoding || 1,
|
||||||
|
parameters: {},
|
||||||
|
preferredPayloadType: rtp.payload,
|
||||||
|
rtcpFeedback: []
|
||||||
|
};
|
||||||
|
|
||||||
|
codecsMap.set(codec.preferredPayloadType, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get codec parameters.
|
||||||
|
for (const fmtp of m.fmtp || []) {
|
||||||
|
const parameters = sdpTransform.parseFmtpConfig(fmtp.config);
|
||||||
|
const codec = codecsMap.get(fmtp.payload);
|
||||||
|
|
||||||
|
if (!codec) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
codec.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RTCP feedback for each codec.
|
||||||
|
for (const fb of m.rtcpFb || []) {
|
||||||
|
const codec = codecsMap.get(fb.payload);
|
||||||
|
|
||||||
|
if (!codec) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
codec.rtcpFeedback.push({
|
||||||
|
parameter: fb.subtype || '',
|
||||||
|
type: fb.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RTP header extensions.
|
||||||
|
for (const ext of m.ext || []) {
|
||||||
|
const preferredId = ext.value;
|
||||||
|
const uri = ext.uri;
|
||||||
|
const headerExtension = {
|
||||||
|
kind,
|
||||||
|
uri,
|
||||||
|
preferredId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if already present.
|
||||||
|
const duplicated = headerExtensions.find(savedHeaderExtension =>
|
||||||
|
headerExtension.kind === savedHeaderExtension.kind
|
||||||
|
&& headerExtension.uri === savedHeaderExtension.uri
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!duplicated) {
|
||||||
|
headerExtensions.push(headerExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
codecs: Array.from(codecsMap.values()),
|
||||||
|
fecMechanisms: [], // TODO
|
||||||
|
headerExtensions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract DTLS parameters from remote description.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {RTCDtlsParameters}
|
||||||
|
*/
|
||||||
|
export function extractDtlsParameters(sdpObject) {
|
||||||
|
const media = getFirstActiveMediaSection(sdpObject);
|
||||||
|
const fingerprint = media.fingerprint || sdpObject.fingerprint;
|
||||||
|
let role;
|
||||||
|
|
||||||
|
switch (media.setup) {
|
||||||
|
case 'active':
|
||||||
|
role = 'client';
|
||||||
|
break;
|
||||||
|
case 'passive':
|
||||||
|
role = 'server';
|
||||||
|
break;
|
||||||
|
case 'actpass':
|
||||||
|
role = 'auto';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
role,
|
||||||
|
fingerprints: [
|
||||||
|
{
|
||||||
|
algorithm: fingerprint.type,
|
||||||
|
value: fingerprint.hash
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract ICE candidates from remote description.
|
||||||
|
* NOTE: This implementation assumes a single BUNDLEd transport and rtcp-mux.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {sequence<RTCIceCandidate>}
|
||||||
|
*/
|
||||||
|
export function extractIceCandidates(sdpObject) {
|
||||||
|
const media = getFirstActiveMediaSection(sdpObject);
|
||||||
|
const candidates = [];
|
||||||
|
|
||||||
|
for (const c of media.candidates) {
|
||||||
|
// Ignore RTCP candidates (we assume rtcp-mux).
|
||||||
|
if (c.component !== 1) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidate = {
|
||||||
|
foundation: c.foundation,
|
||||||
|
ip: c.ip,
|
||||||
|
port: c.port,
|
||||||
|
priority: c.priority,
|
||||||
|
protocol: c.transport.toLowerCase(),
|
||||||
|
type: c.type
|
||||||
|
};
|
||||||
|
|
||||||
|
candidates.push(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract ICE parameters from remote description.
|
||||||
|
* NOTE: This implementation assumes a single BUNDLEd transport.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {RTCIceParameters}
|
||||||
|
*/
|
||||||
|
export function extractIceParameters(sdpObject) {
|
||||||
|
const media = getFirstActiveMediaSection(sdpObject);
|
||||||
|
const usernameFragment = media.iceUfrag;
|
||||||
|
const password = media.icePwd;
|
||||||
|
const icelite = sdpObject.icelite === 'ice-lite';
|
||||||
|
|
||||||
|
return {
|
||||||
|
icelite,
|
||||||
|
password,
|
||||||
|
usernameFragment
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract MID values from remote description.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {map<String, String>} Ordered Map with MID as key and kind as value.
|
||||||
|
*/
|
||||||
|
export function extractMids(sdpObject) {
|
||||||
|
const midToKind = new Map();
|
||||||
|
|
||||||
|
// Ignore disabled media sections.
|
||||||
|
for (const m of sdpObject.media) {
|
||||||
|
midToKind.set(m.mid, m.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return midToKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract tracks information.
|
||||||
|
* @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
||||||
|
* @return {Map}
|
||||||
|
*/
|
||||||
|
export function extractTrackInfos(sdpObject) {
|
||||||
|
// Map with info about receiving media.
|
||||||
|
// - index: Media SSRC
|
||||||
|
// - value: Object
|
||||||
|
// - kind: 'audio' / 'video'
|
||||||
|
// - ssrc: Media SSRC
|
||||||
|
// - rtxSsrc: RTX SSRC (may be unset)
|
||||||
|
// - streamId: MediaStream.jitsiRemoteId
|
||||||
|
// - trackId: MediaStreamTrack.jitsiRemoteId
|
||||||
|
// - cname: CNAME
|
||||||
|
// @type {map<Number, Object>}
|
||||||
|
const infos = new Map();
|
||||||
|
|
||||||
|
// Map with stream SSRC as index and associated RTX SSRC as value.
|
||||||
|
// @type {map<Number, Number>}
|
||||||
|
const rtxMap = new Map();
|
||||||
|
|
||||||
|
// Set of RTX SSRC values.
|
||||||
|
const rtxSet = new Set();
|
||||||
|
|
||||||
|
for (const m of sdpObject.media) {
|
||||||
|
const kind = m.type;
|
||||||
|
|
||||||
|
if (kind !== 'audio' && kind !== 'video') {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RTX information.
|
||||||
|
for (const ssrcGroup of m.ssrcGroups || []) {
|
||||||
|
// Just consider FID.
|
||||||
|
if (ssrcGroup.semantics !== 'FID') {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssrcs
|
||||||
|
= ssrcGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
|
||||||
|
const ssrc = ssrcs[0];
|
||||||
|
const rtxSsrc = ssrcs[1];
|
||||||
|
|
||||||
|
rtxMap.set(ssrc, rtxSsrc);
|
||||||
|
rtxSet.add(rtxSsrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ssrcObject of m.ssrcs || []) {
|
||||||
|
const ssrc = ssrcObject.id;
|
||||||
|
|
||||||
|
// Ignore RTX.
|
||||||
|
if (rtxSet.has(ssrc)) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = infos.get(ssrc);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
info = {
|
||||||
|
kind,
|
||||||
|
rtxSsrc: rtxMap.get(ssrc),
|
||||||
|
ssrc
|
||||||
|
};
|
||||||
|
|
||||||
|
infos.set(ssrc, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ssrcObject.attribute) {
|
||||||
|
case 'cname': {
|
||||||
|
info.cname = ssrcObject.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'msid': {
|
||||||
|
const values = ssrcObject.value.split(' ');
|
||||||
|
const streamId = values[0];
|
||||||
|
const trackId = values[1];
|
||||||
|
|
||||||
|
info.streamId = streamId;
|
||||||
|
info.trackId = trackId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'mslabel': {
|
||||||
|
const streamId = ssrcObject.value;
|
||||||
|
|
||||||
|
info.streamId = streamId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'label': {
|
||||||
|
const trackId = ssrcObject.value;
|
||||||
|
|
||||||
|
info.trackId = trackId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get local ORTC RTP capabilities filtered and adapted to the given remote RTP
|
||||||
|
* capabilities.
|
||||||
|
* @param {RTCRtpCapabilities} filterWithCapabilities - RTP capabilities to
|
||||||
|
* filter with.
|
||||||
|
* @return {RTCRtpCapabilities}
|
||||||
|
*/
|
||||||
|
export function getLocalCapabilities(filterWithCapabilities) {
|
||||||
|
const localFullCapabilities = RTCRtpReceiver.getCapabilities();
|
||||||
|
const localCapabilities = {
|
||||||
|
codecs: [],
|
||||||
|
fecMechanisms: [],
|
||||||
|
headerExtensions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map of RTX and codec payloads.
|
||||||
|
// - index: Codec payloadType
|
||||||
|
// - value: Associated RTX payloadType
|
||||||
|
// @type {map<Number, Number>}
|
||||||
|
const remoteRtxMap = new Map();
|
||||||
|
|
||||||
|
// Set codecs.
|
||||||
|
for (const remoteCodec of filterWithCapabilities.codecs) {
|
||||||
|
const remoteCodecName = remoteCodec.name.toLowerCase();
|
||||||
|
|
||||||
|
if (remoteCodecName === 'rtx') {
|
||||||
|
remoteRtxMap.set(
|
||||||
|
remoteCodec.parameters.apt, remoteCodec.preferredPayloadType);
|
||||||
|
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const localCodec = localFullCapabilities.codecs.find(codec =>
|
||||||
|
codec.name.toLowerCase() === remoteCodecName
|
||||||
|
&& codec.kind === remoteCodec.kind
|
||||||
|
&& codec.clockRate === remoteCodec.clockRate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!localCodec) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const codec = {
|
||||||
|
clockRate: localCodec.clockRate,
|
||||||
|
kind: localCodec.kind,
|
||||||
|
mimeType: `${localCodec.kind}/${localCodec.name}`,
|
||||||
|
name: localCodec.name,
|
||||||
|
numChannels: localCodec.numChannels || 1,
|
||||||
|
parameters: {},
|
||||||
|
preferredPayloadType: remoteCodec.preferredPayloadType,
|
||||||
|
rtcpFeedback: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const remoteParamName of Object.keys(remoteCodec.parameters)) {
|
||||||
|
const remoteParamValue
|
||||||
|
= remoteCodec.parameters[remoteParamName];
|
||||||
|
|
||||||
|
for (const localParamName of Object.keys(localCodec.parameters)) {
|
||||||
|
const localParamValue
|
||||||
|
= localCodec.parameters[localParamName];
|
||||||
|
|
||||||
|
if (localParamName !== remoteParamName) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We should consider much more cases here, but Edge
|
||||||
|
// does not support many codec parameters.
|
||||||
|
if (localParamValue === remoteParamValue) {
|
||||||
|
// Use this RTP parameter.
|
||||||
|
codec.parameters[localParamName] = localParamValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const remoteFb of remoteCodec.rtcpFeedback) {
|
||||||
|
const localFb = localCodec.rtcpFeedback.find(fb =>
|
||||||
|
fb.type === remoteFb.type
|
||||||
|
&& fb.parameter === remoteFb.parameter
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localFb) {
|
||||||
|
// Use this RTCP feedback.
|
||||||
|
codec.rtcpFeedback.push(localFb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use this codec.
|
||||||
|
localCapabilities.codecs.push(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add RTX for video codecs.
|
||||||
|
for (const codec of localCapabilities.codecs) {
|
||||||
|
const payloadType = codec.preferredPayloadType;
|
||||||
|
|
||||||
|
if (!remoteRtxMap.has(payloadType)) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const rtxCodec = {
|
||||||
|
clockRate: codec.clockRate,
|
||||||
|
kind: codec.kind,
|
||||||
|
mimeType: `${codec.kind}/rtx`,
|
||||||
|
name: 'rtx',
|
||||||
|
parameters: {
|
||||||
|
apt: payloadType
|
||||||
|
},
|
||||||
|
preferredPayloadType: remoteRtxMap.get(payloadType),
|
||||||
|
rtcpFeedback: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add RTX codec.
|
||||||
|
localCapabilities.codecs.push(rtxCodec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add RTP header extensions.
|
||||||
|
for (const remoteExtension of filterWithCapabilities.headerExtensions) {
|
||||||
|
const localExtension
|
||||||
|
= localFullCapabilities.headerExtensions.find(extension =>
|
||||||
|
extension.kind === remoteExtension.kind
|
||||||
|
&& extension.uri === remoteExtension.uri
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localExtension) {
|
||||||
|
const extension = {
|
||||||
|
kind: localExtension.kind,
|
||||||
|
preferredEncrypt: Boolean(remoteExtension.preferredEncrypt),
|
||||||
|
preferredId: remoteExtension.preferredId,
|
||||||
|
uri: localExtension.uri
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use this RTP header extension.
|
||||||
|
localCapabilities.headerExtensions.push(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add FEC mechanisms.
|
||||||
|
// NOTE: We don't support FEC yet and, in fact, neither does Edge.
|
||||||
|
for (const remoteFecMechanism of filterWithCapabilities.fecMechanisms) {
|
||||||
|
const localFecMechanism
|
||||||
|
= localFullCapabilities.fecMechanisms.find(fec =>
|
||||||
|
fec === remoteFecMechanism
|
||||||
|
);
|
||||||
|
|
||||||
|
if (localFecMechanism) {
|
||||||
|
// Use this FEC mechanism.
|
||||||
|
localCapabilities.fecMechanisms.push(localFecMechanism);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return localCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first acive media section.
|
||||||
|
* @param {Object} sdpObject - SDP object generated by sdp-transform.
|
||||||
|
* @return {Object} SDP media section as parsed by sdp-transform.
|
||||||
|
*/
|
||||||
|
function getFirstActiveMediaSection(sdpObject) {
|
||||||
|
return sdpObject.media.find(m =>
|
||||||
|
m.iceUfrag && m.port !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -9,13 +9,26 @@ import ReactDOM from 'react-dom';
|
||||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
import randomString from 'random-string';
|
import randomString from 'random-string';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
import utils from './utils';
|
import * as utils from './utils';
|
||||||
|
import edgeRTCPeerConnection from './edge/RTCPeerConnection';
|
||||||
|
import edgeRTCSessionDescription from './edge/RTCSessionDescription';
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
|
||||||
|
// TODO: TMP
|
||||||
|
global.BROWSER = browser;
|
||||||
|
|
||||||
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();
|
const logger = new Logger();
|
||||||
|
|
||||||
logger.debug('detected browser [name:"%s", version:%s]', browser.name, browser.version);
|
logger.warn('detected browser [name:"%s", version:%s]', browser.name, browser.version);
|
||||||
|
|
||||||
|
if (browser.msedge)
|
||||||
|
{
|
||||||
|
logger.warn('EDGE detected, overriding WebRTC global classes');
|
||||||
|
|
||||||
|
window.RTCPeerConnection = edgeRTCPeerConnection;
|
||||||
|
window.RTCSessionDescription = edgeRTCSessionDescription;
|
||||||
|
}
|
||||||
|
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
module.exports =
|
export function getProtooUrl(peerId, roomId)
|
||||||
{
|
|
||||||
getProtooUrl(peerId, roomId)
|
|
||||||
{
|
{
|
||||||
let hostname = window.location.hostname;
|
let hostname = window.location.hostname;
|
||||||
let port = config.protoo.listenPort;
|
let port = config.protoo.listenPort;
|
||||||
|
|
@ -12,4 +10,3 @@ module.exports =
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import browser from 'bowser';
|
import browser from 'bowser';
|
||||||
|
import randomNumberLib from 'random-number';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
|
|
||||||
const logger = new Logger('utils');
|
const logger = new Logger('utils');
|
||||||
|
const randomNumberGenerator = randomNumberLib.generator(
|
||||||
|
{
|
||||||
|
min : 10000000,
|
||||||
|
max : 99999999,
|
||||||
|
integer : true
|
||||||
|
});
|
||||||
|
|
||||||
let mediaQueryDetectorElem;
|
let mediaQueryDetectorElem;
|
||||||
|
|
||||||
module.exports =
|
export function initialize()
|
||||||
{
|
|
||||||
initialize()
|
|
||||||
{
|
{
|
||||||
logger.debug('initialize()');
|
logger.debug('initialize()');
|
||||||
|
|
||||||
|
|
@ -17,27 +22,32 @@ module.exports =
|
||||||
mediaQueryDetectorElem = document.getElementById('mediasoup-demo-app-media-query-detector');
|
mediaQueryDetectorElem = document.getElementById('mediasoup-demo-app-media-query-detector');
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
isDesktop()
|
export function isDesktop()
|
||||||
{
|
{
|
||||||
return !!mediaQueryDetectorElem.offsetParent;
|
return !!mediaQueryDetectorElem.offsetParent;
|
||||||
},
|
}
|
||||||
|
|
||||||
isMobile()
|
export function isMobile()
|
||||||
{
|
{
|
||||||
return !mediaQueryDetectorElem.offsetParent;
|
return !mediaQueryDetectorElem.offsetParent;
|
||||||
},
|
}
|
||||||
|
|
||||||
isPlanB()
|
export function isPlanB()
|
||||||
{
|
{
|
||||||
if (browser.chrome || browser.chromium || browser.opera)
|
if (browser.chrome || browser.chromium || browser.opera || browser.msedge)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
|
|
||||||
closeMediaStream(stream)
|
export function randomNumber()
|
||||||
|
{
|
||||||
|
return randomNumberGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeMediaStream(stream)
|
||||||
{
|
{
|
||||||
if (!stream)
|
if (!stream)
|
||||||
return;
|
return;
|
||||||
|
|
@ -49,4 +59,3 @@ module.exports =
|
||||||
tracks[i].stop();
|
tracks[i].stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"hark": "ibc/hark#main-with-raf",
|
"hark": "ibc/hark#main-with-raf",
|
||||||
"material-ui": "^0.17.4",
|
"material-ui": "^0.17.4",
|
||||||
"protoo-client": "^1.1.4",
|
"protoo-client": "^1.1.4",
|
||||||
|
"random-number": "0.0.7",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
"react-addons-css-transition-group": "^15.5.2",
|
"react-addons-css-transition-group": "^15.5.2",
|
||||||
|
|
@ -24,7 +25,8 @@
|
||||||
"react-tap-event-plugin": "^2.0.1",
|
"react-tap-event-plugin": "^2.0.1",
|
||||||
"sdp-transform": "^2.3.0",
|
"sdp-transform": "^2.3.0",
|
||||||
"url-parse": "^1.1.8",
|
"url-parse": "^1.1.8",
|
||||||
"webrtc-adapter": "^3.3.3"
|
"webrtc-adapter": "^3.3.3",
|
||||||
|
"yaeti": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-transform-object-assign": "^6.22.0",
|
"babel-plugin-transform-object-assign": "^6.22.0",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"debug": "^2.6.4",
|
"debug": "^2.6.4",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"mediasoup": "^1.0.1",
|
"mediasoup": "^1.2.3",
|
||||||
"protoo-server": "^1.1.4"
|
"protoo-server": "^1.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue