@lls/lls-audio 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1244 -0
- package/common/adapter.js +2592 -0
- package/common/waveform.js +181 -0
- package/package.json +50 -0
- package/player/Audio.js +192 -0
- package/player/CustomPlayer.js +218 -0
- package/player/CustomWave.js +158 -0
- package/player/Speed.js +98 -0
- package/player/Wave.js +173 -0
- package/player/index.js +218 -0
- package/player/styles/fallbackTimelineClassNames.js +43 -0
- package/player/styles/sliderDefaultClassNames.js +120 -0
- package/player/styles/styles.js +65 -0
- package/player/styles/timelineClassNames.js +62 -0
- package/player/styles/volumeClassNames.js +44 -0
- package/player/utils.js +53 -0
- package/recorder/AsyncLoader.js +84 -0
- package/recorder/DesktopAudioRecorder.js +165 -0
- package/recorder/DesktopRecorder.js +144 -0
- package/recorder/MobileRecorder.js +46 -0
- package/recorder/index.js +35 -0
- package/stories/customPlayer.js +21 -0
- package/stories/index.js +53 -0
- package/stories/player.js +21 -0
- package/stories/recorder.js +30 -0
- package/utils/audio.js +123 -0
- package/utils/audioContext.js +10 -0
|
@@ -0,0 +1,2592 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
4
|
+
|
|
5
|
+
(function (f) {
|
|
6
|
+
if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined") {
|
|
7
|
+
module.exports = f();
|
|
8
|
+
} else if (typeof define === "function" && define.amd) {
|
|
9
|
+
define([], f);
|
|
10
|
+
} else {
|
|
11
|
+
var g;if (typeof window !== "undefined") {
|
|
12
|
+
g = window;
|
|
13
|
+
} else if (typeof global !== "undefined") {
|
|
14
|
+
g = global;
|
|
15
|
+
} else if (typeof self !== "undefined") {
|
|
16
|
+
g = self;
|
|
17
|
+
} else {
|
|
18
|
+
g = this;
|
|
19
|
+
}g.adapter = f();
|
|
20
|
+
}
|
|
21
|
+
})(function () {
|
|
22
|
+
var define, module, exports;return function e(t, n, r) {
|
|
23
|
+
function s(o, u) {
|
|
24
|
+
if (!n[o]) {
|
|
25
|
+
if (!t[o]) {
|
|
26
|
+
var a = typeof require == "function" && require;if (!u && a) return a(o, !0);if (i) return i(o, !0);var f = new Error("Cannot find module '" + o + "'");throw f.code = "MODULE_NOT_FOUND", f;
|
|
27
|
+
}var l = n[o] = { exports: {} };t[o][0].call(l.exports, function (e) {
|
|
28
|
+
var n = t[o][1][e];return s(n ? n : e);
|
|
29
|
+
}, l, l.exports, e, t, n, r);
|
|
30
|
+
}return n[o].exports;
|
|
31
|
+
}var i = typeof require == "function" && require;for (var o = 0; o < r.length; o++) {
|
|
32
|
+
s(r[o]);
|
|
33
|
+
}return s;
|
|
34
|
+
}({ 1: [function (require, module, exports) {
|
|
35
|
+
/* eslint-env node */
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
// SDP helpers.
|
|
39
|
+
|
|
40
|
+
var SDPUtils = {};
|
|
41
|
+
|
|
42
|
+
// Generate an alphanumeric identifier for cname or mids.
|
|
43
|
+
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
|
|
44
|
+
SDPUtils.generateIdentifier = function () {
|
|
45
|
+
return Math.random().toString(36).substr(2, 10);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The RTCP CNAME used by all peerconnections from the same JS.
|
|
49
|
+
SDPUtils.localCName = SDPUtils.generateIdentifier();
|
|
50
|
+
|
|
51
|
+
// Splits SDP into lines, dealing with both CRLF and LF.
|
|
52
|
+
SDPUtils.splitLines = function (blob) {
|
|
53
|
+
return blob.trim().split('\n').map(function (line) {
|
|
54
|
+
return line.trim();
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
|
|
58
|
+
SDPUtils.splitSections = function (blob) {
|
|
59
|
+
var parts = blob.split('\nm=');
|
|
60
|
+
return parts.map(function (part, index) {
|
|
61
|
+
return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Returns lines that start with a certain prefix.
|
|
66
|
+
SDPUtils.matchPrefix = function (blob, prefix) {
|
|
67
|
+
return SDPUtils.splitLines(blob).filter(function (line) {
|
|
68
|
+
return line.indexOf(prefix) === 0;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Parses an ICE candidate line. Sample input:
|
|
73
|
+
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
|
|
74
|
+
// rport 55996"
|
|
75
|
+
SDPUtils.parseCandidate = function (line) {
|
|
76
|
+
var parts;
|
|
77
|
+
// Parse both variants.
|
|
78
|
+
if (line.indexOf('a=candidate:') === 0) {
|
|
79
|
+
parts = line.substring(12).split(' ');
|
|
80
|
+
} else {
|
|
81
|
+
parts = line.substring(10).split(' ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var candidate = {
|
|
85
|
+
foundation: parts[0],
|
|
86
|
+
component: parts[1],
|
|
87
|
+
protocol: parts[2].toLowerCase(),
|
|
88
|
+
priority: parseInt(parts[3], 10),
|
|
89
|
+
ip: parts[4],
|
|
90
|
+
port: parseInt(parts[5], 10),
|
|
91
|
+
// skip parts[6] == 'typ'
|
|
92
|
+
type: parts[7]
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
for (var i = 8; i < parts.length; i += 2) {
|
|
96
|
+
switch (parts[i]) {
|
|
97
|
+
case 'raddr':
|
|
98
|
+
candidate.relatedAddress = parts[i + 1];
|
|
99
|
+
break;
|
|
100
|
+
case 'rport':
|
|
101
|
+
candidate.relatedPort = parseInt(parts[i + 1], 10);
|
|
102
|
+
break;
|
|
103
|
+
case 'tcptype':
|
|
104
|
+
candidate.tcpType = parts[i + 1];
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
// Unknown extensions are silently ignored.
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return candidate;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Translates a candidate object into SDP candidate attribute.
|
|
115
|
+
SDPUtils.writeCandidate = function (candidate) {
|
|
116
|
+
var sdp = [];
|
|
117
|
+
sdp.push(candidate.foundation);
|
|
118
|
+
sdp.push(candidate.component);
|
|
119
|
+
sdp.push(candidate.protocol.toUpperCase());
|
|
120
|
+
sdp.push(candidate.priority);
|
|
121
|
+
sdp.push(candidate.ip);
|
|
122
|
+
sdp.push(candidate.port);
|
|
123
|
+
|
|
124
|
+
var type = candidate.type;
|
|
125
|
+
sdp.push('typ');
|
|
126
|
+
sdp.push(type);
|
|
127
|
+
if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) {
|
|
128
|
+
sdp.push('raddr');
|
|
129
|
+
sdp.push(candidate.relatedAddress); // was: relAddr
|
|
130
|
+
sdp.push('rport');
|
|
131
|
+
sdp.push(candidate.relatedPort); // was: relPort
|
|
132
|
+
}
|
|
133
|
+
if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
|
|
134
|
+
sdp.push('tcptype');
|
|
135
|
+
sdp.push(candidate.tcpType);
|
|
136
|
+
}
|
|
137
|
+
return 'candidate:' + sdp.join(' ');
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
|
|
141
|
+
// a=rtpmap:111 opus/48000/2
|
|
142
|
+
SDPUtils.parseRtpMap = function (line) {
|
|
143
|
+
var parts = line.substr(9).split(' ');
|
|
144
|
+
var parsed = {
|
|
145
|
+
payloadType: parseInt(parts.shift(), 10) // was: id
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
parts = parts[0].split('/');
|
|
149
|
+
|
|
150
|
+
parsed.name = parts[0];
|
|
151
|
+
parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
|
|
152
|
+
// was: channels
|
|
153
|
+
parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
|
|
154
|
+
return parsed;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Generate an a=rtpmap line from RTCRtpCodecCapability or
|
|
158
|
+
// RTCRtpCodecParameters.
|
|
159
|
+
SDPUtils.writeRtpMap = function (codec) {
|
|
160
|
+
var pt = codec.payloadType;
|
|
161
|
+
if (codec.preferredPayloadType !== undefined) {
|
|
162
|
+
pt = codec.preferredPayloadType;
|
|
163
|
+
}
|
|
164
|
+
return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
|
|
168
|
+
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
|
|
169
|
+
SDPUtils.parseExtmap = function (line) {
|
|
170
|
+
var parts = line.substr(9).split(' ');
|
|
171
|
+
return {
|
|
172
|
+
id: parseInt(parts[0], 10),
|
|
173
|
+
uri: parts[1]
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
|
|
178
|
+
// RTCRtpHeaderExtension.
|
|
179
|
+
SDPUtils.writeExtmap = function (headerExtension) {
|
|
180
|
+
return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + ' ' + headerExtension.uri + '\r\n';
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Parses an ftmp line, returns dictionary. Sample input:
|
|
184
|
+
// a=fmtp:96 vbr=on;cng=on
|
|
185
|
+
// Also deals with vbr=on; cng=on
|
|
186
|
+
SDPUtils.parseFmtp = function (line) {
|
|
187
|
+
var parsed = {};
|
|
188
|
+
var kv;
|
|
189
|
+
var parts = line.substr(line.indexOf(' ') + 1).split(';');
|
|
190
|
+
for (var j = 0; j < parts.length; j++) {
|
|
191
|
+
kv = parts[j].trim().split('=');
|
|
192
|
+
parsed[kv[0].trim()] = kv[1];
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
|
|
198
|
+
SDPUtils.writeFmtp = function (codec) {
|
|
199
|
+
var line = '';
|
|
200
|
+
var pt = codec.payloadType;
|
|
201
|
+
if (codec.preferredPayloadType !== undefined) {
|
|
202
|
+
pt = codec.preferredPayloadType;
|
|
203
|
+
}
|
|
204
|
+
if (codec.parameters && Object.keys(codec.parameters).length) {
|
|
205
|
+
var params = [];
|
|
206
|
+
Object.keys(codec.parameters).forEach(function (param) {
|
|
207
|
+
params.push(param + '=' + codec.parameters[param]);
|
|
208
|
+
});
|
|
209
|
+
line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
|
|
210
|
+
}
|
|
211
|
+
return line;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
|
|
215
|
+
// a=rtcp-fb:98 nack rpsi
|
|
216
|
+
SDPUtils.parseRtcpFb = function (line) {
|
|
217
|
+
var parts = line.substr(line.indexOf(' ') + 1).split(' ');
|
|
218
|
+
return {
|
|
219
|
+
type: parts.shift(),
|
|
220
|
+
parameter: parts.join(' ')
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
|
|
224
|
+
SDPUtils.writeRtcpFb = function (codec) {
|
|
225
|
+
var lines = '';
|
|
226
|
+
var pt = codec.payloadType;
|
|
227
|
+
if (codec.preferredPayloadType !== undefined) {
|
|
228
|
+
pt = codec.preferredPayloadType;
|
|
229
|
+
}
|
|
230
|
+
if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
|
|
231
|
+
// FIXME: special handling for trr-int?
|
|
232
|
+
codec.rtcpFeedback.forEach(function (fb) {
|
|
233
|
+
lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n';
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return lines;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Parses an RFC 5576 ssrc media attribute. Sample input:
|
|
240
|
+
// a=ssrc:3735928559 cname:something
|
|
241
|
+
SDPUtils.parseSsrcMedia = function (line) {
|
|
242
|
+
var sp = line.indexOf(' ');
|
|
243
|
+
var parts = {
|
|
244
|
+
ssrc: parseInt(line.substr(7, sp - 7), 10)
|
|
245
|
+
};
|
|
246
|
+
var colon = line.indexOf(':', sp);
|
|
247
|
+
if (colon > -1) {
|
|
248
|
+
parts.attribute = line.substr(sp + 1, colon - sp - 1);
|
|
249
|
+
parts.value = line.substr(colon + 1);
|
|
250
|
+
} else {
|
|
251
|
+
parts.attribute = line.substr(sp + 1);
|
|
252
|
+
}
|
|
253
|
+
return parts;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Extracts DTLS parameters from SDP media section or sessionpart.
|
|
257
|
+
// FIXME: for consistency with other functions this should only
|
|
258
|
+
// get the fingerprint line as input. See also getIceParameters.
|
|
259
|
+
SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) {
|
|
260
|
+
var lines = SDPUtils.splitLines(mediaSection);
|
|
261
|
+
// Search in session part, too.
|
|
262
|
+
lines = lines.concat(SDPUtils.splitLines(sessionpart));
|
|
263
|
+
var fpLine = lines.filter(function (line) {
|
|
264
|
+
return line.indexOf('a=fingerprint:') === 0;
|
|
265
|
+
})[0].substr(14);
|
|
266
|
+
// Note: a=setup line is ignored since we use the 'auto' role.
|
|
267
|
+
var dtlsParameters = {
|
|
268
|
+
role: 'auto',
|
|
269
|
+
fingerprints: [{
|
|
270
|
+
algorithm: fpLine.split(' ')[0],
|
|
271
|
+
value: fpLine.split(' ')[1]
|
|
272
|
+
}]
|
|
273
|
+
};
|
|
274
|
+
return dtlsParameters;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Serializes DTLS parameters to SDP.
|
|
278
|
+
SDPUtils.writeDtlsParameters = function (params, setupType) {
|
|
279
|
+
var sdp = 'a=setup:' + setupType + '\r\n';
|
|
280
|
+
params.fingerprints.forEach(function (fp) {
|
|
281
|
+
sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
|
|
282
|
+
});
|
|
283
|
+
return sdp;
|
|
284
|
+
};
|
|
285
|
+
// Parses ICE information from SDP media section or sessionpart.
|
|
286
|
+
// FIXME: for consistency with other functions this should only
|
|
287
|
+
// get the ice-ufrag and ice-pwd lines as input.
|
|
288
|
+
SDPUtils.getIceParameters = function (mediaSection, sessionpart) {
|
|
289
|
+
var lines = SDPUtils.splitLines(mediaSection);
|
|
290
|
+
// Search in session part, too.
|
|
291
|
+
lines = lines.concat(SDPUtils.splitLines(sessionpart));
|
|
292
|
+
var iceParameters = {
|
|
293
|
+
usernameFragment: lines.filter(function (line) {
|
|
294
|
+
return line.indexOf('a=ice-ufrag:') === 0;
|
|
295
|
+
})[0].substr(12),
|
|
296
|
+
password: lines.filter(function (line) {
|
|
297
|
+
return line.indexOf('a=ice-pwd:') === 0;
|
|
298
|
+
})[0].substr(10)
|
|
299
|
+
};
|
|
300
|
+
return iceParameters;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Serializes ICE parameters to SDP.
|
|
304
|
+
SDPUtils.writeIceParameters = function (params) {
|
|
305
|
+
return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n';
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Parses the SDP media section and returns RTCRtpParameters.
|
|
309
|
+
SDPUtils.parseRtpParameters = function (mediaSection) {
|
|
310
|
+
var description = {
|
|
311
|
+
codecs: [],
|
|
312
|
+
headerExtensions: [],
|
|
313
|
+
fecMechanisms: [],
|
|
314
|
+
rtcp: []
|
|
315
|
+
};
|
|
316
|
+
var lines = SDPUtils.splitLines(mediaSection);
|
|
317
|
+
var mline = lines[0].split(' ');
|
|
318
|
+
for (var i = 3; i < mline.length; i++) {
|
|
319
|
+
// find all codecs from mline[3..]
|
|
320
|
+
var pt = mline[i];
|
|
321
|
+
var rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0];
|
|
322
|
+
if (rtpmapline) {
|
|
323
|
+
var codec = SDPUtils.parseRtpMap(rtpmapline);
|
|
324
|
+
var fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' ');
|
|
325
|
+
// Only the first a=fmtp:<pt> is considered.
|
|
326
|
+
codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
|
|
327
|
+
codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb);
|
|
328
|
+
description.codecs.push(codec);
|
|
329
|
+
// parse FEC mechanisms from rtpmap lines.
|
|
330
|
+
switch (codec.name.toUpperCase()) {
|
|
331
|
+
case 'RED':
|
|
332
|
+
case 'ULPFEC':
|
|
333
|
+
description.fecMechanisms.push(codec.name.toUpperCase());
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
// only RED and ULPFEC are recognized as FEC mechanisms.
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function (line) {
|
|
342
|
+
description.headerExtensions.push(SDPUtils.parseExtmap(line));
|
|
343
|
+
});
|
|
344
|
+
// FIXME: parse rtcp.
|
|
345
|
+
return description;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Generates parts of the SDP media section describing the capabilities /
|
|
349
|
+
// parameters.
|
|
350
|
+
SDPUtils.writeRtpDescription = function (kind, caps) {
|
|
351
|
+
var sdp = '';
|
|
352
|
+
|
|
353
|
+
// Build the mline.
|
|
354
|
+
sdp += 'm=' + kind + ' ';
|
|
355
|
+
sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
|
|
356
|
+
sdp += ' UDP/TLS/RTP/SAVPF ';
|
|
357
|
+
sdp += caps.codecs.map(function (codec) {
|
|
358
|
+
if (codec.preferredPayloadType !== undefined) {
|
|
359
|
+
return codec.preferredPayloadType;
|
|
360
|
+
}
|
|
361
|
+
return codec.payloadType;
|
|
362
|
+
}).join(' ') + '\r\n';
|
|
363
|
+
|
|
364
|
+
sdp += 'c=IN IP4 0.0.0.0\r\n';
|
|
365
|
+
sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
|
|
366
|
+
|
|
367
|
+
// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
|
|
368
|
+
caps.codecs.forEach(function (codec) {
|
|
369
|
+
sdp += SDPUtils.writeRtpMap(codec);
|
|
370
|
+
sdp += SDPUtils.writeFmtp(codec);
|
|
371
|
+
sdp += SDPUtils.writeRtcpFb(codec);
|
|
372
|
+
});
|
|
373
|
+
// FIXME: add headerExtensions, fecMechanismş and rtcp.
|
|
374
|
+
sdp += 'a=rtcp-mux\r\n';
|
|
375
|
+
return sdp;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Parses the SDP media section and returns an array of
|
|
379
|
+
// RTCRtpEncodingParameters.
|
|
380
|
+
SDPUtils.parseRtpEncodingParameters = function (mediaSection) {
|
|
381
|
+
var encodingParameters = [];
|
|
382
|
+
var description = SDPUtils.parseRtpParameters(mediaSection);
|
|
383
|
+
var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
|
|
384
|
+
var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
|
|
385
|
+
|
|
386
|
+
// filter a=ssrc:... cname:, ignore PlanB-msid
|
|
387
|
+
var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) {
|
|
388
|
+
return SDPUtils.parseSsrcMedia(line);
|
|
389
|
+
}).filter(function (parts) {
|
|
390
|
+
return parts.attribute === 'cname';
|
|
391
|
+
});
|
|
392
|
+
var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
|
|
393
|
+
var secondarySsrc;
|
|
394
|
+
|
|
395
|
+
var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(function (line) {
|
|
396
|
+
var parts = line.split(' ');
|
|
397
|
+
parts.shift();
|
|
398
|
+
return parts.map(function (part) {
|
|
399
|
+
return parseInt(part, 10);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
|
|
403
|
+
secondarySsrc = flows[0][1];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
description.codecs.forEach(function (codec) {
|
|
407
|
+
if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
|
|
408
|
+
var encParam = {
|
|
409
|
+
ssrc: primarySsrc,
|
|
410
|
+
codecPayloadType: parseInt(codec.parameters.apt, 10),
|
|
411
|
+
rtx: {
|
|
412
|
+
payloadType: codec.payloadType,
|
|
413
|
+
ssrc: secondarySsrc
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
encodingParameters.push(encParam);
|
|
417
|
+
if (hasRed) {
|
|
418
|
+
encParam = JSON.parse(JSON.stringify(encParam));
|
|
419
|
+
encParam.fec = {
|
|
420
|
+
ssrc: secondarySsrc,
|
|
421
|
+
mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
|
|
422
|
+
};
|
|
423
|
+
encodingParameters.push(encParam);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
if (encodingParameters.length === 0 && primarySsrc) {
|
|
428
|
+
encodingParameters.push({
|
|
429
|
+
ssrc: primarySsrc
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// we support both b=AS and b=TIAS but interpret AS as TIAS.
|
|
434
|
+
var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
|
|
435
|
+
if (bandwidth.length) {
|
|
436
|
+
if (bandwidth[0].indexOf('b=TIAS:') === 0) {
|
|
437
|
+
bandwidth = parseInt(bandwidth[0].substr(7), 10);
|
|
438
|
+
} else if (bandwidth[0].indexOf('b=AS:') === 0) {
|
|
439
|
+
bandwidth = parseInt(bandwidth[0].substr(5), 10);
|
|
440
|
+
}
|
|
441
|
+
encodingParameters.forEach(function (params) {
|
|
442
|
+
params.maxBitrate = bandwidth;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return encodingParameters;
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
SDPUtils.writeSessionBoilerplate = function () {
|
|
449
|
+
// FIXME: sess-id should be an NTP timestamp.
|
|
450
|
+
return 'v=0\r\n' + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n';
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
SDPUtils.writeMediaSection = function (transceiver, caps, type, stream) {
|
|
454
|
+
var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
|
|
455
|
+
|
|
456
|
+
// Map ICE parameters (ufrag, pwd) to SDP.
|
|
457
|
+
sdp += SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());
|
|
458
|
+
|
|
459
|
+
// Map DTLS parameters to SDP.
|
|
460
|
+
sdp += SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(), type === 'offer' ? 'actpass' : 'active');
|
|
461
|
+
|
|
462
|
+
sdp += 'a=mid:' + transceiver.mid + '\r\n';
|
|
463
|
+
|
|
464
|
+
if (transceiver.rtpSender && transceiver.rtpReceiver) {
|
|
465
|
+
sdp += 'a=sendrecv\r\n';
|
|
466
|
+
} else if (transceiver.rtpSender) {
|
|
467
|
+
sdp += 'a=sendonly\r\n';
|
|
468
|
+
} else if (transceiver.rtpReceiver) {
|
|
469
|
+
sdp += 'a=recvonly\r\n';
|
|
470
|
+
} else {
|
|
471
|
+
sdp += 'a=inactive\r\n';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
|
|
475
|
+
if (transceiver.rtpSender) {
|
|
476
|
+
var msid = 'msid:' + stream.id + ' ' + transceiver.rtpSender.track.id + '\r\n';
|
|
477
|
+
sdp += 'a=' + msid;
|
|
478
|
+
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' ' + msid;
|
|
479
|
+
}
|
|
480
|
+
// FIXME: this should be written by writeRtpDescription.
|
|
481
|
+
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + ' cname:' + SDPUtils.localCName + '\r\n';
|
|
482
|
+
return sdp;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// Gets the direction from the mediaSection or the sessionpart.
|
|
486
|
+
SDPUtils.getDirection = function (mediaSection, sessionpart) {
|
|
487
|
+
// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
|
|
488
|
+
var lines = SDPUtils.splitLines(mediaSection);
|
|
489
|
+
for (var i = 0; i < lines.length; i++) {
|
|
490
|
+
switch (lines[i]) {
|
|
491
|
+
case 'a=sendrecv':
|
|
492
|
+
case 'a=sendonly':
|
|
493
|
+
case 'a=recvonly':
|
|
494
|
+
case 'a=inactive':
|
|
495
|
+
return lines[i].substr(2);
|
|
496
|
+
default:
|
|
497
|
+
// FIXME: What should happen here?
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (sessionpart) {
|
|
501
|
+
return SDPUtils.getDirection(sessionpart);
|
|
502
|
+
}
|
|
503
|
+
return 'sendrecv';
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// Expose public methods.
|
|
507
|
+
module.exports = SDPUtils;
|
|
508
|
+
}, {}], 2: [function (require, module, exports) {
|
|
509
|
+
/*
|
|
510
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
511
|
+
*
|
|
512
|
+
* Use of this source code is governed by a BSD-style license
|
|
513
|
+
* that can be found in the LICENSE file in the root of the source
|
|
514
|
+
* tree.
|
|
515
|
+
*/
|
|
516
|
+
/* eslint-env node */
|
|
517
|
+
|
|
518
|
+
'use strict';
|
|
519
|
+
|
|
520
|
+
// Shimming starts here.
|
|
521
|
+
|
|
522
|
+
(function () {
|
|
523
|
+
// Utils.
|
|
524
|
+
var logging = require('./utils').log;
|
|
525
|
+
var browserDetails = require('./utils').browserDetails;
|
|
526
|
+
// Export to the adapter global object visible in the browser.
|
|
527
|
+
module.exports.browserDetails = browserDetails;
|
|
528
|
+
module.exports.extractVersion = require('./utils').extractVersion;
|
|
529
|
+
module.exports.disableLog = require('./utils').disableLog;
|
|
530
|
+
|
|
531
|
+
// Uncomment the line below if you want logging to occur, including logging
|
|
532
|
+
// for the switch statement below. Can also be turned on in the browser via
|
|
533
|
+
// adapter.disableLog(false), but then logging from the switch statement below
|
|
534
|
+
// will not appear.
|
|
535
|
+
// require('./utils').disableLog(false);
|
|
536
|
+
|
|
537
|
+
// Browser shims.
|
|
538
|
+
var chromeShim = require('./chrome/chrome_shim') || null;
|
|
539
|
+
var edgeShim = require('./edge/edge_shim') || null;
|
|
540
|
+
var firefoxShim = require('./firefox/firefox_shim') || null;
|
|
541
|
+
var safariShim = require('./safari/safari_shim') || null;
|
|
542
|
+
|
|
543
|
+
// Shim browser if found.
|
|
544
|
+
switch (browserDetails.browser) {
|
|
545
|
+
case 'opera': // fallthrough as it uses chrome shims
|
|
546
|
+
case 'chrome':
|
|
547
|
+
if (!chromeShim || !chromeShim.shimPeerConnection) {
|
|
548
|
+
logging('Chrome shim is not included in this adapter release.');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
logging('adapter.js shimming chrome.');
|
|
552
|
+
// Export to the adapter global object visible in the browser.
|
|
553
|
+
module.exports.browserShim = chromeShim;
|
|
554
|
+
|
|
555
|
+
chromeShim.shimGetUserMedia();
|
|
556
|
+
chromeShim.shimMediaStream();
|
|
557
|
+
chromeShim.shimSourceObject();
|
|
558
|
+
chromeShim.shimPeerConnection();
|
|
559
|
+
chromeShim.shimOnTrack();
|
|
560
|
+
break;
|
|
561
|
+
case 'firefox':
|
|
562
|
+
if (!firefoxShim || !firefoxShim.shimPeerConnection) {
|
|
563
|
+
logging('Firefox shim is not included in this adapter release.');
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
logging('adapter.js shimming firefox.');
|
|
567
|
+
// Export to the adapter global object visible in the browser.
|
|
568
|
+
module.exports.browserShim = firefoxShim;
|
|
569
|
+
|
|
570
|
+
firefoxShim.shimGetUserMedia();
|
|
571
|
+
firefoxShim.shimSourceObject();
|
|
572
|
+
firefoxShim.shimPeerConnection();
|
|
573
|
+
firefoxShim.shimOnTrack();
|
|
574
|
+
break;
|
|
575
|
+
case 'edge':
|
|
576
|
+
if (!edgeShim || !edgeShim.shimPeerConnection) {
|
|
577
|
+
logging('MS edge shim is not included in this adapter release.');
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
logging('adapter.js shimming edge.');
|
|
581
|
+
// Export to the adapter global object visible in the browser.
|
|
582
|
+
module.exports.browserShim = edgeShim;
|
|
583
|
+
|
|
584
|
+
edgeShim.shimGetUserMedia();
|
|
585
|
+
edgeShim.shimPeerConnection();
|
|
586
|
+
break;
|
|
587
|
+
case 'safari':
|
|
588
|
+
if (!safariShim) {
|
|
589
|
+
logging('Safari shim is not included in this adapter release.');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
logging('adapter.js shimming safari.');
|
|
593
|
+
// Export to the adapter global object visible in the browser.
|
|
594
|
+
module.exports.browserShim = safariShim;
|
|
595
|
+
|
|
596
|
+
safariShim.shimGetUserMedia();
|
|
597
|
+
break;
|
|
598
|
+
default:
|
|
599
|
+
logging('Unsupported browser!');
|
|
600
|
+
}
|
|
601
|
+
})();
|
|
602
|
+
}, { "./chrome/chrome_shim": 3, "./edge/edge_shim": 5, "./firefox/firefox_shim": 7, "./safari/safari_shim": 9, "./utils": 10 }], 3: [function (require, module, exports) {
|
|
603
|
+
|
|
604
|
+
/*
|
|
605
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
606
|
+
*
|
|
607
|
+
* Use of this source code is governed by a BSD-style license
|
|
608
|
+
* that can be found in the LICENSE file in the root of the source
|
|
609
|
+
* tree.
|
|
610
|
+
*/
|
|
611
|
+
/* eslint-env node */
|
|
612
|
+
'use strict';
|
|
613
|
+
|
|
614
|
+
var logging = require('../utils.js').log;
|
|
615
|
+
var browserDetails = require('../utils.js').browserDetails;
|
|
616
|
+
|
|
617
|
+
var chromeShim = {
|
|
618
|
+
shimMediaStream: function shimMediaStream() {
|
|
619
|
+
window.MediaStream = window.MediaStream || window.webkitMediaStream;
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
shimOnTrack: function shimOnTrack() {
|
|
623
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) {
|
|
624
|
+
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
|
|
625
|
+
get: function get() {
|
|
626
|
+
return this._ontrack;
|
|
627
|
+
},
|
|
628
|
+
set: function set(f) {
|
|
629
|
+
var self = this;
|
|
630
|
+
if (this._ontrack) {
|
|
631
|
+
this.removeEventListener('track', this._ontrack);
|
|
632
|
+
this.removeEventListener('addstream', this._ontrackpoly);
|
|
633
|
+
}
|
|
634
|
+
this.addEventListener('track', this._ontrack = f);
|
|
635
|
+
this.addEventListener('addstream', this._ontrackpoly = function (e) {
|
|
636
|
+
// onaddstream does not fire when a track is added to an existing
|
|
637
|
+
// stream. But stream.onaddtrack is implemented so we use that.
|
|
638
|
+
e.stream.addEventListener('addtrack', function (te) {
|
|
639
|
+
var event = new Event('track');
|
|
640
|
+
event.track = te.track;
|
|
641
|
+
event.receiver = { track: te.track };
|
|
642
|
+
event.streams = [e.stream];
|
|
643
|
+
self.dispatchEvent(event);
|
|
644
|
+
});
|
|
645
|
+
e.stream.getTracks().forEach(function (track) {
|
|
646
|
+
var event = new Event('track');
|
|
647
|
+
event.track = track;
|
|
648
|
+
event.receiver = { track: track };
|
|
649
|
+
event.streams = [e.stream];
|
|
650
|
+
this.dispatchEvent(event);
|
|
651
|
+
}.bind(this));
|
|
652
|
+
}.bind(this));
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
shimSourceObject: function shimSourceObject() {
|
|
659
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
|
|
660
|
+
if (window.HTMLMediaElement && !('srcObject' in window.HTMLMediaElement.prototype)) {
|
|
661
|
+
// Shim the srcObject property, once, when HTMLMediaElement is found.
|
|
662
|
+
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
|
|
663
|
+
get: function get() {
|
|
664
|
+
return this._srcObject;
|
|
665
|
+
},
|
|
666
|
+
set: function set(stream) {
|
|
667
|
+
var self = this;
|
|
668
|
+
// Use _srcObject as a private property for this shim
|
|
669
|
+
this._srcObject = stream;
|
|
670
|
+
if (this.src) {
|
|
671
|
+
URL.revokeObjectURL(this.src);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!stream) {
|
|
675
|
+
this.src = '';
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
this.src = URL.createObjectURL(stream);
|
|
679
|
+
// We need to recreate the blob url when a track is added or
|
|
680
|
+
// removed. Doing it manually since we want to avoid a recursion.
|
|
681
|
+
stream.addEventListener('addtrack', function () {
|
|
682
|
+
if (self.src) {
|
|
683
|
+
URL.revokeObjectURL(self.src);
|
|
684
|
+
}
|
|
685
|
+
self.src = URL.createObjectURL(stream);
|
|
686
|
+
});
|
|
687
|
+
stream.addEventListener('removetrack', function () {
|
|
688
|
+
if (self.src) {
|
|
689
|
+
URL.revokeObjectURL(self.src);
|
|
690
|
+
}
|
|
691
|
+
self.src = URL.createObjectURL(stream);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
shimPeerConnection: function shimPeerConnection() {
|
|
700
|
+
// The RTCPeerConnection object.
|
|
701
|
+
window.RTCPeerConnection = function (pcConfig, pcConstraints) {
|
|
702
|
+
// Translate iceTransportPolicy to iceTransports,
|
|
703
|
+
// see https://code.google.com/p/webrtc/issues/detail?id=4869
|
|
704
|
+
logging('PeerConnection');
|
|
705
|
+
if (pcConfig && pcConfig.iceTransportPolicy) {
|
|
706
|
+
pcConfig.iceTransports = pcConfig.iceTransportPolicy;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints);
|
|
710
|
+
var origGetStats = pc.getStats.bind(pc);
|
|
711
|
+
pc.getStats = function (selector, successCallback, errorCallback) {
|
|
712
|
+
var self = this;
|
|
713
|
+
var args = arguments;
|
|
714
|
+
|
|
715
|
+
// If selector is a function then we are in the old style stats so just
|
|
716
|
+
// pass back the original getStats format to avoid breaking old users.
|
|
717
|
+
if (arguments.length > 0 && typeof selector === 'function') {
|
|
718
|
+
return origGetStats(selector, successCallback);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
var fixChromeStats_ = function fixChromeStats_(response) {
|
|
722
|
+
var standardReport = {};
|
|
723
|
+
var reports = response.result();
|
|
724
|
+
reports.forEach(function (report) {
|
|
725
|
+
var standardStats = {
|
|
726
|
+
id: report.id,
|
|
727
|
+
timestamp: report.timestamp,
|
|
728
|
+
type: report.type
|
|
729
|
+
};
|
|
730
|
+
report.names().forEach(function (name) {
|
|
731
|
+
standardStats[name] = report.stat(name);
|
|
732
|
+
});
|
|
733
|
+
standardReport[standardStats.id] = standardStats;
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
return standardReport;
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// shim getStats with maplike support
|
|
740
|
+
var makeMapStats = function makeMapStats(stats, legacyStats) {
|
|
741
|
+
var map = new Map(Object.keys(stats).map(function (key) {
|
|
742
|
+
return [key, stats[key]];
|
|
743
|
+
}));
|
|
744
|
+
legacyStats = legacyStats || stats;
|
|
745
|
+
Object.keys(legacyStats).forEach(function (key) {
|
|
746
|
+
map[key] = legacyStats[key];
|
|
747
|
+
});
|
|
748
|
+
return map;
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
if (arguments.length >= 2) {
|
|
752
|
+
var successCallbackWrapper_ = function successCallbackWrapper_(response) {
|
|
753
|
+
args[1](makeMapStats(fixChromeStats_(response)));
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
return origGetStats.apply(this, [successCallbackWrapper_, arguments[0]]);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// promise-support
|
|
760
|
+
return new Promise(function (resolve, reject) {
|
|
761
|
+
if (args.length === 1 && (typeof selector === "undefined" ? "undefined" : _typeof(selector)) === 'object') {
|
|
762
|
+
origGetStats.apply(self, [function (response) {
|
|
763
|
+
resolve(makeMapStats(fixChromeStats_(response)));
|
|
764
|
+
}, reject]);
|
|
765
|
+
} else {
|
|
766
|
+
// Preserve legacy chrome stats only on legacy access of stats obj
|
|
767
|
+
origGetStats.apply(self, [function (response) {
|
|
768
|
+
resolve(makeMapStats(fixChromeStats_(response), response.result()));
|
|
769
|
+
}, reject]);
|
|
770
|
+
}
|
|
771
|
+
}).then(successCallback, errorCallback);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
return pc;
|
|
775
|
+
};
|
|
776
|
+
window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
|
|
777
|
+
|
|
778
|
+
// wrap static methods. Currently just generateCertificate.
|
|
779
|
+
if (webkitRTCPeerConnection.generateCertificate) {
|
|
780
|
+
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
|
|
781
|
+
get: function get() {
|
|
782
|
+
return webkitRTCPeerConnection.generateCertificate;
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
['createOffer', 'createAnswer'].forEach(function (method) {
|
|
788
|
+
var nativeMethod = webkitRTCPeerConnection.prototype[method];
|
|
789
|
+
webkitRTCPeerConnection.prototype[method] = function () {
|
|
790
|
+
var self = this;
|
|
791
|
+
if (arguments.length < 1 || arguments.length === 1 && _typeof(arguments[0]) === 'object') {
|
|
792
|
+
var opts = arguments.length === 1 ? arguments[0] : undefined;
|
|
793
|
+
return new Promise(function (resolve, reject) {
|
|
794
|
+
nativeMethod.apply(self, [resolve, reject, opts]);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return nativeMethod.apply(this, arguments);
|
|
798
|
+
};
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// add promise support -- natively available in Chrome 51
|
|
802
|
+
if (browserDetails.version < 51) {
|
|
803
|
+
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) {
|
|
804
|
+
var nativeMethod = webkitRTCPeerConnection.prototype[method];
|
|
805
|
+
webkitRTCPeerConnection.prototype[method] = function () {
|
|
806
|
+
var args = arguments;
|
|
807
|
+
var self = this;
|
|
808
|
+
var promise = new Promise(function (resolve, reject) {
|
|
809
|
+
nativeMethod.apply(self, [args[0], resolve, reject]);
|
|
810
|
+
});
|
|
811
|
+
if (args.length < 2) {
|
|
812
|
+
return promise;
|
|
813
|
+
}
|
|
814
|
+
return promise.then(function () {
|
|
815
|
+
args[1].apply(null, []);
|
|
816
|
+
}, function (err) {
|
|
817
|
+
if (args.length >= 3) {
|
|
818
|
+
args[2].apply(null, [err]);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
};
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// shim implicit creation of RTCSessionDescription/RTCIceCandidate
|
|
826
|
+
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) {
|
|
827
|
+
var nativeMethod = webkitRTCPeerConnection.prototype[method];
|
|
828
|
+
webkitRTCPeerConnection.prototype[method] = function () {
|
|
829
|
+
arguments[0] = new (method === 'addIceCandidate' ? RTCIceCandidate : RTCSessionDescription)(arguments[0]);
|
|
830
|
+
return nativeMethod.apply(this, arguments);
|
|
831
|
+
};
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// support for addIceCandidate(null or undefined)
|
|
835
|
+
var nativeAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate;
|
|
836
|
+
RTCPeerConnection.prototype.addIceCandidate = function () {
|
|
837
|
+
if (!arguments[0]) {
|
|
838
|
+
if (arguments[1]) {
|
|
839
|
+
arguments[1].apply(null);
|
|
840
|
+
}
|
|
841
|
+
return Promise.resolve();
|
|
842
|
+
}
|
|
843
|
+
return nativeAddIceCandidate.apply(this, arguments);
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
// Expose public methods.
|
|
849
|
+
module.exports = {
|
|
850
|
+
shimMediaStream: chromeShim.shimMediaStream,
|
|
851
|
+
shimOnTrack: chromeShim.shimOnTrack,
|
|
852
|
+
shimSourceObject: chromeShim.shimSourceObject,
|
|
853
|
+
shimPeerConnection: chromeShim.shimPeerConnection,
|
|
854
|
+
shimGetUserMedia: require('./getusermedia')
|
|
855
|
+
};
|
|
856
|
+
}, { "../utils.js": 10, "./getusermedia": 4 }], 4: [function (require, module, exports) {
|
|
857
|
+
/*
|
|
858
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
859
|
+
*
|
|
860
|
+
* Use of this source code is governed by a BSD-style license
|
|
861
|
+
* that can be found in the LICENSE file in the root of the source
|
|
862
|
+
* tree.
|
|
863
|
+
*/
|
|
864
|
+
/* eslint-env node */
|
|
865
|
+
'use strict';
|
|
866
|
+
|
|
867
|
+
var logging = require('../utils.js').log;
|
|
868
|
+
|
|
869
|
+
// Expose public methods.
|
|
870
|
+
module.exports = function () {
|
|
871
|
+
var constraintsToChrome_ = function constraintsToChrome_(c) {
|
|
872
|
+
if ((typeof c === "undefined" ? "undefined" : _typeof(c)) !== 'object' || c.mandatory || c.optional) {
|
|
873
|
+
return c;
|
|
874
|
+
}
|
|
875
|
+
var cc = {};
|
|
876
|
+
Object.keys(c).forEach(function (key) {
|
|
877
|
+
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
var r = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] };
|
|
881
|
+
if (r.exact !== undefined && typeof r.exact === 'number') {
|
|
882
|
+
r.min = r.max = r.exact;
|
|
883
|
+
}
|
|
884
|
+
var oldname_ = function oldname_(prefix, name) {
|
|
885
|
+
if (prefix) {
|
|
886
|
+
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
887
|
+
}
|
|
888
|
+
return name === 'deviceId' ? 'sourceId' : name;
|
|
889
|
+
};
|
|
890
|
+
if (r.ideal !== undefined) {
|
|
891
|
+
cc.optional = cc.optional || [];
|
|
892
|
+
var oc = {};
|
|
893
|
+
if (typeof r.ideal === 'number') {
|
|
894
|
+
oc[oldname_('min', key)] = r.ideal;
|
|
895
|
+
cc.optional.push(oc);
|
|
896
|
+
oc = {};
|
|
897
|
+
oc[oldname_('max', key)] = r.ideal;
|
|
898
|
+
cc.optional.push(oc);
|
|
899
|
+
} else {
|
|
900
|
+
oc[oldname_('', key)] = r.ideal;
|
|
901
|
+
cc.optional.push(oc);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (r.exact !== undefined && typeof r.exact !== 'number') {
|
|
905
|
+
cc.mandatory = cc.mandatory || {};
|
|
906
|
+
cc.mandatory[oldname_('', key)] = r.exact;
|
|
907
|
+
} else {
|
|
908
|
+
['min', 'max'].forEach(function (mix) {
|
|
909
|
+
if (r[mix] !== undefined) {
|
|
910
|
+
cc.mandatory = cc.mandatory || {};
|
|
911
|
+
cc.mandatory[oldname_(mix, key)] = r[mix];
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
if (c.advanced) {
|
|
917
|
+
cc.optional = (cc.optional || []).concat(c.advanced);
|
|
918
|
+
}
|
|
919
|
+
return cc;
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
var shimConstraints_ = function shimConstraints_(constraints, func) {
|
|
923
|
+
constraints = JSON.parse(JSON.stringify(constraints));
|
|
924
|
+
if (constraints && constraints.audio) {
|
|
925
|
+
constraints.audio = constraintsToChrome_(constraints.audio);
|
|
926
|
+
}
|
|
927
|
+
if (constraints && _typeof(constraints.video) === 'object') {
|
|
928
|
+
// Shim facingMode for mobile, where it defaults to "user".
|
|
929
|
+
var face = constraints.video.facingMode;
|
|
930
|
+
face = face && ((typeof face === "undefined" ? "undefined" : _typeof(face)) === 'object' ? face : { ideal: face });
|
|
931
|
+
|
|
932
|
+
if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode)) {
|
|
933
|
+
delete constraints.video.facingMode;
|
|
934
|
+
if (face.exact === 'environment' || face.ideal === 'environment') {
|
|
935
|
+
// Look for "back" in label, or use last cam (typically back cam).
|
|
936
|
+
return navigator.mediaDevices.enumerateDevices().then(function (devices) {
|
|
937
|
+
devices = devices.filter(function (d) {
|
|
938
|
+
return d.kind === 'videoinput';
|
|
939
|
+
});
|
|
940
|
+
var back = devices.find(function (d) {
|
|
941
|
+
return d.label.toLowerCase().indexOf('back') !== -1;
|
|
942
|
+
}) || devices.length && devices[devices.length - 1];
|
|
943
|
+
if (back) {
|
|
944
|
+
constraints.video.deviceId = face.exact ? { exact: back.deviceId } : { ideal: back.deviceId };
|
|
945
|
+
}
|
|
946
|
+
constraints.video = constraintsToChrome_(constraints.video);
|
|
947
|
+
logging('chrome: ' + JSON.stringify(constraints));
|
|
948
|
+
return func(constraints);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
constraints.video = constraintsToChrome_(constraints.video);
|
|
953
|
+
}
|
|
954
|
+
logging('chrome: ' + JSON.stringify(constraints));
|
|
955
|
+
return func(constraints);
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
var shimError_ = function shimError_(e) {
|
|
959
|
+
return {
|
|
960
|
+
name: {
|
|
961
|
+
PermissionDeniedError: 'NotAllowedError',
|
|
962
|
+
ConstraintNotSatisfiedError: 'OverconstrainedError'
|
|
963
|
+
}[e.name] || e.name,
|
|
964
|
+
message: e.message,
|
|
965
|
+
constraint: e.constraintName,
|
|
966
|
+
toString: function toString() {
|
|
967
|
+
return this.name + (this.message && ': ') + this.message;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) {
|
|
973
|
+
shimConstraints_(constraints, function (c) {
|
|
974
|
+
navigator.webkitGetUserMedia(c, onSuccess, function (e) {
|
|
975
|
+
onError(shimError_(e));
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
navigator.getUserMedia = getUserMedia_;
|
|
981
|
+
|
|
982
|
+
// Returns the result of getUserMedia as a Promise.
|
|
983
|
+
var getUserMediaPromise_ = function getUserMediaPromise_(constraints) {
|
|
984
|
+
return new Promise(function (resolve, reject) {
|
|
985
|
+
navigator.getUserMedia(constraints, resolve, reject);
|
|
986
|
+
});
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
if (!navigator.mediaDevices) {
|
|
990
|
+
navigator.mediaDevices = {
|
|
991
|
+
getUserMedia: getUserMediaPromise_,
|
|
992
|
+
enumerateDevices: function enumerateDevices() {
|
|
993
|
+
return new Promise(function (resolve) {
|
|
994
|
+
var kinds = { audio: 'audioinput', video: 'videoinput' };
|
|
995
|
+
return MediaStreamTrack.getSources(function (devices) {
|
|
996
|
+
resolve(devices.map(function (device) {
|
|
997
|
+
return { label: device.label,
|
|
998
|
+
kind: kinds[device.kind],
|
|
999
|
+
deviceId: device.id,
|
|
1000
|
+
groupId: '' };
|
|
1001
|
+
}));
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// A shim for getUserMedia method on the mediaDevices object.
|
|
1009
|
+
// TODO(KaptenJansson) remove once implemented in Chrome stable.
|
|
1010
|
+
if (!navigator.mediaDevices.getUserMedia) {
|
|
1011
|
+
navigator.mediaDevices.getUserMedia = function (constraints) {
|
|
1012
|
+
return getUserMediaPromise_(constraints);
|
|
1013
|
+
};
|
|
1014
|
+
} else {
|
|
1015
|
+
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
|
|
1016
|
+
// function which returns a Promise, it does not accept spec-style
|
|
1017
|
+
// constraints.
|
|
1018
|
+
var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
1019
|
+
navigator.mediaDevices.getUserMedia = function (cs) {
|
|
1020
|
+
return shimConstraints_(cs, function (c) {
|
|
1021
|
+
return origGetUserMedia(c).then(function (stream) {
|
|
1022
|
+
if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) {
|
|
1023
|
+
stream.getTracks().forEach(function (track) {
|
|
1024
|
+
track.stop();
|
|
1025
|
+
});
|
|
1026
|
+
throw new DOMException('', 'NotFoundError');
|
|
1027
|
+
}
|
|
1028
|
+
return stream;
|
|
1029
|
+
}, function (e) {
|
|
1030
|
+
return Promise.reject(shimError_(e));
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Dummy devicechange event methods.
|
|
1037
|
+
// TODO(KaptenJansson) remove once implemented in Chrome stable.
|
|
1038
|
+
if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
|
|
1039
|
+
navigator.mediaDevices.addEventListener = function () {
|
|
1040
|
+
logging('Dummy mediaDevices.addEventListener called.');
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
|
|
1044
|
+
navigator.mediaDevices.removeEventListener = function () {
|
|
1045
|
+
logging('Dummy mediaDevices.removeEventListener called.');
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}, { "../utils.js": 10 }], 5: [function (require, module, exports) {
|
|
1050
|
+
/*
|
|
1051
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
1052
|
+
*
|
|
1053
|
+
* Use of this source code is governed by a BSD-style license
|
|
1054
|
+
* that can be found in the LICENSE file in the root of the source
|
|
1055
|
+
* tree.
|
|
1056
|
+
*/
|
|
1057
|
+
/* eslint-env node */
|
|
1058
|
+
'use strict';
|
|
1059
|
+
|
|
1060
|
+
var SDPUtils = require('sdp');
|
|
1061
|
+
var browserDetails = require('../utils').browserDetails;
|
|
1062
|
+
|
|
1063
|
+
var edgeShim = {
|
|
1064
|
+
shimPeerConnection: function shimPeerConnection() {
|
|
1065
|
+
if (window.RTCIceGatherer) {
|
|
1066
|
+
// ORTC defines an RTCIceCandidate object but no constructor.
|
|
1067
|
+
// Not implemented in Edge.
|
|
1068
|
+
if (!window.RTCIceCandidate) {
|
|
1069
|
+
window.RTCIceCandidate = function (args) {
|
|
1070
|
+
return args;
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
// ORTC does not have a session description object but
|
|
1074
|
+
// other browsers (i.e. Chrome) that will support both PC and ORTC
|
|
1075
|
+
// in the future might have this defined already.
|
|
1076
|
+
if (!window.RTCSessionDescription) {
|
|
1077
|
+
window.RTCSessionDescription = function (args) {
|
|
1078
|
+
return args;
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
// this adds an additional event listener to MediaStrackTrack that signals
|
|
1082
|
+
// when a tracks enabled property was changed.
|
|
1083
|
+
var origMSTEnabled = Object.getOwnPropertyDescriptor(MediaStreamTrack.prototype, 'enabled');
|
|
1084
|
+
Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
|
|
1085
|
+
set: function set(value) {
|
|
1086
|
+
origMSTEnabled.set.call(this, value);
|
|
1087
|
+
var ev = new Event('enabled');
|
|
1088
|
+
ev.enabled = value;
|
|
1089
|
+
this.dispatchEvent(ev);
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
window.RTCPeerConnection = function (config) {
|
|
1095
|
+
var self = this;
|
|
1096
|
+
|
|
1097
|
+
var _eventTarget = document.createDocumentFragment();
|
|
1098
|
+
['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach(function (method) {
|
|
1099
|
+
self[method] = _eventTarget[method].bind(_eventTarget);
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
this.onicecandidate = null;
|
|
1103
|
+
this.onaddstream = null;
|
|
1104
|
+
this.ontrack = null;
|
|
1105
|
+
this.onremovestream = null;
|
|
1106
|
+
this.onsignalingstatechange = null;
|
|
1107
|
+
this.oniceconnectionstatechange = null;
|
|
1108
|
+
this.onnegotiationneeded = null;
|
|
1109
|
+
this.ondatachannel = null;
|
|
1110
|
+
|
|
1111
|
+
this.localStreams = [];
|
|
1112
|
+
this.remoteStreams = [];
|
|
1113
|
+
this.getLocalStreams = function () {
|
|
1114
|
+
return self.localStreams;
|
|
1115
|
+
};
|
|
1116
|
+
this.getRemoteStreams = function () {
|
|
1117
|
+
return self.remoteStreams;
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
this.localDescription = new RTCSessionDescription({
|
|
1121
|
+
type: '',
|
|
1122
|
+
sdp: ''
|
|
1123
|
+
});
|
|
1124
|
+
this.remoteDescription = new RTCSessionDescription({
|
|
1125
|
+
type: '',
|
|
1126
|
+
sdp: ''
|
|
1127
|
+
});
|
|
1128
|
+
this.signalingState = 'stable';
|
|
1129
|
+
this.iceConnectionState = 'new';
|
|
1130
|
+
this.iceGatheringState = 'new';
|
|
1131
|
+
|
|
1132
|
+
this.iceOptions = {
|
|
1133
|
+
gatherPolicy: 'all',
|
|
1134
|
+
iceServers: []
|
|
1135
|
+
};
|
|
1136
|
+
if (config && config.iceTransportPolicy) {
|
|
1137
|
+
switch (config.iceTransportPolicy) {
|
|
1138
|
+
case 'all':
|
|
1139
|
+
case 'relay':
|
|
1140
|
+
this.iceOptions.gatherPolicy = config.iceTransportPolicy;
|
|
1141
|
+
break;
|
|
1142
|
+
case 'none':
|
|
1143
|
+
// FIXME: remove once implementation and spec have added this.
|
|
1144
|
+
throw new TypeError('iceTransportPolicy "none" not supported');
|
|
1145
|
+
default:
|
|
1146
|
+
// don't set iceTransportPolicy.
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
this.usingBundle = config && config.bundlePolicy === 'max-bundle';
|
|
1151
|
+
|
|
1152
|
+
if (config && config.iceServers) {
|
|
1153
|
+
// Edge does not like
|
|
1154
|
+
// 1) stun:
|
|
1155
|
+
// 2) turn: that does not have all of turn:host:port?transport=udp
|
|
1156
|
+
// 3) turn: with ipv6 addresses
|
|
1157
|
+
var iceServers = JSON.parse(JSON.stringify(config.iceServers));
|
|
1158
|
+
this.iceOptions.iceServers = iceServers.filter(function (server) {
|
|
1159
|
+
if (server && server.urls) {
|
|
1160
|
+
var urls = server.urls;
|
|
1161
|
+
if (typeof urls === 'string') {
|
|
1162
|
+
urls = [urls];
|
|
1163
|
+
}
|
|
1164
|
+
urls = urls.filter(function (url) {
|
|
1165
|
+
return url.indexOf('turn:') === 0 && url.indexOf('transport=udp') !== -1 && url.indexOf('turn:[') === -1 || url.indexOf('stun:') === 0 && browserDetails.version >= 14393;
|
|
1166
|
+
})[0];
|
|
1167
|
+
return !!urls;
|
|
1168
|
+
}
|
|
1169
|
+
return false;
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
this._config = config;
|
|
1173
|
+
|
|
1174
|
+
// per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
|
|
1175
|
+
// everything that is needed to describe a SDP m-line.
|
|
1176
|
+
this.transceivers = [];
|
|
1177
|
+
|
|
1178
|
+
// since the iceGatherer is currently created in createOffer but we
|
|
1179
|
+
// must not emit candidates until after setLocalDescription we buffer
|
|
1180
|
+
// them in this array.
|
|
1181
|
+
this._localIceCandidatesBuffer = [];
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
window.RTCPeerConnection.prototype._emitBufferedCandidates = function () {
|
|
1185
|
+
var self = this;
|
|
1186
|
+
var sections = SDPUtils.splitSections(self.localDescription.sdp);
|
|
1187
|
+
// FIXME: need to apply ice candidates in a way which is async but
|
|
1188
|
+
// in-order
|
|
1189
|
+
this._localIceCandidatesBuffer.forEach(function (event) {
|
|
1190
|
+
var end = !event.candidate || Object.keys(event.candidate).length === 0;
|
|
1191
|
+
if (end) {
|
|
1192
|
+
for (var j = 1; j < sections.length; j++) {
|
|
1193
|
+
if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
|
|
1194
|
+
sections[j] += 'a=end-of-candidates\r\n';
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
} else if (event.candidate.candidate.indexOf('typ endOfCandidates') === -1) {
|
|
1198
|
+
sections[event.candidate.sdpMLineIndex + 1] += 'a=' + event.candidate.candidate + '\r\n';
|
|
1199
|
+
}
|
|
1200
|
+
self.localDescription.sdp = sections.join('');
|
|
1201
|
+
self.dispatchEvent(event);
|
|
1202
|
+
if (self.onicecandidate !== null) {
|
|
1203
|
+
self.onicecandidate(event);
|
|
1204
|
+
}
|
|
1205
|
+
if (!event.candidate && self.iceGatheringState !== 'complete') {
|
|
1206
|
+
var complete = self.transceivers.every(function (transceiver) {
|
|
1207
|
+
return transceiver.iceGatherer && transceiver.iceGatherer.state === 'completed';
|
|
1208
|
+
});
|
|
1209
|
+
if (complete) {
|
|
1210
|
+
self.iceGatheringState = 'complete';
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
this._localIceCandidatesBuffer = [];
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
window.RTCPeerConnection.prototype.getConfiguration = function () {
|
|
1218
|
+
return this._config;
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
window.RTCPeerConnection.prototype.addStream = function (stream) {
|
|
1222
|
+
// Clone is necessary for local demos mostly, attaching directly
|
|
1223
|
+
// to two different senders does not work (build 10547).
|
|
1224
|
+
var clonedStream = stream.clone();
|
|
1225
|
+
stream.getTracks().forEach(function (track, idx) {
|
|
1226
|
+
var clonedTrack = clonedStream.getTracks()[idx];
|
|
1227
|
+
track.addEventListener('enabled', function (event) {
|
|
1228
|
+
clonedTrack.enabled = event.enabled;
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
this.localStreams.push(clonedStream);
|
|
1232
|
+
this._maybeFireNegotiationNeeded();
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
window.RTCPeerConnection.prototype.removeStream = function (stream) {
|
|
1236
|
+
var idx = this.localStreams.indexOf(stream);
|
|
1237
|
+
if (idx > -1) {
|
|
1238
|
+
this.localStreams.splice(idx, 1);
|
|
1239
|
+
this._maybeFireNegotiationNeeded();
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
window.RTCPeerConnection.prototype.getSenders = function () {
|
|
1244
|
+
return this.transceivers.filter(function (transceiver) {
|
|
1245
|
+
return !!transceiver.rtpSender;
|
|
1246
|
+
}).map(function (transceiver) {
|
|
1247
|
+
return transceiver.rtpSender;
|
|
1248
|
+
});
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
window.RTCPeerConnection.prototype.getReceivers = function () {
|
|
1252
|
+
return this.transceivers.filter(function (transceiver) {
|
|
1253
|
+
return !!transceiver.rtpReceiver;
|
|
1254
|
+
}).map(function (transceiver) {
|
|
1255
|
+
return transceiver.rtpReceiver;
|
|
1256
|
+
});
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
// Determines the intersection of local and remote capabilities.
|
|
1260
|
+
window.RTCPeerConnection.prototype._getCommonCapabilities = function (localCapabilities, remoteCapabilities) {
|
|
1261
|
+
var commonCapabilities = {
|
|
1262
|
+
codecs: [],
|
|
1263
|
+
headerExtensions: [],
|
|
1264
|
+
fecMechanisms: []
|
|
1265
|
+
};
|
|
1266
|
+
localCapabilities.codecs.forEach(function (lCodec) {
|
|
1267
|
+
for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
|
|
1268
|
+
var rCodec = remoteCapabilities.codecs[i];
|
|
1269
|
+
if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && lCodec.clockRate === rCodec.clockRate) {
|
|
1270
|
+
// number of channels is the highest common number of channels
|
|
1271
|
+
rCodec.numChannels = Math.min(lCodec.numChannels, rCodec.numChannels);
|
|
1272
|
+
// push rCodec so we reply with offerer payload type
|
|
1273
|
+
commonCapabilities.codecs.push(rCodec);
|
|
1274
|
+
|
|
1275
|
+
// determine common feedback mechanisms
|
|
1276
|
+
rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function (fb) {
|
|
1277
|
+
for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
|
|
1278
|
+
if (lCodec.rtcpFeedback[j].type === fb.type && lCodec.rtcpFeedback[j].parameter === fb.parameter) {
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return false;
|
|
1283
|
+
});
|
|
1284
|
+
// FIXME: also need to determine .parameters
|
|
1285
|
+
// see https://github.com/openpeer/ortc/issues/569
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
localCapabilities.headerExtensions.forEach(function (lHeaderExtension) {
|
|
1292
|
+
for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) {
|
|
1293
|
+
var rHeaderExtension = remoteCapabilities.headerExtensions[i];
|
|
1294
|
+
if (lHeaderExtension.uri === rHeaderExtension.uri) {
|
|
1295
|
+
commonCapabilities.headerExtensions.push(rHeaderExtension);
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
// FIXME: fecMechanisms
|
|
1302
|
+
return commonCapabilities;
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// Create ICE gatherer, ICE transport and DTLS transport.
|
|
1306
|
+
window.RTCPeerConnection.prototype._createIceAndDtlsTransports = function (mid, sdpMLineIndex) {
|
|
1307
|
+
var self = this;
|
|
1308
|
+
var iceGatherer = new RTCIceGatherer(self.iceOptions);
|
|
1309
|
+
var iceTransport = new RTCIceTransport(iceGatherer);
|
|
1310
|
+
iceGatherer.onlocalcandidate = function (evt) {
|
|
1311
|
+
var event = new Event('icecandidate');
|
|
1312
|
+
event.candidate = { sdpMid: mid, sdpMLineIndex: sdpMLineIndex };
|
|
1313
|
+
|
|
1314
|
+
var cand = evt.candidate;
|
|
1315
|
+
var end = !cand || Object.keys(cand).length === 0;
|
|
1316
|
+
// Edge emits an empty object for RTCIceCandidateComplete‥
|
|
1317
|
+
if (end) {
|
|
1318
|
+
// polyfill since RTCIceGatherer.state is not implemented in
|
|
1319
|
+
// Edge 10547 yet.
|
|
1320
|
+
if (iceGatherer.state === undefined) {
|
|
1321
|
+
iceGatherer.state = 'completed';
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Emit a candidate with type endOfCandidates to make the samples
|
|
1325
|
+
// work. Edge requires addIceCandidate with this empty candidate
|
|
1326
|
+
// to start checking. The real solution is to signal
|
|
1327
|
+
// end-of-candidates to the other side when getting the null
|
|
1328
|
+
// candidate but some apps (like the samples) don't do that.
|
|
1329
|
+
event.candidate.candidate = 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
|
|
1330
|
+
} else {
|
|
1331
|
+
// RTCIceCandidate doesn't have a component, needs to be added
|
|
1332
|
+
cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
|
|
1333
|
+
event.candidate.candidate = SDPUtils.writeCandidate(cand);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// update local description.
|
|
1337
|
+
var sections = SDPUtils.splitSections(self.localDescription.sdp);
|
|
1338
|
+
if (event.candidate.candidate.indexOf('typ endOfCandidates') === -1) {
|
|
1339
|
+
sections[event.candidate.sdpMLineIndex + 1] += 'a=' + event.candidate.candidate + '\r\n';
|
|
1340
|
+
} else {
|
|
1341
|
+
sections[event.candidate.sdpMLineIndex + 1] += 'a=end-of-candidates\r\n';
|
|
1342
|
+
}
|
|
1343
|
+
self.localDescription.sdp = sections.join('');
|
|
1344
|
+
|
|
1345
|
+
var complete = self.transceivers.every(function (transceiver) {
|
|
1346
|
+
return transceiver.iceGatherer && transceiver.iceGatherer.state === 'completed';
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
// Emit candidate if localDescription is set.
|
|
1350
|
+
// Also emits null candidate when all gatherers are complete.
|
|
1351
|
+
switch (self.iceGatheringState) {
|
|
1352
|
+
case 'new':
|
|
1353
|
+
self._localIceCandidatesBuffer.push(event);
|
|
1354
|
+
if (end && complete) {
|
|
1355
|
+
self._localIceCandidatesBuffer.push(new Event('icecandidate'));
|
|
1356
|
+
}
|
|
1357
|
+
break;
|
|
1358
|
+
case 'gathering':
|
|
1359
|
+
self._emitBufferedCandidates();
|
|
1360
|
+
self.dispatchEvent(event);
|
|
1361
|
+
if (self.onicecandidate !== null) {
|
|
1362
|
+
self.onicecandidate(event);
|
|
1363
|
+
}
|
|
1364
|
+
if (complete) {
|
|
1365
|
+
self.dispatchEvent(new Event('icecandidate'));
|
|
1366
|
+
if (self.onicecandidate !== null) {
|
|
1367
|
+
self.onicecandidate(new Event('icecandidate'));
|
|
1368
|
+
}
|
|
1369
|
+
self.iceGatheringState = 'complete';
|
|
1370
|
+
}
|
|
1371
|
+
break;
|
|
1372
|
+
case 'complete':
|
|
1373
|
+
// should not happen... currently!
|
|
1374
|
+
break;
|
|
1375
|
+
default:
|
|
1376
|
+
// no-op.
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
iceTransport.onicestatechange = function () {
|
|
1381
|
+
self._updateConnectionState();
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
var dtlsTransport = new RTCDtlsTransport(iceTransport);
|
|
1385
|
+
dtlsTransport.ondtlsstatechange = function () {
|
|
1386
|
+
self._updateConnectionState();
|
|
1387
|
+
};
|
|
1388
|
+
dtlsTransport.onerror = function () {
|
|
1389
|
+
// onerror does not set state to failed by itself.
|
|
1390
|
+
dtlsTransport.state = 'failed';
|
|
1391
|
+
self._updateConnectionState();
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
return {
|
|
1395
|
+
iceGatherer: iceGatherer,
|
|
1396
|
+
iceTransport: iceTransport,
|
|
1397
|
+
dtlsTransport: dtlsTransport
|
|
1398
|
+
};
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// Start the RTP Sender and Receiver for a transceiver.
|
|
1402
|
+
window.RTCPeerConnection.prototype._transceive = function (transceiver, send, recv) {
|
|
1403
|
+
var params = this._getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities);
|
|
1404
|
+
if (send && transceiver.rtpSender) {
|
|
1405
|
+
params.encodings = transceiver.sendEncodingParameters;
|
|
1406
|
+
params.rtcp = {
|
|
1407
|
+
cname: SDPUtils.localCName
|
|
1408
|
+
};
|
|
1409
|
+
if (transceiver.recvEncodingParameters.length) {
|
|
1410
|
+
params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
|
|
1411
|
+
}
|
|
1412
|
+
transceiver.rtpSender.send(params);
|
|
1413
|
+
}
|
|
1414
|
+
if (recv && transceiver.rtpReceiver) {
|
|
1415
|
+
// remove RTX field in Edge 14942
|
|
1416
|
+
if (transceiver.kind === 'video' && transceiver.recvEncodingParameters) {
|
|
1417
|
+
transceiver.recvEncodingParameters.forEach(function (p) {
|
|
1418
|
+
delete p.rtx;
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
params.encodings = transceiver.recvEncodingParameters;
|
|
1422
|
+
params.rtcp = {
|
|
1423
|
+
cname: transceiver.cname
|
|
1424
|
+
};
|
|
1425
|
+
if (transceiver.sendEncodingParameters.length) {
|
|
1426
|
+
params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
|
|
1427
|
+
}
|
|
1428
|
+
transceiver.rtpReceiver.receive(params);
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
window.RTCPeerConnection.prototype.setLocalDescription = function (description) {
|
|
1433
|
+
var self = this;
|
|
1434
|
+
var sections;
|
|
1435
|
+
var sessionpart;
|
|
1436
|
+
if (description.type === 'offer') {
|
|
1437
|
+
// FIXME: What was the purpose of this empty if statement?
|
|
1438
|
+
// if (!this._pendingOffer) {
|
|
1439
|
+
// } else {
|
|
1440
|
+
if (this._pendingOffer) {
|
|
1441
|
+
// VERY limited support for SDP munging. Limited to:
|
|
1442
|
+
// * changing the order of codecs
|
|
1443
|
+
sections = SDPUtils.splitSections(description.sdp);
|
|
1444
|
+
sessionpart = sections.shift();
|
|
1445
|
+
sections.forEach(function (mediaSection, sdpMLineIndex) {
|
|
1446
|
+
var caps = SDPUtils.parseRtpParameters(mediaSection);
|
|
1447
|
+
self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
|
|
1448
|
+
});
|
|
1449
|
+
this.transceivers = this._pendingOffer;
|
|
1450
|
+
delete this._pendingOffer;
|
|
1451
|
+
}
|
|
1452
|
+
} else if (description.type === 'answer') {
|
|
1453
|
+
sections = SDPUtils.splitSections(self.remoteDescription.sdp);
|
|
1454
|
+
sessionpart = sections.shift();
|
|
1455
|
+
var isIceLite = SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0;
|
|
1456
|
+
sections.forEach(function (mediaSection, sdpMLineIndex) {
|
|
1457
|
+
var transceiver = self.transceivers[sdpMLineIndex];
|
|
1458
|
+
var iceGatherer = transceiver.iceGatherer;
|
|
1459
|
+
var iceTransport = transceiver.iceTransport;
|
|
1460
|
+
var dtlsTransport = transceiver.dtlsTransport;
|
|
1461
|
+
var localCapabilities = transceiver.localCapabilities;
|
|
1462
|
+
var remoteCapabilities = transceiver.remoteCapabilities;
|
|
1463
|
+
|
|
1464
|
+
var rejected = mediaSection.split('\n', 1)[0].split(' ', 2)[1] === '0';
|
|
1465
|
+
|
|
1466
|
+
if (!rejected && !transceiver.isDatachannel) {
|
|
1467
|
+
var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, sessionpart);
|
|
1468
|
+
if (isIceLite) {
|
|
1469
|
+
var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:').map(function (cand) {
|
|
1470
|
+
return SDPUtils.parseCandidate(cand);
|
|
1471
|
+
}).filter(function (cand) {
|
|
1472
|
+
return cand.component === '1';
|
|
1473
|
+
});
|
|
1474
|
+
// ice-lite only includes host candidates in the SDP so we can
|
|
1475
|
+
// use setRemoteCandidates (which implies an
|
|
1476
|
+
// RTCIceCandidateComplete)
|
|
1477
|
+
if (cands.length) {
|
|
1478
|
+
iceTransport.setRemoteCandidates(cands);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, sessionpart);
|
|
1482
|
+
if (isIceLite) {
|
|
1483
|
+
remoteDtlsParameters.role = 'server';
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (!self.usingBundle || sdpMLineIndex === 0) {
|
|
1487
|
+
iceTransport.start(iceGatherer, remoteIceParameters, isIceLite ? 'controlling' : 'controlled');
|
|
1488
|
+
dtlsTransport.start(remoteDtlsParameters);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// Calculate intersection of capabilities.
|
|
1492
|
+
var params = self._getCommonCapabilities(localCapabilities, remoteCapabilities);
|
|
1493
|
+
|
|
1494
|
+
// Start the RTCRtpSender. The RTCRtpReceiver for this
|
|
1495
|
+
// transceiver has already been started in setRemoteDescription.
|
|
1496
|
+
self._transceive(transceiver, params.codecs.length > 0, false);
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
this.localDescription = {
|
|
1502
|
+
type: description.type,
|
|
1503
|
+
sdp: description.sdp
|
|
1504
|
+
};
|
|
1505
|
+
switch (description.type) {
|
|
1506
|
+
case 'offer':
|
|
1507
|
+
this._updateSignalingState('have-local-offer');
|
|
1508
|
+
break;
|
|
1509
|
+
case 'answer':
|
|
1510
|
+
this._updateSignalingState('stable');
|
|
1511
|
+
break;
|
|
1512
|
+
default:
|
|
1513
|
+
throw new TypeError('unsupported type "' + description.type + '"');
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// If a success callback was provided, emit ICE candidates after it
|
|
1517
|
+
// has been executed. Otherwise, emit callback after the Promise is
|
|
1518
|
+
// resolved.
|
|
1519
|
+
var hasCallback = arguments.length > 1 && typeof arguments[1] === 'function';
|
|
1520
|
+
if (hasCallback) {
|
|
1521
|
+
var cb = arguments[1];
|
|
1522
|
+
window.setTimeout(function () {
|
|
1523
|
+
cb();
|
|
1524
|
+
if (self.iceGatheringState === 'new') {
|
|
1525
|
+
self.iceGatheringState = 'gathering';
|
|
1526
|
+
}
|
|
1527
|
+
self._emitBufferedCandidates();
|
|
1528
|
+
}, 0);
|
|
1529
|
+
}
|
|
1530
|
+
var p = Promise.resolve();
|
|
1531
|
+
p.then(function () {
|
|
1532
|
+
if (!hasCallback) {
|
|
1533
|
+
if (self.iceGatheringState === 'new') {
|
|
1534
|
+
self.iceGatheringState = 'gathering';
|
|
1535
|
+
}
|
|
1536
|
+
// Usually candidates will be emitted earlier.
|
|
1537
|
+
window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
return p;
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
window.RTCPeerConnection.prototype.setRemoteDescription = function (description) {
|
|
1544
|
+
var self = this;
|
|
1545
|
+
var stream = new MediaStream();
|
|
1546
|
+
var receiverList = [];
|
|
1547
|
+
var sections = SDPUtils.splitSections(description.sdp);
|
|
1548
|
+
var sessionpart = sections.shift();
|
|
1549
|
+
var isIceLite = SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0;
|
|
1550
|
+
this.usingBundle = SDPUtils.matchPrefix(sessionpart, 'a=group:BUNDLE ').length > 0;
|
|
1551
|
+
sections.forEach(function (mediaSection, sdpMLineIndex) {
|
|
1552
|
+
var lines = SDPUtils.splitLines(mediaSection);
|
|
1553
|
+
var mline = lines[0].substr(2).split(' ');
|
|
1554
|
+
var kind = mline[0];
|
|
1555
|
+
var rejected = mline[1] === '0';
|
|
1556
|
+
var direction = SDPUtils.getDirection(mediaSection, sessionpart);
|
|
1557
|
+
|
|
1558
|
+
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
|
|
1559
|
+
if (mid.length) {
|
|
1560
|
+
mid = mid[0].substr(6);
|
|
1561
|
+
} else {
|
|
1562
|
+
mid = SDPUtils.generateIdentifier();
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// Reject datachannels which are not implemented yet.
|
|
1566
|
+
if (kind === 'application' && mline[2] === 'DTLS/SCTP') {
|
|
1567
|
+
self.transceivers[sdpMLineIndex] = {
|
|
1568
|
+
mid: mid,
|
|
1569
|
+
isDatachannel: true
|
|
1570
|
+
};
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
var transceiver;
|
|
1575
|
+
var iceGatherer;
|
|
1576
|
+
var iceTransport;
|
|
1577
|
+
var dtlsTransport;
|
|
1578
|
+
var rtpSender;
|
|
1579
|
+
var rtpReceiver;
|
|
1580
|
+
var sendEncodingParameters;
|
|
1581
|
+
var recvEncodingParameters;
|
|
1582
|
+
var localCapabilities;
|
|
1583
|
+
|
|
1584
|
+
var track;
|
|
1585
|
+
// FIXME: ensure the mediaSection has rtcp-mux set.
|
|
1586
|
+
var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
|
|
1587
|
+
var remoteIceParameters;
|
|
1588
|
+
var remoteDtlsParameters;
|
|
1589
|
+
if (!rejected) {
|
|
1590
|
+
remoteIceParameters = SDPUtils.getIceParameters(mediaSection, sessionpart);
|
|
1591
|
+
remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, sessionpart);
|
|
1592
|
+
remoteDtlsParameters.role = 'client';
|
|
1593
|
+
}
|
|
1594
|
+
recvEncodingParameters = SDPUtils.parseRtpEncodingParameters(mediaSection);
|
|
1595
|
+
|
|
1596
|
+
var cname;
|
|
1597
|
+
// Gets the first SSRC. Note that with RTX there might be multiple
|
|
1598
|
+
// SSRCs.
|
|
1599
|
+
var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) {
|
|
1600
|
+
return SDPUtils.parseSsrcMedia(line);
|
|
1601
|
+
}).filter(function (obj) {
|
|
1602
|
+
return obj.attribute === 'cname';
|
|
1603
|
+
})[0];
|
|
1604
|
+
if (remoteSsrc) {
|
|
1605
|
+
cname = remoteSsrc.value;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
var isComplete = SDPUtils.matchPrefix(mediaSection, 'a=end-of-candidates', sessionpart).length > 0;
|
|
1609
|
+
var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:').map(function (cand) {
|
|
1610
|
+
return SDPUtils.parseCandidate(cand);
|
|
1611
|
+
}).filter(function (cand) {
|
|
1612
|
+
return cand.component === '1';
|
|
1613
|
+
});
|
|
1614
|
+
if (description.type === 'offer' && !rejected) {
|
|
1615
|
+
var transports = self.usingBundle && sdpMLineIndex > 0 ? {
|
|
1616
|
+
iceGatherer: self.transceivers[0].iceGatherer,
|
|
1617
|
+
iceTransport: self.transceivers[0].iceTransport,
|
|
1618
|
+
dtlsTransport: self.transceivers[0].dtlsTransport
|
|
1619
|
+
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
|
|
1620
|
+
|
|
1621
|
+
if (isComplete) {
|
|
1622
|
+
transports.iceTransport.setRemoteCandidates(cands);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
localCapabilities = RTCRtpReceiver.getCapabilities(kind);
|
|
1626
|
+
|
|
1627
|
+
// filter RTX until additional stuff needed for RTX is implemented
|
|
1628
|
+
// in adapter.js
|
|
1629
|
+
localCapabilities.codecs = localCapabilities.codecs.filter(function (codec) {
|
|
1630
|
+
return codec.name !== 'rtx';
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
sendEncodingParameters = [{
|
|
1634
|
+
ssrc: (2 * sdpMLineIndex + 2) * 1001
|
|
1635
|
+
}];
|
|
1636
|
+
|
|
1637
|
+
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
|
|
1638
|
+
|
|
1639
|
+
track = rtpReceiver.track;
|
|
1640
|
+
receiverList.push([track, rtpReceiver]);
|
|
1641
|
+
// FIXME: not correct when there are multiple streams but that is
|
|
1642
|
+
// not currently supported in this shim.
|
|
1643
|
+
stream.addTrack(track);
|
|
1644
|
+
|
|
1645
|
+
// FIXME: look at direction.
|
|
1646
|
+
if (self.localStreams.length > 0 && self.localStreams[0].getTracks().length >= sdpMLineIndex) {
|
|
1647
|
+
var localTrack;
|
|
1648
|
+
if (kind === 'audio') {
|
|
1649
|
+
localTrack = self.localStreams[0].getAudioTracks()[0];
|
|
1650
|
+
} else if (kind === 'video') {
|
|
1651
|
+
localTrack = self.localStreams[0].getVideoTracks()[0];
|
|
1652
|
+
}
|
|
1653
|
+
if (localTrack) {
|
|
1654
|
+
rtpSender = new RTCRtpSender(localTrack, transports.dtlsTransport);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
self.transceivers[sdpMLineIndex] = {
|
|
1659
|
+
iceGatherer: transports.iceGatherer,
|
|
1660
|
+
iceTransport: transports.iceTransport,
|
|
1661
|
+
dtlsTransport: transports.dtlsTransport,
|
|
1662
|
+
localCapabilities: localCapabilities,
|
|
1663
|
+
remoteCapabilities: remoteCapabilities,
|
|
1664
|
+
rtpSender: rtpSender,
|
|
1665
|
+
rtpReceiver: rtpReceiver,
|
|
1666
|
+
kind: kind,
|
|
1667
|
+
mid: mid,
|
|
1668
|
+
cname: cname,
|
|
1669
|
+
sendEncodingParameters: sendEncodingParameters,
|
|
1670
|
+
recvEncodingParameters: recvEncodingParameters
|
|
1671
|
+
};
|
|
1672
|
+
// Start the RTCRtpReceiver now. The RTPSender is started in
|
|
1673
|
+
// setLocalDescription.
|
|
1674
|
+
self._transceive(self.transceivers[sdpMLineIndex], false, direction === 'sendrecv' || direction === 'sendonly');
|
|
1675
|
+
} else if (description.type === 'answer' && !rejected) {
|
|
1676
|
+
transceiver = self.transceivers[sdpMLineIndex];
|
|
1677
|
+
iceGatherer = transceiver.iceGatherer;
|
|
1678
|
+
iceTransport = transceiver.iceTransport;
|
|
1679
|
+
dtlsTransport = transceiver.dtlsTransport;
|
|
1680
|
+
rtpSender = transceiver.rtpSender;
|
|
1681
|
+
rtpReceiver = transceiver.rtpReceiver;
|
|
1682
|
+
sendEncodingParameters = transceiver.sendEncodingParameters;
|
|
1683
|
+
localCapabilities = transceiver.localCapabilities;
|
|
1684
|
+
|
|
1685
|
+
self.transceivers[sdpMLineIndex].recvEncodingParameters = recvEncodingParameters;
|
|
1686
|
+
self.transceivers[sdpMLineIndex].remoteCapabilities = remoteCapabilities;
|
|
1687
|
+
self.transceivers[sdpMLineIndex].cname = cname;
|
|
1688
|
+
|
|
1689
|
+
if ((isIceLite || isComplete) && cands.length) {
|
|
1690
|
+
iceTransport.setRemoteCandidates(cands);
|
|
1691
|
+
}
|
|
1692
|
+
if (!self.usingBundle || sdpMLineIndex === 0) {
|
|
1693
|
+
iceTransport.start(iceGatherer, remoteIceParameters, 'controlling');
|
|
1694
|
+
dtlsTransport.start(remoteDtlsParameters);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
self._transceive(transceiver, direction === 'sendrecv' || direction === 'recvonly', direction === 'sendrecv' || direction === 'sendonly');
|
|
1698
|
+
|
|
1699
|
+
if (rtpReceiver && (direction === 'sendrecv' || direction === 'sendonly')) {
|
|
1700
|
+
track = rtpReceiver.track;
|
|
1701
|
+
receiverList.push([track, rtpReceiver]);
|
|
1702
|
+
stream.addTrack(track);
|
|
1703
|
+
} else {
|
|
1704
|
+
// FIXME: actually the receiver should be created later.
|
|
1705
|
+
delete transceiver.rtpReceiver;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
this.remoteDescription = {
|
|
1711
|
+
type: description.type,
|
|
1712
|
+
sdp: description.sdp
|
|
1713
|
+
};
|
|
1714
|
+
switch (description.type) {
|
|
1715
|
+
case 'offer':
|
|
1716
|
+
this._updateSignalingState('have-remote-offer');
|
|
1717
|
+
break;
|
|
1718
|
+
case 'answer':
|
|
1719
|
+
this._updateSignalingState('stable');
|
|
1720
|
+
break;
|
|
1721
|
+
default:
|
|
1722
|
+
throw new TypeError('unsupported type "' + description.type + '"');
|
|
1723
|
+
}
|
|
1724
|
+
if (stream.getTracks().length) {
|
|
1725
|
+
self.remoteStreams.push(stream);
|
|
1726
|
+
window.setTimeout(function () {
|
|
1727
|
+
var event = new Event('addstream');
|
|
1728
|
+
event.stream = stream;
|
|
1729
|
+
self.dispatchEvent(event);
|
|
1730
|
+
if (self.onaddstream !== null) {
|
|
1731
|
+
window.setTimeout(function () {
|
|
1732
|
+
self.onaddstream(event);
|
|
1733
|
+
}, 0);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
receiverList.forEach(function (item) {
|
|
1737
|
+
var track = item[0];
|
|
1738
|
+
var receiver = item[1];
|
|
1739
|
+
var trackEvent = new Event('track');
|
|
1740
|
+
trackEvent.track = track;
|
|
1741
|
+
trackEvent.receiver = receiver;
|
|
1742
|
+
trackEvent.streams = [stream];
|
|
1743
|
+
self.dispatchEvent(event);
|
|
1744
|
+
if (self.ontrack !== null) {
|
|
1745
|
+
window.setTimeout(function () {
|
|
1746
|
+
self.ontrack(trackEvent);
|
|
1747
|
+
}, 0);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
}, 0);
|
|
1751
|
+
}
|
|
1752
|
+
if (arguments.length > 1 && typeof arguments[1] === 'function') {
|
|
1753
|
+
window.setTimeout(arguments[1], 0);
|
|
1754
|
+
}
|
|
1755
|
+
return Promise.resolve();
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
window.RTCPeerConnection.prototype.close = function () {
|
|
1759
|
+
this.transceivers.forEach(function (transceiver) {
|
|
1760
|
+
/* not yet
|
|
1761
|
+
if (transceiver.iceGatherer) {
|
|
1762
|
+
transceiver.iceGatherer.close();
|
|
1763
|
+
}
|
|
1764
|
+
*/
|
|
1765
|
+
if (transceiver.iceTransport) {
|
|
1766
|
+
transceiver.iceTransport.stop();
|
|
1767
|
+
}
|
|
1768
|
+
if (transceiver.dtlsTransport) {
|
|
1769
|
+
transceiver.dtlsTransport.stop();
|
|
1770
|
+
}
|
|
1771
|
+
if (transceiver.rtpSender) {
|
|
1772
|
+
transceiver.rtpSender.stop();
|
|
1773
|
+
}
|
|
1774
|
+
if (transceiver.rtpReceiver) {
|
|
1775
|
+
transceiver.rtpReceiver.stop();
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
// FIXME: clean up tracks, local streams, remote streams, etc
|
|
1779
|
+
this._updateSignalingState('closed');
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1782
|
+
// Update the signaling state.
|
|
1783
|
+
window.RTCPeerConnection.prototype._updateSignalingState = function (newState) {
|
|
1784
|
+
this.signalingState = newState;
|
|
1785
|
+
var event = new Event('signalingstatechange');
|
|
1786
|
+
this.dispatchEvent(event);
|
|
1787
|
+
if (this.onsignalingstatechange !== null) {
|
|
1788
|
+
this.onsignalingstatechange(event);
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
// Determine whether to fire the negotiationneeded event.
|
|
1793
|
+
window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function () {
|
|
1794
|
+
// Fire away (for now).
|
|
1795
|
+
var event = new Event('negotiationneeded');
|
|
1796
|
+
this.dispatchEvent(event);
|
|
1797
|
+
if (this.onnegotiationneeded !== null) {
|
|
1798
|
+
this.onnegotiationneeded(event);
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
// Update the connection state.
|
|
1803
|
+
window.RTCPeerConnection.prototype._updateConnectionState = function () {
|
|
1804
|
+
var self = this;
|
|
1805
|
+
var newState;
|
|
1806
|
+
var states = {
|
|
1807
|
+
'new': 0,
|
|
1808
|
+
closed: 0,
|
|
1809
|
+
connecting: 0,
|
|
1810
|
+
checking: 0,
|
|
1811
|
+
connected: 0,
|
|
1812
|
+
completed: 0,
|
|
1813
|
+
failed: 0
|
|
1814
|
+
};
|
|
1815
|
+
this.transceivers.forEach(function (transceiver) {
|
|
1816
|
+
states[transceiver.iceTransport.state]++;
|
|
1817
|
+
states[transceiver.dtlsTransport.state]++;
|
|
1818
|
+
});
|
|
1819
|
+
// ICETransport.completed and connected are the same for this purpose.
|
|
1820
|
+
states.connected += states.completed;
|
|
1821
|
+
|
|
1822
|
+
newState = 'new';
|
|
1823
|
+
if (states.failed > 0) {
|
|
1824
|
+
newState = 'failed';
|
|
1825
|
+
} else if (states.connecting > 0 || states.checking > 0) {
|
|
1826
|
+
newState = 'connecting';
|
|
1827
|
+
} else if (states.disconnected > 0) {
|
|
1828
|
+
newState = 'disconnected';
|
|
1829
|
+
} else if (states.new > 0) {
|
|
1830
|
+
newState = 'new';
|
|
1831
|
+
} else if (states.connected > 0 || states.completed > 0) {
|
|
1832
|
+
newState = 'connected';
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if (newState !== self.iceConnectionState) {
|
|
1836
|
+
self.iceConnectionState = newState;
|
|
1837
|
+
var event = new Event('iceconnectionstatechange');
|
|
1838
|
+
this.dispatchEvent(event);
|
|
1839
|
+
if (this.oniceconnectionstatechange !== null) {
|
|
1840
|
+
this.oniceconnectionstatechange(event);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
window.RTCPeerConnection.prototype.createOffer = function () {
|
|
1846
|
+
var self = this;
|
|
1847
|
+
if (this._pendingOffer) {
|
|
1848
|
+
throw new Error('createOffer called while there is a pending offer.');
|
|
1849
|
+
}
|
|
1850
|
+
var offerOptions;
|
|
1851
|
+
if (arguments.length === 1 && typeof arguments[0] !== 'function') {
|
|
1852
|
+
offerOptions = arguments[0];
|
|
1853
|
+
} else if (arguments.length === 3) {
|
|
1854
|
+
offerOptions = arguments[2];
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
var tracks = [];
|
|
1858
|
+
var numAudioTracks = 0;
|
|
1859
|
+
var numVideoTracks = 0;
|
|
1860
|
+
// Default to sendrecv.
|
|
1861
|
+
if (this.localStreams.length) {
|
|
1862
|
+
numAudioTracks = this.localStreams[0].getAudioTracks().length;
|
|
1863
|
+
numVideoTracks = this.localStreams[0].getVideoTracks().length;
|
|
1864
|
+
}
|
|
1865
|
+
// Determine number of audio and video tracks we need to send/recv.
|
|
1866
|
+
if (offerOptions) {
|
|
1867
|
+
// Reject Chrome legacy constraints.
|
|
1868
|
+
if (offerOptions.mandatory || offerOptions.optional) {
|
|
1869
|
+
throw new TypeError('Legacy mandatory/optional constraints not supported.');
|
|
1870
|
+
}
|
|
1871
|
+
if (offerOptions.offerToReceiveAudio !== undefined) {
|
|
1872
|
+
numAudioTracks = offerOptions.offerToReceiveAudio;
|
|
1873
|
+
}
|
|
1874
|
+
if (offerOptions.offerToReceiveVideo !== undefined) {
|
|
1875
|
+
numVideoTracks = offerOptions.offerToReceiveVideo;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (this.localStreams.length) {
|
|
1879
|
+
// Push local streams.
|
|
1880
|
+
this.localStreams[0].getTracks().forEach(function (track) {
|
|
1881
|
+
tracks.push({
|
|
1882
|
+
kind: track.kind,
|
|
1883
|
+
track: track,
|
|
1884
|
+
wantReceive: track.kind === 'audio' ? numAudioTracks > 0 : numVideoTracks > 0
|
|
1885
|
+
});
|
|
1886
|
+
if (track.kind === 'audio') {
|
|
1887
|
+
numAudioTracks--;
|
|
1888
|
+
} else if (track.kind === 'video') {
|
|
1889
|
+
numVideoTracks--;
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
// Create M-lines for recvonly streams.
|
|
1894
|
+
while (numAudioTracks > 0 || numVideoTracks > 0) {
|
|
1895
|
+
if (numAudioTracks > 0) {
|
|
1896
|
+
tracks.push({
|
|
1897
|
+
kind: 'audio',
|
|
1898
|
+
wantReceive: true
|
|
1899
|
+
});
|
|
1900
|
+
numAudioTracks--;
|
|
1901
|
+
}
|
|
1902
|
+
if (numVideoTracks > 0) {
|
|
1903
|
+
tracks.push({
|
|
1904
|
+
kind: 'video',
|
|
1905
|
+
wantReceive: true
|
|
1906
|
+
});
|
|
1907
|
+
numVideoTracks--;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
var sdp = SDPUtils.writeSessionBoilerplate();
|
|
1912
|
+
var transceivers = [];
|
|
1913
|
+
tracks.forEach(function (mline, sdpMLineIndex) {
|
|
1914
|
+
// For each track, create an ice gatherer, ice transport,
|
|
1915
|
+
// dtls transport, potentially rtpsender and rtpreceiver.
|
|
1916
|
+
var track = mline.track;
|
|
1917
|
+
var kind = mline.kind;
|
|
1918
|
+
var mid = SDPUtils.generateIdentifier();
|
|
1919
|
+
|
|
1920
|
+
var transports = self.usingBundle && sdpMLineIndex > 0 ? {
|
|
1921
|
+
iceGatherer: transceivers[0].iceGatherer,
|
|
1922
|
+
iceTransport: transceivers[0].iceTransport,
|
|
1923
|
+
dtlsTransport: transceivers[0].dtlsTransport
|
|
1924
|
+
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
|
|
1925
|
+
|
|
1926
|
+
var localCapabilities = RTCRtpSender.getCapabilities(kind);
|
|
1927
|
+
// filter RTX until additional stuff needed for RTX is implemented
|
|
1928
|
+
// in adapter.js
|
|
1929
|
+
localCapabilities.codecs = localCapabilities.codecs.filter(function (codec) {
|
|
1930
|
+
return codec.name !== 'rtx';
|
|
1931
|
+
});
|
|
1932
|
+
localCapabilities.codecs.forEach(function (codec) {
|
|
1933
|
+
// work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
|
|
1934
|
+
// by adding level-asymmetry-allowed=1
|
|
1935
|
+
if (codec.name === 'H264' && codec.parameters['level-asymmetry-allowed'] === undefined) {
|
|
1936
|
+
codec.parameters['level-asymmetry-allowed'] = '1';
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
|
|
1940
|
+
var rtpSender;
|
|
1941
|
+
var rtpReceiver;
|
|
1942
|
+
|
|
1943
|
+
// generate an ssrc now, to be used later in rtpSender.send
|
|
1944
|
+
var sendEncodingParameters = [{
|
|
1945
|
+
ssrc: (2 * sdpMLineIndex + 1) * 1001
|
|
1946
|
+
}];
|
|
1947
|
+
if (track) {
|
|
1948
|
+
rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (mline.wantReceive) {
|
|
1952
|
+
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
transceivers[sdpMLineIndex] = {
|
|
1956
|
+
iceGatherer: transports.iceGatherer,
|
|
1957
|
+
iceTransport: transports.iceTransport,
|
|
1958
|
+
dtlsTransport: transports.dtlsTransport,
|
|
1959
|
+
localCapabilities: localCapabilities,
|
|
1960
|
+
remoteCapabilities: null,
|
|
1961
|
+
rtpSender: rtpSender,
|
|
1962
|
+
rtpReceiver: rtpReceiver,
|
|
1963
|
+
kind: kind,
|
|
1964
|
+
mid: mid,
|
|
1965
|
+
sendEncodingParameters: sendEncodingParameters,
|
|
1966
|
+
recvEncodingParameters: null
|
|
1967
|
+
};
|
|
1968
|
+
});
|
|
1969
|
+
if (this.usingBundle) {
|
|
1970
|
+
sdp += 'a=group:BUNDLE ' + transceivers.map(function (t) {
|
|
1971
|
+
return t.mid;
|
|
1972
|
+
}).join(' ') + '\r\n';
|
|
1973
|
+
}
|
|
1974
|
+
tracks.forEach(function (mline, sdpMLineIndex) {
|
|
1975
|
+
var transceiver = transceivers[sdpMLineIndex];
|
|
1976
|
+
sdp += SDPUtils.writeMediaSection(transceiver, transceiver.localCapabilities, 'offer', self.localStreams[0]);
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
this._pendingOffer = transceivers;
|
|
1980
|
+
var desc = new RTCSessionDescription({
|
|
1981
|
+
type: 'offer',
|
|
1982
|
+
sdp: sdp
|
|
1983
|
+
});
|
|
1984
|
+
if (arguments.length && typeof arguments[0] === 'function') {
|
|
1985
|
+
window.setTimeout(arguments[0], 0, desc);
|
|
1986
|
+
}
|
|
1987
|
+
return Promise.resolve(desc);
|
|
1988
|
+
};
|
|
1989
|
+
|
|
1990
|
+
window.RTCPeerConnection.prototype.createAnswer = function () {
|
|
1991
|
+
var self = this;
|
|
1992
|
+
|
|
1993
|
+
var sdp = SDPUtils.writeSessionBoilerplate();
|
|
1994
|
+
if (this.usingBundle) {
|
|
1995
|
+
sdp += 'a=group:BUNDLE ' + this.transceivers.map(function (t) {
|
|
1996
|
+
return t.mid;
|
|
1997
|
+
}).join(' ') + '\r\n';
|
|
1998
|
+
}
|
|
1999
|
+
this.transceivers.forEach(function (transceiver) {
|
|
2000
|
+
if (transceiver.isDatachannel) {
|
|
2001
|
+
sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + 'c=IN IP4 0.0.0.0\r\n' + 'a=mid:' + transceiver.mid + '\r\n';
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
// Calculate intersection of capabilities.
|
|
2005
|
+
var commonCapabilities = self._getCommonCapabilities(transceiver.localCapabilities, transceiver.remoteCapabilities);
|
|
2006
|
+
|
|
2007
|
+
sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, 'answer', self.localStreams[0]);
|
|
2008
|
+
});
|
|
2009
|
+
|
|
2010
|
+
var desc = new RTCSessionDescription({
|
|
2011
|
+
type: 'answer',
|
|
2012
|
+
sdp: sdp
|
|
2013
|
+
});
|
|
2014
|
+
if (arguments.length && typeof arguments[0] === 'function') {
|
|
2015
|
+
window.setTimeout(arguments[0], 0, desc);
|
|
2016
|
+
}
|
|
2017
|
+
return Promise.resolve(desc);
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
window.RTCPeerConnection.prototype.addIceCandidate = function (candidate) {
|
|
2021
|
+
if (!candidate) {
|
|
2022
|
+
this.transceivers.forEach(function (transceiver) {
|
|
2023
|
+
transceiver.iceTransport.addRemoteCandidate({});
|
|
2024
|
+
});
|
|
2025
|
+
} else {
|
|
2026
|
+
var mLineIndex = candidate.sdpMLineIndex;
|
|
2027
|
+
if (candidate.sdpMid) {
|
|
2028
|
+
for (var i = 0; i < this.transceivers.length; i++) {
|
|
2029
|
+
if (this.transceivers[i].mid === candidate.sdpMid) {
|
|
2030
|
+
mLineIndex = i;
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
var transceiver = this.transceivers[mLineIndex];
|
|
2036
|
+
if (transceiver) {
|
|
2037
|
+
var cand = Object.keys(candidate.candidate).length > 0 ? SDPUtils.parseCandidate(candidate.candidate) : {};
|
|
2038
|
+
// Ignore Chrome's invalid candidates since Edge does not like them.
|
|
2039
|
+
if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
// Ignore RTCP candidates, we assume RTCP-MUX.
|
|
2043
|
+
if (cand.component !== '1') {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
// A dirty hack to make samples work.
|
|
2047
|
+
if (cand.type === 'endOfCandidates') {
|
|
2048
|
+
cand = {};
|
|
2049
|
+
}
|
|
2050
|
+
transceiver.iceTransport.addRemoteCandidate(cand);
|
|
2051
|
+
|
|
2052
|
+
// update the remoteDescription.
|
|
2053
|
+
var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
|
|
2054
|
+
sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() : 'a=end-of-candidates') + '\r\n';
|
|
2055
|
+
this.remoteDescription.sdp = sections.join('');
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (arguments.length > 1 && typeof arguments[1] === 'function') {
|
|
2059
|
+
window.setTimeout(arguments[1], 0);
|
|
2060
|
+
}
|
|
2061
|
+
return Promise.resolve();
|
|
2062
|
+
};
|
|
2063
|
+
|
|
2064
|
+
window.RTCPeerConnection.prototype.getStats = function () {
|
|
2065
|
+
var promises = [];
|
|
2066
|
+
this.transceivers.forEach(function (transceiver) {
|
|
2067
|
+
['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', 'dtlsTransport'].forEach(function (method) {
|
|
2068
|
+
if (transceiver[method]) {
|
|
2069
|
+
promises.push(transceiver[method].getStats());
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
});
|
|
2073
|
+
var cb = arguments.length > 1 && typeof arguments[1] === 'function' && arguments[1];
|
|
2074
|
+
return new Promise(function (resolve) {
|
|
2075
|
+
// shim getStats with maplike support
|
|
2076
|
+
var results = new Map();
|
|
2077
|
+
Promise.all(promises).then(function (res) {
|
|
2078
|
+
res.forEach(function (result) {
|
|
2079
|
+
Object.keys(result).forEach(function (id) {
|
|
2080
|
+
results.set(id, result[id]);
|
|
2081
|
+
results[id] = result[id];
|
|
2082
|
+
});
|
|
2083
|
+
});
|
|
2084
|
+
if (cb) {
|
|
2085
|
+
window.setTimeout(cb, 0, results);
|
|
2086
|
+
}
|
|
2087
|
+
resolve(results);
|
|
2088
|
+
});
|
|
2089
|
+
});
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
|
|
2094
|
+
// Expose public methods.
|
|
2095
|
+
module.exports = {
|
|
2096
|
+
shimPeerConnection: edgeShim.shimPeerConnection,
|
|
2097
|
+
shimGetUserMedia: require('./getusermedia')
|
|
2098
|
+
};
|
|
2099
|
+
}, { "../utils": 10, "./getusermedia": 6, "sdp": 1 }], 6: [function (require, module, exports) {
|
|
2100
|
+
/*
|
|
2101
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
2102
|
+
*
|
|
2103
|
+
* Use of this source code is governed by a BSD-style license
|
|
2104
|
+
* that can be found in the LICENSE file in the root of the source
|
|
2105
|
+
* tree.
|
|
2106
|
+
*/
|
|
2107
|
+
/* eslint-env node */
|
|
2108
|
+
'use strict';
|
|
2109
|
+
|
|
2110
|
+
// Expose public methods.
|
|
2111
|
+
|
|
2112
|
+
module.exports = function () {
|
|
2113
|
+
var shimError_ = function shimError_(e) {
|
|
2114
|
+
return {
|
|
2115
|
+
name: { PermissionDeniedError: 'NotAllowedError' }[e.name] || e.name,
|
|
2116
|
+
message: e.message,
|
|
2117
|
+
constraint: e.constraint,
|
|
2118
|
+
toString: function toString() {
|
|
2119
|
+
return this.name;
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
};
|
|
2123
|
+
|
|
2124
|
+
// getUserMedia error shim.
|
|
2125
|
+
var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
2126
|
+
navigator.mediaDevices.getUserMedia = function (c) {
|
|
2127
|
+
return origGetUserMedia(c).catch(function (e) {
|
|
2128
|
+
return Promise.reject(shimError_(e));
|
|
2129
|
+
});
|
|
2130
|
+
};
|
|
2131
|
+
};
|
|
2132
|
+
}, {}], 7: [function (require, module, exports) {
|
|
2133
|
+
/*
|
|
2134
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
2135
|
+
*
|
|
2136
|
+
* Use of this source code is governed by a BSD-style license
|
|
2137
|
+
* that can be found in the LICENSE file in the root of the source
|
|
2138
|
+
* tree.
|
|
2139
|
+
*/
|
|
2140
|
+
/* eslint-env node */
|
|
2141
|
+
'use strict';
|
|
2142
|
+
|
|
2143
|
+
var browserDetails = require('../utils').browserDetails;
|
|
2144
|
+
|
|
2145
|
+
var firefoxShim = {
|
|
2146
|
+
shimOnTrack: function shimOnTrack() {
|
|
2147
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) {
|
|
2148
|
+
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
|
|
2149
|
+
get: function get() {
|
|
2150
|
+
return this._ontrack;
|
|
2151
|
+
},
|
|
2152
|
+
set: function set(f) {
|
|
2153
|
+
if (this._ontrack) {
|
|
2154
|
+
this.removeEventListener('track', this._ontrack);
|
|
2155
|
+
this.removeEventListener('addstream', this._ontrackpoly);
|
|
2156
|
+
}
|
|
2157
|
+
this.addEventListener('track', this._ontrack = f);
|
|
2158
|
+
this.addEventListener('addstream', this._ontrackpoly = function (e) {
|
|
2159
|
+
e.stream.getTracks().forEach(function (track) {
|
|
2160
|
+
var event = new Event('track');
|
|
2161
|
+
event.track = track;
|
|
2162
|
+
event.receiver = { track: track };
|
|
2163
|
+
event.streams = [e.stream];
|
|
2164
|
+
this.dispatchEvent(event);
|
|
2165
|
+
}.bind(this));
|
|
2166
|
+
}.bind(this));
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
},
|
|
2171
|
+
|
|
2172
|
+
shimSourceObject: function shimSourceObject() {
|
|
2173
|
+
// Firefox has supported mozSrcObject since FF22, unprefixed in 42.
|
|
2174
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
|
|
2175
|
+
if (window.HTMLMediaElement && !('srcObject' in window.HTMLMediaElement.prototype)) {
|
|
2176
|
+
// Shim the srcObject property, once, when HTMLMediaElement is found.
|
|
2177
|
+
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
|
|
2178
|
+
get: function get() {
|
|
2179
|
+
return this.mozSrcObject;
|
|
2180
|
+
},
|
|
2181
|
+
set: function set(stream) {
|
|
2182
|
+
this.mozSrcObject = stream;
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2188
|
+
|
|
2189
|
+
shimPeerConnection: function shimPeerConnection() {
|
|
2190
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
|
|
2191
|
+
return; // probably media.peerconnection.enabled=false in about:config
|
|
2192
|
+
}
|
|
2193
|
+
// The RTCPeerConnection object.
|
|
2194
|
+
if (!window.RTCPeerConnection) {
|
|
2195
|
+
window.RTCPeerConnection = function (pcConfig, pcConstraints) {
|
|
2196
|
+
if (browserDetails.version < 38) {
|
|
2197
|
+
// .urls is not supported in FF < 38.
|
|
2198
|
+
// create RTCIceServers with a single url.
|
|
2199
|
+
if (pcConfig && pcConfig.iceServers) {
|
|
2200
|
+
var newIceServers = [];
|
|
2201
|
+
for (var i = 0; i < pcConfig.iceServers.length; i++) {
|
|
2202
|
+
var server = pcConfig.iceServers[i];
|
|
2203
|
+
if (server.hasOwnProperty('urls')) {
|
|
2204
|
+
for (var j = 0; j < server.urls.length; j++) {
|
|
2205
|
+
var newServer = {
|
|
2206
|
+
url: server.urls[j]
|
|
2207
|
+
};
|
|
2208
|
+
if (server.urls[j].indexOf('turn') === 0) {
|
|
2209
|
+
newServer.username = server.username;
|
|
2210
|
+
newServer.credential = server.credential;
|
|
2211
|
+
}
|
|
2212
|
+
newIceServers.push(newServer);
|
|
2213
|
+
}
|
|
2214
|
+
} else {
|
|
2215
|
+
newIceServers.push(pcConfig.iceServers[i]);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
pcConfig.iceServers = newIceServers;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return new mozRTCPeerConnection(pcConfig, pcConstraints);
|
|
2222
|
+
};
|
|
2223
|
+
window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
|
|
2224
|
+
|
|
2225
|
+
// wrap static methods. Currently just generateCertificate.
|
|
2226
|
+
if (mozRTCPeerConnection.generateCertificate) {
|
|
2227
|
+
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
|
|
2228
|
+
get: function get() {
|
|
2229
|
+
return mozRTCPeerConnection.generateCertificate;
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
window.RTCSessionDescription = mozRTCSessionDescription;
|
|
2235
|
+
window.RTCIceCandidate = mozRTCIceCandidate;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
|
|
2239
|
+
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) {
|
|
2240
|
+
var nativeMethod = RTCPeerConnection.prototype[method];
|
|
2241
|
+
RTCPeerConnection.prototype[method] = function () {
|
|
2242
|
+
arguments[0] = new (method === 'addIceCandidate' ? RTCIceCandidate : RTCSessionDescription)(arguments[0]);
|
|
2243
|
+
return nativeMethod.apply(this, arguments);
|
|
2244
|
+
};
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
// support for addIceCandidate(null or undefined)
|
|
2248
|
+
var nativeAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate;
|
|
2249
|
+
RTCPeerConnection.prototype.addIceCandidate = function () {
|
|
2250
|
+
if (!arguments[0]) {
|
|
2251
|
+
if (arguments[1]) {
|
|
2252
|
+
arguments[1].apply(null);
|
|
2253
|
+
}
|
|
2254
|
+
return Promise.resolve();
|
|
2255
|
+
}
|
|
2256
|
+
return nativeAddIceCandidate.apply(this, arguments);
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
if (browserDetails.version < 48) {
|
|
2260
|
+
// shim getStats with maplike support
|
|
2261
|
+
var makeMapStats = function makeMapStats(stats) {
|
|
2262
|
+
var map = new Map();
|
|
2263
|
+
Object.keys(stats).forEach(function (key) {
|
|
2264
|
+
map.set(key, stats[key]);
|
|
2265
|
+
map[key] = stats[key];
|
|
2266
|
+
});
|
|
2267
|
+
return map;
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2270
|
+
var nativeGetStats = RTCPeerConnection.prototype.getStats;
|
|
2271
|
+
RTCPeerConnection.prototype.getStats = function (selector, onSucc, onErr) {
|
|
2272
|
+
return nativeGetStats.apply(this, [selector || null]).then(function (stats) {
|
|
2273
|
+
return makeMapStats(stats);
|
|
2274
|
+
}).then(onSucc, onErr);
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// Expose public methods.
|
|
2281
|
+
module.exports = {
|
|
2282
|
+
shimOnTrack: firefoxShim.shimOnTrack,
|
|
2283
|
+
shimSourceObject: firefoxShim.shimSourceObject,
|
|
2284
|
+
shimPeerConnection: firefoxShim.shimPeerConnection,
|
|
2285
|
+
shimGetUserMedia: require('./getusermedia')
|
|
2286
|
+
};
|
|
2287
|
+
}, { "../utils": 10, "./getusermedia": 8 }], 8: [function (require, module, exports) {
|
|
2288
|
+
/*
|
|
2289
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
2290
|
+
*
|
|
2291
|
+
* Use of this source code is governed by a BSD-style license
|
|
2292
|
+
* that can be found in the LICENSE file in the root of the source
|
|
2293
|
+
* tree.
|
|
2294
|
+
*/
|
|
2295
|
+
/* eslint-env node */
|
|
2296
|
+
'use strict';
|
|
2297
|
+
|
|
2298
|
+
var logging = require('../utils').log;
|
|
2299
|
+
var browserDetails = require('../utils').browserDetails;
|
|
2300
|
+
|
|
2301
|
+
// Expose public methods.
|
|
2302
|
+
module.exports = function () {
|
|
2303
|
+
var shimError_ = function shimError_(e) {
|
|
2304
|
+
return {
|
|
2305
|
+
name: {
|
|
2306
|
+
SecurityError: 'NotAllowedError',
|
|
2307
|
+
PermissionDeniedError: 'NotAllowedError'
|
|
2308
|
+
}[e.name] || e.name,
|
|
2309
|
+
message: {
|
|
2310
|
+
'The operation is insecure.': 'The request is not allowed by the ' + 'user agent or the platform in the current context.'
|
|
2311
|
+
}[e.message] || e.message,
|
|
2312
|
+
constraint: e.constraint,
|
|
2313
|
+
toString: function toString() {
|
|
2314
|
+
return this.name + (this.message && ': ') + this.message;
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
// getUserMedia constraints shim.
|
|
2320
|
+
var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) {
|
|
2321
|
+
var constraintsToFF37_ = function constraintsToFF37_(c) {
|
|
2322
|
+
if ((typeof c === "undefined" ? "undefined" : _typeof(c)) !== 'object' || c.require) {
|
|
2323
|
+
return c;
|
|
2324
|
+
}
|
|
2325
|
+
var require = [];
|
|
2326
|
+
Object.keys(c).forEach(function (key) {
|
|
2327
|
+
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
var r = c[key] = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] };
|
|
2331
|
+
if (r.min !== undefined || r.max !== undefined || r.exact !== undefined) {
|
|
2332
|
+
require.push(key);
|
|
2333
|
+
}
|
|
2334
|
+
if (r.exact !== undefined) {
|
|
2335
|
+
if (typeof r.exact === 'number') {
|
|
2336
|
+
r.min = r.max = r.exact;
|
|
2337
|
+
} else {
|
|
2338
|
+
c[key] = r.exact;
|
|
2339
|
+
}
|
|
2340
|
+
delete r.exact;
|
|
2341
|
+
}
|
|
2342
|
+
if (r.ideal !== undefined) {
|
|
2343
|
+
c.advanced = c.advanced || [];
|
|
2344
|
+
var oc = {};
|
|
2345
|
+
if (typeof r.ideal === 'number') {
|
|
2346
|
+
oc[key] = { min: r.ideal, max: r.ideal };
|
|
2347
|
+
} else {
|
|
2348
|
+
oc[key] = r.ideal;
|
|
2349
|
+
}
|
|
2350
|
+
c.advanced.push(oc);
|
|
2351
|
+
delete r.ideal;
|
|
2352
|
+
if (!Object.keys(r).length) {
|
|
2353
|
+
delete c[key];
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
if (require.length) {
|
|
2358
|
+
c.require = require;
|
|
2359
|
+
}
|
|
2360
|
+
return c;
|
|
2361
|
+
};
|
|
2362
|
+
constraints = JSON.parse(JSON.stringify(constraints));
|
|
2363
|
+
if (browserDetails.version < 38) {
|
|
2364
|
+
logging('spec: ' + JSON.stringify(constraints));
|
|
2365
|
+
if (constraints.audio) {
|
|
2366
|
+
constraints.audio = constraintsToFF37_(constraints.audio);
|
|
2367
|
+
}
|
|
2368
|
+
if (constraints.video) {
|
|
2369
|
+
constraints.video = constraintsToFF37_(constraints.video);
|
|
2370
|
+
}
|
|
2371
|
+
logging('ff37: ' + JSON.stringify(constraints));
|
|
2372
|
+
}
|
|
2373
|
+
return navigator.mozGetUserMedia(constraints, onSuccess, function (e) {
|
|
2374
|
+
onError(shimError_(e));
|
|
2375
|
+
});
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
// Returns the result of getUserMedia as a Promise.
|
|
2379
|
+
var getUserMediaPromise_ = function getUserMediaPromise_(constraints) {
|
|
2380
|
+
return new Promise(function (resolve, reject) {
|
|
2381
|
+
getUserMedia_(constraints, resolve, reject);
|
|
2382
|
+
});
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
// Shim for mediaDevices on older versions.
|
|
2386
|
+
if (!navigator.mediaDevices) {
|
|
2387
|
+
navigator.mediaDevices = { getUserMedia: getUserMediaPromise_,
|
|
2388
|
+
addEventListener: function addEventListener() {},
|
|
2389
|
+
removeEventListener: function removeEventListener() {}
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
navigator.mediaDevices.enumerateDevices = navigator.mediaDevices.enumerateDevices || function () {
|
|
2393
|
+
return new Promise(function (resolve) {
|
|
2394
|
+
var infos = [{ kind: 'audioinput', deviceId: 'default', label: '', groupId: '' }, { kind: 'videoinput', deviceId: 'default', label: '', groupId: '' }];
|
|
2395
|
+
resolve(infos);
|
|
2396
|
+
});
|
|
2397
|
+
};
|
|
2398
|
+
|
|
2399
|
+
if (browserDetails.version < 41) {
|
|
2400
|
+
// Work around http://bugzil.la/1169665
|
|
2401
|
+
var orgEnumerateDevices = navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
|
|
2402
|
+
navigator.mediaDevices.enumerateDevices = function () {
|
|
2403
|
+
return orgEnumerateDevices().then(undefined, function (e) {
|
|
2404
|
+
if (e.name === 'NotFoundError') {
|
|
2405
|
+
return [];
|
|
2406
|
+
}
|
|
2407
|
+
throw e;
|
|
2408
|
+
});
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
if (browserDetails.version < 49) {
|
|
2412
|
+
var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
2413
|
+
navigator.mediaDevices.getUserMedia = function (c) {
|
|
2414
|
+
return origGetUserMedia(c).then(function (stream) {
|
|
2415
|
+
// Work around https://bugzil.la/802326
|
|
2416
|
+
if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) {
|
|
2417
|
+
stream.getTracks().forEach(function (track) {
|
|
2418
|
+
track.stop();
|
|
2419
|
+
});
|
|
2420
|
+
throw new DOMException('The object can not be found here.', 'NotFoundError');
|
|
2421
|
+
}
|
|
2422
|
+
return stream;
|
|
2423
|
+
}, function (e) {
|
|
2424
|
+
return Promise.reject(shimError_(e));
|
|
2425
|
+
});
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
navigator.getUserMedia = function (constraints, onSuccess, onError) {
|
|
2429
|
+
if (browserDetails.version < 44) {
|
|
2430
|
+
return getUserMedia_(constraints, onSuccess, onError);
|
|
2431
|
+
}
|
|
2432
|
+
// Replace Firefox 44+'s deprecation warning with unprefixed version.
|
|
2433
|
+
console.warn('navigator.getUserMedia has been replaced by ' + 'navigator.mediaDevices.getUserMedia');
|
|
2434
|
+
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
|
|
2435
|
+
};
|
|
2436
|
+
};
|
|
2437
|
+
}, { "../utils": 10 }], 9: [function (require, module, exports) {
|
|
2438
|
+
/*
|
|
2439
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
2440
|
+
*
|
|
2441
|
+
* Use of this source code is governed by a BSD-style license
|
|
2442
|
+
* that can be found in the LICENSE file in the root of the source
|
|
2443
|
+
* tree.
|
|
2444
|
+
*/
|
|
2445
|
+
'use strict';
|
|
2446
|
+
|
|
2447
|
+
var safariShim = {
|
|
2448
|
+
// TODO: DrAlex, should be here, double check against LayoutTests
|
|
2449
|
+
// shimOnTrack: function() { },
|
|
2450
|
+
|
|
2451
|
+
// TODO: once the back-end for the mac port is done, add.
|
|
2452
|
+
// TODO: check for webkitGTK+
|
|
2453
|
+
// shimPeerConnection: function() { },
|
|
2454
|
+
|
|
2455
|
+
shimGetUserMedia: function shimGetUserMedia() {
|
|
2456
|
+
navigator.getUserMedia = navigator.webkitGetUserMedia;
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
|
|
2460
|
+
// Expose public methods.
|
|
2461
|
+
module.exports = {
|
|
2462
|
+
shimGetUserMedia: safariShim.shimGetUserMedia
|
|
2463
|
+
// TODO
|
|
2464
|
+
// shimOnTrack: safariShim.shimOnTrack,
|
|
2465
|
+
// shimPeerConnection: safariShim.shimPeerConnection
|
|
2466
|
+
};
|
|
2467
|
+
}, {}], 10: [function (require, module, exports) {
|
|
2468
|
+
/*
|
|
2469
|
+
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
2470
|
+
*
|
|
2471
|
+
* Use of this source code is governed by a BSD-style license
|
|
2472
|
+
* that can be found in the LICENSE file in the root of the source
|
|
2473
|
+
* tree.
|
|
2474
|
+
*/
|
|
2475
|
+
/* eslint-env node */
|
|
2476
|
+
'use strict';
|
|
2477
|
+
|
|
2478
|
+
var logDisabled_ = true;
|
|
2479
|
+
|
|
2480
|
+
// Utility methods.
|
|
2481
|
+
var utils = {
|
|
2482
|
+
disableLog: function disableLog(bool) {
|
|
2483
|
+
if (typeof bool !== 'boolean') {
|
|
2484
|
+
return new Error('Argument type: ' + (typeof bool === "undefined" ? "undefined" : _typeof(bool)) + '. Please use a boolean.');
|
|
2485
|
+
}
|
|
2486
|
+
logDisabled_ = bool;
|
|
2487
|
+
return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled';
|
|
2488
|
+
},
|
|
2489
|
+
|
|
2490
|
+
log: function log() {
|
|
2491
|
+
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
|
|
2492
|
+
if (logDisabled_) {
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
if (typeof console !== 'undefined' && typeof console.log === 'function') {
|
|
2496
|
+
console.log.apply(console, arguments);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
},
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* Extract browser version out of the provided user agent string.
|
|
2503
|
+
*
|
|
2504
|
+
* @param {!string} uastring userAgent string.
|
|
2505
|
+
* @param {!string} expr Regular expression used as match criteria.
|
|
2506
|
+
* @param {!number} pos position in the version string to be returned.
|
|
2507
|
+
* @return {!number} browser version.
|
|
2508
|
+
*/
|
|
2509
|
+
extractVersion: function extractVersion(uastring, expr, pos) {
|
|
2510
|
+
var match = uastring.match(expr);
|
|
2511
|
+
return match && match.length >= pos && parseInt(match[pos], 10);
|
|
2512
|
+
},
|
|
2513
|
+
|
|
2514
|
+
/**
|
|
2515
|
+
* Browser detector.
|
|
2516
|
+
*
|
|
2517
|
+
* @return {object} result containing browser and version
|
|
2518
|
+
* properties.
|
|
2519
|
+
*/
|
|
2520
|
+
detectBrowser: function detectBrowser() {
|
|
2521
|
+
// Returned result object.
|
|
2522
|
+
var result = {};
|
|
2523
|
+
result.browser = null;
|
|
2524
|
+
result.version = null;
|
|
2525
|
+
|
|
2526
|
+
// Fail early if it's not a browser
|
|
2527
|
+
if (typeof window === 'undefined' || !window.navigator) {
|
|
2528
|
+
result.browser = 'Not a browser.';
|
|
2529
|
+
return result;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
// Firefox.
|
|
2533
|
+
if (navigator.mozGetUserMedia) {
|
|
2534
|
+
result.browser = 'firefox';
|
|
2535
|
+
result.version = this.extractVersion(navigator.userAgent, /Firefox\/([0-9]+)\./, 1);
|
|
2536
|
+
|
|
2537
|
+
// all webkit-based browsers
|
|
2538
|
+
} else if (navigator.webkitGetUserMedia) {
|
|
2539
|
+
// Chrome, Chromium, Webview, Opera, all use the chrome shim for now
|
|
2540
|
+
if (window.webkitRTCPeerConnection) {
|
|
2541
|
+
result.browser = 'chrome';
|
|
2542
|
+
result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/([0-9]+)\./, 2);
|
|
2543
|
+
|
|
2544
|
+
// Safari or unknown webkit-based
|
|
2545
|
+
// for the time being Safari has support for MediaStreams but not webRTC
|
|
2546
|
+
} else {
|
|
2547
|
+
// Safari UA substrings of interest for reference:
|
|
2548
|
+
// - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr)
|
|
2549
|
+
// - safari UI version: Version/9.0.3 (unique to Safari)
|
|
2550
|
+
// - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr)
|
|
2551
|
+
//
|
|
2552
|
+
// if the webkit version and safari UI webkit versions are equals,
|
|
2553
|
+
// ... this is a stable version.
|
|
2554
|
+
//
|
|
2555
|
+
// only the internal webkit version is important today to know if
|
|
2556
|
+
// media streams are supported
|
|
2557
|
+
//
|
|
2558
|
+
if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
|
|
2559
|
+
result.browser = 'safari';
|
|
2560
|
+
result.version = this.extractVersion(navigator.userAgent, /AppleWebKit\/([0-9]+)\./, 1);
|
|
2561
|
+
|
|
2562
|
+
// unknown webkit-based browser
|
|
2563
|
+
} else {
|
|
2564
|
+
result.browser = 'Unsupported webkit-based browser ' + 'with GUM support but no WebRTC support.';
|
|
2565
|
+
return result;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// Edge.
|
|
2570
|
+
} else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
|
|
2571
|
+
result.browser = 'edge';
|
|
2572
|
+
result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2);
|
|
2573
|
+
|
|
2574
|
+
// Default fallthrough: not supported.
|
|
2575
|
+
} else {
|
|
2576
|
+
result.browser = 'Not a supported browser.';
|
|
2577
|
+
return result;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
return result;
|
|
2581
|
+
}
|
|
2582
|
+
};
|
|
2583
|
+
|
|
2584
|
+
// Export.
|
|
2585
|
+
module.exports = {
|
|
2586
|
+
log: utils.log,
|
|
2587
|
+
disableLog: utils.disableLog,
|
|
2588
|
+
browserDetails: utils.detectBrowser(),
|
|
2589
|
+
extractVersion: utils.extractVersion
|
|
2590
|
+
};
|
|
2591
|
+
}, {}] }, {}, [2])(2);
|
|
2592
|
+
});
|