Browse code

Created repository.

DoubleBastionAdmin authored on 26/01/2022 20:32:42
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2004 @@
1
+/**
2
+ *  Copyright (C) 2021  Double Bastion LLC
3
+ *
4
+ *  This file is part of Roundpin, which is licensed under the
5
+ *  GNU Affero General Public License Version 3.0. The license terms
6
+ *  are detailed in the "LICENSE.txt" file located in the root directory.
7
+ *
8
+ *  The file content from below is identical with that of the
9
+ *  original file "sdp-interop-sl-1.4.0.js". The copyright
10
+ *  notice for the original content follows:
11
+ *
12
+ *  Copyright @ 2015 Atlassian Pty Ltd
13
+ *  License: Apache License Version 2.0
14
+ */
15
+
16
+(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.SdpInterop = 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){
17
+var grammar = module.exports = {
18
+  v: [{
19
+    name: 'version',
20
+    reg: /^(\d*)$/
21
+  }],
22
+  o: [{ //o=- 20518 0 IN IP4 203.0.113.1
23
+    // NB: sessionId will be a String in most cases because it is huge
24
+    name: 'origin',
25
+    reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
26
+    names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
27
+    format: '%s %s %d %s IP%d %s'
28
+  }],
29
+  // default parsing of these only (though some of these feel outdated)
30
+  s: [{ name: 'name' }],
31
+  i: [{ name: 'description' }],
32
+  u: [{ name: 'uri' }],
33
+  e: [{ name: 'email' }],
34
+  p: [{ name: 'phone' }],
35
+  z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
36
+  r: [{ name: 'repeats' }],   // TODO: this one can also be parsed properly
37
+  //k: [{}], // outdated thing ignored
38
+  t: [{ //t=0 0
39
+    name: 'timing',
40
+    reg: /^(\d*) (\d*)/,
41
+    names: ['start', 'stop'],
42
+    format: '%d %d'
43
+  }],
44
+  c: [{ //c=IN IP4 10.47.197.26
45
+    name: 'connection',
46
+    reg: /^IN IP(\d) (\S*)/,
47
+    names: ['version', 'ip'],
48
+    format: 'IN IP%d %s'
49
+  }],
50
+  b: [{ //b=AS:4000
51
+    push: 'bandwidth',
52
+    reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
53
+    names: ['type', 'limit'],
54
+    format: '%s:%s'
55
+  }],
56
+  m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
57
+    // NB: special - pushes to session
58
+    // TODO: rtp/fmtp should be filtered by the payloads found here?
59
+    reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
60
+    names: ['type', 'port', 'protocol', 'payloads'],
61
+    format: '%s %d %s %s'
62
+  }],
63
+  a: [
64
+    { //a=rtpmap:110 opus/48000/2
65
+      push: 'rtp',
66
+      reg: /^rtpmap:(\d*) ([\w\-\.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
67
+      names: ['payload', 'codec', 'rate', 'encoding'],
68
+      format: function (o) {
69
+        return (o.encoding) ?
70
+          'rtpmap:%d %s/%s/%s':
71
+          o.rate ?
72
+          'rtpmap:%d %s/%s':
73
+          'rtpmap:%d %s';
74
+      }
75
+    },
76
+    { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
77
+      //a=fmtp:111 minptime=10; useinbandfec=1
78
+      push: 'fmtp',
79
+      reg: /^fmtp:(\d*) ([\S| ]*)/,
80
+      names: ['payload', 'config'],
81
+      format: 'fmtp:%d %s'
82
+    },
83
+    { //a=control:streamid=0
84
+      name: 'control',
85
+      reg: /^control:(.*)/,
86
+      format: 'control:%s'
87
+    },
88
+    { //a=rtcp:65179 IN IP4 193.84.77.194
89
+      name: 'rtcp',
90
+      reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
91
+      names: ['port', 'netType', 'ipVer', 'address'],
92
+      format: function (o) {
93
+        return (o.address != null) ?
94
+          'rtcp:%d %s IP%d %s':
95
+          'rtcp:%d';
96
+      }
97
+    },
98
+    { //a=rtcp-fb:98 trr-int 100
99
+      push: 'rtcpFbTrrInt',
100
+      reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
101
+      names: ['payload', 'value'],
102
+      format: 'rtcp-fb:%d trr-int %d'
103
+    },
104
+    { //a=rtcp-fb:98 nack rpsi
105
+      push: 'rtcpFb',
106
+      reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
107
+      names: ['payload', 'type', 'subtype'],
108
+      format: function (o) {
109
+        return (o.subtype != null) ?
110
+          'rtcp-fb:%s %s %s':
111
+          'rtcp-fb:%s %s';
112
+      }
113
+    },
114
+    { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
115
+      //a=extmap:1/recvonly URI-gps-string
116
+      push: 'ext',
117
+      reg: /^extmap:(\d+)(?:\/(\w+))? (\S*)(?: (\S*))?/,
118
+      names: ['value', 'direction', 'uri', 'config'],
119
+      format: function (o) {
120
+        return 'extmap:%d' + (o.direction ? '/%s' : '%v') + ' %s' + (o.config ? ' %s' : '');
121
+      }
122
+    },
123
+    { //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
124
+      push: 'crypto',
125
+      reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
126
+      names: ['id', 'suite', 'config', 'sessionConfig'],
127
+      format: function (o) {
128
+        return (o.sessionConfig != null) ?
129
+          'crypto:%d %s %s %s':
130
+          'crypto:%d %s %s';
131
+      }
132
+    },
133
+    { //a=setup:actpass
134
+      name: 'setup',
135
+      reg: /^setup:(\w*)/,
136
+      format: 'setup:%s'
137
+    },
138
+    { //a=mid:1
139
+      name: 'mid',
140
+      reg: /^mid:([^\s]*)/,
141
+      format: 'mid:%s'
142
+    },
143
+    { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
144
+      name: 'msid',
145
+      reg: /^msid:(.*)/,
146
+      format: 'msid:%s'
147
+    },
148
+    { //a=ptime:20
149
+      name: 'ptime',
150
+      reg: /^ptime:(\d*)/,
151
+      format: 'ptime:%d'
152
+    },
153
+    { //a=maxptime:60
154
+      name: 'maxptime',
155
+      reg: /^maxptime:(\d*)/,
156
+      format: 'maxptime:%d'
157
+    },
158
+    { //a=sendrecv
159
+      name: 'direction',
160
+      reg: /^(sendrecv|recvonly|sendonly|inactive)/
161
+    },
162
+    { //a=ice-lite
163
+      name: 'icelite',
164
+      reg: /^(ice-lite)/
165
+    },
166
+    { //a=ice-ufrag:F7gI
167
+      name: 'iceUfrag',
168
+      reg: /^ice-ufrag:(\S*)/,
169
+      format: 'ice-ufrag:%s'
170
+    },
171
+    { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
172
+      name: 'icePwd',
173
+      reg: /^ice-pwd:(\S*)/,
174
+      format: 'ice-pwd:%s'
175
+    },
176
+    { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
177
+      name: 'fingerprint',
178
+      reg: /^fingerprint:(\S*) (\S*)/,
179
+      names: ['type', 'hash'],
180
+      format: 'fingerprint:%s %s'
181
+    },
182
+    { //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
183
+      //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10
184
+      //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10
185
+      //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10
186
+      //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10
187
+      push:'candidates',
188
+      reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/,
189
+      names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],
190
+      format: function (o) {
191
+        var str = 'candidate:%s %d %s %d %s %d typ %s';
192
+
193
+        str += (o.raddr != null) ? ' raddr %s rport %d' : '%v%v';
194
+
195
+        // NB: candidate has three optional chunks, so %void middles one if it's missing
196
+        str += (o.tcptype != null) ? ' tcptype %s' : '%v';
197
+
198
+        if (o.generation != null) {
199
+          str += ' generation %d';
200
+        }
201
+
202
+        str += (o['network-id'] != null) ? ' network-id %d' : '%v';
203
+        str += (o['network-cost'] != null) ? ' network-cost %d' : '%v';
204
+        return str;
205
+      }
206
+    },
207
+    { //a=end-of-candidates (keep after the candidates line for readability)
208
+      name: 'endOfCandidates',
209
+      reg: /^(end-of-candidates)/
210
+    },
211
+    { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
212
+      name: 'remoteCandidates',
213
+      reg: /^remote-candidates:(.*)/,
214
+      format: 'remote-candidates:%s'
215
+    },
216
+    { //a=ice-options:google-ice
217
+      name: 'iceOptions',
218
+      reg: /^ice-options:(\S*)/,
219
+      format: 'ice-options:%s'
220
+    },
221
+    { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
222
+      push: 'ssrcs',
223
+      reg: /^ssrc:(\d*) ([\w_]*)(?::(.*))?/,
224
+      names: ['id', 'attribute', 'value'],
225
+      format: function (o) {
226
+        var str = 'ssrc:%d';
227
+        if (o.attribute != null) {
228
+          str += ' %s';
229
+          if (o.value != null) {
230
+            str += ':%s';
231
+          }
232
+        }
233
+        return str;
234
+      }
235
+    },
236
+    { //a=ssrc-group:FEC 1 2
237
+      //a=ssrc-group:FEC-FR 3004364195 1080772241
238
+      push: 'ssrcGroups',
239
+      // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
240
+      reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/,
241
+      names: ['semantics', 'ssrcs'],
242
+      format: 'ssrc-group:%s %s'
243
+    },
244
+    { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
245
+      name: 'msidSemantic',
246
+      reg: /^msid-semantic:\s?(\w*) (\S*)/,
247
+      names: ['semantic', 'token'],
248
+      format: 'msid-semantic: %s %s' // space after ':' is not accidental
249
+    },
250
+    { //a=group:BUNDLE audio video
251
+      push: 'groups',
252
+      reg: /^group:(\w*) (.*)/,
253
+      names: ['type', 'mids'],
254
+      format: 'group:%s %s'
255
+    },
256
+    { //a=rtcp-mux
257
+      name: 'rtcpMux',
258
+      reg: /^(rtcp-mux)/
259
+    },
260
+    { //a=rtcp-rsize
261
+      name: 'rtcpRsize',
262
+      reg: /^(rtcp-rsize)/
263
+    },
264
+    { //a=sctpmap:5000 webrtc-datachannel 1024
265
+      name: 'sctpmap',
266
+      reg: /^sctpmap:([\w_\/]*) (\S*)(?: (\S*))?/,
267
+      names: ['sctpmapNumber', 'app', 'maxMessageSize'],
268
+      format: function (o) {
269
+        return (o.maxMessageSize != null) ?
270
+          'sctpmap:%s %s %s' :
271
+          'sctpmap:%s %s';
272
+      }
273
+    },
274
+    { //a=x-google-flag:conference
275
+      name: 'xGoogleFlag',
276
+      reg: /^x-google-flag:([^\s]*)/,
277
+      format: 'x-google-flag:%s'
278
+    },
279
+    { //a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0
280
+      push: 'rids',
281
+      reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/,
282
+      names: ['id', 'direction', 'params'],
283
+      format: function (o) {
284
+        return (o.params) ? 'rid:%s %s %s' : 'rid:%s %s';
285
+      }
286
+    },
287
+    { //a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]
288
+      //a=imageattr:* send [x=800,y=640] recv *
289
+      //a=imageattr:100 recv [x=320,y=240]
290
+      push: 'imageattrs',
291
+      reg: new RegExp(
292
+        //a=imageattr:97
293
+        '^imageattr:(\\d+|\\*)' +
294
+        //send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]
295
+        '[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' +
296
+        //recv [x=330,y=250]
297
+        '(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?'
298
+      ),
299
+      names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],
300
+      format: function (o) {
301
+        return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');
302
+      }
303
+    },
304
+    { //a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8
305
+      //a=simulcast:recv 1;4,5 send 6;7
306
+      name: 'simulcast',
307
+      reg: new RegExp(
308
+        //a=simulcast:
309
+        '^simulcast:' +
310
+        //send 1,2,3;~4,~5
311
+        '(send|recv) ([a-zA-Z0-9\\-_~;,]+)' +
312
+        //space + recv 6;~7,~8
313
+        '(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' +
314
+        //end
315
+        '$'
316
+      ),
317
+      names: ['dir1', 'list1', 'dir2', 'list2'],
318
+      format: function (o) {
319
+        return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');
320
+      }
321
+    },
322
+    { //Old simulcast draft 03 (implemented by Firefox)
323
+      //  https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03
324
+      //a=simulcast: recv pt=97;98 send pt=97
325
+      //a=simulcast: send rid=5;6;7 paused=6,7
326
+      name: 'simulcast_03',
327
+      reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/,
328
+      names: ['value'],
329
+      format: 'simulcast: %s'
330
+    },
331
+    {
332
+      //a=framerate:25
333
+      //a=framerate:29.97
334
+      name: 'framerate',
335
+      reg: /^framerate:(\d+(?:$|\.\d+))/,
336
+      format: 'framerate:%s'
337
+    },
338
+    { // any a= that we don't understand is kepts verbatim on media.invalid
339
+      push: 'invalid',
340
+      names: ['value']
341
+    }
342
+  ]
343
+};
344
+
345
+// set sensible defaults to avoid polluting the grammar with boring details
346
+Object.keys(grammar).forEach(function (key) {
347
+  var objs = grammar[key];
348
+  objs.forEach(function (obj) {
349
+    if (!obj.reg) {
350
+      obj.reg = /(.*)/;
351
+    }
352
+    if (!obj.format) {
353
+      obj.format = '%s';
354
+    }
355
+  });
356
+});
357
+
358
+},{}],2:[function(require,module,exports){
359
+var parser = require('./parser');
360
+var writer = require('./writer');
361
+
362
+exports.write = writer;
363
+exports.parse = parser.parse;
364
+exports.parseFmtpConfig = parser.parseFmtpConfig;
365
+exports.parseParams = parser.parseParams;
366
+exports.parsePayloads = parser.parsePayloads;
367
+exports.parseRemoteCandidates = parser.parseRemoteCandidates;
368
+exports.parseImageAttributes = parser.parseImageAttributes;
369
+exports.parseSimulcastStreamList = parser.parseSimulcastStreamList;
370
+
371
+},{"./parser":3,"./writer":4}],3:[function(require,module,exports){
372
+var toIntIfInt = function (v) {
373
+  return String(Number(v)) === v ? Number(v) : v;
374
+};
375
+
376
+var attachProperties = function (match, location, names, rawName) {
377
+  if (rawName && !names) {
378
+    location[rawName] = toIntIfInt(match[1]);
379
+  }
380
+  else {
381
+    for (var i = 0; i < names.length; i += 1) {
382
+      if (match[i+1] != null) {
383
+        location[names[i]] = toIntIfInt(match[i+1]);
384
+      }
385
+    }
386
+  }
387
+};
388
+
389
+var parseReg = function (obj, location, content) {
390
+  var needsBlank = obj.name && obj.names;
391
+  if (obj.push && !location[obj.push]) {
392
+    location[obj.push] = [];
393
+  }
394
+  else if (needsBlank && !location[obj.name]) {
395
+    location[obj.name] = {};
396
+  }
397
+  var keyLocation = obj.push ?
398
+    {} :  // blank object that will be pushed
399
+    needsBlank ? location[obj.name] : location; // otherwise, named location or root
400
+
401
+  attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
402
+
403
+  if (obj.push) {
404
+    location[obj.push].push(keyLocation);
405
+  }
406
+};
407
+
408
+var grammar = require('./grammar');
409
+var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
410
+
411
+exports.parse = function (sdp) {
412
+  var session = {}
413
+    , media = []
414
+    , location = session; // points at where properties go under (one of the above)
415
+
416
+  // parse lines we understand
417
+  sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
418
+    var type = l[0];
419
+    var content = l.slice(2);
420
+    if (type === 'm') {
421
+      media.push({rtp: [], fmtp: []});
422
+      location = media[media.length-1]; // point at latest media line
423
+    }
424
+
425
+    for (var j = 0; j < (grammar[type] || []).length; j += 1) {
426
+      var obj = grammar[type][j];
427
+      if (obj.reg.test(content)) {
428
+        return parseReg(obj, location, content);
429
+      }
430
+    }
431
+  });
432
+
433
+  session.media = media; // link it up
434
+  return session;
435
+};
436
+
437
+var paramReducer = function (acc, expr) {
438
+  var s = expr.split(/=(.+)/, 2);
439
+  if (s.length === 2) {
440
+    acc[s[0]] = toIntIfInt(s[1]);
441
+  }
442
+  return acc;
443
+};
444
+
445
+exports.parseParams = function (str) {
446
+  return str.split(/\;\s?/).reduce(paramReducer, {});
447
+};
448
+
449
+// For backward compatibility - alias will be removed in 3.0.0
450
+exports.parseFmtpConfig = exports.parseParams;
451
+
452
+exports.parsePayloads = function (str) {
453
+  return str.split(' ').map(Number);
454
+};
455
+
456
+exports.parseRemoteCandidates = function (str) {
457
+  var candidates = [];
458
+  var parts = str.split(' ').map(toIntIfInt);
459
+  for (var i = 0; i < parts.length; i += 3) {
460
+    candidates.push({
461
+      component: parts[i],
462
+      ip: parts[i + 1],
463
+      port: parts[i + 2]
464
+    });
465
+  }
466
+  return candidates;
467
+};
468
+
469
+exports.parseImageAttributes = function (str) {
470
+  return str.split(' ').map(function (item) {
471
+    return item.substring(1, item.length-1).split(',').reduce(paramReducer, {});
472
+  });
473
+};
474
+
475
+exports.parseSimulcastStreamList = function (str) {
476
+  return str.split(';').map(function (stream) {
477
+    return stream.split(',').map(function (format) {
478
+      var scid, paused = false;
479
+
480
+      if (format[0] !== '~') {
481
+        scid = toIntIfInt(format);
482
+      } else {
483
+        scid = toIntIfInt(format.substring(1, format.length));
484
+        paused = true;
485
+      }
486
+
487
+      return {
488
+        scid: scid,
489
+        paused: paused
490
+      };
491
+    });
492
+  });
493
+};
494
+
495
+},{"./grammar":1}],4:[function(require,module,exports){
496
+var grammar = require('./grammar');
497
+
498
+// customized util.format - discards excess arguments and can void middle ones
499
+var formatRegExp = /%[sdv%]/g;
500
+var format = function (formatStr) {
501
+  var i = 1;
502
+  var args = arguments;
503
+  var len = args.length;
504
+  return formatStr.replace(formatRegExp, function (x) {
505
+    if (i >= len) {
506
+      return x; // missing argument
507
+    }
508
+    var arg = args[i];
509
+    i += 1;
510
+    switch (x) {
511
+    case '%%':
512
+      return '%';
513
+    case '%s':
514
+      return String(arg);
515
+    case '%d':
516
+      return Number(arg);
517
+    case '%v':
518
+      return '';
519
+    }
520
+  });
521
+  // NB: we discard excess arguments - they are typically undefined from makeLine
522
+};
523
+
524
+var makeLine = function (type, obj, location) {
525
+  var str = obj.format instanceof Function ?
526
+    (obj.format(obj.push ? location : location[obj.name])) :
527
+    obj.format;
528
+
529
+  var args = [type + '=' + str];
530
+  if (obj.names) {
531
+    for (var i = 0; i < obj.names.length; i += 1) {
532
+      var n = obj.names[i];
533
+      if (obj.name) {
534
+        args.push(location[obj.name][n]);
535
+      }
536
+      else { // for mLine and push attributes
537
+        args.push(location[obj.names[i]]);
538
+      }
539
+    }
540
+  }
541
+  else {
542
+    args.push(location[obj.name]);
543
+  }
544
+  return format.apply(null, args);
545
+};
546
+
547
+// RFC specified order
548
+// TODO: extend this with all the rest
549
+var defaultOuterOrder = [
550
+  'v', 'o', 's', 'i',
551
+  'u', 'e', 'p', 'c',
552
+  'b', 't', 'r', 'z', 'a'
553
+];
554
+var defaultInnerOrder = ['i', 'c', 'b', 'a'];
555
+
556
+
557
+module.exports = function (session, opts) {
558
+  opts = opts || {};
559
+  // ensure certain properties exist
560
+  if (session.version == null) {
561
+    session.version = 0; // 'v=0' must be there (only defined version atm)
562
+  }
563
+  if (session.name == null) {
564
+    session.name = ' '; // 's= ' must be there if no meaningful name set
565
+  }
566
+  session.media.forEach(function (mLine) {
567
+    if (mLine.payloads == null) {
568
+      mLine.payloads = '';
569
+    }
570
+  });
571
+
572
+  var outerOrder = opts.outerOrder || defaultOuterOrder;
573
+  var innerOrder = opts.innerOrder || defaultInnerOrder;
574
+  var sdp = [];
575
+
576
+  // loop through outerOrder for matching properties on session
577
+  outerOrder.forEach(function (type) {
578
+    grammar[type].forEach(function (obj) {
579
+      if (obj.name in session && session[obj.name] != null) {
580
+        sdp.push(makeLine(type, obj, session));
581
+      }
582
+      else if (obj.push in session && session[obj.push] != null) {
583
+        session[obj.push].forEach(function (el) {
584
+          sdp.push(makeLine(type, obj, el));
585
+        });
586
+      }
587
+    });
588
+  });
589
+
590
+  // then for each media line, follow the innerOrder
591
+  session.media.forEach(function (mLine) {
592
+    sdp.push(makeLine('m', grammar.m[0], mLine));
593
+
594
+    innerOrder.forEach(function (type) {
595
+      grammar[type].forEach(function (obj) {
596
+        if (obj.name in mLine && mLine[obj.name] != null) {
597
+          sdp.push(makeLine(type, obj, mLine));
598
+        }
599
+        else if (obj.push in mLine && mLine[obj.push] != null) {
600
+          mLine[obj.push].forEach(function (el) {
601
+            sdp.push(makeLine(type, obj, el));
602
+          });
603
+        }
604
+      });
605
+    });
606
+  });
607
+
608
+  return sdp.join('\r\n') + '\r\n';
609
+};
610
+
611
+},{"./grammar":1}],5:[function(require,module,exports){
612
+/* Copyright @ 2015 Atlassian Pty Ltd
613
+ *
614
+ * Licensed under the Apache License, Version 2.0 (the "License");
615
+ * you may not use this file except in compliance with the License.
616
+ * You may obtain a copy of the License at
617
+ *
618
+ *     http://www.apache.org/licenses/LICENSE-2.0
619
+ *
620
+ * Unless required by applicable law or agreed to in writing, software
621
+ * distributed under the License is distributed on an "AS IS" BASIS,
622
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
623
+ * See the License for the specific language governing permissions and
624
+ * limitations under the License.
625
+ */
626
+
627
+var SdpInterop = module.exports = {
628
+    InteropFF: require('./interop_on_ff'),
629
+    InteropChrome: require('./interop_on_chrome'),
630
+    transform: require('./transform')
631
+};
632
+
633
+},{"./interop_on_chrome":7,"./interop_on_ff":8,"./transform":11}],6:[function(require,module,exports){
634
+/* Copyright @ 2015 Atlassian Pty Ltd
635
+ *
636
+ * Licensed under the Apache License, Version 2.0 (the "License");
637
+ * you may not use this file except in compliance with the License.
638
+ * You may obtain a copy of the License at
639
+ *
640
+ *     http://www.apache.org/licenses/LICENSE-2.0
641
+ *
642
+ * Unless required by applicable law or agreed to in writing, software
643
+ * distributed under the License is distributed on an "AS IS" BASIS,
644
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
645
+ * See the License for the specific language governing permissions and
646
+ * limitations under the License.
647
+ */
648
+
649
+module.exports = function arrayEquals(array) {
650
+    // if the other array is a falsy value, return
651
+    if (!array)
652
+        return false;
653
+
654
+    // compare lengths - can save a lot of time
655
+    if (this.length != array.length)
656
+        return false;
657
+
658
+    for (var i = 0, l = this.length; i < l; i++) {
659
+        // Check if we have nested arrays
660
+        if (this[i] instanceof Array && array[i] instanceof Array) {
661
+            // recurse into the nested arrays
662
+            if (!arrayEquals.apply(this[i], [array[i]]))
663
+                return false;
664
+        } else if (this[i] != array[i]) {
665
+            // Warning - two different object instances will never be equal:
666
+            // {x:20} != {x:20}
667
+            return false;
668
+        }
669
+    }
670
+    return true;
671
+};
672
+
673
+
674
+},{}],7:[function(require,module,exports){
675
+/**
676
+ * Copyright(c) Starleaf Ltd. 2016.
677
+ */
678
+
679
+
680
+"use strict";
681
+
682
+
683
+//Small library for plan b interop - Designed to be run on chrome.
684
+//Assumes you will do the following - convert unified plan received on the wire into plan B
685
+//before setting the remote description
686
+//Convert plan b generated by chrome into unified plan prior to sending.
687
+
688
+var Interop = function () {
689
+    var cache = {};
690
+
691
+    var copyObj = function (obj) {
692
+        return JSON.parse(JSON.stringify(obj));
693
+    };
694
+
695
+    var toUnifiedPlan = function (desc) {
696
+        var uplan = require('./on_chrome/to-unified-plan')(desc, cache);
697
+        //cache a copy
698
+        cache.local = copyObj(uplan.sdp);
699
+        return uplan;
700
+    };
701
+
702
+    var toPlanB = function (desc) {
703
+        //cache the last unified plan we received on the wire
704
+        cache.remote = copyObj(desc.sdp);
705
+        return require('./on_chrome/to-plan-b')(desc, cache);
706
+    };
707
+
708
+
709
+    var that = {};
710
+    that.toUnifiedPlan = toUnifiedPlan;
711
+    that.toPlanB = toPlanB;
712
+    return that;
713
+};
714
+
715
+module.exports = Interop;
716
+},{"./on_chrome/to-plan-b":9,"./on_chrome/to-unified-plan":10}],8:[function(require,module,exports){
717
+/* Copyright @ 2015 Atlassian Pty Ltd
718
+ *
719
+ * Licensed under the Apache License, Version 2.0 (the "License");
720
+ * you may not use this file except in compliance with the License.
721
+ * You may obtain a copy of the License at
722
+ *
723
+ *     http://www.apache.org/licenses/LICENSE-2.0
724
+ *
725
+ * Unless required by applicable law or agreed to in writing, software
726
+ * distributed under the License is distributed on an "AS IS" BASIS,
727
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
728
+ * See the License for the specific language governing permissions and
729
+ * limitations under the License.
730
+ */
731
+
732
+/* global RTCSessionDescription */
733
+/* jshint -W097 */
734
+"use strict";
735
+
736
+var transform = require('./transform');
737
+var arrayEquals = require('./array-equals');
738
+
739
+function Interop() {
740
+
741
+    /**
742
+     * This map holds the most recent Unified Plan offer/answer SDP that was
743
+     * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and
744
+     * the SDP string as values.
745
+     *
746
+     * @type {{}}
747
+     */
748
+    this.cache = {};
749
+}
750
+
751
+module.exports = Interop;
752
+
753
+/**
754
+ * Returns the index of the first m-line with the given media type and with a
755
+ * direction which allows sending, in the last Unified Plan description with
756
+ * type "answer" converted to Plan B. Returns {null} if there is no saved
757
+ * answer, or if none of its m-lines with the given type allow sending.
758
+ * @param type the media type ("audio" or "video").
759
+ * @returns {*}
760
+ */
761
+Interop.prototype.getFirstSendingIndexFromAnswer = function (type) {
762
+    if (!this.cache.answer) {
763
+        return null;
764
+    }
765
+
766
+    var session = transform.parse(this.cache.answer);
767
+    if (session && session.media && Array.isArray(session.media)) {
768
+        for (var i = 0; i < session.media.length; i++) {
769
+            if (session.media[i].type == type &&
770
+                (!session.media[i].direction /* default to sendrecv */ ||
771
+                session.media[i].direction === 'sendrecv' ||
772
+                session.media[i].direction === 'sendonly')) {
773
+                return i;
774
+            }
775
+        }
776
+    }
777
+
778
+    return null;
779
+};
780
+
781
+/**
782
+ * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A
783
+ * PeerConnection wrapper transforms the SDP to Plan B before passing it to the
784
+ * application.
785
+ *
786
+ * @param desc
787
+ * @returns {*}
788
+ */
789
+Interop.prototype.toPlanB = function (desc) {
790
+    var self = this;
791
+    //#region Preliminary input validation.
792
+
793
+    if (typeof desc !== 'object' || desc === null ||
794
+        typeof desc.sdp !== 'string') {
795
+        console.warn('An empty description was passed as an argument.');
796
+        return desc;
797
+    }
798
+
799
+    // Objectify the SDP for easier manipulation.
800
+    var session = transform.parse(desc.sdp);
801
+
802
+    // If the SDP contains no media, there's nothing to transform.
803
+    if (typeof session.media === 'undefined' || !Array.isArray(session.media) || session.media.length === 0) {
804
+        console.warn('The description has no media.');
805
+        return desc;
806
+    }
807
+
808
+    // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
809
+    // SDP has a video, an audio and a data "channel" at most.
810
+    if (session.media.length <= 3 && session.media.every(function (m) {
811
+                return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
812
+            }
813
+        )) {
814
+        console.warn('This description does not look like Unified Plan.');
815
+        return desc;
816
+    }
817
+
818
+    //#endregion
819
+
820
+    // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
821
+    var sdp = desc.sdp;
822
+    var rewrite = false;
823
+    for (var i = 0; i < session.media.length; i++) {
824
+        var uLine = session.media[i];
825
+        uLine.rtp.forEach(function (rtp) {
826
+                if (rtp.codec === 'NULL') {
827
+                    rewrite = true;
828
+                    var offer = transform.parse(self.cache.offer);
829
+                    rtp.codec = offer.media[i].rtp[0].codec;
830
+                }
831
+            }
832
+        );
833
+    }
834
+    if (rewrite) {
835
+        sdp = transform.write(session);
836
+    }
837
+
838
+    // Unified Plan SDP is our "precious". Cache it for later use in the Plan B
839
+    // -> Unified Plan transformation.
840
+    this.cache[desc.type] = sdp;
841
+
842
+    //#region Convert from Unified Plan to Plan B.
843
+
844
+    // We rebuild the session.media array.
845
+    var media = session.media;
846
+    session.media = [];
847
+
848
+    // Associative array that maps channel types to channel objects for fast
849
+    // access to channel objects by their type, e.g. type2bl['audio']->channel
850
+    // obj.
851
+    var type2bl = {};
852
+
853
+    // Used to build the group:BUNDLE value after the channels construction
854
+    // loop.
855
+    var types = [];
856
+
857
+    // Implode the Unified Plan m-lines/tracks into Plan B channels.
858
+    media.forEach(function (uLine) {
859
+
860
+            // rtcp-mux is required in the Plan B SDP.
861
+            if ((typeof uLine.rtcpMux !== 'string' ||
862
+                uLine.rtcpMux !== 'rtcp-mux') &&
863
+                uLine.direction !== 'inactive') {
864
+                throw new Error('Cannot convert to Plan B because m-lines ' +
865
+                    'without the rtcp-mux attribute were found.'
866
+                );
867
+            }
868
+
869
+            if (uLine.type === 'application') {
870
+                session.media.push(uLine);
871
+                types.push(uLine.mid);
872
+                return;
873
+            }
874
+
875
+            // If we don't have a channel for this uLine.type, then use this
876
+            // uLine as the channel basis.
877
+            if (typeof type2bl[uLine.type] === 'undefined') {
878
+                type2bl[uLine.type] = uLine;
879
+            }
880
+
881
+            // Add sources to the channel and handle a=msid.
882
+            if (typeof uLine.sources === 'object') {
883
+                Object.keys(uLine.sources).forEach(function (ssrc) {
884
+                        if (typeof type2bl[uLine.type].sources !== 'object')
885
+                            type2bl[uLine.type].sources = {};
886
+
887
+                        // Assign the sources to the channel.
888
+                        type2bl[uLine.type].sources[ssrc] =
889
+                            uLine.sources[ssrc];
890
+
891
+                        if (typeof uLine.msid !== 'undefined') {
892
+                            // In Plan B the msid is an SSRC attribute. Also, we don't
893
+                            // care about the obsolete label and mslabel attributes.
894
+                            //
895
+                            // Note that it is not guaranteed that the uLine will
896
+                            // have an msid. recvonly channels in particular don't have
897
+                            // one.
898
+                            type2bl[uLine.type].sources[ssrc].msid =
899
+                                uLine.msid;
900
+                        }
901
+                        // NOTE ssrcs in ssrc groups will share msids, as
902
+                        // draft-uberti-rtcweb-plan-00 mandates.
903
+                    }
904
+                );
905
+            }
906
+
907
+            // Add ssrc groups to the channel.
908
+            if (typeof uLine.ssrcGroups !== 'undefined' &&
909
+                Array.isArray(uLine.ssrcGroups)) {
910
+                // Create the ssrcGroups array, if it's not defined.
911
+                if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' || !Array.isArray(type2bl[uLine.type].ssrcGroups
912
+                    )) {
913
+                    type2bl[uLine.type].ssrcGroups = [];
914
+                }
915
+
916
+                type2bl[uLine.type].ssrcGroups =
917
+                    type2bl[uLine.type].ssrcGroups.concat(
918
+                        uLine.ssrcGroups
919
+                    );
920
+            }
921
+
922
+            if (type2bl[uLine.type] === uLine) {
923
+                // Copy ICE related stuff from the principal media line.
924
+                uLine.candidates = media[0].candidates;
925
+                uLine.iceUfrag = media[0].iceUfrag;
926
+                uLine.icePwd = media[0].icePwd;
927
+                uLine.fingerprint = media[0].fingerprint;
928
+
929
+                // Plan B mids are in ['audio', 'video', 'data']
930
+                uLine.mid = uLine.type;
931
+
932
+                // Plan B doesn't support/need the bundle-only attribute.
933
+                delete uLine.bundleOnly;
934
+
935
+                // In Plan B the msid is an SSRC attribute.
936
+                delete uLine.msid;
937
+
938
+                // Used to build the group:BUNDLE value after this loop.
939
+                types.push(uLine.type);
940
+
941
+                // Add the channel to the new media array.
942
+                session.media.push(uLine);
943
+            }
944
+        }
945
+    );
946
+
947
+    // We regenerate the BUNDLE group with the new mids.
948
+    session.groups.some(function (group) {
949
+            if (group.type === 'BUNDLE') {
950
+                group.mids = types.join(' ');
951
+                return true;
952
+            }
953
+        }
954
+    );
955
+
956
+    // msid semantic
957
+    session.msidSemantic = {
958
+        semantic: 'WMS',
959
+        token: '*'
960
+    };
961
+
962
+    var resStr = transform.write(session);
963
+
964
+    return new RTCSessionDescription({
965
+            type: desc.type,
966
+            sdp: resStr
967
+        }
968
+    );
969
+
970
+    //#endregion
971
+};
972
+
973
+/**
974
+ * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A
975
+ * PeerConnection wrapper transforms the SDP to Unified Plan before passing it
976
+ * to FF.
977
+ *
978
+ * @param desc
979
+ * @returns {*}
980
+ */
981
+Interop.prototype.toUnifiedPlan = function (desc) {
982
+    var self = this;
983
+    //#region Preliminary input validation.
984
+
985
+    if (typeof desc !== 'object' || desc === null ||
986
+        typeof desc.sdp !== 'string') {
987
+        console.warn('An empty description was passed as an argument.');
988
+        return desc;
989
+    }
990
+
991
+    var session = transform.parse(desc.sdp);
992
+
993
+    // If the SDP contains no media, there's nothing to transform.
994
+    if (typeof session.media === 'undefined' || !Array.isArray(session.media) || session.media.length === 0) {
995
+        console.warn('The description has no media.');
996
+        return desc;
997
+    }
998
+
999
+    // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
1000
+    // a video, an audio and a data "channel" at most.
1001
+    if (session.media.length > 3 || !session.media.every(function (m) {
1002
+                return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
1003
+            }
1004
+        )) {
1005
+        console.warn('This description does not look like Plan B.');
1006
+        return desc;
1007
+    }
1008
+
1009
+    // Make sure this Plan B SDP can be converted to a Unified Plan SDP.
1010
+    var mids = [];
1011
+    session.media.forEach(function (m) {
1012
+            mids.push(m.mid);
1013
+        }
1014
+    );
1015
+
1016
+    var hasBundle = false;
1017
+    if (typeof session.groups !== 'undefined' &&
1018
+        Array.isArray(session.groups)) {
1019
+        hasBundle = session.groups.every(function (g) {
1020
+                return g.type !== 'BUNDLE' ||
1021
+                    arrayEquals.apply(g.mids.sort(), [mids.sort()]);
1022
+            }
1023
+        );
1024
+    }
1025
+
1026
+    if (!hasBundle) {
1027
+        throw new Error("Cannot convert to Unified Plan because m-lines that" +
1028
+            " are not bundled were found."
1029
+        );
1030
+    }
1031
+
1032
+    //#endregion
1033
+
1034
+
1035
+    //#region Convert from Plan B to Unified Plan.
1036
+
1037
+    // Unfortunately, a Plan B offer/answer doesn't have enough information to
1038
+    // rebuild an equivalent Unified Plan offer/answer.
1039
+    //
1040
+    // For example, if this is a local answer (in Unified Plan style) that we
1041
+    // convert to Plan B prior to handing it over to the application (the
1042
+    // PeerConnection wrapper called us, for instance, after a successful
1043
+    // createAnswer), we want to remember the m-line at which we've seen the
1044
+    // (local) SSRC. That's because when the application wants to do call the
1045
+    // SLD method, forcing us to do the inverse transformation (from Plan B to
1046
+    // Unified Plan), we need to know to which m-line to assign the (local)
1047
+    // SSRC. We also need to know all the other m-lines that the original
1048
+    // answer had and include them in the transformed answer as well.
1049
+    //
1050
+    // Another example is if this is a remote offer that we convert to Plan B
1051
+    // prior to giving it to the application, we want to remember the mid at
1052
+    // which we've seen the (remote) SSRC.
1053
+    //
1054
+    // In the iteration that follows, we use the cached Unified Plan (if it
1055
+    // exists) to assign mids to ssrcs.
1056
+
1057
+    var cached;
1058
+    if (typeof this.cache[desc.type] !== 'undefined') {
1059
+        cached = transform.parse(this.cache[desc.type]);
1060
+    }
1061
+
1062
+    var recvonlySsrcs = {
1063
+        audio: {},
1064
+        video: {}
1065
+    };
1066
+
1067
+    // A helper map that sends mids to m-line objects. We use it later to
1068
+    // rebuild the Unified Plan style session.media array.
1069
+    var mid2ul = {};
1070
+    session.media.forEach(function (bLine) {
1071
+            if ((typeof bLine.rtcpMux !== 'string' ||
1072
+                bLine.rtcpMux !== 'rtcp-mux') &&
1073
+                bLine.direction !== 'inactive') {
1074
+                throw new Error("Cannot convert to Unified Plan because m-lines " +
1075
+                    "without the rtcp-mux attribute were found."
1076
+                );
1077
+            }
1078
+
1079
+            if (bLine.type === 'application') {
1080
+                mid2ul[bLine.mid] = bLine;
1081
+                return;
1082
+            }
1083
+
1084
+            // With rtcp-mux and bundle all the channels should have the same ICE
1085
+            // stuff.
1086
+            var sources = bLine.sources;
1087
+            var ssrcGroups = bLine.ssrcGroups;
1088
+            var candidates = bLine.candidates;
1089
+            var iceUfrag = bLine.iceUfrag;
1090
+            var icePwd = bLine.icePwd;
1091
+            var fingerprint = bLine.fingerprint;
1092
+            var port = bLine.port;
1093
+
1094
+            // We'll use the "bLine" object as a prototype for each new "mLine"
1095
+            // that we create, but first we need to clean it up a bit.
1096
+            delete bLine.sources;
1097
+            delete bLine.ssrcGroups;
1098
+            delete bLine.candidates;
1099
+            delete bLine.iceUfrag;
1100
+            delete bLine.icePwd;
1101
+            delete bLine.fingerprint;
1102
+            delete bLine.port;
1103
+            delete bLine.mid;
1104
+
1105
+            // inverted ssrc group map
1106
+            var ssrc2group = {};
1107
+            if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {
1108
+                ssrcGroups.forEach(function (ssrcGroup) {
1109
+
1110
+                        // TODO(gp) find out how to receive simulcast with FF. For the
1111
+                        // time being, hide it.
1112
+                        if (ssrcGroup.semantics === 'SIM') {
1113
+                            return;
1114
+                        }
1115
+
1116
+                        // XXX This might brake if an SSRC is in more than one group
1117
+                        // for some reason.
1118
+                        if (typeof ssrcGroup.ssrcs !== 'undefined' &&
1119
+                            Array.isArray(ssrcGroup.ssrcs)) {
1120
+                            ssrcGroup.ssrcs.forEach(function (ssrc) {
1121
+                                    if (typeof ssrc2group[ssrc] === 'undefined') {
1122
+                                        ssrc2group[ssrc] = [];
1123
+                                    }
1124
+
1125
+                                    ssrc2group[ssrc].push(ssrcGroup);
1126
+                                }
1127
+                            );
1128
+                        }
1129
+                    }
1130
+                );
1131
+            }
1132
+
1133
+            // ssrc to m-line index.
1134
+            var ssrc2ml = {};
1135
+
1136
+            if (typeof sources === 'object') {
1137
+
1138
+                // Explode the Plan B channel sources with one m-line per source.
1139
+                Object.keys(sources).forEach(function (ssrc) {
1140
+
1141
+                        // The (unified) m-line for this SSRC. We either create it from
1142
+                        // scratch or, if it's a grouped SSRC, we re-use a related
1143
+                        // mline. In other words, if the source is grouped with another
1144
+                        // source, put the two together in the same m-line.
1145
+                        var uLine;
1146
+
1147
+                        // We assume here that we are the answerer in the O/A, so any
1148
+                        // offers which we translate come from the remote side, while
1149
+                        // answers are local. So the check below is to make that we
1150
+                        // handle receive-only SSRCs in a special way only if they come
1151
+                        // from the remote side.
1152
+                        if (desc.type === 'offer') {
1153
+                            // We want to detect SSRCs which are used by a remote peer
1154
+                            // in an m-line with direction=recvonly (i.e. they are
1155
+                            // being used for RTCP only).
1156
+                            // This information would have gotten lost if the remote
1157
+                            // peer used Unified Plan and their local description was
1158
+                            // translated to Plan B. So we use the lack of an MSID
1159
+                            // attribute to deduce a "receive only" SSRC.
1160
+                            if (!sources[ssrc].msid) {
1161
+                                recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];
1162
+                                // Receive-only SSRCs must not create new m-lines. We
1163
+                                // will assign them to an existing m-line later.
1164
+                                return;
1165
+                            }
1166
+                        }
1167
+
1168
+                        if (typeof ssrc2group[ssrc] !== 'undefined' &&
1169
+                            Array.isArray(ssrc2group[ssrc])) {
1170
+                            ssrc2group[ssrc].some(function (ssrcGroup) {
1171
+                                    // ssrcGroup.ssrcs *is* an Array, no need to check
1172
+                                    // again here.
1173
+                                    return ssrcGroup.ssrcs.some(function (related) {
1174
+                                            if (typeof ssrc2ml[related] === 'object') {
1175
+                                                uLine = ssrc2ml[related];
1176
+                                                return true;
1177
+                                            }
1178
+                                        }
1179
+                                    );
1180
+                                }
1181
+                            );
1182
+                        }
1183
+
1184
+                        if (typeof uLine === 'object') {
1185
+                            // the m-line already exists. Just add the source.
1186
+                            uLine.sources[ssrc] = sources[ssrc];
1187
+                            delete sources[ssrc].msid;
1188
+                        } else {
1189
+                            // Use the "bLine" as a prototype for the "uLine".
1190
+                            uLine = Object.create(bLine);
1191
+                            ssrc2ml[ssrc] = uLine;
1192
+
1193
+                            if (typeof sources[ssrc].msid !== 'undefined') {
1194
+                                // Assign the msid of the source to the m-line. Note
1195
+                                // that it is not guaranteed that the source will have
1196
+                                // msid. In particular "recvonly" sources don't have an
1197
+                                // msid. Note that "recvonly" is a term only defined
1198
+                                // for m-lines.
1199
+                                uLine.msid = sources[ssrc].msid;
1200
+                                uLine.direction = 'sendrecv';
1201
+                                delete sources[ssrc].msid;
1202
+                            }
1203
+
1204
+                            // We assign one SSRC per media line.
1205
+                            uLine.sources = {};
1206
+                            uLine.sources[ssrc] = sources[ssrc];
1207
+                            uLine.ssrcGroups = ssrc2group[ssrc];
1208
+
1209
+                            // Use the cached Unified Plan SDP (if it exists) to assign
1210
+                            // SSRCs to mids.
1211
+                            if (typeof cached !== 'undefined' &&
1212
+                                typeof cached.media !== 'undefined' &&
1213
+                                Array.isArray(cached.media)) {
1214
+
1215
+                                cached.media.forEach(function (m) {
1216
+                                        if (typeof m.sources === 'object') {
1217
+                                            Object.keys(m.sources).forEach(function (s) {
1218
+                                                    if (s === ssrc) {
1219
+                                                        uLine.mid = m.mid;
1220
+                                                    }
1221
+                                                }
1222
+                                            );
1223
+                                        }
1224
+                                    }
1225
+                                );
1226
+                            }
1227
+
1228
+                            if (typeof uLine.mid === 'undefined') {
1229
+
1230
+                                // If this is an SSRC that we see for the first time
1231
+                                // assign it a new mid. This is typically the case when
1232
+                                // this method is called to transform a remote
1233
+                                // description for the first time or when there is a
1234
+                                // new SSRC in the remote description because a new
1235
+                                // peer has joined the conference. Local SSRCs should
1236
+                                // have already been added to the map in the toPlanB
1237
+                                // method.
1238
+                                //
1239
+                                // Because FF generates answers in Unified Plan style,
1240
+                                // we MUST already have a cached answer with all the
1241
+                                // local SSRCs mapped to some m-line/mid.
1242
+
1243
+                                if (desc.type === 'answer') {
1244
+                                    throw new Error("An unmapped SSRC was found.");
1245
+                                }
1246
+
1247
+                                uLine.mid = [bLine.type, '-', ssrc].join('');
1248
+                            }
1249
+
1250
+                            // Include the candidates in the 1st media line.
1251
+                            uLine.candidates = candidates;
1252
+                            uLine.iceUfrag = iceUfrag;
1253
+                            uLine.icePwd = icePwd;
1254
+                            uLine.fingerprint = fingerprint;
1255
+                            uLine.port = port;
1256
+
1257
+                            mid2ul[uLine.mid] = uLine;
1258
+                        }
1259
+                    }
1260
+                );
1261
+            }
1262
+        }
1263
+    );
1264
+
1265
+    // Rebuild the media array in the right order and add the missing mLines
1266
+    // (missing from the Plan B SDP).
1267
+    session.media = [];
1268
+    mids = []; // reuse
1269
+
1270
+    if (desc.type === 'answer') {
1271
+
1272
+        // The media lines in the answer must match the media lines in the
1273
+        // offer. The order is important too. Here we assume that Firefox is
1274
+        // the answerer, so we merely have to use the reconstructed (unified)
1275
+        // answer to update the cached (unified) answer accordingly.
1276
+        //
1277
+        // In the general case, one would have to use the cached (unified)
1278
+        // offer to find the m-lines that are missing from the reconstructed
1279
+        // answer, potentially grabbing them from the cached (unified) answer.
1280
+        // One has to be careful with this approach because inactive m-lines do
1281
+        // not always have an mid, making it tricky (impossible?) to find where
1282
+        // exactly and which m-lines are missing from the reconstructed answer.
1283
+
1284
+        for (var i = 0; i < cached.media.length; i++) {
1285
+            var uLine = cached.media[i];
1286
+
1287
+            if (typeof mid2ul[uLine.mid] === 'undefined') {
1288
+
1289
+                // The mid isn't in the reconstructed (unified) answer.
1290
+                // This is either a (unified) m-line containing a remote
1291
+                // track only, or a (unified) m-line containing a remote
1292
+                // track and a local track that has been removed.
1293
+                // In either case, it MUST exist in the cached
1294
+                // (unified) answer.
1295
+                //
1296
+                // In case this is a removed local track, clean-up
1297
+                // the (unified) m-line and make sure it's 'recvonly' or
1298
+                // 'inactive'.
1299
+
1300
+                delete uLine.msid;
1301
+                delete uLine.sources;
1302
+                delete uLine.ssrcGroups;
1303
+                if (!uLine.direction
1304
+                    || uLine.direction === 'sendrecv')
1305
+                    uLine.direction = 'recvonly';
1306
+                else if (uLine.direction === 'sendonly')
1307
+                    uLine.direction = 'inactive';
1308
+            } else {
1309
+                // This is an (unified) m-line/channel that contains a local
1310
+                // track (sendrecv or sendonly channel) or it's a unified
1311
+                // recvonly m-line/channel. In either case, since we're
1312
+                // going from PlanB -> Unified Plan this m-line MUST
1313
+                // exist in the cached answer.
1314
+            }
1315
+
1316
+            session.media.push(uLine);
1317
+
1318
+            if (typeof uLine.mid === 'string') {
1319
+                // inactive lines don't/may not have an mid.
1320
+                mids.push(uLine.mid);
1321
+            }
1322
+        }
1323
+    } else {
1324
+
1325
+        // SDP offer/answer (and the JSEP spec) forbids removing an m-section
1326
+        // under any circumstances. If we are no longer interested in sending a
1327
+        // track, we just remove the msid and ssrc attributes and set it to
1328
+        // either a=recvonly (as the reofferer, we must use recvonly if the
1329
+        // other side was previously sending on the m-section, but we can also
1330
+        // leave the possibility open if it wasn't previously in use), or
1331
+        // a=inactive.
1332
+
1333
+        if (typeof cached !== 'undefined' &&
1334
+            typeof cached.media !== 'undefined' &&
1335
+            Array.isArray(cached.media)) {
1336
+            cached.media.forEach(function (uLine) {
1337
+                    mids.push(uLine.mid);
1338
+                    if (typeof mid2ul[uLine.mid] !== 'undefined') {
1339
+                        session.media.push(mid2ul[uLine.mid]);
1340
+                    } else {
1341
+                        delete uLine.msid;
1342
+                        delete uLine.sources;
1343
+                        delete uLine.ssrcGroups;
1344
+                        if (!uLine.direction
1345
+                            || uLine.direction === 'sendrecv')
1346
+                            uLine.direction = 'recvonly';
1347
+                        if (!uLine.direction
1348
+                            || uLine.direction === 'sendonly')
1349
+                            uLine.direction = 'inactive';
1350
+                        session.media.push(uLine);
1351
+                    }
1352
+                }
1353
+            );
1354
+        }
1355
+
1356
+        // Add all the remaining (new) m-lines of the transformed SDP.
1357
+        Object.keys(mid2ul).forEach(function (mid) {
1358
+                if (mids.indexOf(mid) === -1) {
1359
+                    mids.push(mid);
1360
+                    if (mid2ul[mid].direction === 'recvonly') {
1361
+                        // This is a remote recvonly channel. Add its SSRC to the
1362
+                        // appropriate sendrecv or sendonly channel.
1363
+                        // TODO(gp) what if we don't have sendrecv/sendonly
1364
+                        // channel?
1365
+
1366
+                        session.media.some(function (uLine) {
1367
+                                if ((uLine.direction === 'sendrecv' ||
1368
+                                    uLine.direction === 'sendonly') &&
1369
+                                    uLine.type === mid2ul[mid].type) {
1370
+
1371
+                                    // mid2ul[mid] shouldn't have any ssrc-groups
1372
+                                    Object.keys(mid2ul[mid].sources).forEach(
1373
+                                        function (ssrc) {
1374
+                                            uLine.sources[ssrc] =
1375
+                                                mid2ul[mid].sources[ssrc];
1376
+                                        }
1377
+                                    );
1378
+
1379
+                                    return true;
1380
+                                }
1381
+                            }
1382
+                        );
1383
+                    } else {
1384
+                        session.media.push(mid2ul[mid]);
1385
+                    }
1386
+                }
1387
+            }
1388
+        );
1389
+    }
1390
+
1391
+    // After we have constructed the Plan Unified m-lines we can figure out
1392
+    // where (in which m-line) to place the 'recvonly SSRCs'.
1393
+    // Note: we assume here that we are the answerer in the O/A, so any offers
1394
+    // which we translate come from the remote side, while answers are local
1395
+    // (and so our last local description is cached as an 'answer').
1396
+    ["audio", "video"].forEach(function (type) {
1397
+            if (!session || !session.media || !Array.isArray(session.media))
1398
+                return;
1399
+
1400
+            var idx = null;
1401
+            if (Object.keys(recvonlySsrcs[type]).length > 0) {
1402
+                idx = self.getFirstSendingIndexFromAnswer(type);
1403
+                if (idx === null) {
1404
+                    // If this is the first offer we receive, we don't have a
1405
+                    // cached answer. Assume that we will be sending media using
1406
+                    // the first m-line for each media type.
1407
+
1408
+                    for (var i = 0; i < session.media.length; i++) {
1409
+                        if (session.media[i].type === type) {
1410
+                            idx = i;
1411
+                            break;
1412
+                        }
1413
+                    }
1414
+                }
1415
+            }
1416
+
1417
+            if (idx && session.media.length > idx) {
1418
+                var mLine = session.media[idx];
1419
+                Object.keys(recvonlySsrcs[type]).forEach(function (ssrc) {
1420
+                        if (mLine.sources && mLine.sources[ssrc]) {
1421
+                            console.warn("Replacing an existing SSRC.");
1422
+                        }
1423
+                        if (!mLine.sources) {
1424
+                            mLine.sources = {};
1425
+                        }
1426
+
1427
+                        mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];
1428
+                    }
1429
+                );
1430
+            }
1431
+        }
1432
+    );
1433
+
1434
+    // We regenerate the BUNDLE group (since we regenerated the mids)
1435
+    session.groups.some(function (group) {
1436
+            if (group.type === 'BUNDLE') {
1437
+                group.mids = mids.join(' ');
1438
+                return true;
1439
+            }
1440
+        }
1441
+    );
1442
+
1443
+    // msid semantic
1444
+    session.msidSemantic = {
1445
+        semantic: 'WMS',
1446
+        token: '*'
1447
+    };
1448
+
1449
+    var resStr = transform.write(session);
1450
+
1451
+    // Cache the transformed SDP (Unified Plan) for later re-use in this
1452
+    // function.
1453
+    this.cache[desc.type] = resStr;
1454
+
1455
+    return new RTCSessionDescription({
1456
+            type: desc.type,
1457
+            sdp: resStr
1458
+        }
1459
+    );
1460
+
1461
+    //#endregion
1462
+};
1463
+
1464
+},{"./array-equals":6,"./transform":11}],9:[function(require,module,exports){
1465
+/**
1466
+ * Copyright(c) Starleaf Ltd. 2016.
1467
+ */
1468
+
1469
+
1470
+"use strict";
1471
+
1472
+var transform = require('../transform');
1473
+
1474
+module.exports = function (desc, cache) {
1475
+    if (typeof desc !== 'object' || desc === null ||
1476
+        typeof desc.sdp !== 'string') {
1477
+        console.warn('An empty description was passed as an argument.');
1478
+        return desc;
1479
+    }
1480
+
1481
+    // Objectify the SDP for easier manipulation.
1482
+    var session = transform.parse(desc.sdp);
1483
+
1484
+    // If the SDP contains no media, there's nothing to transform.
1485
+    if (typeof session.media === 'undefined' || !Array.isArray(session.media) || session.media.length === 0) {
1486
+        console.warn('The description has no media.');
1487
+        return desc;
1488
+    }
1489
+
1490
+    // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
1491
+    // SDP has a video, an audio and a data "channel" at most.
1492
+    if (session.media.length <= 3 && session.media.every(function (m) {
1493
+            return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
1494
+        })) {
1495
+        console.warn('This description does not look like Unified Plan.');
1496
+        return desc;
1497
+    }
1498
+
1499
+    //#endregion
1500
+
1501
+    // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
1502
+    var rewrite = false;
1503
+    for (var i = 0; i < session.media.length; i++) {
1504
+        var uLine = session.media[i];
1505
+        uLine.rtp.forEach(function (rtp) {
1506
+            if (rtp.codec === 'NULL') {
1507
+                rewrite = true;
1508
+                var offer = transform.parse(cache.local);
1509
+                rtp.codec = offer.media[i].rtp[0].codec;
1510
+            }
1511
+        });
1512
+    }
1513
+
1514
+    if (rewrite) {
1515
+        desc.sdp = transform.write(session);
1516
+    }
1517
+
1518
+    // Unified Plan SDP is our "precious". Cache it for later use in the Plan B
1519
+    // -> Unified Plan transformation.
1520
+
1521
+    //#region Convert from Unified Plan to Plan B.
1522
+
1523
+    // We rebuild the session.media array.
1524
+    var media = session.media;
1525
+    session.media = [];
1526
+
1527
+    // Associative array that maps channel types to channel objects for fast
1528
+    // access to channel objects by their type, e.g. type2bl['audio']->channel
1529
+    // obj.
1530
+    var type2bl = {};
1531
+
1532
+    // Used to build the group:BUNDLE value after the channels construction
1533
+    // loop.
1534
+    var types = [];
1535
+
1536
+    // Implode the Unified Plan m-lines/tracks into Plan B channels.
1537
+    media.forEach(function (uLine, index) {
1538
+
1539
+        // If we don't have a channel for this uLine.type, then use this
1540
+        // uLine as the channel basis.
1541
+        if (typeof type2bl[uLine.type] === 'undefined') {
1542
+            type2bl[uLine.type] = uLine;
1543
+        }
1544
+
1545
+        if (uLine.port === 0) {
1546
+            if (index > 1 && uLine.type !== 'data') { //it's a secondary video stream - drop without further ado
1547
+                return;
1548
+            }
1549
+            else {
1550
+                delete uLine.mid;
1551
+                uLine.mid = uLine.type;
1552
+                //types.push(uLine.type);
1553
+                session.media.push(uLine);
1554
+                return;
1555
+            }
1556
+        }
1557
+
1558
+        if (uLine.type === 'application') {
1559
+            session.media.push(uLine);
1560
+            types.push(uLine.mid);
1561
+            return;
1562
+        }
1563
+        // Add sources to the channel and handle a=msid.
1564
+        if (typeof uLine.sources === 'object') {
1565
+            Object.keys(uLine.sources).forEach(function (ssrc) {
1566
+                if (typeof type2bl[uLine.type].sources !== 'object')
1567
+                    type2bl[uLine.type].sources = {};
1568
+
1569
+                // Assign the sources to the channel.
1570
+                type2bl[uLine.type].sources[ssrc] =
1571
+                    uLine.sources[ssrc];
1572
+
1573
+                if (typeof uLine.msid !== 'undefined') {
1574
+                    // In Plan B the msid is an SSRC attribute. Also, we don't
1575
+                    // care about the obsolete label and mslabel attributes.
1576
+                    //
1577
+                    // Note that it is not guaranteed that the uLine will
1578
+                    // have an msid. recvonly channels in particular don't have
1579
+                    // one.
1580
+                    type2bl[uLine.type].sources[ssrc].msid =
1581
+                        uLine.msid;
1582
+                }
1583
+                // NOTE ssrcs in ssrc groups will share msids, as
1584
+                // draft-uberti-rtcweb-plan-00 mandates.
1585
+            });
1586
+        }
1587
+
1588
+        // Add ssrc groups to the channel.
1589
+        if (typeof uLine.ssrcGroups !== 'undefined' &&
1590
+            Array.isArray(uLine.ssrcGroups)) {
1591
+
1592
+            // Create the ssrcGroups array, if it's not defined.
1593
+            if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' || !Array.isArray(
1594
+                    type2bl[uLine.type].ssrcGroups)) {
1595
+                type2bl[uLine.type].ssrcGroups = [];
1596
+            }
1597
+
1598
+            type2bl[uLine.type].ssrcGroups =
1599
+                type2bl[uLine.type].ssrcGroups.concat(
1600
+                    uLine.ssrcGroups);
1601
+        }
1602
+
1603
+        if (type2bl[uLine.type] === uLine) {
1604
+            // Copy ICE related stuff from the principal media line.
1605
+            uLine.candidates = media[0].candidates;
1606
+            uLine.iceUfrag = media[0].iceUfrag;
1607
+            uLine.icePwd = media[0].icePwd;
1608
+            uLine.fingerprint = media[0].fingerprint;
1609
+
1610
+            // Plan B mids are in ['audio', 'video', 'data']
1611
+            uLine.mid = uLine.type;
1612
+
1613
+            // Plan B doesn't support/need the bundle-only attribute.
1614
+            delete uLine.bundleOnly;
1615
+
1616
+            // In Plan B the msid is an SSRC attribute.
1617
+            delete uLine.msid;
1618
+
1619
+            // Used to build the group:BUNDLE value after this loop.
1620
+            types.push(uLine.type);
1621
+
1622
+            // Add the channel to the new media array.
1623
+            session.media.push(uLine);
1624
+        }
1625
+    });
1626
+
1627
+    // We regenerate the BUNDLE group with the new mids.
1628
+    session.groups.some(function (group) {
1629
+        if (group.type === 'BUNDLE') {
1630
+            group.mids = types.join(' ');
1631
+            return true;
1632
+        }
1633
+    });
1634
+
1635
+    // msid semantic
1636
+    session.msidSemantic = {
1637
+        semantic: 'WMS',
1638
+        token: '*'
1639
+    };
1640
+
1641
+    var resStr = transform.write(session);
1642
+
1643
+    return new window.RTCSessionDescription({
1644
+        type: desc.type,
1645
+        sdp: resStr
1646
+    });
1647
+};
1648
+},{"../transform":11}],10:[function(require,module,exports){
1649
+/**
1650
+ * Copyright(c) Starleaf Ltd. 2016.
1651
+ */
1652
+
1653
+
1654
+"use strict";
1655
+
1656
+
1657
+var transform = require('../transform');
1658
+var arrayEquals = require('../array-equals');
1659
+
1660
+var copyObj = function (obj) {
1661
+    return JSON.parse(JSON.stringify(obj));
1662
+};
1663
+
1664
+module.exports = function (desc, cache) {
1665
+
1666
+    if (typeof desc !== 'object' || desc === null ||
1667
+        typeof desc.sdp !== 'string') {
1668
+        console.warn('An empty description was passed as an argument.');
1669
+        return desc;
1670
+    }
1671
+
1672
+    var session = transform.parse(desc.sdp);
1673
+
1674
+    // If the SDP contains no media, there's nothing to transform.
1675
+    if (typeof session.media === 'undefined' || !Array.isArray(session.media) || session.media.length === 0) {
1676
+        console.warn('The description has no media.');
1677
+        return desc;
1678
+    }
1679
+
1680
+    // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
1681
+    // a video, an audio and a data "channel" at most.
1682
+    if (session.media.length > 3 || !session.media.every(function (m) {
1683
+                return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
1684
+            }
1685
+        )) {
1686
+        console.warn('This description does not look like Plan B.');
1687
+        return desc;
1688
+    }
1689
+
1690
+    // Make sure this Plan B SDP can be converted to a Unified Plan SDP.
1691
+    var bmids = [];
1692
+    session.media.forEach(function (m) {
1693
+            if(m.port !== 0) { //ignore disabled streams, these can be removed from the bundle
1694
+                bmids.push(m.mid);
1695
+            }
1696
+        }
1697
+    );
1698
+
1699
+    var hasBundle = false;
1700
+    if (typeof session.groups !== 'undefined' &&
1701
+        Array.isArray(session.groups)) {
1702
+        hasBundle = session.groups.every(function (g) {
1703
+                return g.type !== 'BUNDLE' ||
1704
+                    arrayEquals.apply(g.mids.sort(), [bmids.sort()]);
1705
+            }
1706
+        );
1707
+    }
1708
+
1709
+    if (!hasBundle) {
1710
+        throw new Error("Cannot convert to Unified Plan because m-lines that" +
1711
+            " are not bundled were found."
1712
+        );
1713
+    }
1714
+
1715
+    var localRef = null;
1716
+    if (typeof cache.local !== 'undefined')
1717
+        localRef = transform.parse(cache.local);
1718
+
1719
+    var remoteRef = null;
1720
+    if (typeof cache.remote !== 'undefined')
1721
+        remoteRef = transform.parse(cache.remote);
1722
+
1723
+
1724
+    var mLines = [];
1725
+
1726
+    session.media.forEach(function (bLine, index, lines) {
1727
+
1728
+        var uLine;
1729
+        var ssrc;
1730
+
1731
+        /*if ((typeof bLine.rtcpMux !== 'string' ||
1732
+            bLine.rtcpMux !== 'rtcp-mux') &&
1733
+            bLine.direction !== 'inactive') {
1734
+            throw new Error("Cannot convert to Unified Plan because m-lines " +
1735
+                "without the rtcp-mux attribute were found.");
1736
+        }*/
1737
+        if(bLine.port === 0) {
1738
+            // change the mid to the last used mid for this media type, for consistency
1739
+            if(localRef !== null && localRef.media.length > index) {
1740
+                bLine.mid = localRef.media[index].mid;
1741
+            }
1742
+            mLines.push(bLine);
1743
+            return;
1744
+        }
1745
+
1746
+        // if we're offering to recv-only on chrome, we won't have any ssrcs at all
1747
+        if (!bLine.sources) {
1748
+            uLine = copyObj(bLine);
1749
+            uLine.sources = {};
1750
+            uLine.mid = uLine.type + "-" + 1;
1751
+            mLines.push(uLine);
1752
+            return;
1753
+        }
1754
+
1755
+        var sources = bLine.sources || null;
1756
+
1757
+        if (!sources) {
1758
+            throw new Error("can't convert to unified plan - each m-line must have an ssrc");
1759
+        }
1760
+
1761
+        var ssrcGroups = bLine.ssrcGroups || [];
1762
+        bLine.rtcp.port = bLine.port;
1763
+
1764
+        var sourcesKeys = Object.keys(sources);
1765
+        if (sourcesKeys.length === 0) {
1766
+            return;
1767
+        }
1768
+        else if (sourcesKeys.length == 1) {
1769
+            ssrc = sourcesKeys[0];
1770
+            uLine = copyObj(bLine);
1771
+            uLine.mid = uLine.type + "-" + ssrc;
1772
+            mLines.push(uLine);
1773
+        }
1774
+        else {
1775
+            //we might need to split this line
1776
+            delete bLine.sources;
1777
+            delete bLine.ssrcGroups;
1778
+
1779
+            ssrcGroups.forEach(function (ssrcGroup) {
1780
+                //update in use ssrcs so we don't accidentally override it
1781
+                var primary = ssrcGroup.ssrcs[0];
1782
+                //use the first ssrc as the main ssrc for this m-line;
1783
+                var copyLine = copyObj(bLine);
1784
+                copyLine.sources = {};
1785
+                copyLine.sources[primary] = sources[primary];
1786
+                copyLine.mid = copyLine.type + "-" + primary;
1787
+                mLines.push(copyLine);
1788
+            });
1789
+        }
1790
+    });
1791
+
1792
+    if (desc.type === 'offer') {
1793
+        if (localRef) {
1794
+            // you can never remove media streams from SDP.
1795
+            while (mLines.length < localRef.media.length) {
1796
+                var copyline = localRef.media[mLines.length];
1797
+                copyline.port = 0;
1798
+                mLines.push(copyline);
1799
+            }
1800
+        }
1801
+    }
1802
+    else {
1803
+        //if we're answering, if the browser accepted the transformed plan b we passed it,
1804
+        //then we're implicitly accepting every stream.
1805
+        //Check all the offers mlines - if we're missing one, we need to add it to our unified plan in recvOnly.
1806
+        //in this case the far end will need to dynamically determine our real SSRC for the RTCP stream,
1807
+        //as chrome won't tell us!
1808
+
1809
+        if (remoteRef === undefined) {
1810
+            throw Error("remote cache required to generate answer?");
1811
+        }
1812
+        remoteRef.media.forEach(function(remoteline, index) {
1813
+            if(index < mLines.length) {
1814
+                // the line is already present in the plan-b, so will be handled correctly by the browser;
1815
+                return;
1816
+            }
1817
+            if(remoteline.mid === undefined) {
1818
+                console.warn("remote sdp has undefined mid attribute");
1819
+                return;
1820
+            }
1821
+            if(remoteline.port === 0) {
1822
+                var disabledline = {};
1823
+                disabledline.port = 0;
1824
+                disabledline.type = remoteline.type;
1825
+                disabledline.protocol = remoteline.protocol;
1826
+                disabledline.payloads = remoteline.payloads;
1827
+                disabledline.mid = remoteline.mid;
1828
+                if(!session.connection) {
1829
+                    if(mLines[0].connection) {
1830
+                        disabledline.connection = copyObj(mLines[0].connection);
1831
+                    } else {
1832
+                        throw Error("missing connection attribute from sdp");
1833
+                    }
1834
+                } else {
1835
+                    disabledline.connection = copyObj(session.connection);
1836
+                }
1837
+                disabledline.connection.ip = "0.0.0.0";
1838
+
1839
+                mLines.push(disabledline);
1840
+                console.log("added disabled m line to the media");
1841
+            }
1842
+            else {
1843
+                for(var i = 0; i < mLines.length; i ++) {
1844
+                    var typeref = mLines[i];
1845
+                    //check if we have any lines of the same type in the current answer to
1846
+                    // build this new line from.
1847
+                    if(typeref.type === remoteline.type) {
1848
+                        var linecopy = copyObj(typeref);
1849
+                        linecopy.mid = remoteline.mid;
1850
+                        linecopy.direction = "recvonly";
1851
+                        mLines.push(linecopy);
1852
+                        break;
1853
+                    }
1854
+                }
1855
+            }
1856
+        });
1857
+    }
1858
+
1859
+    session.media = mLines;
1860
+
1861
+    var mids = [];
1862
+    session.media.forEach(function (mLine) {
1863
+            mids.push(mLine.mid);
1864
+        }
1865
+    );
1866
+
1867
+    session.groups.some(function (group) {
1868
+            if (group.type === 'BUNDLE') {
1869
+                group.mids = mids.join(' ');
1870
+                return true;
1871
+            }
1872
+        }
1873
+    );
1874
+
1875
+
1876
+    // msid semantic
1877
+    session.msidSemantic = {
1878
+        semantic: 'WMS',
1879
+        token: '*'
1880
+    };
1881
+
1882
+    var resStr = transform.write(session);
1883
+    return new window.RTCSessionDescription({
1884
+            type: desc.type,
1885
+            sdp: resStr
1886
+        }
1887
+    );
1888
+};
1889
+},{"../array-equals":6,"../transform":11}],11:[function(require,module,exports){
1890
+/* Copyright @ 2015 Atlassian Pty Ltd
1891
+ *
1892
+ * Licensed under the Apache License, Version 2.0 (the "License");
1893
+ * you may not use this file except in compliance with the License.
1894
+ * You may obtain a copy of the License at
1895
+ *
1896
+ *     http://www.apache.org/licenses/LICENSE-2.0
1897
+ *
1898
+ * Unless required by applicable law or agreed to in writing, software
1899
+ * distributed under the License is distributed on an "AS IS" BASIS,
1900
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1901
+ * See the License for the specific language governing permissions and
1902
+ * limitations under the License.
1903
+ */
1904
+
1905
+var transform = require('sdp-transform');
1906
+
1907
+exports.write = function(session, opts) {
1908
+
1909
+  if (typeof session !== 'undefined' &&
1910
+      typeof session.media !== 'undefined' &&
1911
+      Array.isArray(session.media)) {
1912
+
1913
+    session.media.forEach(function (mLine) {
1914
+      // expand sources to ssrcs
1915
+      if (typeof mLine.sources !== 'undefined' &&
1916
+        Object.keys(mLine.sources).length !== 0) {
1917
+          mLine.ssrcs = [];
1918
+          Object.keys(mLine.sources).forEach(function (ssrc) {
1919
+            var source = mLine.sources[ssrc];
1920
+            Object.keys(source).forEach(function (attribute) {
1921
+              mLine.ssrcs.push({
1922
+                id: ssrc,
1923
+                attribute: attribute,
1924
+                value: source[attribute]
1925
+              });
1926
+            });
1927
+          });
1928
+          delete mLine.sources;
1929
+        }
1930
+
1931
+      // join ssrcs in ssrc groups
1932
+      if (typeof mLine.ssrcGroups !== 'undefined' &&
1933
+        Array.isArray(mLine.ssrcGroups)) {
1934
+          mLine.ssrcGroups.forEach(function (ssrcGroup) {
1935
+            if (typeof ssrcGroup.ssrcs !== 'undefined' &&
1936
+                Array.isArray(ssrcGroup.ssrcs)) {
1937
+              ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
1938
+            }
1939
+          });
1940
+        }
1941
+    });
1942
+  }
1943
+
1944
+  // join group mids
1945
+  if (typeof session !== 'undefined' &&
1946
+      typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
1947
+
1948
+    session.groups.forEach(function (g) {
1949
+      if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
1950
+        g.mids = g.mids.join(' ');
1951
+      }
1952
+    });
1953
+  }
1954
+
1955
+  return transform.write(session, opts);
1956
+};
1957
+
1958
+exports.parse = function(sdp) {
1959
+  var session = transform.parse(sdp);
1960
+
1961
+  if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
1962
+      Array.isArray(session.media)) {
1963
+
1964
+    session.media.forEach(function (mLine) {
1965
+      // group sources attributes by ssrc
1966
+      if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
1967
+        mLine.sources = {};
1968
+        mLine.ssrcs.forEach(function (ssrc) {
1969
+          if (!mLine.sources[ssrc.id])
1970
+          mLine.sources[ssrc.id] = {};
1971
+        mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
1972
+        });
1973
+
1974
+        delete mLine.ssrcs;
1975
+      }
1976
+
1977
+      // split ssrcs in ssrc groups
1978
+      if (typeof mLine.ssrcGroups !== 'undefined' &&
1979
+        Array.isArray(mLine.ssrcGroups)) {
1980
+          mLine.ssrcGroups.forEach(function (ssrcGroup) {
1981
+            if (typeof ssrcGroup.ssrcs === 'string') {
1982
+              ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
1983
+            }
1984
+          });
1985
+        }
1986
+    });
1987
+  }
1988
+  // split group mids
1989
+  if (typeof session !== 'undefined' &&
1990
+      typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
1991
+
1992
+    session.groups.forEach(function (g) {
1993
+      if (typeof g.mids === 'string') {
1994
+        g.mids = g.mids.split(' ');
1995
+      }
1996
+    });
1997
+  }
1998
+
1999
+  return session;
2000
+};
2001
+
2002
+
2003
+},{"sdp-transform":2}]},{},[5])(5)
2004
+});
0 2005
\ No newline at end of file