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