[
  {
    "path": ".airtap.yml",
    "content": "sauce_connect: true\nloopback: airtap.local\nbrowsers:\n  - name: chrome\n    version: latest\n  - name: firefox\n    version: latest\n  - name: safari\n    version: latest\n  - name: microsoftedge\n    version: latest\n  - name: android\n    version: latest\n  - name: iphone\n    version: latest\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n"
  },
  {
    "path": ".npmignore",
    "content": ".airtap.yml\n.travis.yml\ntest/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - lts/*\naddons:\n  sauce_connect: true\n  hosts:\n    - airtap.local\nenv:\n  global:\n  - secure: mA8liwK511sAY/bepFwFl0qDM/WCrPSOGnwzjOJLf1cUT/kGIZ4uJah91g0zH1Afdq11zZN4QHZIBA8jA4oOPNJPNTeULf3UPkg7pvC3aTbVFLr1AVd/KvHDdnlLUUJ8QtiR5y70XRxcizZ6E3TeO3mwm13u+YIYhP4BdBD5JKxZhV5AFvwOshch0VQb90TN3E8g3PV6czJlh3CZ5rcFEDzXVZUxflDW8oaaachdEprjxj+sQSVP7cQZdcDdqoMzbxcpB6EneUcy7kqLqj6FNxfkZ+TgvyniDawjtsu4D3k0JaWJqdP+Urs9/Aqz2qtGBkR9eB3GVJNY+cXtZmnyyrJLv1HaXywm/NmLsK6bDQ+smMSTuOwryn9yar47E3r55LSiPaWJS+Xkt6CKSMHUzOEb2MC5oZRXad5eneeTM4eiVh03DrbpP7XNV4NyfPTl0wgVaXWf1tjbJVrVNVTfkXRBLVqvUknh1oi1We1pIQtVdMc2n3ObMWPwZCyzOr40nyIwpCKqP/O721xWbFOUXOl7HS3eFfheMMyTPqgg/Uo6+EXYoBkCpX+13o+6VnrmfXcG20fAWYK7l7UgR3KmIGxuiFbrZVgPdTRsdQP/7bKilIi6MJrMkVDOcbTnD9tl103LQZoJ5n++4X/KxjRoubwoNxSJzw2V1CZmUpBbUAo=\n  - secure: WzNEtVpO4sZkAKSpKshm1HL3Z9CZ89BU1Qwv8kVDcUAy+6iKSMZLXQmmeCkWUfPWXkXupF1oAOBjKrtvt3Q19JRrwcEeED9keRc+wE3NeJcpZ4dB7a/d1IbhTK4rym0S+yqYNWDGpGwQxrmTg9n0CyUZUVtslbv355yCpq9wRUtHI2qfxPiVmUIfMivVAJ73KHfOo/X3OrRm4Ibo1Fbeem/9VcIfViMB6d48kk6mT74GQANf3bRHmkIXZ/eqLo4f3A1hB0m1sObqt7d9SmGnmRDMFXMgUXnkrHQ1Vu+cGhV+bDueKkKGYvTdPd9Bfb73AaZQR+thxfft2c4ZJPClxjBkeCG/kqXfK7uOhFkDglyqTlDywzKtJl/8jgV1RVk7+t9NlK7ynPJyPgqobZSItN428vLgVPzN46YHnctKEqIz+oVNj4O9PCQhEeEFRMFoS2syruzGMFR5eeFR+WxzADAJDZG3XWyiGwQL9akNF501EKH2oAtKxtU9hFmsz0/eS+3yhlhl2VVSsuMsZ+T+KpJm/2aeMB2qfA2t3DQSdaaz7zZNwOhlq0Z1uM2XlkmyWhq91OyyDELM1iwI1N4pnGMDryqPjYdEezFAxYiBWDi2792EZ+9MesAkdktAmOcRTs7yLrxGMupy2wKfSYz/j6CjAu7oJEpFFF2RFGjvDDE=\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Feross Aboukhadijeh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# mediasource [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]\n\n[travis-image]: https://img.shields.io/travis/feross/mediasource/master.svg\n[travis-url]: https://travis-ci.org/feross/mediasource\n[npm-image]: https://img.shields.io/npm/v/mediasource.svg\n[npm-url]: https://npmjs.org/package/mediasource\n[downloads-image]: https://img.shields.io/npm/dm/mediasource.svg\n[downloads-url]: https://npmjs.org/package/mediasource\n[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg\n[standard-url]: https://standardjs.com\n\n### MediaSource API as a node.js Writable stream\n\n[![Sauce Test Status](https://saucelabs.com/browser-matrix/mediasource.svg)](https://saucelabs.com/u/mediasource)\n\nStream video/audio into a `<video>` or `<audio>` tag by attaching node.js Writable streams.\n\nThis package is used by [WebTorrent](http://webtorrent.io) (along with other approaches)\nto support media streaming.\n\n## install\n\n```\nnpm install mediasource\n```\n\n## usage\n\n```js\nvar MediaElementWrapper = require('mediasource')\n\nfunction createElem (tagName) {\n  var elem = document.createElement(tagName)\n  elem.controls = true\n  elem.autoplay = true // for chrome\n  elem.play() // for firefox\n  document.body.appendChild(elem)\n  return elem\n}\n\nvar elem = createElem('video')\n\nvar readable = // ... get a readable stream from somewhere\nvar wrapper = new MediaElementWrapper(elem)\n// The correct mime type, including codecs, must be provided\nvar writable = wrapper.createWriteStream('video/webm; codecs=\"vorbis, vp8\"')\n\nelem.addEventListener('error', function () {\n  // listen for errors on the video/audio element directly\n  var errorCode = elem.error\n  var detailedError = wrapper.detailedError\n  // wrapper.detailedError will often have a more detailed error message\n})\n\nwritable.on('error', function (err) {\n  // listening to the stream 'error' event is optional\n})\n\nreadable.pipe(writable)\n\n// media should start playing now!\n```\n\n### advanced usage\n\n`wrapper.createWriteStream()` can be called multiple times if different tracks (e.g. audio and video) need to\nbe passed in separate streams. Each call should be made with the correct mime type.\n\nInstead of a mime type, an existing MediaSourceStream (as returned by `wrapper.createWriteStream()`) can be\npassed as the single argument to `wrapper.createWriteStream()`, which will cause the existing stream to be\nreplaced by the newly returned stream. This is useful when you want to cancel the existing stream\nand replace it with a new one, e.g. when seeking.\n\n### should one use this package?\n\nNaively using this package will not work for many video formats, nor will it support\nseeking. For an approach that is more likely to work for all video files, and\nsupports seeking, take a look at\n[videostream](https://github.com/jhiesey/videostream).\n\nOr for a package that tries multiple approaches, including `videostream` and this\npackage (`mediasource`), as well as a Blob API (non-streaming) approach, and works\nfor many non-video file types, consider\n[render-media](https://github.com/feross/render-media).\n\n### options\n\n#### opts.bufferDuration\n\nSpecify how many seconds of media should be put into the browser's buffer before applying backpressure.\n\n### errors\n\nHandle errors by listening to the `'error'` event on the `<video>` or `<audio>` tag.\n\nSome (but not all) errors will also cause `wrapper.detailedError` to be set to an error value that has\na more informative error message.\n\n## license\n\nMIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).\n"
  },
  {
    "path": "index.js",
    "content": "/*! mediasource. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */\nmodule.exports = MediaElementWrapper\n\nvar inherits = require('inherits')\nvar stream = require('readable-stream')\nvar toArrayBuffer = require('to-arraybuffer')\n\nvar MediaSource = typeof window !== 'undefined' && window.MediaSource\n\nvar DEFAULT_BUFFER_DURATION = 60 // seconds\n\nfunction MediaElementWrapper (elem, opts) {\n  var self = this\n  if (!(self instanceof MediaElementWrapper)) return new MediaElementWrapper(elem, opts)\n\n  if (!MediaSource) throw new Error('web browser lacks MediaSource support')\n\n  if (!opts) opts = {}\n  self._debug = opts.debug\n  self._bufferDuration = opts.bufferDuration || DEFAULT_BUFFER_DURATION\n  self._elem = elem\n  self._mediaSource = new MediaSource()\n  self._streams = []\n  self.detailedError = null\n\n  self._errorHandler = function () {\n    self._elem.removeEventListener('error', self._errorHandler)\n    var streams = self._streams.slice()\n    streams.forEach(function (stream) {\n      stream.destroy(self._elem.error)\n    })\n  }\n  self._elem.addEventListener('error', self._errorHandler)\n\n  self._elem.src = window.URL.createObjectURL(self._mediaSource)\n}\n\n/*\n * `obj` can be a previous value returned by this function\n * or a string\n */\nMediaElementWrapper.prototype.createWriteStream = function (obj) {\n  var self = this\n\n  return new MediaSourceStream(self, obj)\n}\n\n/*\n * Use to trigger an error on the underlying media element\n */\nMediaElementWrapper.prototype.error = function (err) {\n  var self = this\n\n  // be careful not to overwrite any existing detailedError values\n  if (!self.detailedError) {\n    self.detailedError = err\n  }\n  self._dumpDebugData()\n  try {\n    self._mediaSource.endOfStream('decode')\n  } catch (err) {}\n\n  try {\n    // Attempt to clean up object URL\n    window.URL.revokeObjectURL(self._elem.src)\n  } catch (err) {}\n}\n\n/*\n * When self._debug is set, dump all data to files\n */\nMediaElementWrapper.prototype._dumpDebugData = function () {\n  var self = this\n\n  if (self._debug) {\n    self._debug = false // prevent multiple dumps on multiple errors\n    self._streams.forEach(function (stream, i) {\n      downloadBuffers(stream._debugBuffers, 'mediasource-stream-' + i)\n    })\n  }\n}\n\ninherits(MediaSourceStream, stream.Writable)\n\nfunction MediaSourceStream (wrapper, obj) {\n  var self = this\n  stream.Writable.call(self)\n\n  self._wrapper = wrapper\n  self._elem = wrapper._elem\n  self._mediaSource = wrapper._mediaSource\n  self._allStreams = wrapper._streams\n  self._allStreams.push(self)\n  self._bufferDuration = wrapper._bufferDuration\n  self._sourceBuffer = null\n  self._debugBuffers = []\n\n  self._openHandler = function () {\n    self._onSourceOpen()\n  }\n  self._flowHandler = function () {\n    self._flow()\n  }\n  self._errorHandler = function (err) {\n    if (!self.destroyed) {\n      self.emit('error', err)\n    }\n  }\n\n  if (typeof obj === 'string') {\n    self._type = obj\n    // Need to create a new sourceBuffer\n    if (self._mediaSource.readyState === 'open') {\n      self._createSourceBuffer()\n    } else {\n      self._mediaSource.addEventListener('sourceopen', self._openHandler)\n    }\n  } else if (obj._sourceBuffer === null) {\n    obj.destroy()\n    self._type = obj._type // The old stream was created but hasn't finished initializing\n    self._mediaSource.addEventListener('sourceopen', self._openHandler)\n  } else if (obj._sourceBuffer) {\n    obj.destroy()\n    self._type = obj._type\n    self._sourceBuffer = obj._sourceBuffer // Copy over the old sourceBuffer\n    self._debugBuffers = obj._debugBuffers // Copy over previous debug data\n    self._sourceBuffer.addEventListener('updateend', self._flowHandler)\n    self._sourceBuffer.addEventListener('error', self._errorHandler)\n  } else {\n    throw new Error('The argument to MediaElementWrapper.createWriteStream must be a string or a previous stream returned from that function')\n  }\n\n  self._elem.addEventListener('timeupdate', self._flowHandler)\n\n  self.on('error', function (err) {\n    self._wrapper.error(err)\n  })\n\n  self.on('finish', function () {\n    if (self.destroyed) return\n    self._finished = true\n    if (self._allStreams.every(function (other) { return other._finished })) {\n      self._wrapper._dumpDebugData()\n      try {\n        self._mediaSource.endOfStream()\n      } catch (err) {}\n    }\n  })\n}\n\nMediaSourceStream.prototype._onSourceOpen = function () {\n  var self = this\n  if (self.destroyed) return\n\n  self._mediaSource.removeEventListener('sourceopen', self._openHandler)\n  self._createSourceBuffer()\n}\n\nMediaSourceStream.prototype.destroy = function (err) {\n  var self = this\n  if (self.destroyed) return\n  self.destroyed = true\n\n  // Remove from allStreams\n  self._allStreams.splice(self._allStreams.indexOf(self), 1)\n\n  self._mediaSource.removeEventListener('sourceopen', self._openHandler)\n  self._elem.removeEventListener('timeupdate', self._flowHandler)\n  if (self._sourceBuffer) {\n    self._sourceBuffer.removeEventListener('updateend', self._flowHandler)\n    self._sourceBuffer.removeEventListener('error', self._errorHandler)\n    if (self._mediaSource.readyState === 'open') {\n      self._sourceBuffer.abort()\n    }\n  }\n\n  if (err) self.emit('error', err)\n  self.emit('close')\n}\n\nMediaSourceStream.prototype._createSourceBuffer = function () {\n  var self = this\n  if (self.destroyed) return\n\n  if (MediaSource.isTypeSupported(self._type)) {\n    self._sourceBuffer = self._mediaSource.addSourceBuffer(self._type)\n    self._sourceBuffer.addEventListener('updateend', self._flowHandler)\n    self._sourceBuffer.addEventListener('error', self._errorHandler)\n    if (self._cb) {\n      var cb = self._cb\n      self._cb = null\n      cb()\n    }\n  } else {\n    self.destroy(new Error('The provided type is not supported'))\n  }\n}\n\nMediaSourceStream.prototype._write = function (chunk, encoding, cb) {\n  var self = this\n  if (self.destroyed) return\n  if (!self._sourceBuffer) {\n    self._cb = function (err) {\n      if (err) return cb(err)\n      self._write(chunk, encoding, cb)\n    }\n    return\n  }\n\n  if (self._sourceBuffer.updating) {\n    return cb(new Error('Cannot append buffer while source buffer updating'))\n  }\n\n  var arr = toArrayBuffer(chunk)\n  if (self._wrapper._debug) {\n    self._debugBuffers.push(arr)\n  }\n\n  try {\n    self._sourceBuffer.appendBuffer(arr)\n  } catch (err) {\n    // appendBuffer can throw for a number of reasons, most notably when the data\n    // being appended is invalid or if appendBuffer is called after another error\n    // already occurred on the media element. In Chrome, there may be useful debugging\n    // info in chrome://media-internals\n    self.destroy(err)\n    return\n  }\n  self._cb = cb\n}\n\nMediaSourceStream.prototype._flow = function () {\n  var self = this\n\n  if (self.destroyed || !self._sourceBuffer || self._sourceBuffer.updating) {\n    return\n  }\n\n  if (self._mediaSource.readyState === 'open') {\n    // check buffer size\n    if (self._getBufferDuration() > self._bufferDuration) {\n      return\n    }\n  }\n\n  if (self._cb) {\n    var cb = self._cb\n    self._cb = null\n    cb()\n  }\n}\n\n// TODO: if zero actually works in all browsers, remove the logic associated with this below\nvar EPSILON = 0\n\nMediaSourceStream.prototype._getBufferDuration = function () {\n  var self = this\n\n  var buffered = self._sourceBuffer.buffered\n  var currentTime = self._elem.currentTime\n  var bufferEnd = -1 // end of the buffer\n  // This is a little over complex because some browsers seem to separate the\n  // buffered region into multiple sections with slight gaps.\n  for (var i = 0; i < buffered.length; i++) {\n    var start = buffered.start(i)\n    var end = buffered.end(i) + EPSILON\n\n    if (start > currentTime) {\n      // Reached past the joined buffer\n      break\n    } else if (bufferEnd >= 0 || currentTime <= end) {\n      // Found the start/continuation of the joined buffer\n      bufferEnd = end\n    }\n  }\n\n  var bufferedTime = bufferEnd - currentTime\n  if (bufferedTime < 0) {\n    bufferedTime = 0\n  }\n\n  return bufferedTime\n}\n\nfunction downloadBuffers (bufs, name) {\n  var a = document.createElement('a')\n  a.href = window.URL.createObjectURL(new window.Blob(bufs))\n  a.download = name\n  a.click()\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mediasource\",\n  \"description\": \"MediaSource API as a node.js Writable stream\",\n  \"version\": \"2.4.0\",\n  \"author\": {\n    \"name\": \"Feross Aboukhadijeh\",\n    \"email\": \"feross@feross.org\",\n    \"url\": \"https://feross.org\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/feross/mediasource/issues\"\n  },\n  \"dependencies\": {\n    \"inherits\": \"^2.0.4\",\n    \"readable-stream\": \"^3.6.0\",\n    \"to-arraybuffer\": \"^1.0.1\"\n  },\n  \"devDependencies\": {\n    \"airtap\": \"^3.0.0\",\n    \"brfs\": \"^2.0.2\",\n    \"standard\": \"*\",\n    \"tape\": \"^5.0.1\"\n  },\n  \"homepage\": \"https://github.com/feross/mediasource\",\n  \"keywords\": [\n    \"mediasource\",\n    \"media source\",\n    \"mediasource api\",\n    \"writable\",\n    \"write\",\n    \"stream\",\n    \"write stream\",\n    \"writable stream\"\n  ],\n  \"license\": \"MIT\",\n  \"main\": \"index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/feross/mediasource.git\"\n  },\n  \"scripts\": {\n    \"test\": \"standard && airtap -- test/*.js\",\n    \"test-local\": \"airtap --local -- test/*.js\"\n  },\n  \"funding\": [\n    {\n      \"type\": \"github\",\n      \"url\": \"https://github.com/sponsors/feross\"\n    },\n    {\n      \"type\": \"patreon\",\n      \"url\": \"https://www.patreon.com/feross\"\n    },\n    {\n      \"type\": \"consulting\",\n      \"url\": \"https://feross.org/support\"\n    }\n  ]\n}\n"
  },
  {
    "path": "test/basic.js",
    "content": "var fs = require('fs')\nvar MediaElementWrapper = require('../')\nvar path = require('path')\nvar stream = require('stream')\nvar test = require('tape')\n\nvar FILE = fs.readFileSync(path.join(__dirname, 'test.mp4'))\nvar CODEC_TYPE = 'video/mp4; codecs=\"avc1.42e01e\"'\n\nif (!window.MediaSource) {\n  test.only('MediaSource support', (t) => {\n    t.pass('browser lacks support')\n    t.end()\n  })\n}\n\ntest('basic test', function (t) {\n  t.plan(2)\n\n  var elem = createElem('video')\n  var readable = new stream.PassThrough()\n  var wrapper = new MediaElementWrapper(elem)\n  var writable = wrapper.createWriteStream(CODEC_TYPE)\n\n  readable.on('error', function (err) { t.fail(err) })\n  writable.on('error', function (err) { t.fail(err) })\n  elem.addEventListener('error', function (err) { t.fail(err) })\n\n  elem.addEventListener('playing', function () {\n    t.pass('got the \"playing\" event')\n  })\n\n  elem.addEventListener('progress', onProgress)\n\n  function onProgress () {\n    t.pass('got a \"progress\" event')\n    elem.removeEventListener('progress', onProgress)\n  }\n\n  readable.pipe(writable)\n  readable.write(FILE)\n})\n\n// Don't fail when createWriteStream() is called twice before mediasource opens\n// See: https://github.com/feross/mediasource/pull/5\ntest('call createWriteStream() twice immediately', function (t) {\n  t.plan(3)\n\n  var elem = createElem('video')\n  var readable = new stream.PassThrough()\n  var wrapper = new MediaElementWrapper(elem)\n\n  var writable = wrapper.createWriteStream(CODEC_TYPE)\n\n  t.doesNotThrow(function () {\n    writable = wrapper.createWriteStream(writable)\n  })\n\n  readable.on('error', function (err) { t.fail(err) })\n  writable.on('error', function (err) { t.fail(err) })\n  elem.addEventListener('error', function (err) { t.fail(err) })\n\n  elem.addEventListener('playing', function () {\n    t.pass('got the \"playing\" event')\n  })\n\n  elem.addEventListener('progress', onProgress)\n\n  function onProgress () {\n    t.pass('got a \"progress\" event')\n    elem.removeEventListener('progress', onProgress)\n  }\n\n  readable.pipe(writable)\n  readable.write(FILE)\n})\n\nfunction createElem (tagName) {\n  var elem = document.createElement(tagName)\n  elem.controls = true\n  elem.muted = true // make autoplay work\n  elem.autoplay = true // for chrome\n  document.body.insertBefore(elem, document.body.firstChild)\n  return elem\n}\n"
  },
  {
    "path": "test/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"0.0.0\",\n  \"browserify\": {\n    \"transform\": [\"brfs\"]\n  }\n}\n"
  }
]