github.com/cnotch/ipchub@v1.1.0/demos/rtsp/rtsp.dev.js (about)

     1  (function () {
     2      'use strict';
     3  
     4      // ERROR=0, WARN=1, LOG=2, DEBUG=3
     5      const LogLevel = {
     6          Error: 0,
     7          Warn: 1,
     8          Log: 2,
     9          Debug: 3
    10      };
    11  
    12      let DEFAULT_LOG_LEVEL = LogLevel.Debug;
    13  
    14      function setDefaultLogLevel(level) {
    15          DEFAULT_LOG_LEVEL = level;
    16      }
    17      class Logger {
    18          constructor(level = DEFAULT_LOG_LEVEL, tag) {
    19              this.tag = tag;
    20              this.setLevel(level);
    21          }
    22          
    23          setLevel(level) {
    24              this.level = level;
    25          }
    26          
    27          static get level_map() { return {
    28              [LogLevel.Debug]:'log',
    29              [LogLevel.Log]:'log',
    30              [LogLevel.Warn]:'warn',
    31              [LogLevel.Error]:'error'
    32          }};
    33  
    34          _log(lvl, args) {
    35              args = Array.prototype.slice.call(args);
    36              if (this.tag) {
    37                  args.unshift(`[${this.tag}]`);
    38              }
    39              if (this.level>=lvl) console[Logger.level_map[lvl]].apply(console, args);
    40          }
    41          log(){
    42              this._log(LogLevel.Log, arguments);
    43          }
    44          debug(){
    45              this._log(LogLevel.Debug, arguments);
    46          }
    47          error(){
    48              this._log(LogLevel.Error, arguments);
    49          }
    50          warn(){
    51              this._log(LogLevel.Warn, arguments);
    52          }
    53      }
    54  
    55      const taggedLoggers = new Map();
    56      function getTagged(tag) {
    57          if (!taggedLoggers.has(tag)) {
    58              taggedLoggers.set(tag, new Logger(DEFAULT_LOG_LEVEL, tag));
    59          }
    60          return taggedLoggers.get(tag);
    61      }
    62      const Log = new Logger();
    63  
    64      class Url {
    65          static parse(url) {
    66              let ret = {};
    67  
    68              let regex = /^([^:]+):\/\/([^\/]+)(.*)$/;  //protocol, login, urlpath
    69              let result = regex.exec(url);
    70  
    71              if (!result) {
    72                  throw new Error("bad url");
    73              }
    74  
    75              ret.full = url;
    76              ret.protocol = result[1];
    77              ret.urlpath = result[3];
    78  
    79              let parts = ret.urlpath.split('/');
    80              ret.basename = parts.pop().split(/\?|#/)[0];
    81              ret.basepath = parts.join('/');
    82  
    83              let loginSplit = result[2].split('@');
    84              let hostport = loginSplit[0].split(':');
    85              let userpass = [ null, null ];
    86              if (loginSplit.length === 2) {
    87                  userpass = loginSplit[0].split(':');
    88                  hostport = loginSplit[1].split(':');
    89              }
    90  
    91              ret.user = userpass[0];
    92              ret.pass = userpass[1];
    93              ret.host = hostport[0];
    94              ret.auth = (ret.user && ret.pass) ? `${ret.user}:${ret.pass}` : '';
    95  
    96              ret.port = (null == hostport[1]) ? Url.protocolDefaultPort(ret.protocol) : hostport[1];
    97              ret.portDefined = (null != hostport[1]);
    98              ret.location = `${ret.host}:${ret.port}`;
    99  
   100              if (ret.protocol == 'unix') {
   101                  ret.socket = ret.port;
   102                  ret.port = undefined;
   103              }
   104  
   105              return ret;
   106          }
   107  
   108          static full(parsed) {
   109              return `${parsed.protocol}://${parsed.location}/${parsed.urlpath}`;
   110          }
   111  
   112          static isAbsolute(url) {
   113              return /^[^:]+:\/\//.test(url);
   114          }
   115  
   116          static protocolDefaultPort(protocol) {
   117              switch (protocol) {
   118                  case 'rtsp': return 554;
   119                  case 'http': return 80;
   120                  case 'https': return 443;
   121              }
   122  
   123              return 0;
   124          }
   125      }
   126  
   127      const listener = Symbol("event_listener");
   128      const listeners = Symbol("event_listeners");
   129  
   130      class DestructibleEventListener {
   131          constructor(eventListener) {
   132              this[listener] = eventListener;
   133              this[listeners] = new Map();
   134          }
   135  
   136          clear() {
   137              if (this[listeners]) {
   138                  for (let entry of this[listeners]) {
   139                      for (let fn of entry[1]) {
   140                          this[listener].removeEventListener(entry[0], fn);
   141                      }
   142                  }        }
   143              this[listeners].clear();
   144          }
   145  
   146          destroy() {
   147              this.clear();
   148              this[listeners] = null;
   149          }
   150  
   151          on(event, selector, fn) {
   152              if (fn == undefined) {
   153                  fn = selector;
   154                  selector = null;
   155              }
   156              if (selector) {
   157                  return this.addEventListener(event, (e) => {
   158                      if (e.target.matches(selector)) {
   159                          fn(e);
   160                      }
   161                  });
   162              } else {
   163                  return this.addEventListener(event, fn);
   164              }
   165          }
   166  
   167          addEventListener(event, fn) {
   168              if (!this[listeners].has(event)) {
   169                  this[listeners].set(event, new Set());
   170              }
   171              this[listeners].get(event).add(fn);
   172              this[listener].addEventListener(event, fn, false);
   173              return fn;
   174          }
   175  
   176          removeEventListener(event, fn) {
   177              this[listener].removeEventListener(event, fn, false);
   178              if (this[listeners].has(event)) {
   179                  //this[listeners].set(event, new Set());
   180                  let ev = this[listeners].get(event);
   181                  ev.delete(fn);
   182                  if (!ev.size) {
   183                      this[listeners].delete(event);
   184                  }
   185              }
   186          }
   187  
   188          dispatchEvent(event) {
   189              if (this[listener]) {
   190                  this[listener].dispatchEvent(event);
   191              }
   192          }
   193      }
   194  
   195      class EventEmitter {
   196          constructor(element=null) {
   197              this[listener] = new DestructibleEventListener(element || document.createElement('div'));
   198          }
   199  
   200          clear() {
   201              if (this[listener]) {
   202                  this[listener].clear();
   203              }
   204          }
   205  
   206          destroy() {
   207              if (this[listener]) {
   208                  this[listener].destroy();
   209                  this[listener] = null;
   210              }
   211          }
   212  
   213          on(event, selector, fn) {
   214              if (this[listener]) {
   215                  return this[listener].on(event, selector, fn);
   216              }
   217              return null;
   218          }
   219  
   220          addEventListener(event, fn) {
   221              if (this[listener]) {
   222                  return this[listener].addEventListener(event, fn, false);
   223              }
   224              return null;
   225          }
   226  
   227          removeEventListener(event, fn) {
   228              if (this[listener]) {
   229                  this[listener].removeEventListener(event, fn, false);
   230              }
   231          }
   232  
   233          dispatchEvent(event, data) {
   234              if (this[listener]) {
   235                  this[listener].dispatchEvent(new CustomEvent(event, {detail: data}));
   236              }
   237          }
   238      }
   239  
   240      class EventSourceWrapper {
   241          constructor(eventSource) {
   242              this.eventSource = eventSource;
   243              this[listeners] = new Map();
   244          }
   245  
   246          on(event, selector, fn) {
   247              if (!this[listeners].has(event)) {
   248                  this[listeners].set(event, new Set());
   249              }
   250              let listener = this.eventSource.on(event, selector, fn);
   251              if (listener) {
   252                  this[listeners].get(event).add(listener);
   253              }
   254          }
   255  
   256          off(event, fn){
   257              this.eventSource.removeEventListener(event, fn);
   258          }
   259  
   260          clear() {
   261              this.eventSource.clear();
   262              this[listeners].clear();
   263          }
   264  
   265          destroy() {
   266              this.eventSource.clear();
   267              this[listeners] = null;
   268              this.eventSource = null;
   269          }
   270      }
   271  
   272      /**
   273       * Generate MP4 Box
   274       * got from: https://github.com/dailymotion/hls.js
   275       */
   276  
   277      class MP4 {
   278          static init() {
   279              MP4.types = {
   280                  avc1: [], // codingname
   281                  avcC: [],
   282                  btrt: [],
   283                  dinf: [],
   284                  dref: [],
   285                  esds: [],
   286                  ftyp: [],
   287                  hdlr: [],
   288                  mdat: [],
   289                  mdhd: [],
   290                  mdia: [],
   291                  mfhd: [],
   292                  minf: [],
   293                  moof: [],
   294                  moov: [],
   295                  mp4a: [],
   296                  mvex: [],
   297                  mvhd: [],
   298                  sdtp: [],
   299                  stbl: [],
   300                  stco: [],
   301                  stsc: [],
   302                  stsd: [],
   303                  stsz: [],
   304                  stts: [],
   305                  tfdt: [],
   306                  tfhd: [],
   307                  traf: [],
   308                  trak: [],
   309                  trun: [],
   310                  trex: [],
   311                  tkhd: [],
   312                  vmhd: [],
   313                  smhd: []
   314              };
   315  
   316              var i;
   317              for (i in MP4.types) {
   318                  if (MP4.types.hasOwnProperty(i)) {
   319                      MP4.types[i] = [
   320                          i.charCodeAt(0),
   321                          i.charCodeAt(1),
   322                          i.charCodeAt(2),
   323                          i.charCodeAt(3)
   324                      ];
   325                  }
   326              }
   327  
   328              var videoHdlr = new Uint8Array([
   329                  0x00, // version 0
   330                  0x00, 0x00, 0x00, // flags
   331                  0x00, 0x00, 0x00, 0x00, // pre_defined
   332                  0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
   333                  0x00, 0x00, 0x00, 0x00, // reserved
   334                  0x00, 0x00, 0x00, 0x00, // reserved
   335                  0x00, 0x00, 0x00, 0x00, // reserved
   336                  0x56, 0x69, 0x64, 0x65,
   337                  0x6f, 0x48, 0x61, 0x6e,
   338                  0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
   339              ]);
   340  
   341              var audioHdlr = new Uint8Array([
   342                  0x00, // version 0
   343                  0x00, 0x00, 0x00, // flags
   344                  0x00, 0x00, 0x00, 0x00, // pre_defined
   345                  0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
   346                  0x00, 0x00, 0x00, 0x00, // reserved
   347                  0x00, 0x00, 0x00, 0x00, // reserved
   348                  0x00, 0x00, 0x00, 0x00, // reserved
   349                  0x53, 0x6f, 0x75, 0x6e,
   350                  0x64, 0x48, 0x61, 0x6e,
   351                  0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
   352              ]);
   353  
   354              MP4.HDLR_TYPES = {
   355                  'video': videoHdlr,
   356                  'audio': audioHdlr
   357              };
   358  
   359              var dref = new Uint8Array([
   360                  0x00, // version 0
   361                  0x00, 0x00, 0x00, // flags
   362                  0x00, 0x00, 0x00, 0x01, // entry_count
   363                  0x00, 0x00, 0x00, 0x0c, // entry_size
   364                  0x75, 0x72, 0x6c, 0x20, // 'url' type
   365                  0x00, // version 0
   366                  0x00, 0x00, 0x01 // entry_flags
   367              ]);
   368  
   369              var stco = new Uint8Array([
   370                  0x00, // version
   371                  0x00, 0x00, 0x00, // flags
   372                  0x00, 0x00, 0x00, 0x00 // entry_count
   373              ]);
   374  
   375              MP4.STTS = MP4.STSC = MP4.STCO = stco;
   376  
   377              MP4.STSZ = new Uint8Array([
   378                  0x00, // version
   379                  0x00, 0x00, 0x00, // flags
   380                  0x00, 0x00, 0x00, 0x00, // sample_size
   381                  0x00, 0x00, 0x00, 0x00, // sample_count
   382              ]);
   383              MP4.VMHD = new Uint8Array([
   384                  0x00, // version
   385                  0x00, 0x00, 0x01, // flags
   386                  0x00, 0x00, // graphicsmode
   387                  0x00, 0x00,
   388                  0x00, 0x00,
   389                  0x00, 0x00 // opcolor
   390              ]);
   391              MP4.SMHD = new Uint8Array([
   392                  0x00, // version
   393                  0x00, 0x00, 0x00, // flags
   394                  0x00, 0x00, // balance
   395                  0x00, 0x00 // reserved
   396              ]);
   397  
   398              MP4.STSD = new Uint8Array([
   399                  0x00, // version 0
   400                  0x00, 0x00, 0x00, // flags
   401                  0x00, 0x00, 0x00, 0x01]);// entry_count
   402  
   403              var majorBrand = new Uint8Array([105,115,111,109]); // isom
   404              var avc1Brand = new Uint8Array([97,118,99,49]); // avc1
   405              var minorVersion = new Uint8Array([0, 0, 0, 1]);
   406  
   407              MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
   408              MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
   409          }
   410  
   411          static box(type, ...payload) {
   412              var size = 8,
   413                  i = payload.length,
   414                  len = i,
   415                  result;
   416              // calculate the total size we need to allocate
   417              while (i--) {
   418                  size += payload[i].byteLength;
   419              }
   420              result = new Uint8Array(size);
   421              result[0] = (size >> 24) & 0xff;
   422              result[1] = (size >> 16) & 0xff;
   423              result[2] = (size >> 8) & 0xff;
   424              result[3] = size  & 0xff;
   425              result.set(type, 4);
   426              // copy the payload into the result
   427              for (i = 0, size = 8; i < len; ++i) {
   428                  // copy payload[i] array @ offset size
   429                  result.set(payload[i], size);
   430                  size += payload[i].byteLength;
   431              }
   432              return result;
   433          }
   434  
   435          static hdlr(type) {
   436              return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
   437          }
   438  
   439          static mdat(data) {
   440              return MP4.box(MP4.types.mdat, data);
   441          }
   442  
   443          static mdhd(timescale, duration) {
   444              return MP4.box(MP4.types.mdhd, new Uint8Array([
   445                  0x00, // version 0
   446                  0x00, 0x00, 0x00, // flags
   447                  0x00, 0x00, 0x00, 0x02, // creation_time
   448                  0x00, 0x00, 0x00, 0x03, // modification_time
   449                  (timescale >> 24) & 0xFF,
   450                  (timescale >> 16) & 0xFF,
   451                  (timescale >>  8) & 0xFF,
   452                  timescale & 0xFF, // timescale
   453                  (duration >> 24),
   454                  (duration >> 16) & 0xFF,
   455                  (duration >>  8) & 0xFF,
   456                  duration & 0xFF, // duration
   457                  0x55, 0xc4, // 'und' language (undetermined)
   458                  0x00, 0x00
   459              ]));
   460          }
   461  
   462          static mdia(track) {
   463              return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track));
   464          }
   465  
   466          static mfhd(sequenceNumber) {
   467              return MP4.box(MP4.types.mfhd, new Uint8Array([
   468                  0x00,
   469                  0x00, 0x00, 0x00, // flags
   470                  (sequenceNumber >> 24),
   471                  (sequenceNumber >> 16) & 0xFF,
   472                  (sequenceNumber >>  8) & 0xFF,
   473                  sequenceNumber & 0xFF, // sequence_number
   474              ]));
   475          }
   476  
   477          static minf(track) {
   478              if (track.type === 'audio') {
   479                  return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));
   480              } else {
   481                  return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
   482              }
   483          }
   484  
   485          static moof(sn, baseMediaDecodeTime, track) {
   486              return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track,baseMediaDecodeTime));
   487          }
   488          /**
   489           * @param tracks... (optional) {array} the tracks associated with this movie
   490           */
   491          static moov(tracks, duration, timescale) {
   492              var
   493                  i = tracks.length,
   494                  boxes = [];
   495  
   496              while (i--) {
   497                  boxes[i] = MP4.trak(tracks[i]);
   498              }
   499  
   500              return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(timescale, duration)].concat(boxes).concat(MP4.mvex(tracks)));
   501          }
   502  
   503          static mvex(tracks) {
   504              var
   505                  i = tracks.length,
   506                  boxes = [];
   507  
   508              while (i--) {
   509                  boxes[i] = MP4.trex(tracks[i]);
   510              }
   511              return MP4.box.apply(null, [MP4.types.mvex].concat(boxes));
   512          }
   513  
   514          static mvhd(timescale,duration) {
   515              var
   516                  bytes = new Uint8Array([
   517                      0x00, // version 0
   518                      0x00, 0x00, 0x00, // flags
   519                      0x00, 0x00, 0x00, 0x01, // creation_time
   520                      0x00, 0x00, 0x00, 0x02, // modification_time
   521                      (timescale >> 24) & 0xFF,
   522                      (timescale >> 16) & 0xFF,
   523                      (timescale >>  8) & 0xFF,
   524                      timescale & 0xFF, // timescale
   525                      (duration >> 24) & 0xFF,
   526                      (duration >> 16) & 0xFF,
   527                      (duration >>  8) & 0xFF,
   528                      duration & 0xFF, // duration
   529                      0x00, 0x01, 0x00, 0x00, // 1.0 rate
   530                      0x01, 0x00, // 1.0 volume
   531                      0x00, 0x00, // reserved
   532                      0x00, 0x00, 0x00, 0x00, // reserved
   533                      0x00, 0x00, 0x00, 0x00, // reserved
   534                      0x00, 0x01, 0x00, 0x00,
   535                      0x00, 0x00, 0x00, 0x00,
   536                      0x00, 0x00, 0x00, 0x00,
   537                      0x00, 0x00, 0x00, 0x00,
   538                      0x00, 0x01, 0x00, 0x00,
   539                      0x00, 0x00, 0x00, 0x00,
   540                      0x00, 0x00, 0x00, 0x00,
   541                      0x00, 0x00, 0x00, 0x00,
   542                      0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
   543                      0x00, 0x00, 0x00, 0x00,
   544                      0x00, 0x00, 0x00, 0x00,
   545                      0x00, 0x00, 0x00, 0x00,
   546                      0x00, 0x00, 0x00, 0x00,
   547                      0x00, 0x00, 0x00, 0x00,
   548                      0x00, 0x00, 0x00, 0x00, // pre_defined
   549                      0xff, 0xff, 0xff, 0xff // next_track_ID
   550                  ]);
   551              return MP4.box(MP4.types.mvhd, bytes);
   552          }
   553  
   554          static sdtp(track) {
   555              var
   556                  samples = track.samples || [],
   557                  bytes = new Uint8Array(4 + samples.length),
   558                  flags,
   559                  i;
   560              // leave the full box header (4 bytes) all zero
   561              // write the sample table
   562              for (i = 0; i < samples.length; i++) {
   563                  flags = samples[i].flags;
   564                  bytes[i + 4] = (flags.dependsOn << 4) |
   565                      (flags.isDependedOn << 2) |
   566                      (flags.hasRedundancy);
   567              }
   568  
   569              return MP4.box(MP4.types.sdtp, bytes);
   570          }
   571  
   572          static stbl(track) {
   573              return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));
   574          }
   575  
   576          static avc1(track) {
   577              var sps = [], pps = [], i, data, len;
   578              // assemble the SPSs
   579  
   580              for (i = 0; i < track.sps.length; i++) {
   581                  data = track.sps[i];
   582                  len = data.byteLength;
   583                  sps.push((len >>> 8) & 0xFF);
   584                  sps.push((len & 0xFF));
   585                  sps = sps.concat(Array.prototype.slice.call(data)); // SPS
   586              }
   587  
   588              // assemble the PPSs
   589              for (i = 0; i < track.pps.length; i++) {
   590                  data = track.pps[i];
   591                  len = data.byteLength;
   592                  pps.push((len >>> 8) & 0xFF);
   593                  pps.push((len & 0xFF));
   594                  pps = pps.concat(Array.prototype.slice.call(data));
   595              }
   596  
   597              var avcc = MP4.box(MP4.types.avcC, new Uint8Array([
   598                      0x01,   // version
   599                      sps[3], // profile
   600                      sps[4], // profile compat
   601                      sps[5], // level
   602                      0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes
   603                      0xE0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets
   604                  ].concat(sps).concat([
   605                      track.pps.length // numOfPictureParameterSets
   606                  ]).concat(pps))), // "PPS"
   607                  width = track.width,
   608                  height = track.height;
   609              //console.log('avcc:' + Hex.hexDump(avcc));
   610              return MP4.box(MP4.types.avc1, new Uint8Array([
   611                      0x00, 0x00, 0x00, // reserved
   612                      0x00, 0x00, 0x00, // reserved
   613                      0x00, 0x01, // data_reference_index
   614                      0x00, 0x00, // pre_defined
   615                      0x00, 0x00, // reserved
   616                      0x00, 0x00, 0x00, 0x00,
   617                      0x00, 0x00, 0x00, 0x00,
   618                      0x00, 0x00, 0x00, 0x00, // pre_defined
   619                      (width >> 8) & 0xFF,
   620                      width & 0xff, // width
   621                      (height >> 8) & 0xFF,
   622                      height & 0xff, // height
   623                      0x00, 0x48, 0x00, 0x00, // horizresolution
   624                      0x00, 0x48, 0x00, 0x00, // vertresolution
   625                      0x00, 0x00, 0x00, 0x00, // reserved
   626                      0x00, 0x01, // frame_count
   627                      0x12,
   628                      0x62, 0x69, 0x6E, 0x65, //binelpro.ru
   629                      0x6C, 0x70, 0x72, 0x6F,
   630                      0x2E, 0x72, 0x75, 0x00,
   631                      0x00, 0x00, 0x00, 0x00,
   632                      0x00, 0x00, 0x00, 0x00,
   633                      0x00, 0x00, 0x00, 0x00,
   634                      0x00, 0x00, 0x00, 0x00,
   635                      0x00, 0x00, 0x00, // compressorname
   636                      0x00, 0x18,   // depth = 24
   637                      0x11, 0x11]), // pre_defined = -1
   638                  avcc,
   639                  MP4.box(MP4.types.btrt, new Uint8Array([
   640                      0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
   641                      0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
   642                      0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
   643              );
   644          }
   645  
   646          static esds(track) {
   647              var configlen = track.config.byteLength;
   648              let data = new Uint8Array(26+configlen+3);
   649              data.set([
   650                  0x00, // version 0
   651                  0x00, 0x00, 0x00, // flags
   652  
   653                  0x03, // descriptor_type
   654                  0x17+configlen, // length
   655                  0x00, 0x01, //es_id
   656                  0x00, // stream_priority
   657  
   658                  0x04, // descriptor_type
   659                  0x0f+configlen, // length
   660                  0x40, //codec : mpeg4_audio
   661                  0x15, // stream_type
   662                  0x00, 0x00, 0x00, // buffer_size
   663                  0x00, 0x00, 0x00, 0x00, // maxBitrate
   664                  0x00, 0x00, 0x00, 0x00, // avgBitrate
   665  
   666                  0x05, // descriptor_type
   667                  configlen
   668              ]);
   669              data.set(track.config, 26);
   670              data.set([0x06, 0x01, 0x02], 26+configlen);
   671              // return new Uint8Array([
   672              //     0x00, // version 0
   673              //     0x00, 0x00, 0x00, // flags
   674              //
   675              //     0x03, // descriptor_type
   676              //     0x17+configlen, // length
   677              //     0x00, 0x01, //es_id
   678              //     0x00, // stream_priority
   679              //
   680              //     0x04, // descriptor_type
   681              //     0x0f+configlen, // length
   682              //     0x40, //codec : mpeg4_audio
   683              //     0x15, // stream_type
   684              //     0x00, 0x00, 0x00, // buffer_size
   685              //     0x00, 0x00, 0x00, 0x00, // maxBitrate
   686              //     0x00, 0x00, 0x00, 0x00, // avgBitrate
   687              //
   688              //     0x05 // descriptor_type
   689              // ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
   690              return data;
   691          }
   692  
   693          static mp4a(track) {
   694              var audiosamplerate = track.audiosamplerate;
   695              return MP4.box(MP4.types.mp4a, new Uint8Array([
   696                      0x00, 0x00, 0x00, // reserved
   697                      0x00, 0x00, 0x00, // reserved
   698                      0x00, 0x01, // data_reference_index
   699                      0x00, 0x00, 0x00, 0x00,
   700                      0x00, 0x00, 0x00, 0x00, // reserved
   701                      0x00, track.channelCount, // channelcount
   702                      0x00, 0x10, // sampleSize:16bits
   703                      0x00, 0x00, // pre_defined
   704                      0x00, 0x00, // reserved2
   705                      (audiosamplerate >> 8) & 0xFF,
   706                      audiosamplerate & 0xff, //
   707                      0x00, 0x00]),
   708                  MP4.box(MP4.types.esds, MP4.esds(track)));
   709          }
   710  
   711          static stsd(track) {
   712              if (track.type === 'audio') {
   713                  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
   714              } else {
   715                  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
   716              }
   717          }
   718  
   719          static tkhd(track) {
   720              var id = track.id,
   721                  duration = track.duration,
   722                  width = track.width,
   723                  height = track.height,
   724                  volume = track.volume;
   725              return MP4.box(MP4.types.tkhd, new Uint8Array([
   726                  0x00, // version 0
   727                  0x00, 0x00, 0x07, // flags
   728                  0x00, 0x00, 0x00, 0x00, // creation_time
   729                  0x00, 0x00, 0x00, 0x00, // modification_time
   730                  (id >> 24) & 0xFF,
   731                  (id >> 16) & 0xFF,
   732                  (id >> 8) & 0xFF,
   733                  id & 0xFF, // track_ID
   734                  0x00, 0x00, 0x00, 0x00, // reserved
   735                  (duration >> 24),
   736                  (duration >> 16) & 0xFF,
   737                  (duration >>  8) & 0xFF,
   738                  duration & 0xFF, // duration
   739                  0x00, 0x00, 0x00, 0x00,
   740                  0x00, 0x00, 0x00, 0x00, // reserved
   741                  0x00, 0x00, // layer
   742                  0x00, 0x00, // alternate_group
   743                  (volume>>0)&0xff, (((volume%1)*10)>>0)&0xff, // track volume // FIXME
   744                  0x00, 0x00, // reserved
   745                  0x00, 0x01, 0x00, 0x00,
   746                  0x00, 0x00, 0x00, 0x00,
   747                  0x00, 0x00, 0x00, 0x00,
   748                  0x00, 0x00, 0x00, 0x00,
   749                  0x00, 0x01, 0x00, 0x00,
   750                  0x00, 0x00, 0x00, 0x00,
   751                  0x00, 0x00, 0x00, 0x00,
   752                  0x00, 0x00, 0x00, 0x00,
   753                  0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
   754                  (width >> 8) & 0xFF,
   755                  width & 0xFF,
   756                  0x00, 0x00, // width
   757                  (height >> 8) & 0xFF,
   758                  height & 0xFF,
   759                  0x00, 0x00 // height
   760              ]));
   761          }
   762  
   763          static traf(track,baseMediaDecodeTime) {
   764              var sampleDependencyTable = MP4.sdtp(track),
   765                  id = track.id;
   766              return MP4.box(MP4.types.traf,
   767                  MP4.box(MP4.types.tfhd, new Uint8Array([
   768                      0x00, // version 0
   769                      0x00, 0x00, 0x00, // flags
   770                      (id >> 24),
   771                      (id >> 16) & 0XFF,
   772                      (id >> 8) & 0XFF,
   773                      (id & 0xFF) // track_ID
   774                  ])),
   775                  MP4.box(MP4.types.tfdt, new Uint8Array([
   776                      0x00, // version 0
   777                      0x00, 0x00, 0x00, // flags
   778                      (baseMediaDecodeTime >>24),
   779                      (baseMediaDecodeTime >> 16) & 0XFF,
   780                      (baseMediaDecodeTime >> 8) & 0XFF,
   781                      (baseMediaDecodeTime & 0xFF) // baseMediaDecodeTime
   782                  ])),
   783                  MP4.trun(track,
   784                      sampleDependencyTable.length +
   785                      16 + // tfhd
   786                      16 + // tfdt
   787                      8 +  // traf header
   788                      16 + // mfhd
   789                      8 +  // moof header
   790                      8),  // mdat header
   791                  sampleDependencyTable);
   792          }
   793  
   794          /**
   795           * Generate a track box.
   796           * @param track {object} a track definition
   797           * @return {Uint8Array} the track box
   798           */
   799          static trak(track) {
   800              track.duration = track.duration || 0xffffffff;
   801              return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
   802          }
   803  
   804          static trex(track) {
   805              var id = track.id;
   806              return MP4.box(MP4.types.trex, new Uint8Array([
   807                  0x00, // version 0
   808                  0x00, 0x00, 0x00, // flags
   809                  (id >> 24),
   810                  (id >> 16) & 0XFF,
   811                  (id >> 8) & 0XFF,
   812                  (id & 0xFF), // track_ID
   813                  0x00, 0x00, 0x00, 0x01, // default_sample_description_index
   814                  0x00, 0x00, 0x00, 0x00, // default_sample_duration
   815                  0x00, 0x00, 0x00, 0x00, // default_sample_size
   816                  0x00, 0x01, 0x00, 0x01 // default_sample_flags
   817              ]));
   818          }
   819  
   820          static trun(track, offset) {
   821              var samples= track.samples || [],
   822                  len = samples.length,
   823                  arraylen = 12 + (16 * len),
   824                  array = new Uint8Array(arraylen),
   825                  i,sample,duration,size,flags,cts;
   826              offset += 8 + arraylen;
   827              array.set([
   828                  0x00, // version 0
   829                  0x00, 0x0f, 0x01, // flags
   830                  (len >>> 24) & 0xFF,
   831                  (len >>> 16) & 0xFF,
   832                  (len >>> 8) & 0xFF,
   833                  len & 0xFF, // sample_count
   834                  (offset >>> 24) & 0xFF,
   835                  (offset >>> 16) & 0xFF,
   836                  (offset >>> 8) & 0xFF,
   837                  offset & 0xFF // data_offset
   838              ],0);
   839              for (i = 0; i < len; i++) {
   840                  sample = samples[i];
   841                  duration = sample.duration;
   842                  size = sample.size;
   843                  flags = sample.flags;
   844                  cts = sample.cts;
   845                  array.set([
   846                      (duration >>> 24) & 0xFF,
   847                      (duration >>> 16) & 0xFF,
   848                      (duration >>> 8) & 0xFF,
   849                      duration & 0xFF, // sample_duration
   850                      (size >>> 24) & 0xFF,
   851                      (size >>> 16) & 0xFF,
   852                      (size >>> 8) & 0xFF,
   853                      size & 0xFF, // sample_size
   854                      (flags.isLeading << 2) | flags.dependsOn,
   855                      (flags.isDependedOn << 6) |
   856                      (flags.hasRedundancy << 4) |
   857                      (flags.paddingValue << 1) |
   858                      flags.isNonSync,
   859                      flags.degradPrio & 0xF0 << 8,
   860                      flags.degradPrio & 0x0F, // sample_flags
   861                      (cts >>> 24) & 0xFF,
   862                      (cts >>> 16) & 0xFF,
   863                      (cts >>> 8) & 0xFF,
   864                      cts & 0xFF // sample_composition_time_offset
   865                  ],12+16*i);
   866              }
   867              return MP4.box(MP4.types.trun, array);
   868          }
   869  
   870          static initSegment(tracks, duration, timescale) {
   871              if (!MP4.types) {
   872                  MP4.init();
   873              }
   874              var movie = MP4.moov(tracks, duration, timescale), result;
   875              result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
   876              result.set(MP4.FTYP);
   877              result.set(movie, MP4.FTYP.byteLength);
   878              return result;
   879          }
   880      }
   881  
   882      //import {MP4Inspect} from '../iso-bmff/mp4-inspector.js';
   883  
   884      const LOG_TAG = "mse";
   885      const Log$1 = getTagged(LOG_TAG);
   886  
   887      class MSEBuffer {
   888          constructor(parent, codec) {
   889              this.mediaSource = parent.mediaSource;
   890              this.players = parent.players;
   891              this.cleaning = false;
   892              this.parent = parent;
   893              this.queue = [];
   894              this.cleanResolvers = [];
   895              this.codec = codec;
   896              this.cleanRanges = [];
   897  
   898              Log$1.debug(`Use codec: ${codec}`);
   899  
   900              this.sourceBuffer = this.mediaSource.addSourceBuffer(codec);
   901              this.eventSource = new EventEmitter(this.sourceBuffer);
   902  
   903              this.eventSource.addEventListener('updatestart', (e)=> {
   904                  // this.updating = true;
   905                  // Log.debug('update start');
   906                  if (this.cleaning) {
   907                      Log$1.debug(`${this.codec} cleaning start`);
   908                  }
   909              });
   910  
   911              this.eventSource.addEventListener('update', (e)=> {
   912                  // this.updating = true;
   913                  if (this.cleaning) {
   914                      Log$1.debug(`${this.codec} cleaning update`);
   915                  }
   916              });
   917  
   918              this.eventSource.addEventListener('updateend', (e)=> {
   919                  // Log.debug('update end');
   920                  // this.updating = false;
   921                  if (this.cleaning) {
   922                      Log$1.debug(`${this.codec} cleaning end`);
   923  
   924                      try {
   925                          if (this.sourceBuffer.buffered.length && this.players[0].currentTime < this.sourceBuffer.buffered.start(0)) {
   926                              this.players[0].currentTime = this.sourceBuffer.buffered.start(0);
   927                          }
   928                      } catch (e) {
   929                          // TODO: do something?
   930                      }
   931                      while (this.cleanResolvers.length) {
   932                          let resolver = this.cleanResolvers.shift();
   933                          resolver();
   934                      }
   935                      this.cleaning = false;
   936  
   937                      if (this.cleanRanges.length) {
   938                          this.doCleanup();
   939                          return;
   940                      }
   941                  }
   942                  this.feedNext();
   943              });
   944  
   945              this.eventSource.addEventListener('error', (e)=> {
   946                  Log$1.debug(`Source buffer error: ${this.mediaSource.readyState}`);
   947                  if (this.mediaSource.sourceBuffers.length) {
   948                      this.mediaSource.removeSourceBuffer(this.sourceBuffer);
   949                  }
   950                  this.parent.eventSource.dispatchEvent('error');
   951              });
   952  
   953              this.eventSource.addEventListener('abort', (e)=> {
   954                  Log$1.debug(`Source buffer aborted: ${this.mediaSource.readyState}`);
   955                  if (this.mediaSource.sourceBuffers.length) {
   956                      this.mediaSource.removeSourceBuffer(this.sourceBuffer);
   957                  }
   958                  this.parent.eventSource.dispatchEvent('error');
   959              });
   960  
   961              if (!this.sourceBuffer.updating) {
   962                  this.feedNext();
   963              }
   964              // TODO: cleanup every hour for live streams
   965          }
   966  
   967          destroy() {
   968              this.eventSource.destroy();
   969              this.clear();
   970              this.queue = [];
   971              this.mediaSource.removeSourceBuffer(this.sourceBuffer);
   972          }
   973  
   974          clear() {
   975              this.queue = [];
   976              let promises = [];
   977              for (let i=0; i< this.sourceBuffer.buffered.length; ++i) {
   978                  // TODO: await remove
   979                  this.cleaning = true;
   980                  promises.push(new Promise((resolve, reject)=>{
   981                      this.cleanResolvers.push(resolve);
   982                      if (!this.sourceBuffer.updating) {
   983                          this.sourceBuffer.remove(this.sourceBuffer.buffered.start(i), this.sourceBuffer.buffered.end(i));
   984                          resolve();
   985                      } else {
   986                          this.sourceBuffer.onupdateend = () => {
   987                              if (this.sourceBuffer) {
   988                                  this.sourceBuffer.remove(this.sourceBuffer.buffered.start(i), this.sourceBuffer.buffered.end(i));
   989                              }
   990                              resolve();
   991                          };
   992                      }
   993                  }));
   994              }
   995              return Promise.all(promises);
   996          }
   997  
   998          setLive(is_live) {
   999              this.is_live = is_live;
  1000          }
  1001  
  1002          feedNext() {
  1003              // Log.debug("feed next ", this.sourceBuffer.updating);
  1004              if (!this.sourceBuffer.updating && !this.cleaning && this.queue.length) {
  1005                  this.doAppend(this.queue.shift());
  1006                  // TODO: if is live and current position > 1hr => clean all and restart
  1007              }
  1008          }
  1009  
  1010          doCleanup() {
  1011              if (!this.cleanRanges.length) {
  1012                  this.cleaning = false;
  1013                  this.feedNext();
  1014                  return;
  1015              }
  1016              let range = this.cleanRanges.shift();
  1017              Log$1.debug(`${this.codec} remove range [${range[0]} - ${range[1]}). 
  1018                      \nUpdating: ${this.sourceBuffer.updating}
  1019                      `);
  1020              this.cleaning = true;
  1021              this.sourceBuffer.remove(range[0], range[1]);
  1022          }
  1023  
  1024          initCleanup() {
  1025              if (this.sourceBuffer.buffered.length && !this.sourceBuffer.updating && !this.cleaning) {
  1026                  Log$1.debug(`${this.codec} cleanup`);
  1027                  let removeBound = this.sourceBuffer.buffered.end(this.sourceBuffer.buffered.length-1) - 2;
  1028  
  1029                  for (let i=0; i< this.sourceBuffer.buffered.length; ++i) {
  1030                      let removeStart = this.sourceBuffer.buffered.start(i);
  1031                      let removeEnd = this.sourceBuffer.buffered.end(i);
  1032                      if ((this.players[0].currentTime <= removeStart) || (removeBound <= removeStart)) continue;
  1033  
  1034                      if ((removeBound <= removeEnd) && (removeBound >= removeStart)) {
  1035                          Log$1.debug(`Clear [${removeStart}, ${removeBound}), leave [${removeBound}, ${removeEnd}]`);
  1036                          removeEnd = removeBound;
  1037                          if (removeEnd!=removeStart) {
  1038                              this.cleanRanges.push([removeStart, removeEnd]);
  1039                          }
  1040                          continue; // Do not cleanup buffered range after current position
  1041                      }
  1042                      this.cleanRanges.push([removeStart, removeEnd]);
  1043                  }
  1044  
  1045                  this.doCleanup();
  1046  
  1047                  // let bufferStart = this.sourceBuffer.buffered.start(0);
  1048                  // let removeEnd = this.sourceBuffer.buffered.start(0) + (this.sourceBuffer.buffered.end(0) - this.sourceBuffer.buffered.start(0))/2;
  1049                  // if (this.players[0].currentTime < removeEnd) {
  1050                  //     this.players[0].currentTime = removeEnd;
  1051                  // }
  1052                  // let removeEnd = Math.max(this.players[0].currentTime - 3, this.sourceBuffer.buffered.end(0) - 3);
  1053                  //
  1054                  // if (removeEnd < bufferStart) {
  1055                  //     removeEnd = this.sourceBuffer.buffered.start(0) + (this.sourceBuffer.buffered.end(0) - this.sourceBuffer.buffered.start(0))/2;
  1056                  //     if (this.players[0].currentTime < removeEnd) {
  1057                  //         this.players[0].currentTime = removeEnd;
  1058                  //     }
  1059                  // }
  1060  
  1061                  // if (removeEnd > bufferStart && (removeEnd - bufferStart > 0.5 )) {
  1062                  //     // try {
  1063                  //         Log.debug(`${this.codec} remove range [${bufferStart} - ${removeEnd}).
  1064                  //         \nBuffered end: ${this.sourceBuffer.buffered.end(0)}
  1065                  //         \nUpdating: ${this.sourceBuffer.updating}
  1066                  //         `);
  1067                  //         this.cleaning = true;
  1068                  //         this.sourceBuffer.remove(bufferStart, removeEnd);
  1069                  //     // } catch (e) {
  1070                  //     //     // TODO: implement
  1071                  //     //     Log.error(e);
  1072                  //     // }
  1073                  // } else {
  1074                  //     this.feedNext();
  1075                  // }
  1076              } else {
  1077                  this.feedNext();
  1078              }
  1079          }
  1080  
  1081          doAppend(data) {
  1082              // console.log(MP4Inspect.mp4toJSON(data));
  1083              let err = this.players[0].error;
  1084              if (err) {
  1085                  Log$1.error(`Error occured: ${MSE.ErrorNotes[err.code]}`);
  1086                  try {
  1087                      this.players.forEach((video)=>{video.stop();});
  1088                      this.mediaSource.endOfStream();
  1089                  } catch (e){
  1090  
  1091                  }
  1092                  this.parent.eventSource.dispatchEvent('error');
  1093              } else {
  1094                  try {
  1095                      this.sourceBuffer.appendBuffer(data);
  1096                  } catch (e) {
  1097                      if (e.name === 'QuotaExceededError') {
  1098                          Log$1.debug(`${this.codec} quota fail`);
  1099                          this.queue.unshift(data);
  1100                          this.initCleanup();
  1101                          return;
  1102                      }
  1103  
  1104                      // reconnect on fail
  1105                      Log$1.error(`Error occured while appending buffer. ${e.name}: ${e.message}`);
  1106                      this.parent.eventSource.dispatchEvent('error');
  1107                  }
  1108              }
  1109  
  1110          }
  1111  
  1112          feed(data) {
  1113              this.queue = this.queue.concat(data);
  1114              // Log.debug(this.sourceBuffer.updating, this.updating, this.queue.length);
  1115              if (this.sourceBuffer && !this.sourceBuffer.updating && !this.cleaning) {
  1116                  // Log.debug('enq feed');
  1117                  this.feedNext();
  1118              }
  1119          }
  1120      }
  1121  
  1122      class MSE {
  1123          // static CODEC_AVC_BASELINE = "avc1.42E01E";
  1124          // static CODEC_AVC_MAIN = "avc1.4D401E";
  1125          // static CODEC_AVC_HIGH = "avc1.64001E";
  1126          // static CODEC_VP8 = "vp8";
  1127          // static CODEC_AAC = "mp4a.40.2";
  1128          // static CODEC_VORBIS = "vorbis";
  1129          // static CODEC_THEORA = "theora";
  1130  
  1131          static get ErrorNotes() {return  {
  1132              [MediaError.MEDIA_ERR_ABORTED]: 'fetching process aborted by user',
  1133              [MediaError.MEDIA_ERR_NETWORK]: 'error occurred when downloading',
  1134              [MediaError.MEDIA_ERR_DECODE]: 'error occurred when decoding',
  1135              [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]: 'audio/video not supported'
  1136          }};
  1137  
  1138          static isSupported(codecs) {
  1139              return (window.MediaSource && window.MediaSource.isTypeSupported(`video/mp4; codecs="${codecs.join(',')}"`));
  1140          }
  1141  
  1142          constructor (players) {
  1143              this.players = players;
  1144              const playing = this.players.map((video, idx) => {
  1145                  video.onplaying = function () {
  1146                      playing[idx] = true;
  1147                  };
  1148                  video.onpause = function () {
  1149                      playing[idx] = false;
  1150                  };
  1151                  return !video.paused;
  1152              });
  1153              this.playing = playing;
  1154              this.mediaSource = new MediaSource();
  1155              this.eventSource = new EventEmitter(this.mediaSource);
  1156              this.reset();
  1157          }
  1158  
  1159          destroy() {
  1160              this.reset();
  1161              this.eventSource.destroy();
  1162              this.mediaSource = null;
  1163              this.eventSource = null;
  1164          }
  1165  
  1166          play() {
  1167              this.players.forEach((video, idx)=>{
  1168                  if (video.paused && !this.playing[idx]) {
  1169                      Log$1.debug(`player ${idx}: play`);
  1170                      video.play();
  1171                  }
  1172              });
  1173          }
  1174  
  1175          setLive(is_live) {
  1176              for (let idx in this.buffers) {
  1177                  this.buffers[idx].setLive(is_live);
  1178              }
  1179              this.is_live = is_live;
  1180          }
  1181  
  1182          resetBuffers() {
  1183              this.players.forEach((video, idx)=>{
  1184                  if (!video.paused && this.playing[idx]) {
  1185                      video.pause();
  1186                      video.currentTime = 0;
  1187                  }
  1188              });
  1189  
  1190              let promises = [];
  1191              for (let buffer of this.buffers.values()) {
  1192                  promises.push(buffer.clear());
  1193              }
  1194              return Promise.all(promises).then(()=>{
  1195                  this.mediaSource.endOfStream();
  1196                  this.mediaSource.duration = 0;
  1197                  this.mediaSource.clearLiveSeekableRange();
  1198                  this.play();
  1199              });
  1200          }
  1201  
  1202          clear() {
  1203              this.reset();
  1204              this.players.forEach((video)=>{video.src = URL.createObjectURL(this.mediaSource);});
  1205  
  1206              return this.setupEvents();
  1207          }
  1208  
  1209          setupEvents() {
  1210              this.eventSource.clear();
  1211              this.resolved = false;
  1212              this.mediaReady = new Promise((resolve, reject)=> {
  1213                  this._sourceOpen = ()=> {
  1214                      Log$1.debug(`Media source opened: ${this.mediaSource.readyState}`);
  1215                      if (!this.resolved) {
  1216                          this.resolved = true;
  1217                          resolve();
  1218                      }
  1219                  };
  1220                  this._sourceEnded = ()=>{
  1221                      Log$1.debug(`Media source ended: ${this.mediaSource.readyState}`);
  1222                  };
  1223                  this._sourceClose = ()=>{
  1224                      Log$1.debug(`Media source closed: ${this.mediaSource.readyState}`);
  1225                      if (this.resolved) {
  1226                          this.eventSource.dispatchEvent('sourceclosed');
  1227                      }
  1228                  };
  1229                  this.eventSource.addEventListener('sourceopen', this._sourceOpen);
  1230                  this.eventSource.addEventListener('sourceended', this._sourceEnded);
  1231                  this.eventSource.addEventListener('sourceclose', this._sourceClose);
  1232              });
  1233              return this.mediaReady;
  1234          }
  1235  
  1236          reset() {
  1237              this.ready = false;
  1238              for (let track in this.buffers) {
  1239                  this.buffers[track].destroy();
  1240                  delete this.buffers[track];
  1241              }
  1242              if (this.mediaSource.readyState == 'open') {
  1243                  this.mediaSource.duration = 0;
  1244                  this.mediaSource.endOfStream();
  1245              }
  1246              this.updating = false;
  1247              this.resolved = false;
  1248              this.buffers = {};
  1249              // this.players.forEach((video)=>{video.src = URL.createObjectURL(this.mediaSource)});
  1250              // TODO: remove event listeners for existing media source
  1251              // this.setupEvents();
  1252              // this.clear();
  1253          }
  1254  
  1255          setCodec(track, mimeCodec) {
  1256              return this.mediaReady.then(()=>{
  1257                  this.buffers[track] = new MSEBuffer(this, mimeCodec);
  1258                  this.buffers[track].setLive(this.is_live);
  1259              });
  1260          }
  1261  
  1262          feed(track, data) {
  1263              if (this.buffers[track]) {
  1264                  this.buffers[track].feed(data);
  1265              }
  1266          }
  1267      }
  1268  
  1269      const Log$2 = getTagged('remuxer:base');
  1270      let track_id = 1;
  1271      class BaseRemuxer {
  1272  
  1273          static get MP4_TIMESCALE() { return 90000;}
  1274  
  1275          // TODO: move to ts parser
  1276          // static PTSNormalize(value, reference) {
  1277          //
  1278          //     let offset;
  1279          //     if (reference === undefined) {
  1280          //         return value;
  1281          //     }
  1282          //     if (reference < value) {
  1283          //         // - 2^33
  1284          //         offset = -8589934592;
  1285          //     } else {
  1286          //         // + 2^33
  1287          //         offset = 8589934592;
  1288          //     }
  1289          //     /* PTS is 33bit (from 0 to 2^33 -1)
  1290          //      if diff between value and reference is bigger than half of the amplitude (2^32) then it means that
  1291          //      PTS looping occured. fill the gap */
  1292          //     while (Math.abs(value - reference) > 4294967296) {
  1293          //         value += offset;
  1294          //     }
  1295          //     return value;
  1296          // }
  1297  
  1298          static getTrackID() {
  1299              return track_id++;
  1300          }
  1301  
  1302          constructor(timescale, scaleFactor, params) {
  1303              this.timeOffset = 0;
  1304              this.timescale = timescale;
  1305              this.scaleFactor = scaleFactor;
  1306              this.readyToDecode = false;
  1307              this.samples = [];
  1308              this.seq = 1;
  1309              this.tsAlign = 1;
  1310          }
  1311  
  1312          scaled(timestamp) {
  1313              return timestamp / this.scaleFactor;
  1314          }
  1315  
  1316          unscaled(timestamp) {
  1317              return timestamp * this.scaleFactor;
  1318          }
  1319  
  1320          remux(unit) {
  1321              if (unit) {
  1322                  this.samples.push({
  1323                      unit: unit,
  1324                      pts: unit.pts,
  1325                      dts: unit.dts
  1326                  });
  1327                  return true;
  1328              }
  1329              return false;
  1330          }
  1331  
  1332          static toMS(timestamp) {
  1333              return timestamp/90;
  1334          }
  1335          
  1336          setConfig(config) {
  1337              
  1338          }
  1339  
  1340          insertDscontinuity() {
  1341              this.samples.push(null);
  1342          }
  1343  
  1344          init(initPTS, initDTS, shouldInitialize=true) {
  1345              this.initPTS = Math.min(initPTS, this.samples[0].dts /*- this.unscaled(this.timeOffset)*/);
  1346              this.initDTS = Math.min(initDTS, this.samples[0].dts /*- this.unscaled(this.timeOffset)*/);
  1347              Log$2.debug(`Initial pts=${this.initPTS} dts=${this.initDTS} offset=${this.unscaled(this.timeOffset)}`);
  1348              this.initialized = shouldInitialize;
  1349          }
  1350  
  1351          flush() {
  1352              this.seq++;
  1353              this.mp4track.len = 0;
  1354              this.mp4track.samples = [];
  1355          }
  1356  
  1357          static dtsSortFunc(a,b) {
  1358              return (a.dts-b.dts);
  1359          }
  1360  
  1361          static groupByDts(gop) {
  1362              const groupBy = (xs, key) => {
  1363                  return xs.reduce((rv, x) => {
  1364                      (rv[x[key]] = rv[x[key]] || []).push(x);
  1365                      return rv;
  1366                  }, {});
  1367              };
  1368              return groupBy(gop, 'dts');
  1369          }
  1370  
  1371          getPayloadBase(sampleFunction, setupSample) {
  1372              if (!this.readyToDecode || !this.initialized || !this.samples.length) return null;
  1373              this.samples.sort(BaseRemuxer.dtsSortFunc);
  1374              return true;
  1375              //
  1376              // let payload = new Uint8Array(this.mp4track.len);
  1377              // let offset = 0;
  1378              // let samples=this.mp4track.samples;
  1379              // let mp4Sample, lastDTS, pts, dts;
  1380              //
  1381              // while (this.samples.length) {
  1382              //     let sample = this.samples.shift();
  1383              //     if (sample === null) {
  1384              //         // discontinuity
  1385              //         this.nextDts = undefined;
  1386              //         break;
  1387              //     }
  1388              //
  1389              //     let unit = sample.unit;
  1390              //
  1391              //     pts = Math.round((sample.pts - this.initDTS)/this.tsAlign)*this.tsAlign;
  1392              //     dts = Math.round((sample.dts - this.initDTS)/this.tsAlign)*this.tsAlign;
  1393              //     // ensure DTS is not bigger than PTS
  1394              //     dts = Math.min(pts, dts);
  1395              //
  1396              //     // sampleFunction(pts, dts);   // TODO:
  1397              //
  1398              //     // mp4Sample = setupSample(unit, pts, dts);    // TODO:
  1399              //
  1400              //     payload.set(unit.getData(), offset);
  1401              //     offset += unit.getSize();
  1402              //
  1403              //     samples.push(mp4Sample);
  1404              //     lastDTS = dts;
  1405              // }
  1406              // if (!samples.length) return null;
  1407              //
  1408              // // samplesPostFunction(samples); // TODO:
  1409              //
  1410              // return new Uint8Array(payload.buffer, 0, this.mp4track.len);
  1411          }
  1412      }
  1413  
  1414      const Log$3 = getTagged("remuxer:aac");
  1415      // TODO: asm.js
  1416      class AACRemuxer extends BaseRemuxer {
  1417  
  1418          constructor(timescale, scaleFactor = 1, params={}) {
  1419              super(timescale, scaleFactor);
  1420  
  1421              this.codecstring=MSE.CODEC_AAC;
  1422              this.units = [];
  1423              this.initDTS = undefined;
  1424              this.nextAacPts = undefined;
  1425              this.lastPts = 0;
  1426              this.firstDTS = 0;
  1427              this.firstPTS = 0;
  1428              this.duration = params.duration || 1;
  1429              this.initialized = false;
  1430  
  1431              this.mp4track={
  1432                  id:BaseRemuxer.getTrackID(),
  1433                  type: 'audio',
  1434                  fragmented:true,
  1435                  channelCount:0,
  1436                  audiosamplerate: this.timescale,
  1437                  duration: 0,
  1438                  timescale: this.timescale,
  1439                  volume: 1,
  1440                  samples: [],
  1441                  config: '',
  1442                  len: 0
  1443              };
  1444              if (params.config) {
  1445                  this.setConfig(params.config);
  1446              }
  1447          }
  1448  
  1449          setConfig(config) {
  1450              this.mp4track.channelCount = config.channels;
  1451              this.mp4track.audiosamplerate = config.samplerate;
  1452              if (!this.mp4track.duration) {
  1453                  this.mp4track.duration = (this.duration?this.duration:1)*config.samplerate;
  1454              }
  1455              this.mp4track.timescale = config.samplerate;
  1456              this.mp4track.config = config.config;
  1457              this.mp4track.codec = config.codec;
  1458              this.timescale = config.samplerate;
  1459              this.scaleFactor = BaseRemuxer.MP4_TIMESCALE / config.samplerate;
  1460              this.expectedSampleDuration = 1024 * this.scaleFactor;
  1461              this.readyToDecode = true;
  1462          }
  1463  
  1464          remux(aac) {
  1465              if (super.remux.call(this, aac)) {
  1466                  this.mp4track.len += aac.getSize();
  1467              }
  1468          }
  1469  
  1470          getPayload() {
  1471              if (!this.readyToDecode || !this.samples.length) return null;
  1472              this.samples.sort(function(a, b) {
  1473                  return (a.dts-b.dts);
  1474              });
  1475  
  1476              let payload = new Uint8Array(this.mp4track.len);
  1477              let offset = 0;
  1478              let samples=this.mp4track.samples;
  1479              let mp4Sample, lastDTS, pts, dts;
  1480  
  1481              while (this.samples.length) {
  1482                  let sample = this.samples.shift();
  1483                  if (sample === null) {
  1484                      // discontinuity
  1485                      this.nextDts = undefined;
  1486                      break;
  1487                  }
  1488                  let unit = sample.unit;
  1489                  pts = sample.pts - this.initDTS;
  1490                  dts = sample.dts - this.initDTS;
  1491  
  1492                  if (lastDTS === undefined) {
  1493                      if (this.nextDts) {
  1494                          let delta = Math.round(this.scaled(pts - this.nextAacPts));
  1495                          // if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments
  1496                          if (/*contiguous || */Math.abs(delta) < 600) {
  1497                              // log delta
  1498                              if (delta) {
  1499                                  if (delta > 0) {
  1500                                      Log$3.log(`${delta} ms hole between AAC samples detected,filling it`);
  1501                                      // if we have frame overlap, overlapping for more than half a frame duraion
  1502                                  } else if (delta < -12) {
  1503                                      // drop overlapping audio frames... browser will deal with it
  1504                                      Log$3.log(`${(-delta)} ms overlapping between AAC samples detected, drop frame`);
  1505                                      this.mp4track.len -= unit.getSize();
  1506                                      continue;
  1507                                  }
  1508                                  // set DTS to next DTS
  1509                                  pts = dts = this.nextAacPts;
  1510                              }
  1511                          }
  1512                      }
  1513                      // remember first PTS of our aacSamples, ensure value is positive
  1514                      this.firstDTS = Math.max(0, dts);
  1515                  }
  1516  
  1517                  mp4Sample = {
  1518                      size: unit.getSize(),
  1519                      cts: 0,
  1520                      duration:1024,
  1521                      flags: {
  1522                          isLeading: 0,
  1523                          isDependedOn: 0,
  1524                          hasRedundancy: 0,
  1525                          degradPrio: 0,
  1526                          dependsOn: 1
  1527                      }
  1528                  };
  1529  
  1530                  payload.set(unit.getData(), offset);
  1531                  offset += unit.getSize();
  1532                  samples.push(mp4Sample);
  1533                  lastDTS = dts;
  1534              }
  1535              if (!samples.length) return null;
  1536              this.nextDts =pts+this.expectedSampleDuration;
  1537              return new Uint8Array(payload.buffer, 0, this.mp4track.len);
  1538          }
  1539      }
  1540      //test.bundle.js:42 [remuxer:h264] skip frame from the past at DTS=18397972271140676 with expected DTS=18397998040950484
  1541  
  1542      /**
  1543       * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
  1544      */
  1545  
  1546      class ExpGolomb {
  1547  
  1548        constructor(data) {
  1549          this.data = data;
  1550          // the number of bytes left to examine in this.data
  1551          this.bytesAvailable = this.data.byteLength;
  1552          // the current word being examined
  1553          this.word = 0; // :uint
  1554          // the number of bits left to examine in the current word
  1555          this.bitsAvailable = 0; // :uint
  1556        }
  1557  
  1558        // ():void
  1559        loadWord() {
  1560          var
  1561            position = this.data.byteLength - this.bytesAvailable,
  1562            workingBytes = new Uint8Array(4),
  1563            availableBytes = Math.min(4, this.bytesAvailable);
  1564          if (availableBytes === 0) {
  1565            throw new Error('no bytes available');
  1566          }
  1567          workingBytes.set(this.data.subarray(position, position + availableBytes));
  1568          this.word = new DataView(workingBytes.buffer, workingBytes.byteOffset, workingBytes.byteLength).getUint32(0);
  1569          // track the amount of this.data that has been processed
  1570          this.bitsAvailable = availableBytes * 8;
  1571          this.bytesAvailable -= availableBytes;
  1572        }
  1573  
  1574        // (count:int):void
  1575        skipBits(count) {
  1576          var skipBytes; // :int
  1577          if (this.bitsAvailable > count) {
  1578            this.word <<= count;
  1579            this.bitsAvailable -= count;
  1580          } else {
  1581            count -= this.bitsAvailable;
  1582            skipBytes = count >> 3;
  1583            count -= (skipBytes << 3);
  1584            this.bytesAvailable -= skipBytes;
  1585            this.loadWord();
  1586            this.word <<= count;
  1587            this.bitsAvailable -= count;
  1588          }
  1589        }
  1590  
  1591        // (size:int):uint
  1592        readBits(size) {
  1593          var
  1594            bits = Math.min(this.bitsAvailable, size), // :uint
  1595            valu = this.word >>> (32 - bits); // :uint
  1596          if (size > 32) {
  1597            Log.error('Cannot read more than 32 bits at a time');
  1598          }
  1599          this.bitsAvailable -= bits;
  1600          if (this.bitsAvailable > 0) {
  1601            this.word <<= bits;
  1602          } else if (this.bytesAvailable > 0) {
  1603            this.loadWord();
  1604          }
  1605          bits = size - bits;
  1606          if (bits > 0) {
  1607            return valu << bits | this.readBits(bits);
  1608          } else {
  1609            return valu;
  1610          }
  1611        }
  1612  
  1613        // ():uint
  1614        skipLZ() {
  1615          var leadingZeroCount; // :uint
  1616          for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
  1617            if (0 !== (this.word & (0x80000000 >>> leadingZeroCount))) {
  1618              // the first bit of working word is 1
  1619              this.word <<= leadingZeroCount;
  1620              this.bitsAvailable -= leadingZeroCount;
  1621              return leadingZeroCount;
  1622            }
  1623          }
  1624          // we exhausted word and still have not found a 1
  1625          this.loadWord();
  1626          return leadingZeroCount + this.skipLZ();
  1627        }
  1628  
  1629        // ():void
  1630        skipUEG() {
  1631          this.skipBits(1 + this.skipLZ());
  1632        }
  1633  
  1634        // ():void
  1635        skipEG() {
  1636          this.skipBits(1 + this.skipLZ());
  1637        }
  1638  
  1639        // ():uint
  1640        readUEG() {
  1641          var clz = this.skipLZ(); // :uint
  1642          return this.readBits(clz + 1) - 1;
  1643        }
  1644  
  1645        // ():int
  1646        readEG() {
  1647          var valu = this.readUEG(); // :int
  1648          if (0x01 & valu) {
  1649            // the number is odd if the low order bit is set
  1650            return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
  1651          } else {
  1652            return -1 * (valu >>> 1); // divide by two then make it negative
  1653          }
  1654        }
  1655  
  1656        // Some convenience functions
  1657        // :Boolean
  1658        readBoolean() {
  1659          return 1 === this.readBits(1);
  1660        }
  1661  
  1662        // ():int
  1663        readUByte() {
  1664          return this.readBits(8);
  1665        }
  1666  
  1667        // ():int
  1668        readUShort() {
  1669          return this.readBits(16);
  1670        }
  1671          // ():int
  1672        readUInt() {
  1673          return this.readBits(32);
  1674        }  
  1675      }
  1676  
  1677      // TODO: asm.js
  1678  
  1679      function appendByteArray(buffer1, buffer2) {
  1680          let tmp = new Uint8Array((buffer1.byteLength|0) + (buffer2.byteLength|0));
  1681          tmp.set(buffer1, 0);
  1682          tmp.set(buffer2, buffer1.byteLength|0);
  1683          return tmp;
  1684      }
  1685      function base64ToArrayBuffer(base64) {
  1686          var binary_string =  window.atob(base64);
  1687          var len = binary_string.length;
  1688          var bytes = new Uint8Array( len );
  1689          for (var i = 0; i < len; i++)        {
  1690              bytes[i] = binary_string.charCodeAt(i);
  1691          }
  1692          return bytes.buffer;
  1693      }
  1694  
  1695      function hexToByteArray(hex) {
  1696          let len = hex.length >> 1;
  1697          var bufView = new Uint8Array(len);
  1698          for (var i = 0; i < len; i++) {
  1699              bufView[i] = parseInt(hex.substr(i<<1,2),16);
  1700          }
  1701          return bufView;
  1702      }
  1703  
  1704      function bitSlice(bytearray, start=0, end=bytearray.byteLength*8) {
  1705          let byteLen = Math.ceil((end-start)/8);
  1706          let res = new Uint8Array(byteLen);
  1707          let startByte = start >>> 3;   // /8
  1708          let endByte = (end>>>3) - 1;    // /8
  1709          let bitOffset = start & 0x7;     // %8
  1710          let nBitOffset = 8 - bitOffset;
  1711          let endOffset = 8 - end & 0x7;   // %8
  1712          for (let i=0; i<byteLen; ++i) {
  1713              let tail = 0;
  1714              if (i<endByte) {
  1715                  tail = bytearray[startByte+i+1] >> nBitOffset;
  1716                  if (i == endByte-1 && endOffset < 8) {
  1717                      tail >>= endOffset;
  1718                      tail <<= endOffset;
  1719                  }
  1720              }
  1721              res[i]=(bytearray[startByte+i]<<bitOffset) | tail;
  1722          }
  1723          return res;
  1724      }
  1725  
  1726      class BitArray {
  1727  
  1728          constructor(src) {
  1729              this.src    = new DataView(src.buffer, src.byteOffset, src.byteLength);
  1730              this.bitpos = 0;
  1731              this.byte   = this.src.getUint8(0); /* This should really be undefined, uint wont allow it though */
  1732              this.bytepos = 0;
  1733          }
  1734  
  1735          readBits(length) {
  1736              if (32 < (length|0) || 0 === (length|0)) {
  1737                  /* To big for an uint */
  1738                  throw new Error("too big");
  1739              }
  1740  
  1741              let result = 0;
  1742              for (let i = length; i > 0; --i) {
  1743  
  1744                  /* Shift result one left to make room for another bit,
  1745                   then add the next bit on the stream. */
  1746                  result = ((result|0) << 1) | (((this.byte|0) >> (8 - (++this.bitpos))) & 0x01);
  1747                  if ((this.bitpos|0)>=8) {
  1748                      this.byte = this.src.getUint8(++this.bytepos);
  1749                      this.bitpos &= 0x7;
  1750                  }
  1751              }
  1752  
  1753              return result;
  1754          }
  1755          skipBits(length) {
  1756              this.bitpos += (length|0) & 0x7; // %8
  1757              this.bytepos += (length|0) >>> 3;  // *8
  1758              if (this.bitpos > 7) {
  1759                  this.bitpos &= 0x7;
  1760                  ++this.bytepos;
  1761              }
  1762  
  1763              if (!this.finished()) {
  1764                  this.byte = this.src.getUint8(this.bytepos);
  1765                  return 0;
  1766              } else {
  1767                  return this.bytepos-this.src.byteLength-this.src.bitpos;
  1768              }
  1769          }
  1770          
  1771          finished() {
  1772              return this.bytepos >= this.src.byteLength;
  1773          }
  1774      }
  1775  
  1776      class NALU {
  1777  
  1778          static get NDR() {return 1;}
  1779          static get SLICE_PART_A() {return 2;}
  1780          static get SLICE_PART_B() {return 3;}
  1781          static get SLICE_PART_C() {return 4;}
  1782          static get IDR() {return 5;}
  1783          static get SEI() {return 6;}
  1784          static get SPS() {return 7;}
  1785          static get PPS() {return 8;}
  1786          static get DELIMITER() {return 9;}
  1787          static get EOSEQ() {return 10;}
  1788          static get EOSTR() {return 11;}
  1789          static get FILTER() {return 12;}
  1790          static get STAP_A() {return 24;}
  1791          static get STAP_B() {return 25;}
  1792          static get FU_A() {return 28;}
  1793          static get FU_B() {return 29;}
  1794  
  1795          static get TYPES() {return {
  1796              [NALU.IDR]: 'IDR',
  1797              [NALU.SEI]: 'SEI',
  1798              [NALU.SPS]: 'SPS',
  1799              [NALU.PPS]: 'PPS',
  1800              [NALU.NDR]: 'NDR'
  1801          }};
  1802  
  1803          static type(nalu) {
  1804              if (nalu.ntype in NALU.TYPES) {
  1805                  return NALU.TYPES[nalu.ntype];
  1806              } else {
  1807                  return 'UNKNOWN';
  1808              }
  1809          }
  1810  
  1811          constructor(ntype, nri, data, dts, pts) {
  1812  
  1813              this.data = data;
  1814              this.ntype = ntype;
  1815              this.nri = nri;
  1816              this.dts = dts;
  1817              this.pts = pts ? pts : this.dts;
  1818              this.sliceType = null;
  1819          }
  1820  
  1821          appendData(idata) {
  1822              this.data = appendByteArray(this.data, idata);
  1823          }
  1824  
  1825          toString() {
  1826              return `${NALU.type(this)}(${this.data.byteLength}): NRI: ${this.getNri()}, PTS: ${this.pts}, DTS: ${this.dts}`;
  1827          }
  1828  
  1829          getNri() {
  1830              return this.nri >> 5;
  1831          }
  1832  
  1833          type() {
  1834              return this.ntype;
  1835          }
  1836  
  1837          isKeyframe() {
  1838              return this.ntype === NALU.IDR || this.sliceType === 7;
  1839          }
  1840  
  1841          getSize() {
  1842              return 4 + 1 + this.data.byteLength;
  1843          }
  1844  
  1845          getData() {
  1846              let header = new Uint8Array(5 + this.data.byteLength);
  1847              let view = new DataView(header.buffer);
  1848              view.setUint32(0, this.data.byteLength + 1);
  1849              view.setUint8(4, (0x0 & 0x80) | (this.nri & 0x60) | (this.ntype & 0x1F));
  1850              header.set(this.data, 5);
  1851              return header;
  1852          }
  1853      }
  1854  
  1855      class H264Parser {
  1856  
  1857          constructor(remuxer) {
  1858              this.remuxer = remuxer;
  1859              this.track = remuxer.mp4track;
  1860              this.firstFound = false;
  1861          }
  1862  
  1863          msToScaled(timestamp) {
  1864              return (timestamp - this.remuxer.timeOffset) * this.remuxer.scaleFactor;
  1865          }
  1866  
  1867          parseSPS(sps) {
  1868              var config = H264Parser.readSPS(new Uint8Array(sps));
  1869  
  1870              this.track.width = config.width;
  1871              this.track.height = config.height;
  1872              this.track.sps = [new Uint8Array(sps)];
  1873              // this.track.timescale = this.remuxer.timescale;
  1874              // this.track.duration = this.remuxer.timescale; // TODO: extract duration for non-live client
  1875              this.track.codec = 'avc1.';
  1876  
  1877              let codecarray = new DataView(sps.buffer, sps.byteOffset+1, 4);
  1878              for (let i = 0; i < 3; ++i) {
  1879                  var h = codecarray.getUint8(i).toString(16);
  1880                  if (h.length < 2) {
  1881                      h = '0' + h;
  1882                  }
  1883                  this.track.codec  += h;
  1884              }
  1885          }
  1886  
  1887          parsePPS(pps) {
  1888              this.track.pps = [new Uint8Array(pps)];
  1889          }
  1890  
  1891          parseNAL(unit) {
  1892              if (!unit) return false;
  1893              
  1894              let push = null;
  1895              // console.log(unit.toString());
  1896              switch (unit.type()) {
  1897                  case NALU.NDR:
  1898                  case NALU.IDR:
  1899                      unit.sliceType = H264Parser.parceSliceHeader(unit.data);
  1900                      if (unit.isKeyframe() && !this.firstFound)  {
  1901                          this.firstFound = true;
  1902                      }
  1903                      if (this.firstFound) {
  1904                          push = true;
  1905                      } else {
  1906                          push = false;
  1907                      }
  1908                      break;
  1909                  case NALU.PPS:
  1910                      push = false;
  1911                      if (!this.track.pps) {
  1912                          this.parsePPS(unit.getData().subarray(4));
  1913                          if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
  1914                              this.remuxer.readyToDecode = true;
  1915                          }
  1916                      }
  1917                      break;
  1918                  case NALU.SPS:
  1919                      push = false;
  1920                      if(!this.track.sps) {
  1921                          this.parseSPS(unit.getData().subarray(4));
  1922                          if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
  1923                              this.remuxer.readyToDecode = true;
  1924                          }
  1925                      }
  1926                      break;
  1927                  case NALU.SEI:
  1928                      push = false;
  1929                      let data = new DataView(unit.data.buffer, unit.data.byteOffset, unit.data.byteLength);
  1930                      let byte_idx = 0;
  1931                      let pay_type = data.getUint8(byte_idx);
  1932                      ++byte_idx;
  1933                      let pay_size = 0;
  1934                      let sz = data.getUint8(byte_idx);
  1935                      ++byte_idx;
  1936                      while (sz === 255) {
  1937                          pay_size+=sz;
  1938                          sz = data.getUint8(byte_idx);
  1939                          ++byte_idx;
  1940                      }
  1941                      pay_size+=sz;
  1942  
  1943                      let uuid = unit.data.subarray(byte_idx, byte_idx+16);
  1944                      byte_idx+=16;
  1945                      console.log(`PT: ${pay_type}, PS: ${pay_size}, UUID: ${Array.from(uuid).map(function(i) {
  1946                      return ('0' + i.toString(16)).slice(-2);
  1947                  }).join('')}`);
  1948                      // debugger;
  1949                      break;
  1950                  case NALU.EOSEQ:
  1951                  case NALU.EOSTR:
  1952                      push = false;
  1953                  default:
  1954              }
  1955              if (push === null && unit.getNri() > 0 ) {
  1956                  push=true;
  1957              }
  1958              return push;
  1959          }
  1960  
  1961          static parceSliceHeader(data) {
  1962              let decoder = new ExpGolomb(data);
  1963              let first_mb = decoder.readUEG();
  1964              let slice_type = decoder.readUEG();
  1965              let ppsid = decoder.readUEG();
  1966              let frame_num = decoder.readUByte();
  1967              // console.log(`first_mb: ${first_mb}, slice_type: ${slice_type}, ppsid: ${ppsid}, frame_num: ${frame_num}`);
  1968              return slice_type;
  1969          }
  1970  
  1971          /**
  1972           * Advance the ExpGolomb decoder past a scaling list. The scaling
  1973           * list is optionally transmitted as part of a sequence parameter
  1974           * set and is not relevant to transmuxing.
  1975           * @param decoder {ExpGolomb} exp golomb decoder
  1976           * @param count {number} the number of entries in this scaling list
  1977           * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
  1978           */
  1979          static skipScalingList(decoder, count) {
  1980              let lastScale = 8,
  1981                  nextScale = 8,
  1982                  deltaScale;
  1983              for (let j = 0; j < count; j++) {
  1984                  if (nextScale !== 0) {
  1985                      deltaScale = decoder.readEG();
  1986                      nextScale = (lastScale + deltaScale + 256) % 256;
  1987                  }
  1988                  lastScale = (nextScale === 0) ? lastScale : nextScale;
  1989              }
  1990          }
  1991  
  1992          /**
  1993           * Read a sequence parameter set and return some interesting video
  1994           * properties. A sequence parameter set is the H264 metadata that
  1995           * describes the properties of upcoming video frames.
  1996           * @param data {Uint8Array} the bytes of a sequence parameter set
  1997           * @return {object} an object with configuration parsed from the
  1998           * sequence parameter set, including the dimensions of the
  1999           * associated video frames.
  2000           */
  2001          static readSPS(data) {
  2002              let decoder = new ExpGolomb(data);
  2003              let frameCropLeftOffset = 0,
  2004                  frameCropRightOffset = 0,
  2005                  frameCropTopOffset = 0,
  2006                  frameCropBottomOffset = 0,
  2007                  sarScale = 1,
  2008                  profileIdc,profileCompat,levelIdc,
  2009                  numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
  2010                  picHeightInMapUnitsMinus1,
  2011                  frameMbsOnlyFlag,
  2012                  scalingListCount;
  2013              decoder.readUByte();
  2014              profileIdc = decoder.readUByte(); // profile_idc
  2015              profileCompat = decoder.readBits(5); // constraint_set[0-4]_flag, u(5)
  2016              decoder.skipBits(3); // reserved_zero_3bits u(3),
  2017              levelIdc = decoder.readUByte(); //level_idc u(8)
  2018              decoder.skipUEG(); // seq_parameter_set_id
  2019              // some profiles have more optional data we don't need
  2020              if (profileIdc === 100 ||
  2021                  profileIdc === 110 ||
  2022                  profileIdc === 122 ||
  2023                  profileIdc === 244 ||
  2024                  profileIdc === 44  ||
  2025                  profileIdc === 83  ||
  2026                  profileIdc === 86  ||
  2027                  profileIdc === 118 ||
  2028                  profileIdc === 128) {
  2029                  var chromaFormatIdc = decoder.readUEG();
  2030                  if (chromaFormatIdc === 3) {
  2031                      decoder.skipBits(1); // separate_colour_plane_flag
  2032                  }
  2033                  decoder.skipUEG(); // bit_depth_luma_minus8
  2034                  decoder.skipUEG(); // bit_depth_chroma_minus8
  2035                  decoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
  2036                  if (decoder.readBoolean()) { // seq_scaling_matrix_present_flag
  2037                      scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
  2038                      for (let i = 0; i < scalingListCount; ++i) {
  2039                          if (decoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
  2040                              if (i < 6) {
  2041                                  H264Parser.skipScalingList(decoder, 16);
  2042                              } else {
  2043                                  H264Parser.skipScalingList(decoder, 64);
  2044                              }
  2045                          }
  2046                      }
  2047                  }
  2048              }
  2049              decoder.skipUEG(); // log2_max_frame_num_minus4
  2050              var picOrderCntType = decoder.readUEG();
  2051              if (picOrderCntType === 0) {
  2052                  decoder.readUEG(); //log2_max_pic_order_cnt_lsb_minus4
  2053              } else if (picOrderCntType === 1) {
  2054                  decoder.skipBits(1); // delta_pic_order_always_zero_flag
  2055                  decoder.skipEG(); // offset_for_non_ref_pic
  2056                  decoder.skipEG(); // offset_for_top_to_bottom_field
  2057                  numRefFramesInPicOrderCntCycle = decoder.readUEG();
  2058                  for(let i = 0; i < numRefFramesInPicOrderCntCycle; ++i) {
  2059                      decoder.skipEG(); // offset_for_ref_frame[ i ]
  2060                  }
  2061              }
  2062              decoder.skipUEG(); // max_num_ref_frames
  2063              decoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
  2064              picWidthInMbsMinus1 = decoder.readUEG();
  2065              picHeightInMapUnitsMinus1 = decoder.readUEG();
  2066              frameMbsOnlyFlag = decoder.readBits(1);
  2067              if (frameMbsOnlyFlag === 0) {
  2068                  decoder.skipBits(1); // mb_adaptive_frame_field_flag
  2069              }
  2070              decoder.skipBits(1); // direct_8x8_inference_flag
  2071              if (decoder.readBoolean()) { // frame_cropping_flag
  2072                  frameCropLeftOffset = decoder.readUEG();
  2073                  frameCropRightOffset = decoder.readUEG();
  2074                  frameCropTopOffset = decoder.readUEG();
  2075                  frameCropBottomOffset = decoder.readUEG();
  2076              }
  2077              if (decoder.readBoolean()) {
  2078                  // vui_parameters_present_flag
  2079                  if (decoder.readBoolean()) {
  2080                      // aspect_ratio_info_present_flag
  2081                      let sarRatio;
  2082                      const aspectRatioIdc = decoder.readUByte();
  2083                      switch (aspectRatioIdc) {
  2084                          case 1: sarRatio = [1,1]; break;
  2085                          case 2: sarRatio = [12,11]; break;
  2086                          case 3: sarRatio = [10,11]; break;
  2087                          case 4: sarRatio = [16,11]; break;
  2088                          case 5: sarRatio = [40,33]; break;
  2089                          case 6: sarRatio = [24,11]; break;
  2090                          case 7: sarRatio = [20,11]; break;
  2091                          case 8: sarRatio = [32,11]; break;
  2092                          case 9: sarRatio = [80,33]; break;
  2093                          case 10: sarRatio = [18,11]; break;
  2094                          case 11: sarRatio = [15,11]; break;
  2095                          case 12: sarRatio = [64,33]; break;
  2096                          case 13: sarRatio = [160,99]; break;
  2097                          case 14: sarRatio = [4,3]; break;
  2098                          case 15: sarRatio = [3,2]; break;
  2099                          case 16: sarRatio = [2,1]; break;
  2100                          case 255: {
  2101                              sarRatio = [decoder.readUByte() << 8 | decoder.readUByte(), decoder.readUByte() << 8 | decoder.readUByte()];
  2102                              break;
  2103                          }
  2104                      }
  2105                      if (sarRatio) {
  2106                          sarScale = sarRatio[0] / sarRatio[1];
  2107                      }
  2108                  }
  2109                  if (decoder.readBoolean()) {decoder.skipBits(1);}
  2110  
  2111                  if (decoder.readBoolean()) {
  2112                      decoder.skipBits(4);
  2113                      if (decoder.readBoolean()) {
  2114                          decoder.skipBits(24);
  2115                      }
  2116                  }
  2117                  if (decoder.readBoolean()) {
  2118                      decoder.skipUEG();
  2119                      decoder.skipUEG();
  2120                  }
  2121                  if (decoder.readBoolean()) {
  2122                      let unitsInTick = decoder.readUInt();
  2123                      let timeScale = decoder.readUInt();
  2124                      let fixedFrameRate = decoder.readBoolean();
  2125                      let frameDuration = timeScale/(2*unitsInTick);
  2126                      console.log(`timescale: ${timeScale}; unitsInTick: ${unitsInTick}; fixedFramerate: ${fixedFrameRate}; avgFrameDuration: ${frameDuration}`);
  2127                  }
  2128              }
  2129              return {
  2130                  width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
  2131                  height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset))
  2132              };
  2133          }
  2134  
  2135          static readSliceType(decoder) {
  2136              // skip NALu type
  2137              decoder.readUByte();
  2138              // discard first_mb_in_slice
  2139              decoder.readUEG();
  2140              // return slice_type
  2141              return decoder.readUEG();
  2142          }
  2143      }
  2144  
  2145      const Log$4 = getTagged("remuxer:h264"); 
  2146      // TODO: asm.js
  2147      class H264Remuxer extends BaseRemuxer {
  2148  
  2149          constructor(timescale, scaleFactor=1, params={}) {
  2150              super(timescale, scaleFactor);
  2151  
  2152              this.nextDts = undefined;
  2153              this.readyToDecode = false;
  2154              this.initialized = false;
  2155  
  2156              this.firstDTS=0;
  2157              this.firstPTS=0;
  2158              this.lastDTS=undefined;
  2159              this.lastSampleDuration = 0;
  2160              this.lastDurations = [];
  2161              // this.timescale = 90000;
  2162              this.tsAlign = Math.round(this.timescale/60);
  2163  
  2164              this.mp4track={
  2165                  id:BaseRemuxer.getTrackID(),
  2166                  type: 'video',
  2167                  len:0,
  2168                  fragmented:true,
  2169                  sps:'',
  2170                  pps:'',
  2171                  width:0,
  2172                  height:0,
  2173                  timescale: timescale,
  2174                  duration: timescale,
  2175                  samples: []
  2176              };
  2177              this.samples = [];
  2178              this.lastGopDTS = -99999999999999;
  2179              this.gop=[];
  2180              this.firstUnit = true;
  2181  
  2182              this.h264 = new H264Parser(this);
  2183  
  2184              if (params.sps) {
  2185                  let arr = new Uint8Array(params.sps);
  2186                  if ((arr[0] & 0x1f) === 7) {
  2187                      this.setSPS(arr);
  2188                  } else {
  2189                      Log$4.warn("bad SPS in SDP");
  2190                  }
  2191              }
  2192              if (params.pps) {
  2193                  let arr = new Uint8Array(params.pps);
  2194                  if ((arr[0] & 0x1f) === 8) {
  2195                      this.setPPS(arr);
  2196                  } else {
  2197                      Log$4.warn("bad PPS in SDP");
  2198                  }
  2199              }
  2200  
  2201              if (this.mp4track.pps && this.mp4track.sps) {
  2202                  this.readyToDecode = true;
  2203              }
  2204          }
  2205  
  2206          _scaled(timestamp) {
  2207              return timestamp >>> this.scaleFactor;
  2208          }
  2209  
  2210          _unscaled(timestamp) {
  2211              return timestamp << this.scaleFactor;
  2212          }
  2213  
  2214          setSPS(sps) {
  2215              this.h264.parseSPS(sps);
  2216          }
  2217  
  2218          setPPS(pps) {
  2219              this.h264.parsePPS(pps);
  2220          }
  2221  
  2222          remux(nalu) {
  2223              if (this.lastGopDTS < nalu.dts) {
  2224                  this.gop.sort(BaseRemuxer.dtsSortFunc);
  2225  
  2226                  if (this.gop.length > 1) {
  2227                      // Aggregate multi-slices which belong to one frame
  2228                      const groupedGop = BaseRemuxer.groupByDts(this.gop);
  2229                      this.gop = Object.values(groupedGop).map(group => {
  2230                          return group.reduce((preUnit, curUnit) => {
  2231                              const naluData = curUnit.getData();
  2232                              naluData.set(new Uint8Array([0x0, 0x0, 0x0, 0x1]));
  2233                              preUnit.appendData(naluData);
  2234                              return preUnit;
  2235                          });
  2236                      });
  2237                  }
  2238  
  2239                  for (let unit of this.gop) {
  2240                      // if (this.firstUnit) {
  2241                      //     unit.ntype = 5;//NALU.IDR;
  2242                      //     this.firstUnit = false;
  2243                      // }
  2244                      if (super.remux.call(this, unit)) {
  2245                          this.mp4track.len += unit.getSize();
  2246                      }
  2247                  }
  2248                  this.gop = [];
  2249                  this.lastGopDTS = nalu.dts;
  2250              }
  2251              if (this.h264.parseNAL(nalu)) {
  2252                  this.gop.push(nalu);
  2253              }
  2254          }
  2255  
  2256          getPayload() {
  2257              if (!this.getPayloadBase()) {
  2258                  return null;
  2259              }
  2260  
  2261              let payload = new Uint8Array(this.mp4track.len);
  2262              let offset = 0;
  2263              let samples=this.mp4track.samples;
  2264              let mp4Sample, lastDTS, pts, dts;
  2265  
  2266  
  2267              // Log.debug(this.samples.map((e)=>{
  2268              //     return Math.round((e.dts - this.initDTS));
  2269              // }));
  2270  
  2271              // let minDuration = Number.MAX_SAFE_INTEGER;
  2272              while (this.samples.length) {
  2273                  let sample = this.samples.shift();
  2274                  if (sample === null) {
  2275                      // discontinuity
  2276                      this.nextDts = undefined;
  2277                      break;
  2278                  }
  2279  
  2280                  let unit = sample.unit;
  2281                  
  2282                  pts = sample.pts- this.initDTS; // /*Math.round(*/(sample.pts - this.initDTS)/*/this.tsAlign)*this.tsAlign*/;
  2283                  dts = sample.dts - this.initDTS; ///*Math.round(*/(sample.dts - this.initDTS)/*/this.tsAlign)*this.tsAlign*/;
  2284                  // ensure DTS is not bigger than PTS
  2285                  dts = Math.min(pts,dts);
  2286                  // if not first AVC sample of video track, normalize PTS/DTS with previous sample value
  2287                  // and ensure that sample duration is positive
  2288                  if (lastDTS !== undefined) {
  2289                      let sampleDuration = this.scaled(dts - lastDTS);
  2290                      // Log.debug(`Sample duration: ${sampleDuration}`);
  2291                      if (sampleDuration < 0) {
  2292                          Log$4.log(`invalid AVC sample duration at PTS/DTS: ${pts}/${dts}|lastDTS: ${lastDTS}:${sampleDuration}`);
  2293                          this.mp4track.len -= unit.getSize();
  2294                          continue;
  2295                      }
  2296                      // minDuration = Math.min(sampleDuration, minDuration);
  2297                      this.lastDurations.push(sampleDuration);
  2298                      if (this.lastDurations.length > 100) {
  2299                          this.lastDurations.shift();
  2300                      }
  2301                      mp4Sample.duration = sampleDuration;
  2302                  } else {
  2303                      if (this.nextDts) {
  2304                          let delta = dts - this.nextDts;
  2305                          // if fragment are contiguous, or delta less than 600ms, ensure there is no overlap/hole between fragments
  2306                          if (/*contiguous ||*/ Math.abs(Math.round(BaseRemuxer.toMS(delta))) < 600) {
  2307  
  2308                              if (delta) {
  2309                                  // set DTS to next DTS
  2310                                  // Log.debug(`Video/PTS/DTS adjusted: ${pts}->${Math.max(pts - delta, this.nextDts)}/${dts}->${this.nextDts},delta:${delta}`);
  2311                                  dts = this.nextDts;
  2312                                  // offset PTS as well, ensure that PTS is smaller or equal than new DTS
  2313                                  pts = Math.max(pts - delta, dts);
  2314                              }
  2315                          } else {
  2316                              if (delta < 0) {
  2317                                  Log$4.log(`skip frame from the past at DTS=${dts} with expected DTS=${this.nextDts}`);
  2318                                  this.mp4track.len -= unit.getSize();
  2319                                  continue;
  2320                              }
  2321                          }
  2322                      }
  2323                      // remember first DTS of our avcSamples, ensure value is positive
  2324                      this.firstDTS = Math.max(0, dts);
  2325                  }
  2326  
  2327                  mp4Sample = {
  2328                      size: unit.getSize(),
  2329                      duration: 0,
  2330                      cts: this.scaled(pts - dts),
  2331                      flags: {
  2332                          isLeading: 0,
  2333                          isDependedOn: 0,
  2334                          hasRedundancy: 0,
  2335                          degradPrio: 0
  2336                      }
  2337                  };
  2338                  let flags = mp4Sample.flags;
  2339                  if (sample.unit.isKeyframe() === true) {
  2340                      // the current sample is a key frame
  2341                      flags.dependsOn = 2;
  2342                      flags.isNonSync = 0;
  2343                  } else {
  2344                      flags.dependsOn = 1;
  2345                      flags.isNonSync = 1;
  2346                  }
  2347  
  2348                  payload.set(unit.getData(), offset);
  2349                  offset += unit.getSize();
  2350  
  2351                  samples.push(mp4Sample);
  2352                  lastDTS = dts;
  2353              }
  2354  
  2355              if (!samples.length) return null;
  2356  
  2357              let avgDuration = this.lastDurations.reduce(function(a, b) { return (a|0) + (b|0); }, 0) / (this.lastDurations.length||1)|0;
  2358              if (samples.length >= 2) {
  2359                  this.lastSampleDuration = avgDuration;
  2360                  mp4Sample.duration = avgDuration;
  2361              } else {
  2362                  mp4Sample.duration = this.lastSampleDuration;
  2363              }
  2364  
  2365              if(samples.length && (!this.nextDts || navigator.userAgent.toLowerCase().indexOf('chrome') > -1)) {
  2366                  let flags = samples[0].flags;
  2367                  // chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue
  2368                  // https://code.google.com/p/chromium/issues/detail?id=229412
  2369                  flags.dependsOn = 2;
  2370                  flags.isNonSync = 0;
  2371              }
  2372  
  2373              // next AVC sample DTS should be equal to last sample DTS + last sample duration
  2374              this.nextDts = dts + this.unscaled(this.lastSampleDuration);
  2375              // Log.debug(`next dts: ${this.nextDts}, last duration: ${this.lastSampleDuration}, last dts: ${dts}`);
  2376  
  2377              return new Uint8Array(payload.buffer, 0, this.mp4track.len);
  2378          }
  2379      }
  2380  
  2381      class PayloadType {
  2382          static get H264() {return 1;}
  2383          static get AAC() {return 2;}
  2384  
  2385          static get map() {return {
  2386              [PayloadType.H264]: 'video',
  2387              [PayloadType.AAC]: 'audio'
  2388          }};
  2389  
  2390          static get string_map() {return  {
  2391              H264: PayloadType.H264,
  2392              AAC: PayloadType.AAC,
  2393              'MP4A-LATM': PayloadType.AAC,
  2394              'MPEG4-GENERIC': PayloadType.AAC
  2395          }}
  2396      }
  2397  
  2398      const LOG_TAG$1 = "remuxer";
  2399      const Log$5 = getTagged(LOG_TAG$1);
  2400  
  2401      class Remuxer {
  2402          static get TrackConverters() {return {
  2403              [PayloadType.H264]: H264Remuxer,
  2404              [PayloadType.AAC]:  AACRemuxer
  2405          }};
  2406  
  2407          static get TrackScaleFactor() {return {
  2408              [PayloadType.H264]: 1,//4,
  2409              [PayloadType.AAC]:  0
  2410          }};
  2411  
  2412          static get TrackTimescale() {return {
  2413              [PayloadType.H264]: 90000,//22500,
  2414              [PayloadType.AAC]:  0
  2415          }};
  2416  
  2417          constructor(mediaElement) {
  2418              this.mse = new MSE([mediaElement]);
  2419              this.eventSource = new EventEmitter();
  2420              this.mseEventSource = new EventSourceWrapper(this.mse.eventSource);
  2421              this.mse_ready = true;
  2422  
  2423              this.reset();
  2424  
  2425              this.errorListener = this.mseClose.bind(this);
  2426              this.closeListener = this.mseClose.bind(this);
  2427              this.errorDecodeListener = this.mseErrorDecode.bind(this);
  2428  
  2429              this.eventSource.addEventListener('ready', this.init.bind(this));
  2430          }
  2431  
  2432          initMSEHandlers() {
  2433              this.mseEventSource.on('error', this.errorListener);
  2434              this.mseEventSource.on('sourceclosed', this.closeListener);
  2435              this.mseEventSource.on('errordecode', this.errorDecodeListener);
  2436          }
  2437  
  2438          async reset() {
  2439              this.tracks = {};
  2440              this.initialized = false;
  2441              this.initSegments = {};
  2442              this.codecs = [];
  2443              this.streams = {};
  2444              this.enabled = false;
  2445              await this.mse.clear();
  2446              this.initMSEHandlers();
  2447          }
  2448  
  2449          destroy() {
  2450              this.mseEventSource.destroy();
  2451              this.mse.destroy();
  2452              this.mse = null;
  2453  
  2454              this.detachClient();
  2455  
  2456              this.eventSource.destroy();
  2457          }
  2458  
  2459          onTracks(tracks) {
  2460              Log$5.debug(`ontracks: `, tracks.detail);
  2461              // store available track types
  2462              for (let track of tracks.detail) {
  2463                  this.tracks[track.type] = new Remuxer.TrackConverters[track.type](Remuxer.TrackTimescale[track.type], Remuxer.TrackScaleFactor[track.type], track.params);
  2464                  if (track.offset) {
  2465                      this.tracks[track.type].timeOffset = track.offset;
  2466                  }
  2467                  if (track.duration) {
  2468                      this.tracks[track.type].mp4track.duration = track.duration*(this.tracks[track.type].timescale || Remuxer.TrackTimescale[track.type]);
  2469                      this.tracks[track.type].duration = track.duration;
  2470                  } else {
  2471                      this.tracks[track.type].duration = 1;
  2472                  }
  2473  
  2474                  // this.tracks[track.type].duration
  2475              }
  2476              this.mse.setLive(!this.client.seekable);
  2477          }
  2478  
  2479          setTimeOffset(timeOffset, track) {
  2480              if (this.tracks[track.type]) {
  2481                  this.tracks[track.type].timeOffset = timeOffset;///this.tracks[track.type].scaleFactor;
  2482              }
  2483          }
  2484  
  2485          init() {
  2486              let tracks = [];
  2487              this.codecs = [];
  2488              let initmse = [];
  2489              let initPts = Infinity;
  2490              let initDts = Infinity;
  2491              for (let track_type in this.tracks) {
  2492                  let track = this.tracks[track_type];
  2493                  if (!MSE.isSupported([track.mp4track.codec])) {
  2494                      throw new Error(`${track.mp4track.type} codec ${track.mp4track.codec} is not supported`);
  2495                  }
  2496                  tracks.push(track.mp4track);
  2497                  this.codecs.push(track.mp4track.codec);
  2498                  track.init(initPts, initDts/*, false*/);
  2499                  // initPts = Math.min(track.initPTS, initPts);
  2500                  // initDts = Math.min(track.initDTS, initDts);
  2501              }
  2502  
  2503              for (let track_type in this.tracks) {
  2504                  let track = this.tracks[track_type];
  2505                  //track.init(initPts, initDts);
  2506                  this.initSegments[track_type] = MP4.initSegment([track.mp4track], track.duration*track.timescale, track.timescale);
  2507                  initmse.push(this.initMSE(track_type, track.mp4track.codec));
  2508              }
  2509              this.initialized = true;
  2510              return Promise.all(initmse).then(()=>{
  2511                  //this.mse.play();
  2512                  this.enabled = true;
  2513              });
  2514              
  2515          }
  2516  
  2517          initMSE(track_type, codec) {
  2518              if (MSE.isSupported(this.codecs)) {
  2519                  return this.mse.setCodec(track_type, `${PayloadType.map[track_type]}/mp4; codecs="${codec}"`).then(()=>{
  2520                      this.mse.feed(track_type, this.initSegments[track_type]);
  2521                      // this.mse.play();
  2522                      // this.enabled = true;
  2523                  });
  2524              } else {
  2525                  throw new Error('Codecs are not supported');
  2526              }
  2527          }
  2528  
  2529          mseClose() {
  2530              // this.mse.clear();
  2531              this.client.stop();
  2532              this.eventSource.dispatchEvent('stopped');
  2533          }
  2534  
  2535          mseErrorDecode() {
  2536              if(this.tracks[2]) {
  2537                  console.warn(this.tracks[2].mp4track.type);
  2538                  this.mse.buffers[2].destroy();
  2539                  delete this.tracks[2];
  2540              }
  2541          }
  2542  
  2543          flush() {
  2544              this.onSamples();
  2545  
  2546              if (!this.initialized) {
  2547                  // Log.debug(`Initialize...`);
  2548                  if (Object.keys(this.tracks).length) {
  2549                      for (let track_type in this.tracks) {
  2550                          if (!this.tracks[track_type].readyToDecode || !this.tracks[track_type].samples.length) return;
  2551                          Log$5.debug(`Init MSE for track ${this.tracks[track_type].mp4track.type}`);
  2552                      }
  2553                      this.eventSource.dispatchEvent('ready');
  2554                  }
  2555              } else {
  2556                  for (let track_type in this.tracks) {
  2557                      let track = this.tracks[track_type];
  2558                      let pay = track.getPayload();
  2559                      if (pay && pay.byteLength) {
  2560                          this.mse.feed(track_type, [MP4.moof(track.seq, track.scaled(track.firstDTS), track.mp4track), MP4.mdat(pay)]);
  2561                          track.flush();
  2562                      }
  2563                  }
  2564              }
  2565          }
  2566  
  2567          onSamples(ev) {
  2568              // TODO: check format
  2569              // let data = ev.detail;
  2570              // if (this.tracks[data.pay] && this.client.sampleQueues[data.pay].length) {
  2571                  // console.log(`video ${data.units[0].dts}`);
  2572              for (let qidx in this.client.sampleQueues) {
  2573                  let queue = this.client.sampleQueues[qidx];
  2574                  while (queue.length) {
  2575                      let units = queue.shift();
  2576                      if(units){
  2577                          for (let chunk of units) {
  2578                              if(this.tracks[qidx]) {
  2579                                  this.tracks[qidx].remux(chunk);
  2580                              }
  2581                          }
  2582                      } else {
  2583                          if (!this.initialized) {
  2584                              delete this.tracks[qidx];
  2585                          }
  2586                      }
  2587                  }
  2588              }
  2589              // }
  2590          }
  2591  
  2592          onAudioConfig(ev) {
  2593              if (this.tracks[ev.detail.pay]) {
  2594                  this.tracks[ev.detail.pay].setConfig(ev.detail.config);
  2595              }
  2596          }
  2597  
  2598          attachClient(client) {
  2599              this.detachClient();
  2600              this.client = client;
  2601              this.clientEventSource = new EventSourceWrapper(this.client.eventSource);
  2602              this.clientEventSource.on('samples', this.samplesListener);
  2603              this.clientEventSource.on('audio_config', this.audioConfigListener);
  2604              this.clientEventSource.on('tracks', this.onTracks.bind(this));
  2605              this.clientEventSource.on('flush', this.flush.bind(this));
  2606              this.clientEventSource.on('clear', ()=>{
  2607                  this.reset();
  2608                  this.mse.clear().then(()=>{
  2609                      //this.mse.play();
  2610                      this.initMSEHandlers();
  2611                  });
  2612              });
  2613          }
  2614  
  2615          detachClient() {
  2616              if (this.client) {
  2617                  this.clientEventSource.destroy();
  2618                  // this.client.eventSource.removeEventListener('samples', this.onSamples.bind(this));
  2619                  // this.client.eventSource.removeEventListener('audio_config', this.onAudioConfig.bind(this));
  2620                  // // TODO: clear other listeners
  2621                  // this.client.eventSource.removeEventListener('clear', this._clearListener);
  2622                  // this.client.eventSource.removeEventListener('tracks', this._tracksListener);
  2623                  // this.client.eventSource.removeEventListener('flush', this._flushListener);
  2624                  this.client = null;
  2625              }
  2626          }
  2627      }
  2628  
  2629      class BaseTransport {
  2630          constructor(endpoint, stream_type, config={}) {
  2631              this.stream_type = stream_type;
  2632              this.endpoint = endpoint;
  2633              this.eventSource = new EventEmitter();
  2634              this.dataQueue = [];
  2635          }
  2636  
  2637          static canTransfer(stream_type) {
  2638              return BaseTransport.streamTypes().includes(stream_type);
  2639          }
  2640          
  2641          static streamTypes() {
  2642              return [];
  2643          }
  2644  
  2645          destroy() {
  2646              this.eventSource.destroy();
  2647          }
  2648  
  2649          connect() {
  2650              // TO be impemented
  2651          }
  2652  
  2653          disconnect() {
  2654              // TO be impemented
  2655          }
  2656  
  2657          reconnect() {
  2658              return this.disconnect().then(()=>{
  2659                  return this.connect();
  2660              });
  2661          }
  2662  
  2663          setEndpoint(endpoint) {
  2664              this.endpoint = endpoint;
  2665              return this.reconnect();
  2666          }
  2667  
  2668          send(data) {
  2669              // TO be impemented
  2670              // return this.prepare(data).send();
  2671          }
  2672  
  2673          prepare(data) {
  2674              // TO be impemented
  2675              // return new Request(data);
  2676          }
  2677  
  2678          // onData(type, data) {
  2679          //     this.eventSource.dispatchEvent(type, data);
  2680          // }
  2681      }
  2682  
  2683      const LOG_TAG$2 = "transport:ws";
  2684      const Log$6 = getTagged(LOG_TAG$2);
  2685  
  2686      function bytesToString(data) {
  2687          let len = data.length;
  2688          let dataString = "";
  2689          for (let i = 0; i < len; i++) {
  2690              dataString += String.fromCharCode(data[i]);
  2691          }
  2692          return dataString
  2693      }
  2694  
  2695      class WebsocketTransport extends BaseTransport {
  2696          constructor(endpoint, stream_type, options={
  2697              socket:`${location.protocol.replace('http', 'ws')}//${location.host}/ws/`,
  2698              workers: 1
  2699          }) {
  2700              super(endpoint, stream_type);
  2701              this.socket_url = options.socket;
  2702  
  2703              this.wssocket = undefined;
  2704              this.awaitingPromises = [];
  2705  
  2706              this.ready = this.connect();
  2707          }
  2708  
  2709          destroy() {
  2710              return this.disconnect().then(()=>{
  2711                  return super.destroy();
  2712              });
  2713  
  2714          }
  2715  
  2716          static canTransfer(stream_type) {
  2717              return WebsocketTransport.streamTypes().includes(stream_type);
  2718          }
  2719  
  2720          static streamTypes() {
  2721              return ['rtsp'];
  2722          }
  2723  
  2724          _connect() {
  2725              return new Promise((resolve, reject)=>{
  2726                  this.wssocket = new WebSocket(this.socket_url, 'rtsp');
  2727                  this.wssocket.binaryType = 'arraybuffer';
  2728  
  2729                  this.wssocket.onopen = ()=>{
  2730                      Log$6.log('websocket opened');
  2731                      this.eventSource.dispatchEvent('connected');
  2732                      resolve();
  2733                  };
  2734  
  2735                  this.wssocket.onmessage = (ev)=>{
  2736                      let data = new Uint8Array(ev.data);
  2737                      let prefix = data[0];
  2738                      if (prefix == 36) { // rtpData
  2739                          this.dataQueue.push(data);
  2740                          this.eventSource.dispatchEvent('data');
  2741                      } else  if (prefix == 82){ // rtsp response
  2742                          let rtspResp = bytesToString(data);
  2743                          if (this.awaitingPromises.length) {
  2744                              this.awaitingPromises.shift().resolve(rtspResp);
  2745                          }
  2746                      } else{
  2747                          Log$6.warn('无效包', prefix);
  2748                      }
  2749                  };
  2750  
  2751                  this.wssocket.onerror = (e)=>{
  2752                      Log$6.error(`${e.type}. code: ${e.code}, reason: ${e.reason || 'unknown reason'}`);
  2753                      this.wssocket.close();
  2754                      this.eventSource.dispatchEvent('error', {code: e.code, reason: e.reason || 'unknown reason'});
  2755                  };
  2756  
  2757                  this.wssocket.onclose = (e)=>{
  2758                      Log$6.log(`${e.type}. code: ${e.code}, reason: ${e.reason || 'unknown reason'}`);
  2759                      this.wssocket.onclose = null;
  2760                      this.wssocket.close();
  2761                      this.eventSource.dispatchEvent('disconnected', {code: e.code, reason: e.reason || 'unknown reason'});
  2762                  };
  2763              });
  2764          }
  2765  
  2766          connect() {
  2767              return this.disconnect().then((resolve, reject)=>{
  2768                  return this._connect()
  2769              });
  2770          }
  2771  
  2772          disconnect() {
  2773              return new Promise((resolve, reject)=>{
  2774                  if (this.wssocket){
  2775                      this.wssocket.onclose = ()=>{
  2776                          Log$6.log('websocket closed');
  2777                      };
  2778                      this.wssocket.close();
  2779                      resolve();
  2780                  }
  2781                  else{
  2782                      resolve();
  2783                  }
  2784              });
  2785          }
  2786  
  2787          send(_data) {
  2788              if (!this.wssocket) {
  2789                  throw new Error('websocekt disconnected'); 
  2790              }
  2791  
  2792              return new Promise((resolve, reject)=>{
  2793                      this.awaitingPromises.push({resolve:resolve, reject:reject});
  2794                      this.wssocket.send(_data);
  2795                  });
  2796          }
  2797      }
  2798  
  2799      class State {
  2800          constructor(name, stateMachine) {
  2801              this.stateMachine = stateMachine;
  2802              this.transitions = new Set();
  2803              this.name = name;
  2804          }
  2805  
  2806  
  2807          activate() {
  2808              return Promise.resolve(null);
  2809          }
  2810  
  2811          finishTransition() {}
  2812  
  2813          failHandler() {}
  2814  
  2815          deactivate() {
  2816              return Promise.resolve(null);
  2817          }
  2818      }
  2819  
  2820      class StateMachine {
  2821          constructor() {
  2822              this.storage = {};
  2823              this.currentState = null;
  2824              this.states = new Map();
  2825          }
  2826  
  2827          addState(name, {activate, finishTransition, deactivate}) {
  2828              let state = new State(name, this);
  2829              if (activate) state.activate = activate;
  2830              if (finishTransition) state.finishTransition = finishTransition;
  2831              if (deactivate) state.deactivate = deactivate;
  2832              this.states.set(name, state);
  2833              return this;
  2834          }
  2835  
  2836          addTransition(fromName, toName){
  2837              if (!this.states.has(fromName)) {
  2838                  throw ReferenceError(`No such state: ${fromName} while connecting to ${toName}`);
  2839              }
  2840              if (!this.states.has(toName)) {
  2841                  throw ReferenceError(`No such state: ${toName} while connecting from ${fromName}`);
  2842              }
  2843              this.states.get(fromName).transitions.add(toName);
  2844              return this;
  2845          }
  2846  
  2847          _promisify(res) {
  2848              let promise;
  2849              try {
  2850                  promise = res;
  2851                  if (!promise.then) {
  2852                      promise = Promise.resolve(promise);
  2853                  }
  2854              } catch (e) {
  2855                  promise = Promise.reject(e);
  2856              }
  2857              return promise;
  2858          }
  2859  
  2860          transitionTo(stateName) {
  2861              if (this.currentState == null) {
  2862                  let state = this.states.get(stateName);
  2863                  return this._promisify(state.activate.call(this))
  2864                      .then((data)=> {
  2865                          this.currentState = state;
  2866                          return data;
  2867                      }).then(state.finishTransition.bind(this)).catch((e)=>{
  2868                          state.failHandler();
  2869                          throw e;
  2870                      });
  2871              }
  2872              if (this.currentState.name == stateName) return Promise.resolve();
  2873              if (this.currentState.transitions.has(stateName)) {
  2874                  let state = this.states.get(stateName);
  2875                  return this._promisify(state.deactivate.call(this))
  2876                      .then(state.activate.bind(this)).then((data)=> {
  2877                          this.currentState = state;
  2878                          return data;
  2879                      }).then(state.finishTransition.bind(this)).catch((e)=>{
  2880                          state.failHandler();
  2881                          throw e;
  2882                      });
  2883              } else {
  2884                  return Promise.reject(`No such transition: ${this.currentState.name} to ${stateName}`);
  2885              }
  2886          }
  2887  
  2888      }
  2889  
  2890      const Log$7 = getTagged("parser:sdp");
  2891  
  2892      class SDPParser {
  2893          constructor() {
  2894              this.version = -1;
  2895              this.origin = null;
  2896              this.sessionName = null;
  2897              this.timing = null;
  2898              this.sessionBlock = {};
  2899              this.media = {};
  2900              this.tracks = {};
  2901              this.mediaMap = {};
  2902          }
  2903  
  2904          parse(content) {
  2905              // Log.debug(content);
  2906              return new Promise((resolve, reject) => {
  2907                  var dataString = content;
  2908                  var success = true;
  2909                  var currentMediaBlock = this.sessionBlock;
  2910  
  2911                  // TODO: multiple audio/video tracks
  2912  
  2913                  for (let line of dataString.split("\n")) {
  2914                      line = line.replace(/\r/, '');
  2915                      if (0 === line.length) {
  2916                          /* Empty row (last row perhaps?), skip to next */
  2917                          continue;
  2918                      }
  2919  
  2920                      switch (line.charAt(0)) {
  2921                          case 'v':
  2922                              if (-1 !== this.version) {
  2923                                  Log$7.log('Version present multiple times in SDP');
  2924                                  reject();
  2925                                  return false;
  2926                              }
  2927                              success = success && this._parseVersion(line);
  2928                              break;
  2929  
  2930                          case 'o':
  2931                              if (null !== this.origin) {
  2932                                  Log$7.log('Origin present multiple times in SDP');
  2933                                  reject();
  2934                                  return false;
  2935                              }
  2936                              success = success && this._parseOrigin(line);
  2937                              break;
  2938  
  2939                          case 's':
  2940                              if (null !== this.sessionName) {
  2941                                  Log$7.log('Session Name present multiple times in SDP');
  2942                                  reject();
  2943                                  return false;
  2944                              }
  2945                              success = success && this._parseSessionName(line);
  2946                              break;
  2947  
  2948                          case 't':
  2949                              if (null !== this.timing) {
  2950                                  Log$7.log('Timing present multiple times in SDP');
  2951                                  reject();
  2952                                  return false;
  2953                              }
  2954                              success = success && this._parseTiming(line);
  2955                              break;
  2956  
  2957                          case 'm':
  2958                              if (null !== currentMediaBlock && this.sessionBlock !== currentMediaBlock) {
  2959                                  /* Complete previous block and store it */
  2960                                  this.media[currentMediaBlock.type] = currentMediaBlock;
  2961                              }
  2962  
  2963                              /* A wild media block appears */
  2964                              currentMediaBlock = {};
  2965                              currentMediaBlock.rtpmap = {};
  2966                              this._parseMediaDescription(line, currentMediaBlock);
  2967                              break;
  2968  
  2969                          case 'a':
  2970                              SDPParser._parseAttribute(line, currentMediaBlock);
  2971                              break;
  2972  
  2973                          default:
  2974                              Log$7.log('Ignored unknown SDP directive: ' + line);
  2975                              break;
  2976                      }
  2977  
  2978                      if (!success) {
  2979                          reject();
  2980                          return;
  2981                      }
  2982                  }
  2983  
  2984                  this.media[currentMediaBlock.type] = currentMediaBlock;
  2985  
  2986                  success ? resolve() : reject();
  2987              });
  2988          }
  2989  
  2990          _parseVersion(line) {
  2991              let matches = line.match(/^v=([0-9]+)$/);
  2992              if (!matches || !matches.length) {
  2993                  Log$7.log('\'v=\' (Version) formatted incorrectly: ' + line);
  2994                  return false;
  2995              }
  2996  
  2997              this.version = matches[1];
  2998              if (0 != this.version) {
  2999                  Log$7.log('Unsupported SDP version:' + this.version);
  3000                  return false;
  3001              }
  3002  
  3003              return true;
  3004          }
  3005  
  3006          _parseOrigin(line) {
  3007              let matches = line.match(/^o=([^ ]+) (-?[0-9]+) (-?[0-9]+) (IN) (IP4|IP6) ([^ ]+)$/);
  3008              if (!matches || !matches.length) {
  3009                  Log$7.log('\'o=\' (Origin) formatted incorrectly: ' + line);
  3010                  return false;
  3011              }
  3012  
  3013              this.origin = {};
  3014              this.origin.username = matches[1];
  3015              this.origin.sessionid = matches[2];
  3016              this.origin.sessionversion = matches[3];
  3017              this.origin.nettype = matches[4];
  3018              this.origin.addresstype = matches[5];
  3019              this.origin.unicastaddress = matches[6];
  3020  
  3021              return true;
  3022          }
  3023  
  3024          _parseSessionName(line) {
  3025              let matches = line.match(/^s=([^\r\n]+)$/);
  3026              if (!matches || !matches.length) {
  3027                  Log$7.log('\'s=\' (Session Name) formatted incorrectly: ' + line);
  3028                  return false;
  3029              }
  3030  
  3031              this.sessionName = matches[1];
  3032  
  3033              return true;
  3034          }
  3035  
  3036          _parseTiming(line) {
  3037              let matches = line.match(/^t=([0-9]+) ([0-9]+)$/);
  3038              if (!matches || !matches.length) {
  3039                  Log$7.log('\'t=\' (Timing) formatted incorrectly: ' + line);
  3040                  return false;
  3041              }
  3042  
  3043              this.timing = {};
  3044              this.timing.start = matches[1];
  3045              this.timing.stop = matches[2];
  3046  
  3047              return true;
  3048          }
  3049  
  3050          _parseMediaDescription(line, media) {
  3051              let matches = line.match(/^m=([^ ]+) ([^ ]+) ([^ ]+)[ ]/);
  3052              if (!matches || !matches.length) {
  3053                  Log$7.log('\'m=\' (Media) formatted incorrectly: ' + line);
  3054                  return false;
  3055              }
  3056  
  3057              media.type = matches[1];
  3058              media.port = matches[2];
  3059              media.proto = matches[3];
  3060              media.fmt = line.substr(matches[0].length).split(' ').map(function (fmt, index, array) {
  3061                  return parseInt(fmt);
  3062              });
  3063  
  3064              for (let fmt of media.fmt) {
  3065                  this.mediaMap[fmt] = media;
  3066              }
  3067  
  3068              return true;
  3069          }
  3070  
  3071          static _parseAttribute(line, media) {
  3072              if (null === media) {
  3073                  /* Not in a media block, can't be bothered parsing attributes for session */
  3074                  return true;
  3075              }
  3076  
  3077              var matches;
  3078              /* Used for some cases of below switch-case */
  3079              var separator = line.indexOf(':');
  3080              var attribute = line.substr(0, (-1 === separator) ? 0x7FFFFFFF : separator);
  3081              /* 0x7FF.. is default */
  3082  
  3083              switch (attribute) {
  3084                  case 'a=recvonly':
  3085                  case 'a=sendrecv':
  3086                  case 'a=sendonly':
  3087                  case 'a=inactive':
  3088                      media.mode = line.substr('a='.length);
  3089                      break;
  3090                  case 'a=range':
  3091                      matches = line.match(/^a=range:\s*([a-zA-Z-]+)=([0-9.]+|now)\s*-\s*([0-9.]*)$/);
  3092                      media.range = [Number(matches[2] == "now" ? -1 : matches[2]), Number(matches[3]), matches[1]];
  3093                      break;
  3094                  case 'a=control':
  3095                      media.control = line.substr('a=control:'.length);
  3096                      break;
  3097  
  3098                  case 'a=rtpmap':
  3099                      matches = line.match(/^a=rtpmap:(\d+) (.*)$/);
  3100                      if (null === matches) {
  3101                          Log$7.log('Could not parse \'rtpmap\' of \'a=\'');
  3102                          return false;
  3103                      }
  3104  
  3105                      var payload = parseInt(matches[1]);
  3106                      media.rtpmap[payload] = {};
  3107  
  3108                      var attrs = matches[2].split('/');
  3109                      media.rtpmap[payload].name = attrs[0].toUpperCase();
  3110                      media.rtpmap[payload].clock = attrs[1];
  3111                      if (undefined !== attrs[2]) {
  3112                          media.rtpmap[payload].encparams = attrs[2];
  3113                      }
  3114                      media.ptype = PayloadType.string_map[attrs[0].toUpperCase()];
  3115  
  3116                      break;
  3117  
  3118                  case 'a=fmtp':
  3119                      matches = line.match(/^a=fmtp:(\d+) (.*)$/);
  3120                      if (0 === matches.length) {
  3121                          Log$7.log('Could not parse \'fmtp\'  of \'a=\'');
  3122                          return false;
  3123                      }
  3124  
  3125                      media.fmtp = {};
  3126                      for (var param of matches[2].split(';')) {
  3127                          var idx = param.indexOf('=');
  3128                          media.fmtp[param.substr(0, idx).toLowerCase().trim()] = param.substr(idx + 1).trim();
  3129                      }
  3130                      break;
  3131              }
  3132  
  3133              return true;
  3134          }
  3135  
  3136          getSessionBlock() {
  3137              return this.sessionBlock;
  3138          }
  3139  
  3140          hasMedia(mediaType) {
  3141              return this.media[mediaType] != undefined;
  3142          }
  3143  
  3144          getMediaBlock(mediaType) {
  3145              return this.media[mediaType];
  3146          }
  3147  
  3148          getMediaBlockByPayloadType(pt) {
  3149              // for (var m in this.media) {
  3150              //     if (-1 !== this.media[m].fmt.indexOf(pt)) {
  3151              //         return this.media[m];
  3152              //     }
  3153              // }
  3154              return this.mediaMap[pt] || null;
  3155  
  3156              //ErrorManager.dispatchError(826, [pt], true);
  3157              // Log.error(`failed to find media with payload type ${pt}`);
  3158              //
  3159              // return null;
  3160          }
  3161  
  3162          getMediaBlockList() {
  3163              var res = [];
  3164              for (var m in this.media) {
  3165                  res.push(m);
  3166              }
  3167  
  3168              return res;
  3169          }
  3170      }
  3171  
  3172      const LOG_TAG$3 = "rtsp:stream";
  3173      const Log$8 = getTagged(LOG_TAG$3);
  3174  
  3175      class RTSPStream {
  3176  
  3177          constructor(client, track) {
  3178              this.state = null;
  3179              this.client = client;
  3180              this.track = track;
  3181              this.rtpChannel = 1;
  3182  
  3183              this.stopKeepAlive();
  3184              this.keepaliveInterval = null;
  3185              this.keepaliveTime = 30000;
  3186          }
  3187  
  3188          reset() {
  3189              this.stopKeepAlive();
  3190              this.client.forgetRTPChannel(this.rtpChannel);
  3191              this.client = null;
  3192              this.track = null;
  3193          }
  3194  
  3195          start(lastSetupPromise = null) {
  3196              if (lastSetupPromise != null) {
  3197                  // if a setup was already made, use the same session
  3198                  return lastSetupPromise.then((obj) => this.sendSetup(obj.session))
  3199              } else {
  3200                  return this.sendSetup();
  3201              }
  3202          }
  3203  
  3204          stop() {
  3205              return this.sendTeardown();
  3206          }
  3207  
  3208          getSetupURL(track) {
  3209              let sessionBlock = this.client.sdp.getSessionBlock();
  3210              if (Url.isAbsolute(track.control)) {
  3211                  return track.control;
  3212              } else if (Url.isAbsolute(`${sessionBlock.control}${track.control}`)) {
  3213                  return `${sessionBlock.control}${track.control}`;
  3214              } else if (Url.isAbsolute(`${this.client.contentBase}${track.control}`)) {
  3215                  /* Should probably check session level control before this */
  3216                  return `${this.client.contentBase}${track.control}`;
  3217              }
  3218              else {//need return default
  3219                  return track.control;
  3220              }
  3221              Log$8.error('Can\'t determine track URL from ' +
  3222                  'block.control:' + track.control + ', ' +
  3223                  'session.control:' + sessionBlock.control + ', and ' +
  3224                  'content-base:' + this.client.contentBase);
  3225          }
  3226  
  3227          getControlURL() {
  3228              let ctrl = this.client.sdp.getSessionBlock().control;
  3229              if (Url.isAbsolute(ctrl)) {
  3230                  return ctrl;
  3231              } else if (!ctrl || '*' === ctrl) {
  3232                  return this.client.contentBase;
  3233              } else {
  3234                  return `${this.client.contentBase}${ctrl}`;
  3235              }
  3236          }
  3237  
  3238          sendKeepalive() {
  3239              if (this.client.methods.includes('GET_PARAMETER')) {
  3240                  return this.client.sendRequest('GET_PARAMETER', this.getSetupURL(this.track), {
  3241                      'Session': this.session
  3242                  });
  3243              } else {
  3244                  return this.client.sendRequest('OPTIONS', '*');
  3245              }
  3246          }
  3247  
  3248          stopKeepAlive() {
  3249              clearInterval(this.keepaliveInterval);
  3250          }
  3251  
  3252          startKeepAlive() {
  3253              this.keepaliveInterval = setInterval(() => {
  3254                  this.sendKeepalive().catch((e) => {
  3255                      // Log.error(e);
  3256                      if (e instanceof RTSPError) {
  3257                          if (Number(e.data.parsed.code) == 501) {
  3258                              return;
  3259                          }
  3260                      }
  3261                      this.client.reconnect();
  3262                  });
  3263              }, this.keepaliveTime);
  3264          }
  3265  
  3266          sendRequest(_cmd, _params = {}) {
  3267              let params = {};
  3268              if (this.session) {
  3269                  params['Session'] = this.session;
  3270              }
  3271              Object.assign(params, _params);
  3272              return this.client.sendRequest(_cmd, this.getControlURL(), params);
  3273          }
  3274  
  3275          sendSetup(session = null) {
  3276              this.state = RTSPClientSM.STATE_SETUP;
  3277              this.rtpChannel = this.client.interleaveChannelIndex;
  3278              let interleavedChannels = this.client.interleaveChannelIndex++ + "-" + this.client.interleaveChannelIndex++;
  3279              let params = {
  3280                  'Transport': `RTP/AVP/TCP;unicast;interleaved=${interleavedChannels}`,
  3281                  'Date': new Date().toUTCString()
  3282              };
  3283              if(session){
  3284                  params.Session = session;
  3285              }
  3286              return this.client.sendRequest('SETUP', this.getSetupURL(this.track), params).then((_data) => {
  3287                  this.session = _data.headers['session'];
  3288                  let transport = _data.headers['transport'];
  3289                  if (transport) {
  3290                      let interleaved = transport.match(/interleaved=([0-9]+)-([0-9]+)/)[1];
  3291                      if (interleaved) {
  3292                          this.rtpChannel = Number(interleaved);
  3293                      }
  3294                  }
  3295                  let sessionParamsChunks = this.session.split(';').slice(1);
  3296                  let sessionParams = {};
  3297                  for (let chunk of sessionParamsChunks) {
  3298                      let kv = chunk.split('=');
  3299                      sessionParams[kv[0]] = kv[1];
  3300                  }
  3301                  if (sessionParams['timeout']) {
  3302                      this.keepaliveInterval = Number(sessionParams['timeout']) * 500; // * 1000 / 2
  3303                  }
  3304                  /*if (!/RTP\/AVP\/TCP;unicast;interleaved=/.test(_data.headers["transport"])) {
  3305                      // TODO: disconnect stream and notify client
  3306                      throw new Error("Connection broken");
  3307                  }*/
  3308                  this.client.useRTPChannel(this.rtpChannel);
  3309                  this.startKeepAlive();
  3310                  return {track: this.track, data: _data, session: this.session};
  3311              });
  3312          }
  3313      }
  3314  
  3315      /*
  3316       * JavaScript MD5
  3317       * https://github.com/blueimp/JavaScript-MD5
  3318       *
  3319       * Copyright 2011, Sebastian Tschan
  3320       * https://blueimp.net
  3321       *
  3322       * Licensed under the MIT license:
  3323       * https://opensource.org/licenses/MIT
  3324       *
  3325       * Based on
  3326       * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
  3327       * Digest Algorithm, as defined in RFC 1321.
  3328       * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
  3329       * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
  3330       * Distributed under the BSD License
  3331       * See http://pajhome.org.uk/crypt/md5 for more info.
  3332       */
  3333  
  3334  
  3335      /*
  3336      * Add integers, wrapping at 2^32. This uses 16-bit operations internally
  3337      * to work around bugs in some JS interpreters.
  3338      */
  3339      function safeAdd(x, y) {
  3340          var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  3341          var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  3342          return (msw << 16) | (lsw & 0xFFFF)
  3343      }
  3344  
  3345      /*
  3346      * Bitwise rotate a 32-bit number to the left.
  3347      */
  3348      function bitRotateLeft(num, cnt) {
  3349          return (num << cnt) | (num >>> (32 - cnt))
  3350      }
  3351  
  3352      /*
  3353      * These functions implement the four basic operations the algorithm uses.
  3354      */
  3355      function md5cmn(q, a, b, x, s, t) {
  3356          return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
  3357      }
  3358      function md5ff(a, b, c, d, x, s, t) {
  3359          return md5cmn((b & c) | ((~b) & d), a, b, x, s, t)
  3360      }
  3361      function md5gg(a, b, c, d, x, s, t) {
  3362          return md5cmn((b & d) | (c & (~d)), a, b, x, s, t)
  3363      }
  3364      function md5hh(a, b, c, d, x, s, t) {
  3365          return md5cmn(b ^ c ^ d, a, b, x, s, t)
  3366      }
  3367      function md5ii(a, b, c, d, x, s, t) {
  3368          return md5cmn(c ^ (b | (~d)), a, b, x, s, t)
  3369      }
  3370  
  3371      /*
  3372      * Calculate the MD5 of an array of little-endian words, and a bit length.
  3373      */
  3374      function binlMD5(x, len) {
  3375          /* append padding */
  3376          x[len >> 5] |= 0x80 << (len % 32);
  3377          x[(((len + 64) >>> 9) << 4) + 14] = len;
  3378  
  3379          var i;
  3380          var olda;
  3381          var oldb;
  3382          var oldc;
  3383          var oldd;
  3384          var a = 1732584193;
  3385          var b = -271733879;
  3386          var c = -1732584194;
  3387          var d = 271733878;
  3388  
  3389          for (i = 0; i < x.length; i += 16) {
  3390              olda = a;
  3391              oldb = b;
  3392              oldc = c;
  3393              oldd = d;
  3394  
  3395              a = md5ff(a, b, c, d, x[i], 7, -680876936);
  3396              d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
  3397              c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
  3398              b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
  3399              a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
  3400              d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
  3401              c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
  3402              b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
  3403              a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
  3404              d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
  3405              c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
  3406              b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
  3407              a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
  3408              d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
  3409              c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
  3410              b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
  3411  
  3412              a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
  3413              d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
  3414              c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
  3415              b = md5gg(b, c, d, a, x[i], 20, -373897302);
  3416              a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
  3417              d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
  3418              c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
  3419              b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
  3420              a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
  3421              d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
  3422              c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
  3423              b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
  3424              a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
  3425              d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
  3426              c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
  3427              b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
  3428  
  3429              a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
  3430              d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
  3431              c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
  3432              b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
  3433              a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
  3434              d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
  3435              c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
  3436              b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
  3437              a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
  3438              d = md5hh(d, a, b, c, x[i], 11, -358537222);
  3439              c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
  3440              b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
  3441              a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
  3442              d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
  3443              c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
  3444              b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
  3445  
  3446              a = md5ii(a, b, c, d, x[i], 6, -198630844);
  3447              d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
  3448              c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
  3449              b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
  3450              a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
  3451              d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
  3452              c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
  3453              b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
  3454              a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
  3455              d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
  3456              c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
  3457              b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
  3458              a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
  3459              d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
  3460              c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
  3461              b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
  3462  
  3463              a = safeAdd(a, olda);
  3464              b = safeAdd(b, oldb);
  3465              c = safeAdd(c, oldc);
  3466              d = safeAdd(d, oldd);
  3467          }
  3468          return [a, b, c, d]
  3469      }
  3470  
  3471      /*
  3472      * Convert an array of little-endian words to a string
  3473      */
  3474      function binl2rstr(input) {
  3475          var i;
  3476          var output = '';
  3477          var length32 = input.length * 32;
  3478          for (i = 0; i < length32; i += 8) {
  3479              output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
  3480          }
  3481          return output
  3482      }
  3483  
  3484      /*
  3485      * Convert a raw string to an array of little-endian words
  3486      * Characters >255 have their high-byte silently ignored.
  3487      */
  3488      function rstr2binl(input) {
  3489          var i;
  3490          var output = [];
  3491          output[(input.length >> 2) - 1] = undefined;
  3492          for (i = 0; i < output.length; i += 1) {
  3493              output[i] = 0;
  3494          }
  3495          var length8 = input.length * 8;
  3496          for (i = 0; i < length8; i += 8) {
  3497              output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
  3498          }
  3499          return output
  3500      }
  3501  
  3502      /*
  3503      * Calculate the MD5 of a raw string
  3504      */
  3505      function rstrMD5(s) {
  3506          return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
  3507      }
  3508  
  3509      /*
  3510      * Calculate the HMAC-MD5, of a key and some data (raw strings)
  3511      */
  3512      function rstrHMACMD5(key, data) {
  3513          var i;
  3514          var bkey = rstr2binl(key);
  3515          var ipad = [];
  3516          var opad = [];
  3517          var hash;
  3518          ipad[15] = opad[15] = undefined;
  3519          if (bkey.length > 16) {
  3520              bkey = binlMD5(bkey, key.length * 8);
  3521          }
  3522          for (i = 0; i < 16; i += 1) {
  3523              ipad[i] = bkey[i] ^ 0x36363636;
  3524              opad[i] = bkey[i] ^ 0x5C5C5C5C;
  3525          }
  3526          hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
  3527          return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
  3528      }
  3529  
  3530      /*
  3531      * Convert a raw string to a hex string
  3532      */
  3533      function rstr2hex(input) {
  3534          var hexTab = '0123456789abcdef';
  3535          var output = '';
  3536          var x;
  3537          var i;
  3538          for (i = 0; i < input.length; i += 1) {
  3539              x = input.charCodeAt(i);
  3540              output += hexTab.charAt((x >>> 4) & 0x0F) +
  3541                  hexTab.charAt(x & 0x0F);
  3542          }
  3543          return output
  3544      }
  3545  
  3546      /*
  3547      * Encode a string as utf-8
  3548      */
  3549      function str2rstrUTF8(input) {
  3550          return unescape(encodeURIComponent(input))
  3551      }
  3552  
  3553      /*
  3554      * Take string arguments and return either raw or hex encoded strings
  3555      */
  3556      function rawMD5(s) {
  3557          return rstrMD5(str2rstrUTF8(s))
  3558      }
  3559      function hexMD5(s) {
  3560          return rstr2hex(rawMD5(s))
  3561      }
  3562      function rawHMACMD5(k, d) {
  3563          return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
  3564      }
  3565      function hexHMACMD5(k, d) {
  3566          return rstr2hex(rawHMACMD5(k, d))
  3567      }
  3568  
  3569      function md5(string, key, raw) {
  3570          if (!key) {
  3571              if (!raw) {
  3572                  return hexMD5(string)
  3573              }
  3574              return rawMD5(string)
  3575          }
  3576          if (!raw) {
  3577              return hexHMACMD5(key, string)
  3578          }
  3579          return rawHMACMD5(key, string)
  3580      }
  3581  
  3582      // TODO: asm.js
  3583      class RTP {
  3584          constructor(pkt/*uint8array*/, sdp) {
  3585              let bytes = new DataView(pkt.buffer, pkt.byteOffset, pkt.byteLength);
  3586  
  3587              this.version   = bytes.getUint8(0) >>> 6;
  3588              this.padding   = bytes.getUint8(0) & 0x20 >>> 5;
  3589              this.has_extension = bytes.getUint8(0) & 0x10 >>> 4;
  3590              this.csrc      = bytes.getUint8(0) & 0x0F;
  3591              this.marker    = bytes.getUint8(1) >>> 7;
  3592              this.pt        = bytes.getUint8(1) & 0x7F;
  3593              this.sequence  = bytes.getUint16(2) ;
  3594              this.timestamp = bytes.getUint32(4);
  3595              this.ssrc      = bytes.getUint32(8);
  3596              this.csrcs     = [];
  3597  
  3598              let pktIndex=12;
  3599              if (this.csrc>0) {
  3600                  this.csrcs.push(bytes.getUint32(pktIndex));
  3601                  pktIndex+=4;
  3602              }
  3603              if (this.has_extension==1) {
  3604                  this.extension = bytes.getUint16(pktIndex);
  3605                  this.ehl = bytes.getUint16(pktIndex+2);
  3606                  pktIndex+=4;
  3607                  this.header_data = pkt.slice(pktIndex, this.ehl);
  3608                  pktIndex += this.ehl;
  3609              }
  3610  
  3611              this.headerLength = pktIndex;
  3612              let padLength = 0;
  3613              if (this.padding) {
  3614                  padLength = bytes.getUint8(pkt.byteLength-1);
  3615              }
  3616  
  3617              // this.bodyLength   = pkt.byteLength-this.headerLength-padLength;
  3618  
  3619              this.media = sdp.getMediaBlockByPayloadType(this.pt);
  3620              if (null === this.media) {
  3621                  Log.log(`Media description for payload type: ${this.pt} not provided.`);
  3622              } else {
  3623                  this.type = this.media.ptype;//PayloadType.string_map[this.media.rtpmap[this.media.fmt[0]].name];
  3624              }
  3625  
  3626              this.data = pkt.subarray(pktIndex);
  3627              // this.timestamp = 1000 * (this.timestamp / this.media.rtpmap[this.pt].clock);
  3628              // console.log(this);
  3629          }
  3630          getPayload() {
  3631              return this.data;
  3632          }
  3633  
  3634          getTimestampMS() {
  3635              return this.timestamp; //1000 * (this.timestamp / this.media.rtpmap[this.pt].clock);
  3636          }
  3637  
  3638          toString() {
  3639              return "RTP(" +
  3640                  "version:"   + this.version   + ", " +
  3641                  "padding:"   + this.padding   + ", " +
  3642                  "has_extension:" + this.has_extension + ", " +
  3643                  "csrc:"      + this.csrc      + ", " +
  3644                  "marker:"    + this.marker    + ", " +
  3645                  "pt:"        + this.pt        + ", " +
  3646                  "sequence:"  + this.sequence  + ", " +
  3647                  "timestamp:" + this.timestamp + ", " +
  3648                  "ssrc:"      + this.ssrc      + ")";
  3649          }
  3650  
  3651          isVideo(){return this.media.type == 'video';}
  3652          isAudio(){return this.media.type == 'audio';}
  3653  
  3654          
  3655      }
  3656  
  3657      class RTPFactory {
  3658          constructor(sdp) {
  3659              this.tsOffsets={};
  3660              for (let pay in sdp.media) {
  3661                  for (let pt of sdp.media[pay].fmt) {
  3662                      this.tsOffsets[pt] = {last: 0, overflow: 0};
  3663                  }
  3664              }
  3665          }
  3666  
  3667          build(pkt/*uint8array*/, sdp) {
  3668              let rtp = new RTP(pkt, sdp);
  3669  
  3670              let tsOffset = this.tsOffsets[rtp.pt];
  3671              if (tsOffset) {
  3672                  rtp.timestamp += tsOffset.overflow;
  3673                  if (tsOffset.last && Math.abs(rtp.timestamp - tsOffset.last) > 0x7fffffff) {
  3674                      console.log(`\nlast ts: ${tsOffset.last}\n
  3675                              new ts: ${rtp.timestamp}\n
  3676                              new ts adjusted: ${rtp.timestamp+0xffffffff}\n
  3677                              last overflow: ${tsOffset.overflow}\n
  3678                              new overflow: ${tsOffset.overflow+0xffffffff}\n
  3679                              `);
  3680                      tsOffset.overflow += 0xffffffff;
  3681                      rtp.timestamp += 0xffffffff;
  3682                  }
  3683                  /*if (rtp.timestamp>0xffffffff) {
  3684                      console.log(`ts: ${rtp.timestamp}, seq: ${rtp.sequence}`);
  3685                  }*/
  3686                  tsOffset.last = rtp.timestamp;
  3687              }
  3688  
  3689              return rtp;
  3690          }
  3691      }
  3692  
  3693      class RTSPMessage {
  3694          static get RTSP_1_0() {return  "RTSP/1.0";}
  3695  
  3696          constructor(_rtsp_version) {
  3697              this.version = _rtsp_version;
  3698          }
  3699  
  3700          build(_cmd, _host, _params={}, _payload=null) {
  3701              let requestString = `${_cmd} ${_host} ${this.version}\r\n`;
  3702              for (let param in _params) {
  3703                  requestString+=`${param}: ${_params[param]}\r\n`;
  3704              }
  3705              // TODO: binary payload
  3706              if (_payload) {
  3707                  requestString+=`Content-Length: ${_payload.length}\r\n`;
  3708              }
  3709              requestString+='\r\n';
  3710              if (_payload) {
  3711                  requestString+=_payload;
  3712              }
  3713              return requestString;
  3714          }
  3715  
  3716          parse(_data) {
  3717              let lines = _data.split('\r\n');
  3718              let parsed = {
  3719                  headers:{},
  3720                  body:null,
  3721                  code: 0,
  3722                  statusLine: ''
  3723              };
  3724  
  3725              let match;
  3726              [match, parsed.code, parsed.statusLine] = lines[0].match(new RegExp(`${this.version}[ ]+([0-9]{3})[ ]+(.*)`));
  3727              parsed.code = Number(parsed.code);
  3728              let lineIdx = 1;
  3729  
  3730              while (lines[lineIdx]) {
  3731                  let [k,v] = lines[lineIdx].split(/:(.+)/);
  3732                  parsed.headers[k.toLowerCase()] = v.trim();
  3733                  lineIdx++;
  3734              }
  3735  
  3736              parsed.body = lines.slice(lineIdx).join('\n\r');
  3737  
  3738              return parsed;
  3739          }
  3740  
  3741      }
  3742  
  3743      const MessageBuilder = new RTSPMessage(RTSPMessage.RTSP_1_0);
  3744  
  3745      // TODO: asm.js
  3746      class NALUAsm {
  3747  
  3748          constructor() {
  3749              this.fragmented_nalu = null;
  3750          }
  3751  
  3752  
  3753          static parseNALHeader(hdr) {
  3754              return {
  3755                  nri: hdr & 0x60,
  3756                  type: hdr & 0x1F
  3757              }
  3758          }
  3759  
  3760          parseSingleNALUPacket(rawData, header, dts, pts) {
  3761              return new NALU(header.type,  header.nri, rawData.subarray(0), dts, pts);
  3762          }
  3763  
  3764          parseAggregationPacket(rawData, header, dts, pts) {
  3765              let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);
  3766              let nal_start_idx = 0;
  3767              let don = null;
  3768              if (NALU.STAP_B === header.type) {
  3769                  don = data.getUint16(nal_start_idx);
  3770                  nal_start_idx += 2;
  3771              }
  3772              let ret = [];
  3773              while (nal_start_idx < data.byteLength) {
  3774                  let size = data.getUint16(nal_start_idx);
  3775                  nal_start_idx += 2;
  3776                  let header = NALUAsm.parseNALHeader(data.getInt8(nal_start_idx));
  3777                  nal_start_idx++;
  3778                  let nalu = this.parseSingleNALUPacket(rawData.subarray(nal_start_idx, nal_start_idx+size), header, dts, pts);
  3779                  if (nalu !== null) {
  3780                      ret.push(nalu);
  3781                  }
  3782                  nal_start_idx+=size;
  3783              }
  3784              return ret;
  3785          }
  3786  
  3787          parseFragmentationUnit(rawData, header, dts, pts) {
  3788              let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);
  3789              let nal_start_idx = 0;
  3790              let fu_header = data.getUint8(nal_start_idx);
  3791              let is_start = (fu_header & 0x80) >>> 7;
  3792              let is_end = (fu_header & 0x40) >>> 6;
  3793              let payload_type = fu_header & 0x1F;
  3794              let ret = null;
  3795  
  3796              nal_start_idx++;
  3797              let don = 0;
  3798              if (NALU.FU_B === header.type) {
  3799                  don = data.getUint16(nal_start_idx);
  3800                  nal_start_idx += 2;
  3801              }
  3802  
  3803              if (is_start) {
  3804                  this.fragmented_nalu = new NALU(payload_type, header.nri, rawData.subarray(nal_start_idx), dts, pts);
  3805              }
  3806              if (this.fragmented_nalu && this.fragmented_nalu.ntype === payload_type) {
  3807                  if (!is_start) {
  3808                      this.fragmented_nalu.appendData(rawData.subarray(nal_start_idx));
  3809                  }
  3810                  if (is_end) {
  3811                      ret = this.fragmented_nalu;
  3812                      this.fragmented_nalu = null;
  3813                      return ret;
  3814                  }
  3815              }
  3816              return null;
  3817          }
  3818  
  3819          onNALUFragment(rawData, dts, pts) {
  3820  
  3821              let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);
  3822  
  3823              let header = NALUAsm.parseNALHeader(data.getUint8(0));
  3824  
  3825              let nal_start_idx = 1;
  3826  
  3827              let unit = null;
  3828              if (header.type > 0 && header.type < 24) {
  3829                  unit = this.parseSingleNALUPacket(rawData.subarray(nal_start_idx), header, dts, pts);
  3830              } else if (NALU.FU_A ===  header.type || NALU.FU_B ===  header.type) {
  3831                  unit = this.parseFragmentationUnit(rawData.subarray(nal_start_idx), header, dts, pts);
  3832              } else if (NALU.STAP_A === header.type || NALU.STAP_B === header.type) {
  3833                  return this.parseAggregationPacket(rawData.subarray(nal_start_idx), header, dts, pts);
  3834              } else {
  3835                  /* 30 - 31 is undefined, ignore those (RFC3984). */
  3836                  Log.log('Undefined NAL unit, type: ' + header.type);
  3837                  return null;
  3838              }
  3839              if (unit) {
  3840                  return [unit];
  3841              }
  3842              return null;
  3843          }
  3844      }
  3845  
  3846      class AACFrame {
  3847  
  3848          constructor(data, dts, pts) {
  3849              this.dts = dts;
  3850              this.pts = pts ? pts : this.dts;
  3851  
  3852              this.data=data;//.subarray(offset);
  3853          }
  3854  
  3855          getData() {
  3856              return this.data;
  3857          }
  3858  
  3859          getSize() {
  3860              return this.data.byteLength;
  3861          }
  3862      }
  3863  
  3864      // import {AACParser} from "../parsers/aac.js";
  3865      // TODO: asm.js
  3866      class AACAsm {
  3867          constructor() {
  3868              this.config = null;
  3869          }
  3870  
  3871          onAACFragment(pkt) {
  3872              let rawData = pkt.getPayload();
  3873              if (!pkt.media) {
  3874                  return null;
  3875              }
  3876              let data = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength);
  3877  
  3878              let sizeLength = Number(pkt.media.fmtp['sizelength'] || 0);
  3879              let indexLength = Number(pkt.media.fmtp['indexlength'] || 0);
  3880              let indexDeltaLength = Number(pkt.media.fmtp['indexdeltalength'] || 0);
  3881              let CTSDeltaLength = Number(pkt.media.fmtp['ctsdeltalength'] || 0);
  3882              let DTSDeltaLength = Number(pkt.media.fmtp['dtsdeltalength'] || 0);
  3883              let RandomAccessIndication = Number(pkt.media.fmtp['randomaccessindication'] || 0);
  3884              let StreamStateIndication = Number(pkt.media.fmtp['streamstateindication'] || 0);
  3885              let AuxiliaryDataSizeLength = Number(pkt.media.fmtp['auxiliarydatasizelength'] || 0);
  3886  
  3887              let configHeaderLength =
  3888                  sizeLength + Math.max(indexLength, indexDeltaLength) + CTSDeltaLength + DTSDeltaLength +
  3889                  RandomAccessIndication + StreamStateIndication + AuxiliaryDataSizeLength;
  3890  
  3891  
  3892              let auHeadersLengthPadded = 0;
  3893              let offset = 0;
  3894              let ts = (Math.round(pkt.getTimestampMS()/1024) << 10) * 90000 / this.config.samplerate;
  3895              if (0 !== configHeaderLength) {
  3896                  /* The AU header section is not empty, read it from payload */
  3897                  let auHeadersLengthInBits = data.getUint16(0); // Always 2 octets, without padding
  3898                  auHeadersLengthPadded = 2 + (auHeadersLengthInBits>>>3) + ((auHeadersLengthInBits & 0x7)?1:0); // Add padding
  3899  
  3900                  //this.config = AACParser.parseAudioSpecificConfig(new Uint8Array(rawData, 0 , auHeadersLengthPadded));
  3901                  // TODO: parse config
  3902                  let frames = [];
  3903                  let frameOffset=0;
  3904                  let bits = new BitArray(rawData.subarray(2 + offset));
  3905                  let cts = 0;
  3906                  let dts = 0;
  3907                  for (let offset=0; offset<auHeadersLengthInBits;) {
  3908                      let size = bits.readBits(sizeLength);
  3909                      let idx = bits.readBits(offset?indexDeltaLength:indexLength);
  3910                      offset+=sizeLength+(offset?indexDeltaLength:indexLength)/*+2*/;
  3911                      if (/*ctsPresent &&*/ CTSDeltaLength) {
  3912                          let ctsPresent = bits.readBits(1);
  3913                          cts = bits.readBits(CTSDeltaLength);
  3914                          offset+=CTSDeltaLength;
  3915                      }
  3916                      if (/*dtsPresent && */DTSDeltaLength) {
  3917                          let dtsPresent = bits.readBits(1);
  3918                          dts = bits.readBits(DTSDeltaLength);
  3919                          offset+=CTSDeltaLength;
  3920                      }
  3921                      if (RandomAccessIndication) {
  3922                          bits.skipBits(1);
  3923                          offset+=1;
  3924                      }
  3925                      if (StreamStateIndication) {
  3926                          bits.skipBits(StreamStateIndication);
  3927                          offset+=StreamStateIndication;
  3928                      }
  3929                      frames.push(new AACFrame(rawData.subarray(auHeadersLengthPadded + frameOffset, auHeadersLengthPadded + frameOffset + size), ts+dts, ts+cts));
  3930                      frameOffset+=size;
  3931                  }
  3932                  return frames;
  3933              } else {
  3934                  let aacData = rawData.subarray(auHeadersLengthPadded);
  3935                  while (true) {
  3936                      if (aacData[offset] !=255) break;
  3937                      ++offset;
  3938                  }
  3939                  ++offset;
  3940                  return [new AACFrame(rawData.subarray(auHeadersLengthPadded+offset), ts)];
  3941              }
  3942          }
  3943      }
  3944  
  3945      class RTPPayloadParser {
  3946  
  3947          constructor() {
  3948              this.h264parser = new RTPH264Parser();
  3949              this.aacparser = new RTPAACParser();
  3950          }
  3951  
  3952          parse(rtp) {
  3953              if (rtp.media.type=='video') {
  3954                  return this.h264parser.parse(rtp);
  3955              } else if (rtp.media.type == 'audio') {
  3956                  return this.aacparser.parse(rtp);
  3957              }
  3958              return null;
  3959          }
  3960      }
  3961  
  3962      class RTPH264Parser {
  3963          constructor() {
  3964              this.naluasm = new NALUAsm();
  3965          }
  3966  
  3967          parse(rtp) {
  3968              return this.naluasm.onNALUFragment(rtp.getPayload(), rtp.getTimestampMS());
  3969          }
  3970      }
  3971  
  3972      class RTPAACParser {
  3973  
  3974          constructor() {
  3975              this.scale = 1;
  3976              this.asm = new AACAsm();
  3977          }
  3978  
  3979          setConfig(conf) {
  3980              this.asm.config = conf;
  3981          }
  3982  
  3983          parse(rtp) {
  3984              return this.asm.onAACFragment(rtp);
  3985          }
  3986      }
  3987  
  3988      class BaseClient {
  3989          constructor(options={flush: 100}) {
  3990              this.options = options;
  3991              this.eventSource = new EventEmitter();
  3992  
  3993              Object.defineProperties(this, {
  3994                  sourceUrl: {value: null, writable: true},   // TODO: getter with validator
  3995                  paused: {value: true, writable: true},
  3996                  seekable: {value: false, writable: true},
  3997                  connected: {value: false, writable: true}
  3998              });
  3999  
  4000              this._onData = ()=>{
  4001                  if (this.connected) {
  4002                      while (this.transport.dataQueue.length) {
  4003                          this.onData(this.transport.dataQueue.pop());
  4004                      }
  4005                  }
  4006              };
  4007              this._onConnect = this.onConnected.bind(this);
  4008              this._onDisconnect = this.onDisconnected.bind(this);
  4009          }
  4010  
  4011          static streamType() {
  4012              return null;    
  4013          }
  4014          
  4015          destroy() {
  4016              this.detachTransport();
  4017          }
  4018  
  4019          attachTransport(transport) {
  4020              if (this.transport) {
  4021                  this.detachTransport();
  4022              }
  4023              this.transport = transport;
  4024              this.transport.eventSource.addEventListener('data', this._onData);
  4025              this.transport.eventSource.addEventListener('connected', this._onConnect);
  4026              this.transport.eventSource.addEventListener('disconnected', this._onDisconnect);
  4027          }
  4028  
  4029          detachTransport() {
  4030              if (this.transport) {
  4031                  this.transport.eventSource.removeEventListener('data', this._onData);
  4032                  this.transport.eventSource.removeEventListener('connected', this._onConnect);
  4033                  this.transport.eventSource.removeEventListener('disconnected', this._onDisconnect);
  4034                  this.transport = null;
  4035              }
  4036          }
  4037          reset() {
  4038  
  4039          }
  4040  
  4041          start() {
  4042              Log.log('Client started');
  4043              this.paused = false;
  4044              // this.startStreamFlush();
  4045          }
  4046  
  4047          stop() {
  4048              Log.log('Client paused');
  4049              this.paused = true;
  4050              // this.stopStreamFlush();
  4051          }
  4052  
  4053          seek(timeOffset) {
  4054  
  4055          }
  4056  
  4057          setSource(source) {
  4058              this.stop();
  4059              this.endpoint = source;
  4060              this.sourceUrl = source.urlpath;
  4061          }
  4062  
  4063          startStreamFlush() {
  4064              this.flushInterval = setInterval(()=>{
  4065                  if (!this.paused) {
  4066                      this.eventSource.dispatchEvent('flush');
  4067                  }
  4068              }, this.options.flush);
  4069          }
  4070  
  4071          stopStreamFlush() {
  4072              clearInterval(this.flushInterval);
  4073          }
  4074  
  4075          onData(data) {
  4076  
  4077          }
  4078  
  4079          onConnected() {
  4080              if (!this.seekable) {
  4081                  this.transport.dataQueue = [];
  4082                  this.eventSource.dispatchEvent('clear');
  4083              }
  4084              this.connected = true;
  4085          }
  4086  
  4087          onDisconnected() {
  4088              this.connected = false;
  4089          }
  4090  
  4091          queryCredentials() {
  4092              return Promise.resolve();
  4093          }
  4094  
  4095          setCredentials(user, password) {
  4096              this.endpoint.user = user;
  4097              this.endpoint.pass = password;
  4098              this.endpoint.auth = `${user}:${password}`;
  4099          }
  4100      }
  4101  
  4102      class AACParser {
  4103          static get SampleRates() {return  [
  4104              96000, 88200,
  4105              64000, 48000,
  4106              44100, 32000,
  4107              24000, 22050,
  4108              16000, 12000,
  4109              11025, 8000,
  4110              7350];}
  4111  
  4112          // static Profile = [
  4113          //     0: Null
  4114          //     1: AAC Main
  4115          //     2: AAC LC (Low Complexity)
  4116          //     3: AAC SSR (Scalable Sample Rate)
  4117          //     4: AAC LTP (Long Term Prediction)
  4118          //     5: SBR (Spectral Band Replication)
  4119          //     6: AAC Scalable
  4120          // ]
  4121  
  4122          static parseAudioSpecificConfig(bytesOrBits) {
  4123              let config;
  4124              if (bytesOrBits.byteLength) { // is byteArray
  4125                  config = new BitArray(bytesOrBits);
  4126              } else {
  4127                  config = bytesOrBits;
  4128              }
  4129  
  4130              let bitpos = config.bitpos+(config.src.byteOffset+config.bytepos)*8;
  4131              let prof = config.readBits(5);
  4132              this.codec = `mp4a.40.${prof}`;
  4133              let sfi = config.readBits(4);
  4134              if (sfi == 0xf) config.skipBits(24);
  4135              let channels = config.readBits(4);
  4136  
  4137              return {
  4138                  config: bitSlice(new Uint8Array(config.src.buffer), bitpos, bitpos+16),
  4139                  codec: `mp4a.40.${prof}`,
  4140                  samplerate: AACParser.SampleRates[sfi],
  4141                  channels: channels
  4142              }
  4143          }
  4144  
  4145          static parseStreamMuxConfig(bytes) {
  4146              // ISO_IEC_14496-3 Part 3 Audio. StreamMuxConfig
  4147              let config = new BitArray(bytes);
  4148  
  4149              if (!config.readBits(1)) {
  4150                  config.skipBits(14);
  4151                  return AACParser.parseAudioSpecificConfig(config);
  4152              }
  4153          }
  4154      }
  4155  
  4156      const LOG_TAG$4 = "rtsp:session";
  4157      const Log$9 = getTagged(LOG_TAG$4);
  4158  
  4159      class RTSPSession {
  4160  
  4161          constructor(client, sessionId) {
  4162              this.state = null;
  4163              this.client = client;
  4164              this.sessionId = sessionId;
  4165              this.url = this.getControlURL();
  4166          }
  4167  
  4168          reset() {
  4169              this.client = null;
  4170          }
  4171  
  4172          start() {
  4173              return this.sendPlay();
  4174          }
  4175  
  4176          stop() {
  4177              return this.sendTeardown();
  4178          }
  4179  
  4180          getControlURL() {
  4181              let ctrl = this.client.sdp.getSessionBlock().control;
  4182              if (Url.isAbsolute(ctrl)) {
  4183                  return ctrl;
  4184              } else if (!ctrl || '*' === ctrl) {
  4185                  return this.client.contentBase;
  4186              } else {
  4187                  return `${this.client.contentBase}${ctrl}`;
  4188              }
  4189          }
  4190  
  4191          sendRequest(_cmd, _params = {}) {
  4192              let params = {};
  4193              if (this.sessionId) {
  4194                  params['Session'] = this.sessionId;
  4195              }
  4196              Object.assign(params, _params);
  4197              return this.client.sendRequest(_cmd, this.getControlURL(), params);
  4198          }
  4199  
  4200          async sendPlay(pos = 0) {
  4201              this.state = RTSPClientSM.STATE_PLAY;
  4202              let params = {};
  4203              let range = this.client.sdp.sessionBlock.range;
  4204              if (range) {
  4205                  // TODO: seekable
  4206                  if (range[0] == -1) {
  4207                      range[0] = 0;// Do not handle now at the moment
  4208                  }
  4209                  // params['Range'] = `${range[2]}=${range[0]}-`;
  4210              }
  4211              let data = await this.sendRequest('PLAY', params);
  4212              this.state = RTSPClientSM.STATE_PLAYING;
  4213              return {data: data};
  4214          }
  4215  
  4216          async sendPause() {
  4217              if (!this.client.supports("PAUSE")) {
  4218                  return;
  4219              }
  4220              this.state = RTSPClientSM.STATE_PAUSE;
  4221              await this.sendRequest("PAUSE");
  4222              this.state = RTSPClientSM.STATE_PAUSED;
  4223          }
  4224  
  4225          async sendTeardown() {
  4226              if (this.state != RTSPClientSM.STATE_TEARDOWN) {
  4227                  this.state = RTSPClientSM.STATE_TEARDOWN;
  4228                  await this.sendRequest("TEARDOWN");
  4229                  Log$9.log('RTSPClient: STATE_TEARDOWN');
  4230                  ///this.client.connection.disconnect();
  4231                  // TODO: Notify client
  4232              }
  4233          }
  4234      }
  4235  
  4236      const LOG_TAG$5 = "client:rtsp";
  4237      const Log$a = getTagged(LOG_TAG$5);
  4238  
  4239      class RTSPClient extends BaseClient {
  4240          constructor(options={flush: 200}) {
  4241              super(options);
  4242              this.clientSM = new RTSPClientSM(this);
  4243              this.clientSM.ontracks = (tracks) => {
  4244                  this.eventSource.dispatchEvent('tracks', tracks);
  4245                  this.startStreamFlush();
  4246              };
  4247              this.sampleQueues={};
  4248          }
  4249  
  4250          static streamType() {
  4251              return 'rtsp';
  4252          }
  4253  
  4254          setSource(url) {
  4255              super.setSource(url);
  4256              this.clientSM.setSource(url);
  4257          }
  4258          attachTransport(transport) {
  4259              super.attachTransport(transport);
  4260              this.clientSM.transport = transport;
  4261          }
  4262  
  4263          detachTransport() {
  4264              super.detachTransport();
  4265              this.clientSM.transport = null;
  4266          }
  4267  
  4268          reset() {
  4269              super.reset();
  4270              this.sampleQueues={};
  4271          }
  4272  
  4273          destroy() {
  4274              this.clientSM.destroy();
  4275              return super.destroy();
  4276          }
  4277  
  4278          start() {
  4279              super.start();
  4280              if (this.transport) {
  4281                  return this.transport.ready.then(() => {
  4282                      return this.clientSM.start();
  4283                  });
  4284              } else {
  4285                  return Promise.reject("no transport attached");
  4286              }
  4287          }
  4288  
  4289          stop() {
  4290              super.stop();
  4291              return this.clientSM.stop();
  4292          }
  4293  
  4294          onData(data) {
  4295              this.clientSM.onData(data);
  4296          }
  4297  
  4298          onConnected() {
  4299              this.clientSM.onConnected();
  4300              super.onConnected();
  4301          }
  4302  
  4303          onDisconnected() {
  4304              super.onDisconnected();
  4305              this.clientSM.onDisconnected();
  4306          }
  4307      }
  4308  
  4309      class AuthError extends Error {
  4310          constructor(msg) {
  4311              super(msg);
  4312          }
  4313      }
  4314  
  4315      class RTSPError extends Error {
  4316          constructor(data) {
  4317              super(data.msg);
  4318              this.data = data;
  4319          }
  4320      }
  4321  
  4322      class RTSPClientSM extends StateMachine {
  4323          static get USER_AGENT() {return 'SFRtsp 0.3';}
  4324          static get STATE_INITIAL() {return  1 << 0;}
  4325          static get STATE_OPTIONS() {return 1 << 1;}
  4326          static get STATE_DESCRIBE () {return  1 << 2;}
  4327          static get STATE_SETUP() {return  1 << 3;}
  4328          static get STATE_STREAMS() {return 1 << 4;}
  4329          static get STATE_TEARDOWN() {return  1 << 5;}
  4330          static get STATE_PLAY() {return  1 << 6;}
  4331          static get STATE_PLAYING() {return  1 << 7;}
  4332          static get STATE_PAUSE() {return  1 << 8;}
  4333          static get STATE_PAUSED() {return  1 << 9;}
  4334          // static STATE_PAUSED = 1 << 6;
  4335  
  4336          constructor(parent) {
  4337              super();
  4338  
  4339              this.parent = parent;
  4340              this.transport = null;
  4341              this.payParser = new RTPPayloadParser();
  4342              this.rtp_channels = new Set();
  4343              this.sessions = {};
  4344              this.ontracks = null;
  4345  
  4346              this.addState(RTSPClientSM.STATE_INITIAL,{
  4347              }).addState(RTSPClientSM.STATE_OPTIONS, {
  4348                  activate: this.sendOptions,
  4349                  finishTransition: this.onOptions
  4350              }).addState(RTSPClientSM.STATE_DESCRIBE, {
  4351                  activate: this.sendDescribe,
  4352                  finishTransition: this.onDescribe
  4353              }).addState(RTSPClientSM.STATE_SETUP, {
  4354                  activate: this.sendSetup,
  4355                  finishTransition: this.onSetup
  4356              }).addState(RTSPClientSM.STATE_STREAMS, {
  4357  
  4358              }).addState(RTSPClientSM.STATE_TEARDOWN, {
  4359                  activate: ()=>{
  4360                      this.started = false;
  4361                  },
  4362                  finishTransition: ()=>{
  4363                      return this.transitionTo(RTSPClientSM.STATE_INITIAL)
  4364                  }
  4365              }).addTransition(RTSPClientSM.STATE_INITIAL, RTSPClientSM.STATE_OPTIONS)
  4366                  .addTransition(RTSPClientSM.STATE_INITIAL, RTSPClientSM.STATE_TEARDOWN)
  4367                  .addTransition(RTSPClientSM.STATE_OPTIONS, RTSPClientSM.STATE_DESCRIBE)
  4368                  .addTransition(RTSPClientSM.STATE_DESCRIBE, RTSPClientSM.STATE_SETUP)
  4369                  .addTransition(RTSPClientSM.STATE_SETUP, RTSPClientSM.STATE_STREAMS)
  4370                  .addTransition(RTSPClientSM.STATE_TEARDOWN, RTSPClientSM.STATE_INITIAL)
  4371                  // .addTransition(RTSPClientSM.STATE_STREAMS, RTSPClientSM.STATE_PAUSED)
  4372                  // .addTransition(RTSPClientSM.STATE_PAUSED, RTSPClientSM.STATE_STREAMS)
  4373                  .addTransition(RTSPClientSM.STATE_STREAMS, RTSPClientSM.STATE_TEARDOWN)
  4374                  // .addTransition(RTSPClientSM.STATE_PAUSED, RTSPClientSM.STATE_TEARDOWN)
  4375                  .addTransition(RTSPClientSM.STATE_SETUP, RTSPClientSM.STATE_TEARDOWN)
  4376                  .addTransition(RTSPClientSM.STATE_DESCRIBE, RTSPClientSM.STATE_TEARDOWN)
  4377                  .addTransition(RTSPClientSM.STATE_OPTIONS, RTSPClientSM.STATE_TEARDOWN);
  4378  
  4379              this.reset();
  4380  
  4381              this.shouldReconnect = false;
  4382  
  4383              // TODO: remove listeners
  4384              // this.connection.eventSource.addEventListener('connected', ()=>{
  4385              //     if (this.shouldReconnect) {
  4386              //         this.reconnect();
  4387              //     }
  4388              // });
  4389              // this.connection.eventSource.addEventListener('disconnected', ()=>{
  4390              //     if (this.started) {
  4391              //         this.shouldReconnect = true;
  4392              //     }
  4393              // });
  4394              // this.connection.eventSource.addEventListener('data', (data)=>{
  4395              //     let channel = new DataView(data).getUint8(1);
  4396              //     if (this.rtp_channels.has(channel)) {
  4397              //         this.onRTP({packet: new Uint8Array(data, 4), type: channel});
  4398              //     }
  4399              //
  4400              // });
  4401          }
  4402  
  4403          destroy() {
  4404              this.parent = null;
  4405          }
  4406  
  4407          setSource(url) {
  4408              this.reset();
  4409              this.endpoint = url;
  4410              this.url = `${url.protocol}://${url.location}${url.urlpath}`;
  4411          }
  4412  
  4413          onConnected() {
  4414              if (this.rtpFactory) {
  4415                  this.rtpFactory = null;
  4416              }
  4417              if (this.shouldReconnect) {
  4418                  this.start();
  4419              }
  4420          }
  4421  
  4422          async onDisconnected() {
  4423              this.reset();
  4424              this.shouldReconnect = true;
  4425              await this.transitionTo(RTSPClientSM.STATE_TEARDOWN);
  4426              await this.transitionTo(RTSPClientSM.STATE_INITIAL);
  4427          }
  4428  
  4429          start() {
  4430              if (this.currentState.name !== RTSPClientSM.STATE_STREAMS) {
  4431                  return this.transitionTo(RTSPClientSM.STATE_OPTIONS);
  4432              } else {
  4433                  // TODO: seekable
  4434                  let promises = [];
  4435                  for (let session in this.sessions) {
  4436                      promises.push(this.sessions[session].sendPlay());
  4437                  }
  4438                  return Promise.all(promises);
  4439              }
  4440          }
  4441  
  4442          onData(data) {
  4443              let channel = data[1];
  4444              if (this.rtp_channels.has(channel)) {
  4445                  this.onRTP({packet: data.subarray(4), type: channel});
  4446              }
  4447          }
  4448  
  4449          useRTPChannel(channel) {
  4450              this.rtp_channels.add(channel);
  4451          }
  4452  
  4453          forgetRTPChannel(channel) {
  4454              this.rtp_channels.delete(channel);
  4455          }
  4456  
  4457          stop() {
  4458              this.shouldReconnect = false;
  4459              let promises = [];
  4460              for (let session in this.sessions) {
  4461                  promises.push(this.sessions[session].sendPause());
  4462              }
  4463              return Promise.all(promises);
  4464              // this.mse = null;
  4465          }
  4466  
  4467          async reset() {
  4468              this.authenticator = '';
  4469              this.methods = [];
  4470              this.tracks = [];
  4471              this.rtpBuffer={};
  4472              for (let stream in this.streams) {
  4473                  this.streams[stream].reset();
  4474              }
  4475              for (let session in this.sessions) {
  4476                  this.sessions[session].reset();
  4477              }
  4478              this.streams={};
  4479              this.sessions={};
  4480              this.contentBase = "";
  4481              if (this.currentState) {
  4482                  if (this.currentState.name != RTSPClientSM.STATE_INITIAL) {
  4483                      await this.transitionTo(RTSPClientSM.STATE_TEARDOWN);
  4484                      await this.transitionTo(RTSPClientSM.STATE_INITIAL);
  4485                  }
  4486              } else {
  4487                  await this.transitionTo(RTSPClientSM.STATE_INITIAL);
  4488              }
  4489              this.sdp = null;
  4490              this.interleaveChannelIndex = 0;
  4491              this.session = null;
  4492              this.timeOffset = {};
  4493              this.lastTimestamp = {};
  4494          }
  4495  
  4496          async reconnect() {
  4497              //this.parent.eventSource.dispatchEvent('clear');
  4498              await this.reset();
  4499              if (this.currentState.name != RTSPClientSM.STATE_INITIAL) {
  4500                  await this.transitionTo(RTSPClientSM.STATE_TEARDOWN);
  4501                  return this.transitionTo(RTSPClientSM.STATE_OPTIONS);
  4502              } else {
  4503                  return this.transitionTo(RTSPClientSM.STATE_OPTIONS);
  4504              }
  4505          }
  4506  
  4507          supports(method) {
  4508              return this.methods.includes(method)
  4509          }
  4510  
  4511          parse(_data) {
  4512              Log$a.debug(_data);
  4513              let d=_data.split('\r\n\r\n');
  4514              let parsed =  MessageBuilder.parse(d[0]);
  4515              let len = Number(parsed.headers['content-length']);
  4516              if (len) {
  4517                  parsed.body = d[1];
  4518              } else {
  4519                  parsed.body="";
  4520              }
  4521              return parsed
  4522          }
  4523  
  4524          sendRequest(_cmd, _host, _params={}, _payload=null) {
  4525              this.cSeq++;
  4526              Object.assign(_params, {
  4527                  CSeq: this.cSeq,
  4528                  'User-Agent': RTSPClientSM.USER_AGENT
  4529              });
  4530              if (this.authenticator) {
  4531                  _params['Authorization'] = this.authenticator(_cmd);
  4532              }
  4533              return this.send(MessageBuilder.build(_cmd, _host, _params, _payload), _cmd).catch((e)=>{
  4534                  if ((e instanceof AuthError) && !_params['Authorization'] ) {
  4535                      return this.sendRequest(_cmd, _host, _params, _payload);
  4536                  } else {
  4537                      throw e;
  4538                  }
  4539              });
  4540          }
  4541  
  4542          async send(_data, _method) {
  4543              if (this.transport) {
  4544                  try {
  4545                      await this.transport.ready;
  4546                  } catch(e) {
  4547                      this.onDisconnected();
  4548                      throw e;
  4549                  }
  4550                  Log$a.debug(_data);
  4551                  let response = await this.transport.send(_data);
  4552                  let parsed = this.parse(response);
  4553                  // TODO: parse status codes
  4554                  if (parsed.code == 401 /*&& !this.authenticator */) {
  4555                      Log$a.debug(parsed.headers['www-authenticate']);
  4556                      let auth = parsed.headers['www-authenticate'];
  4557                      let method = auth.substring(0, auth.indexOf(' '));
  4558                      auth = auth.substr(method.length+1);
  4559                      let chunks = auth.split(',');
  4560  
  4561                      let ep = this.parent.endpoint;
  4562                      if (!ep.user || !ep.pass) {
  4563                          try {
  4564                              await this.parent.queryCredentials.call(this.parent);
  4565                          } catch (e) {
  4566                              throw new AuthError();
  4567                          }
  4568                      }
  4569  
  4570                      if (method.toLowerCase() == 'digest') {
  4571                          let parsedChunks = {};
  4572                          for (let chunk of chunks) {
  4573                              let c = chunk.trim();
  4574                              let [k,v] = c.split('=');
  4575                              parsedChunks[k] = v.substr(1, v.length-2);
  4576                          }
  4577                          this.authenticator = (_method)=>{
  4578                              let ep = this.parent.endpoint;
  4579                              let ha1 = md5(`${ep.user}:${parsedChunks.realm}:${ep.pass}`);
  4580                              let ha2 = md5(`${_method}:${this.url}`);
  4581                              let response = md5(`${ha1}:${parsedChunks.nonce}:${ha2}`);
  4582                              let tail=''; // TODO: handle other params
  4583                              return `Digest username="${ep.user}", realm="${parsedChunks.realm}", nonce="${parsedChunks.nonce}", uri="${this.url}", response="${response}"${tail}`;
  4584                          };
  4585                      } else {
  4586                          this.authenticator = ()=>{return `Basic ${btoa(this.parent.endpoint.auth)}`;};
  4587                      }
  4588  
  4589                      throw new AuthError(parsed);
  4590                  }
  4591                  if (parsed.code >= 300) {
  4592                      Log$a.error(parsed.statusLine);
  4593                      throw new RTSPError({msg: `RTSP error: ${parsed.code} ${parsed.statusLine}`, parsed: parsed});
  4594                  }
  4595                  return parsed;
  4596              } else {
  4597                  return Promise.reject("No transport attached");
  4598              }
  4599          }
  4600  
  4601          sendOptions() {
  4602              this.reset();
  4603              this.started = true;
  4604              this.cSeq = 0;
  4605              return this.sendRequest('OPTIONS', '*', {});
  4606          }
  4607  
  4608          onOptions(data) {
  4609              this.methods = data.headers['public'].split(',').map((e)=>e.trim());
  4610              this.transitionTo(RTSPClientSM.STATE_DESCRIBE);
  4611          }
  4612  
  4613          sendDescribe() {
  4614              return this.sendRequest('DESCRIBE', this.url, {
  4615                  'Accept': 'application/sdp'
  4616              }).then((data)=>{
  4617                  this.sdp = new SDPParser();
  4618                  return this.sdp.parse(data.body).catch(()=>{
  4619                      throw new Error("Failed to parse SDP");
  4620                  }).then(()=>{return data;});
  4621              });
  4622          }
  4623  
  4624          onDescribe(data) {
  4625              this.contentBase = data.headers['content-base'] || this.url;// `${this.endpoint.protocol}://${this.endpoint.location}${this.endpoint.urlpath}/`;
  4626              this.tracks = this.sdp.getMediaBlockList();
  4627              this.rtpFactory = new RTPFactory(this.sdp);
  4628  
  4629              Log$a.log('SDP contained ' + this.tracks.length + ' track(s). Calling SETUP for each.');
  4630  
  4631              if (data.headers['session']) {
  4632                  this.session = data.headers['session'];
  4633              }
  4634  
  4635              if (!this.tracks.length) {
  4636                  throw new Error("No tracks in SDP");
  4637              }
  4638  
  4639              this.transitionTo(RTSPClientSM.STATE_SETUP);
  4640          }
  4641  
  4642          sendSetup() {
  4643              let streams=[];
  4644              let lastPromise = null;
  4645  
  4646              // TODO: select first video and first audio tracks
  4647              for (let track_type of this.tracks) {
  4648                  Log$a.log("setup track: "+track_type);
  4649                  // if (track_type=='audio') continue;
  4650                  // if (track_type=='video') continue;
  4651                  let track = this.sdp.getMediaBlock(track_type);
  4652                  if (!PayloadType.string_map[track.rtpmap[track.fmt[0]].name]) continue;
  4653  
  4654                  this.streams[track_type] = new RTSPStream(this, track);
  4655                  let setupPromise = this.streams[track_type].start(lastPromise);
  4656                  lastPromise = setupPromise;
  4657                  this.parent.sampleQueues[PayloadType.string_map[track.rtpmap[track.fmt[0]].name]]=[];
  4658                  this.rtpBuffer[track.fmt[0]]=[];
  4659                  streams.push(setupPromise.then(({track, data})=>{
  4660                      this.timeOffset[track.fmt[0]] = 0;
  4661                      try {
  4662                          let rtp_info = data.headers["rtp-info"].split(';');
  4663                          for (let chunk of rtp_info) {
  4664                              let [key, val] = chunk.split("=");
  4665                              if (key === "rtptime") {
  4666                                  this.timeOffset[track.fmt[0]] = 0;//Number(val);
  4667                              }
  4668                          }
  4669                      } catch (e) {
  4670                          // new Date().getTime();
  4671                      }
  4672                      let params = {
  4673                          timescale: 0,
  4674                          scaleFactor: 0
  4675                      };
  4676                      if (track.fmtp['sprop-parameter-sets']) {
  4677                          let sps_pps = track.fmtp['sprop-parameter-sets'].split(',');
  4678                          params = {
  4679                              sps:base64ToArrayBuffer(sps_pps[0]),
  4680                              pps:base64ToArrayBuffer(sps_pps[1])
  4681                          };
  4682                      } else if (track.fmtp['config']) {
  4683                          let config = track.fmtp['config'];
  4684                          this.has_config = track.fmtp['cpresent']!='0';
  4685                          let generic = track.rtpmap[track.fmt[0]].name == 'MPEG4-GENERIC';
  4686                          if (generic) {
  4687                              params={config:
  4688                                  AACParser.parseAudioSpecificConfig(hexToByteArray(config))
  4689                              };
  4690                              this.payParser.aacparser.setConfig(params.config);
  4691                          } else if (config) {
  4692                              // todo: parse audio specific config for mpeg4-generic
  4693                              params={config:
  4694                                  AACParser.parseStreamMuxConfig(hexToByteArray(config))
  4695                              };
  4696                              this.payParser.aacparser.setConfig(params.config);
  4697                          }
  4698                      }
  4699                      params.duration = this.sdp.sessionBlock.range?this.sdp.sessionBlock.range[1]-this.sdp.sessionBlock.range[0]:1;
  4700                      this.parent.seekable = (params.duration > 1);
  4701                      let res = {
  4702                          track: track,
  4703                          offset: this.timeOffset[track.fmt[0]],
  4704                          type: PayloadType.string_map[track.rtpmap[track.fmt[0]].name],
  4705                          params: params,
  4706                          duration: params.duration
  4707                      };
  4708                      console.log(res, this.timeOffset);
  4709                      let session = data.headers.session.split(';')[0];
  4710                      if (!this.sessions[session]) {
  4711                          this.sessions[session] = new RTSPSession(this, session);
  4712                      }
  4713                      return res;
  4714                  }));
  4715              }
  4716              return Promise.all(streams).then((tracks)=>{
  4717                  let sessionPromises = [];
  4718                  for (let session in this.sessions) {
  4719                      sessionPromises.push(this.sessions[session].start());
  4720                  }
  4721                  return Promise.all(sessionPromises).then(()=>{
  4722                      if (this.ontracks) {
  4723                          this.ontracks(tracks);
  4724                      }
  4725                  })
  4726              }).catch((e)=>{
  4727                  console.error(e);
  4728                  this.stop();
  4729                  this.reset();
  4730              });
  4731          }
  4732  
  4733          onSetup() {
  4734              this.transitionTo(RTSPClientSM.STATE_STREAMS);
  4735          }
  4736  
  4737          onRTP(_data) {
  4738              if (!this.rtpFactory) return;
  4739  
  4740              let rtp = this.rtpFactory.build(_data.packet, this.sdp);
  4741              if (!rtp.type) {
  4742                  return;
  4743              }
  4744  
  4745              if (this.timeOffset[rtp.pt] === undefined) {
  4746                  //console.log(rtp.pt, this.timeOffset[rtp.pt]);
  4747                  this.rtpBuffer[rtp.pt].push(rtp);
  4748                  return;
  4749              }
  4750  
  4751              if (this.lastTimestamp[rtp.pt] === undefined) {
  4752                  this.lastTimestamp[rtp.pt] = rtp.timestamp-this.timeOffset[rtp.pt];
  4753              }
  4754  
  4755              let queue = this.rtpBuffer[rtp.pt];
  4756              queue.push(rtp);
  4757  
  4758              while (queue.length) {
  4759                  let rtp = queue.shift();
  4760  
  4761                  rtp.timestamp = rtp.timestamp-this.timeOffset[rtp.pt]-this.lastTimestamp[rtp.pt];
  4762                  // TODO: overflow
  4763                  // if (rtp.timestamp < 0) {
  4764                  //     rtp.timestamp = (rtp.timestamp + Number.MAX_SAFE_INTEGER) % 0x7fffffff;
  4765                  // }
  4766                  if (rtp.media) {
  4767                      let pay = this.payParser.parse(rtp);
  4768                      if (pay) {
  4769                          // if (typeof pay == typeof []) {
  4770                          this.parent.sampleQueues[rtp.type].push(pay);
  4771                          // } else {
  4772                          //     this.parent.sampleQueues[rtp.type].push([pay]);
  4773                          // }
  4774                      }
  4775                  }
  4776              }
  4777              // this.remuxer.feedRTP();
  4778          }
  4779      }
  4780  
  4781      const Log$b = getTagged('wsplayer');
  4782  
  4783      class WSPlayer {
  4784  
  4785          constructor(node, opts) {
  4786              if (typeof node == typeof '') {
  4787                  this.videoElement = document.getElementById(node);
  4788              } else {
  4789                  this.videoElement = node;
  4790              }
  4791  
  4792              this.eventSource = new EventEmitter();
  4793              this.type = "rtsp";
  4794  
  4795              // TODO: opts 处理
  4796              // this.videoElement.addEventListener('play', ()=>{
  4797              //     if (!this.isPlaying()) {
  4798              //         this.client.start();
  4799              //     }
  4800              // }, false);
  4801  
  4802              this.videoElement.addEventListener('abort', () => {
  4803                  this.unload();
  4804              }, false);
  4805          }
  4806  
  4807          isPlaying() {
  4808              return !(this.player.paused || this.client.paused);
  4809          }
  4810  
  4811          // 加载并启动
  4812          async load(wsurl) {
  4813              await this.unload();
  4814  
  4815              this.url = wsurl;
  4816              try {
  4817                  this.endpoint = Url.parse(wsurl);
  4818                  this.endpoint.protocol="rtsp";
  4819              } catch (e) {
  4820                  return;
  4821              }
  4822              this.transport = new WebsocketTransport(this.endpoint, this.type, {socket:wsurl});
  4823              this.client = new RTSPClient();
  4824              this.remuxer = new Remuxer(this.videoElement);
  4825  
  4826              this.remuxer.attachClient(this.client);
  4827  
  4828              this.client.attachTransport(this.transport);
  4829              this.client.setSource(this.endpoint);
  4830  
  4831              // 确保video元素可用
  4832              this.videoElement.src = "rtsp://placehold";
  4833              this.client.start().catch((e)=>{
  4834                  this.errorHandler(e);
  4835              });
  4836          }
  4837  
  4838          // 卸载
  4839          async unload() {
  4840              if (this.remuxer) {
  4841                  this.remuxer.destroy();
  4842                  this.remuxer = null;
  4843              }
  4844              if (this.client) {
  4845                  this.client.stop();
  4846                  await this.client.destroy();
  4847                  this.client = null;
  4848              }
  4849              if (this.transport) {
  4850                  await this.transport.destroy();
  4851                  this.transport = null;
  4852              }
  4853              // 重置video元素
  4854              this.videoElement.src = "rtsp://placehold";
  4855          }
  4856  
  4857          errorHandler(ev) {
  4858              this.eventSource.dispatchEvent('error', ev);
  4859          }
  4860      }
  4861  
  4862      setDefaultLogLevel(LogLevel.Debug);
  4863      getTagged("transport:ws").setLevel(LogLevel.Debug);
  4864      getTagged("client:rtsp").setLevel(LogLevel.Debug);
  4865      getTagged("mse").setLevel(LogLevel.Debug);
  4866  
  4867      // factory method
  4868      // node = video element
  4869      function createPlayer(node, optionalConfig) {
  4870          return new WSPlayer(node, optionalConfig)
  4871      }
  4872  
  4873      // interfaces
  4874      let rtspjs = {};
  4875      rtspjs.createPlayer = createPlayer;
  4876      rtspjs.getLogger = getTagged;
  4877      window.rtspjs = rtspjs;
  4878  
  4879  }());