Skip to content
Permalink
Browse files

Allow injecting a custom RTCPeerConnection class

  • Loading branch information
legastero committed Mar 4, 2020
1 parent ae53967 commit 75729c58874e5c17a9a6ed0ab93c65d1d0ae6f4f
Showing with 117 additions and 151 deletions.
  1. +0 −2 examples/jingle-demo.html
  2. +3 −3 package-lock.json
  3. +1 −1 package.json
  4. +31 −117 src/jingle/ICESession.ts
  5. +17 −1 src/jingle/SessionManager.ts
  6. +65 −27 src/plugins/jingle.ts
@@ -111,8 +111,6 @@ <h2>Remote Video</h2>
transports: transports
});

client.jingle.config.debug = true;

client.on('raw:incoming', function(data) {
inlog(data);
});

Some generated files are not rendered by default. Learn more.

@@ -20,7 +20,7 @@
"react-native-randombytes": "^3.5.3",
"readable-stream": "^2.3.6",
"sdp": "^2.10.0",
"stanza-shims": "^1.0.0",
"stanza-shims": "^1.1.1",
"tslib": "^1.9.3",
"ws": "^7.2.1"
},
@@ -14,51 +14,44 @@ import {
import BaseSession, { ActionCallback } from './Session';

export default class ICESession extends BaseSession {
public pc: RTCPeerConnection;
public bitrateLimit: number;
public pc!: RTCPeerConnection;
public bitrateLimit: number = 0;
public maximumBitrate?: number;
public currentBitrate?: number;
public maxRelayBandwidth: number;
public candidateBuffer: Array<{
sdpMLineIndex: number;
sdpMid: string;
candidate: string;
} | null>;
} | null> = [];
public transportType: JingleIce['transportType'] = NS_JINGLE_ICE_UDP_1;
public restartingIce: boolean;
public restartingIce: boolean = false;

private _maybeRestartingIce: any;
private _firstTimeConnected?: boolean;

constructor(opts: any) {
super(opts);

this.pc = new (RTCPeerConnection as any)(
{
...opts.config,
iceServers: opts.iceServers
},
opts.constraints
);
this.maxRelayBandwidth = opts.maxRelayBandwidth;

this.pc = this.parent.createPeerConnection({
...opts.config,
iceServers: opts.iceServers
})!;

this.pc.addEventListener('iceconnectionstatechange', () => {
this.pc.oniceconnectionstatechange = () => {
this.onIceStateChange();
});
};

this.pc.addEventListener('icecandidate', e => {
this.pc.onicecandidate = e => {
if (e.candidate) {
this.onIceCandidate(e);
} else {
this.onIceEndOfCandidates();
}
});
};

this.restrictRelayBandwidth();

this.bitrateLimit = 0;
this.maxRelayBandwidth = opts.maxRelayBandwidth;
this.candidateBuffer = [];
this.restartingIce = false;
}

public end(reason: JingleReasonCondition | JingleReason = 'success', silent: boolean = false) {
@@ -107,76 +100,30 @@ export default class ICESession extends BaseSession {
maximumBitrate = Math.min(maximumBitrate, this.maximumBitrate);
}
this.currentBitrate = maximumBitrate;
if (
!(
(window as any).RTCRtpSender &&
'getParameters' in (window as any).RTCRtpSender.prototype
)
) {
return;
}

// changes the maximum bandwidth using RTCRtpSender.setParameters.
const sender = this.pc.getSenders().find(s => !!s.track && s.track.kind === 'video');
if (!sender) {
if (!sender || !sender.getParameters) {
return;
}

let browser = '';
if (window.navigator && (window.navigator as any).mozGetUserMedia) {
browser = 'firefox';
} else if (window.navigator && (window.navigator as any).webkitGetUserMedia) {
browser = 'chrome';
}

const parameters = sender.getParameters();
if (browser === 'firefox' && !parameters.encodings) {
parameters.encodings = [{}];
}
if (maximumBitrate === 0) {
delete parameters.encodings[0].maxBitrate;
} else {
if (!parameters.encodings.length) {
parameters.encodings[0] = {};
}
parameters.encodings[0].maxBitrate = maximumBitrate;
}

try {
await this.processLocal('set-bitrate', async () => {
if (browser === 'chrome') {
await sender.setParameters(parameters);
} else if (browser === 'firefox') {
// Firefox needs renegotiation:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1253499
// but we do not want to intefere with our queue so we
// just hope this gets picked up.
if (this.pc.signalingState !== 'stable') {
await sender.setParameters(parameters);
} else if (
this.pc.localDescription &&
this.pc.localDescription.type === 'offer'
) {
await sender.setParameters(parameters);

const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
await this.pc.setRemoteDescription(this.pc.remoteDescription!);
await this.processBufferedCandidates();
} else if (
this.pc.localDescription &&
this.pc.localDescription.type === 'answer'
) {
await sender.setParameters(parameters);
await this.pc.setRemoteDescription(this.pc.remoteDescription!);
await this.processBufferedCandidates();
const parameters = sender.getParameters();
if (!parameters.encodings || !parameters.encodings.length) {
parameters.encodings = [{}];
}

const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
}
if (maximumBitrate === 0) {
delete parameters.encodings[0].maxBitrate;
} else {
parameters.encodings[0].maxBitrate = maximumBitrate;
}

await sender.setParameters(parameters);
});
} catch (err) {
this._log('error', 'setParameters failed', err);
this._log('error', 'Set maximumBitrate failed', err);
}
}

@@ -265,26 +212,14 @@ export default class ICESession extends BaseSession {
const sdpMid = content.name;
const results = ((content.transport! as JingleIce).candidates || []).map(async json => {
const candidate = SDPUtils.writeCandidate(convertCandidateToIntermediate(json));

let sdpMLineIndex!: number;
// workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1456417
const remoteSDP = this.pc.remoteDescription!.sdp;
const mediaSections = SDPUtils.getMediaSections(remoteSDP);
for (let i = 0; i < mediaSections.length; i++) {
if (SDPUtils.getMid(mediaSections[i]) === sdpMid) {
sdpMLineIndex = i;
break;
}
}

if (this.pc.signalingState === 'stable') {
try {
await this.pc.addIceCandidate({ sdpMid, sdpMLineIndex, candidate });
await this.pc.addIceCandidate({ sdpMid, candidate });
} catch (err) {
this._log('error', 'Could not add ICE candidate', err);
}
} else {
this.candidateBuffer.push({ sdpMid, sdpMLineIndex, candidate });
this.candidateBuffer.push({ sdpMid, candidate });
}
});
return Promise.all(results);
@@ -332,18 +267,6 @@ export default class ICESession extends BaseSession {
return;
}
const candidate = SDPUtils.parseCandidate(e.candidate.candidate);
let usernameFragment: string | undefined = candidate.usernameFragment;

/* monkeypatch ufrag in Firefox */
if (!usernameFragment) {
const json = importFromSDP(this.pc.localDescription!.sdp);
for (const media of json.media) {
if (media.mid === e.candidate.sdpMid && media.iceParameters) {
usernameFragment = media.iceParameters.usernameFragment;
}
}
}

const jingle: Partial<Jingle> = {
contents: [
{
@@ -352,7 +275,7 @@ export default class ICESession extends BaseSession {
transport: {
candidates: [convertIntermediateToCandidate(candidate)],
transportType: this.transportType,
usernameFragment
usernameFragment: candidate.usernameFragment
} as JingleIce
}
]
@@ -440,15 +363,6 @@ export default class ICESession extends BaseSession {
* the TURN server.
*/
private restrictRelayBandwidth() {
if (
!(
(window as any).RTCRtpSender &&
'getParameters' in (window as any).RTCRtpSender.prototype
)
) {
return;
}

this.pc.addEventListener('iceconnectionstatechange', async () => {
if (
this.pc.iceConnectionState !== 'completed' &&
@@ -519,7 +433,7 @@ export default class ICESession extends BaseSession {
clearTimeout(this._maybeRestartingIce);
}
this._maybeRestartingIce = setTimeout(() => {
delete this._maybeRestartingIce;
this._maybeRestartingIce = undefined;
if (this.pc.iceConnectionState === 'disconnected') {
this.restartIce();
}
@@ -1,4 +1,5 @@
import { EventEmitter } from 'events';
import { RTCPeerConnection } from 'stanza-shims';

import { JingleAction, JingleReasonCondition } from '../Constants';
import { NS_JINGLE_FILE_TRANSFER_5, NS_JINGLE_RTP_1 } from '../Namespaces';
@@ -25,9 +26,11 @@ export interface SessionManagerConfig {
rtcpMuxPolicy?: string;
sdpSemantics?: string;
};
hasRTCPeerConnection?: boolean;
peerConnectionConstraints?: any;
performTieBreak?: (session: BaseSession, req: IQ & { jingle: Jingle }) => boolean;
prepareSession?: (opts: any, req?: IQ & { jingle: Jingle }) => BaseSession | undefined;
createPeerConnection?: () => RTCPeerConnection | undefined;
}

export default class SessionManager extends EventEmitter {
@@ -39,6 +42,7 @@ export default class SessionManager extends EventEmitter {

public performTieBreak: (session: BaseSession, req: IQ & { jingle: Jingle }) => boolean;
public prepareSession: (opts: any, req?: IQ & { jingle: Jingle }) => BaseSession | undefined;
public createPeerConnection: (opts?: RTCConfiguration) => RTCPeerConnection | undefined;

constructor(conf: SessionManagerConfig = {}) {
super();
@@ -47,11 +51,14 @@ export default class SessionManager extends EventEmitter {
this.selfID = conf.selfID;
this.sessions = {};
this.peers = {};
this.iceServers = conf.iceServers || [{ urls: 'stun:stun.l.google.com:19302' }];
this.iceServers = conf.iceServers || [];

this.prepareSession =
conf.prepareSession ||
(opts => {
if (!this.config.hasRTCPeerConnection) {
return;
}
if (opts.applicationTypes.indexOf(NS_JINGLE_RTP_1) >= 0) {
return new MediaSession(opts);
}
@@ -74,8 +81,17 @@ export default class SessionManager extends EventEmitter {
return intersection.length > 0;
});

this.createPeerConnection =
conf.createPeerConnection ||
((opts?: RTCConfiguration) => {
if (!!RTCPeerConnection) {
return new (RTCPeerConnection as any)(opts);
}
});

this.config = {
debug: false,
hasRTCPeerConnection: !!RTCPeerConnection,
peerConnectionConfig: {
bundlePolicy: conf.bundlePolicy || 'balanced',
iceTransportPolicy: conf.iceTransportPolicy || 'all',

0 comments on commit 75729c5

Please sign in to comment.
You can’t perform that action at this time.