Full Code of feross/mediasource for AI

master b46c4ed02d98 cached
10 files
18.0 KB
5.5k tokens
6 symbols
1 requests
Download .txt
Repository: feross/mediasource
Branch: master
Commit: b46c4ed02d98
Files: 10
Total size: 18.0 KB

Directory structure:
gitextract_5zrtq0qu/

├── .airtap.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── package.json
└── test/
    ├── basic.js
    └── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .airtap.yml
================================================
sauce_connect: true
loopback: airtap.local
browsers:
  - name: chrome
    version: latest
  - name: firefox
    version: latest
  - name: safari
    version: latest
  - name: microsoftedge
    version: latest
  - name: android
    version: latest
  - name: iphone
    version: latest


================================================
FILE: .gitignore
================================================
node_modules


================================================
FILE: .npmignore
================================================
.airtap.yml
.travis.yml
test/


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - lts/*
addons:
  sauce_connect: true
  hosts:
    - airtap.local
env:
  global:
  - secure: mA8liwK511sAY/bepFwFl0qDM/WCrPSOGnwzjOJLf1cUT/kGIZ4uJah91g0zH1Afdq11zZN4QHZIBA8jA4oOPNJPNTeULf3UPkg7pvC3aTbVFLr1AVd/KvHDdnlLUUJ8QtiR5y70XRxcizZ6E3TeO3mwm13u+YIYhP4BdBD5JKxZhV5AFvwOshch0VQb90TN3E8g3PV6czJlh3CZ5rcFEDzXVZUxflDW8oaaachdEprjxj+sQSVP7cQZdcDdqoMzbxcpB6EneUcy7kqLqj6FNxfkZ+TgvyniDawjtsu4D3k0JaWJqdP+Urs9/Aqz2qtGBkR9eB3GVJNY+cXtZmnyyrJLv1HaXywm/NmLsK6bDQ+smMSTuOwryn9yar47E3r55LSiPaWJS+Xkt6CKSMHUzOEb2MC5oZRXad5eneeTM4eiVh03DrbpP7XNV4NyfPTl0wgVaXWf1tjbJVrVNVTfkXRBLVqvUknh1oi1We1pIQtVdMc2n3ObMWPwZCyzOr40nyIwpCKqP/O721xWbFOUXOl7HS3eFfheMMyTPqgg/Uo6+EXYoBkCpX+13o+6VnrmfXcG20fAWYK7l7UgR3KmIGxuiFbrZVgPdTRsdQP/7bKilIi6MJrMkVDOcbTnD9tl103LQZoJ5n++4X/KxjRoubwoNxSJzw2V1CZmUpBbUAo=
  - secure: WzNEtVpO4sZkAKSpKshm1HL3Z9CZ89BU1Qwv8kVDcUAy+6iKSMZLXQmmeCkWUfPWXkXupF1oAOBjKrtvt3Q19JRrwcEeED9keRc+wE3NeJcpZ4dB7a/d1IbhTK4rym0S+yqYNWDGpGwQxrmTg9n0CyUZUVtslbv355yCpq9wRUtHI2qfxPiVmUIfMivVAJ73KHfOo/X3OrRm4Ibo1Fbeem/9VcIfViMB6d48kk6mT74GQANf3bRHmkIXZ/eqLo4f3A1hB0m1sObqt7d9SmGnmRDMFXMgUXnkrHQ1Vu+cGhV+bDueKkKGYvTdPd9Bfb73AaZQR+thxfft2c4ZJPClxjBkeCG/kqXfK7uOhFkDglyqTlDywzKtJl/8jgV1RVk7+t9NlK7ynPJyPgqobZSItN428vLgVPzN46YHnctKEqIz+oVNj4O9PCQhEeEFRMFoS2syruzGMFR5eeFR+WxzADAJDZG3XWyiGwQL9akNF501EKH2oAtKxtU9hFmsz0/eS+3yhlhl2VVSsuMsZ+T+KpJm/2aeMB2qfA2t3DQSdaaz7zZNwOhlq0Z1uM2XlkmyWhq91OyyDELM1iwI1N4pnGMDryqPjYdEezFAxYiBWDi2792EZ+9MesAkdktAmOcRTs7yLrxGMupy2wKfSYz/j6CjAu7oJEpFFF2RFGjvDDE=


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) Feross Aboukhadijeh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# mediasource [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]

[travis-image]: https://img.shields.io/travis/feross/mediasource/master.svg
[travis-url]: https://travis-ci.org/feross/mediasource
[npm-image]: https://img.shields.io/npm/v/mediasource.svg
[npm-url]: https://npmjs.org/package/mediasource
[downloads-image]: https://img.shields.io/npm/dm/mediasource.svg
[downloads-url]: https://npmjs.org/package/mediasource
[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
[standard-url]: https://standardjs.com

### MediaSource API as a node.js Writable stream

[![Sauce Test Status](https://saucelabs.com/browser-matrix/mediasource.svg)](https://saucelabs.com/u/mediasource)

Stream video/audio into a `<video>` or `<audio>` tag by attaching node.js Writable streams.

This package is used by [WebTorrent](http://webtorrent.io) (along with other approaches)
to support media streaming.

## install

```
npm install mediasource
```

## usage

```js
var MediaElementWrapper = require('mediasource')

function createElem (tagName) {
  var elem = document.createElement(tagName)
  elem.controls = true
  elem.autoplay = true // for chrome
  elem.play() // for firefox
  document.body.appendChild(elem)
  return elem
}

var elem = createElem('video')

var readable = // ... get a readable stream from somewhere
var wrapper = new MediaElementWrapper(elem)
// The correct mime type, including codecs, must be provided
var writable = wrapper.createWriteStream('video/webm; codecs="vorbis, vp8"')

elem.addEventListener('error', function () {
  // listen for errors on the video/audio element directly
  var errorCode = elem.error
  var detailedError = wrapper.detailedError
  // wrapper.detailedError will often have a more detailed error message
})

writable.on('error', function (err) {
  // listening to the stream 'error' event is optional
})

readable.pipe(writable)

// media should start playing now!
```

### advanced usage

`wrapper.createWriteStream()` can be called multiple times if different tracks (e.g. audio and video) need to
be passed in separate streams. Each call should be made with the correct mime type.

Instead of a mime type, an existing MediaSourceStream (as returned by `wrapper.createWriteStream()`) can be
passed as the single argument to `wrapper.createWriteStream()`, which will cause the existing stream to be
replaced by the newly returned stream. This is useful when you want to cancel the existing stream
and replace it with a new one, e.g. when seeking.

### should one use this package?

Naively using this package will not work for many video formats, nor will it support
seeking. For an approach that is more likely to work for all video files, and
supports seeking, take a look at
[videostream](https://github.com/jhiesey/videostream).

Or for a package that tries multiple approaches, including `videostream` and this
package (`mediasource`), as well as a Blob API (non-streaming) approach, and works
for many non-video file types, consider
[render-media](https://github.com/feross/render-media).

### options

#### opts.bufferDuration

Specify how many seconds of media should be put into the browser's buffer before applying backpressure.

### errors

Handle errors by listening to the `'error'` event on the `<video>` or `<audio>` tag.

Some (but not all) errors will also cause `wrapper.detailedError` to be set to an error value that has
a more informative error message.

## license

MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).


================================================
FILE: index.js
================================================
/*! mediasource. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
module.exports = MediaElementWrapper

var inherits = require('inherits')
var stream = require('readable-stream')
var toArrayBuffer = require('to-arraybuffer')

var MediaSource = typeof window !== 'undefined' && window.MediaSource

var DEFAULT_BUFFER_DURATION = 60 // seconds

function MediaElementWrapper (elem, opts) {
  var self = this
  if (!(self instanceof MediaElementWrapper)) return new MediaElementWrapper(elem, opts)

  if (!MediaSource) throw new Error('web browser lacks MediaSource support')

  if (!opts) opts = {}
  self._debug = opts.debug
  self._bufferDuration = opts.bufferDuration || DEFAULT_BUFFER_DURATION
  self._elem = elem
  self._mediaSource = new MediaSource()
  self._streams = []
  self.detailedError = null

  self._errorHandler = function () {
    self._elem.removeEventListener('error', self._errorHandler)
    var streams = self._streams.slice()
    streams.forEach(function (stream) {
      stream.destroy(self._elem.error)
    })
  }
  self._elem.addEventListener('error', self._errorHandler)

  self._elem.src = window.URL.createObjectURL(self._mediaSource)
}

/*
 * `obj` can be a previous value returned by this function
 * or a string
 */
MediaElementWrapper.prototype.createWriteStream = function (obj) {
  var self = this

  return new MediaSourceStream(self, obj)
}

/*
 * Use to trigger an error on the underlying media element
 */
MediaElementWrapper.prototype.error = function (err) {
  var self = this

  // be careful not to overwrite any existing detailedError values
  if (!self.detailedError) {
    self.detailedError = err
  }
  self._dumpDebugData()
  try {
    self._mediaSource.endOfStream('decode')
  } catch (err) {}

  try {
    // Attempt to clean up object URL
    window.URL.revokeObjectURL(self._elem.src)
  } catch (err) {}
}

/*
 * When self._debug is set, dump all data to files
 */
MediaElementWrapper.prototype._dumpDebugData = function () {
  var self = this

  if (self._debug) {
    self._debug = false // prevent multiple dumps on multiple errors
    self._streams.forEach(function (stream, i) {
      downloadBuffers(stream._debugBuffers, 'mediasource-stream-' + i)
    })
  }
}

inherits(MediaSourceStream, stream.Writable)

function MediaSourceStream (wrapper, obj) {
  var self = this
  stream.Writable.call(self)

  self._wrapper = wrapper
  self._elem = wrapper._elem
  self._mediaSource = wrapper._mediaSource
  self._allStreams = wrapper._streams
  self._allStreams.push(self)
  self._bufferDuration = wrapper._bufferDuration
  self._sourceBuffer = null
  self._debugBuffers = []

  self._openHandler = function () {
    self._onSourceOpen()
  }
  self._flowHandler = function () {
    self._flow()
  }
  self._errorHandler = function (err) {
    if (!self.destroyed) {
      self.emit('error', err)
    }
  }

  if (typeof obj === 'string') {
    self._type = obj
    // Need to create a new sourceBuffer
    if (self._mediaSource.readyState === 'open') {
      self._createSourceBuffer()
    } else {
      self._mediaSource.addEventListener('sourceopen', self._openHandler)
    }
  } else if (obj._sourceBuffer === null) {
    obj.destroy()
    self._type = obj._type // The old stream was created but hasn't finished initializing
    self._mediaSource.addEventListener('sourceopen', self._openHandler)
  } else if (obj._sourceBuffer) {
    obj.destroy()
    self._type = obj._type
    self._sourceBuffer = obj._sourceBuffer // Copy over the old sourceBuffer
    self._debugBuffers = obj._debugBuffers // Copy over previous debug data
    self._sourceBuffer.addEventListener('updateend', self._flowHandler)
    self._sourceBuffer.addEventListener('error', self._errorHandler)
  } else {
    throw new Error('The argument to MediaElementWrapper.createWriteStream must be a string or a previous stream returned from that function')
  }

  self._elem.addEventListener('timeupdate', self._flowHandler)

  self.on('error', function (err) {
    self._wrapper.error(err)
  })

  self.on('finish', function () {
    if (self.destroyed) return
    self._finished = true
    if (self._allStreams.every(function (other) { return other._finished })) {
      self._wrapper._dumpDebugData()
      try {
        self._mediaSource.endOfStream()
      } catch (err) {}
    }
  })
}

MediaSourceStream.prototype._onSourceOpen = function () {
  var self = this
  if (self.destroyed) return

  self._mediaSource.removeEventListener('sourceopen', self._openHandler)
  self._createSourceBuffer()
}

MediaSourceStream.prototype.destroy = function (err) {
  var self = this
  if (self.destroyed) return
  self.destroyed = true

  // Remove from allStreams
  self._allStreams.splice(self._allStreams.indexOf(self), 1)

  self._mediaSource.removeEventListener('sourceopen', self._openHandler)
  self._elem.removeEventListener('timeupdate', self._flowHandler)
  if (self._sourceBuffer) {
    self._sourceBuffer.removeEventListener('updateend', self._flowHandler)
    self._sourceBuffer.removeEventListener('error', self._errorHandler)
    if (self._mediaSource.readyState === 'open') {
      self._sourceBuffer.abort()
    }
  }

  if (err) self.emit('error', err)
  self.emit('close')
}

MediaSourceStream.prototype._createSourceBuffer = function () {
  var self = this
  if (self.destroyed) return

  if (MediaSource.isTypeSupported(self._type)) {
    self._sourceBuffer = self._mediaSource.addSourceBuffer(self._type)
    self._sourceBuffer.addEventListener('updateend', self._flowHandler)
    self._sourceBuffer.addEventListener('error', self._errorHandler)
    if (self._cb) {
      var cb = self._cb
      self._cb = null
      cb()
    }
  } else {
    self.destroy(new Error('The provided type is not supported'))
  }
}

MediaSourceStream.prototype._write = function (chunk, encoding, cb) {
  var self = this
  if (self.destroyed) return
  if (!self._sourceBuffer) {
    self._cb = function (err) {
      if (err) return cb(err)
      self._write(chunk, encoding, cb)
    }
    return
  }

  if (self._sourceBuffer.updating) {
    return cb(new Error('Cannot append buffer while source buffer updating'))
  }

  var arr = toArrayBuffer(chunk)
  if (self._wrapper._debug) {
    self._debugBuffers.push(arr)
  }

  try {
    self._sourceBuffer.appendBuffer(arr)
  } catch (err) {
    // appendBuffer can throw for a number of reasons, most notably when the data
    // being appended is invalid or if appendBuffer is called after another error
    // already occurred on the media element. In Chrome, there may be useful debugging
    // info in chrome://media-internals
    self.destroy(err)
    return
  }
  self._cb = cb
}

MediaSourceStream.prototype._flow = function () {
  var self = this

  if (self.destroyed || !self._sourceBuffer || self._sourceBuffer.updating) {
    return
  }

  if (self._mediaSource.readyState === 'open') {
    // check buffer size
    if (self._getBufferDuration() > self._bufferDuration) {
      return
    }
  }

  if (self._cb) {
    var cb = self._cb
    self._cb = null
    cb()
  }
}

// TODO: if zero actually works in all browsers, remove the logic associated with this below
var EPSILON = 0

MediaSourceStream.prototype._getBufferDuration = function () {
  var self = this

  var buffered = self._sourceBuffer.buffered
  var currentTime = self._elem.currentTime
  var bufferEnd = -1 // end of the buffer
  // This is a little over complex because some browsers seem to separate the
  // buffered region into multiple sections with slight gaps.
  for (var i = 0; i < buffered.length; i++) {
    var start = buffered.start(i)
    var end = buffered.end(i) + EPSILON

    if (start > currentTime) {
      // Reached past the joined buffer
      break
    } else if (bufferEnd >= 0 || currentTime <= end) {
      // Found the start/continuation of the joined buffer
      bufferEnd = end
    }
  }

  var bufferedTime = bufferEnd - currentTime
  if (bufferedTime < 0) {
    bufferedTime = 0
  }

  return bufferedTime
}

function downloadBuffers (bufs, name) {
  var a = document.createElement('a')
  a.href = window.URL.createObjectURL(new window.Blob(bufs))
  a.download = name
  a.click()
}


================================================
FILE: package.json
================================================
{
  "name": "mediasource",
  "description": "MediaSource API as a node.js Writable stream",
  "version": "2.4.0",
  "author": {
    "name": "Feross Aboukhadijeh",
    "email": "feross@feross.org",
    "url": "https://feross.org"
  },
  "bugs": {
    "url": "https://github.com/feross/mediasource/issues"
  },
  "dependencies": {
    "inherits": "^2.0.4",
    "readable-stream": "^3.6.0",
    "to-arraybuffer": "^1.0.1"
  },
  "devDependencies": {
    "airtap": "^3.0.0",
    "brfs": "^2.0.2",
    "standard": "*",
    "tape": "^5.0.1"
  },
  "homepage": "https://github.com/feross/mediasource",
  "keywords": [
    "mediasource",
    "media source",
    "mediasource api",
    "writable",
    "write",
    "stream",
    "write stream",
    "writable stream"
  ],
  "license": "MIT",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git://github.com/feross/mediasource.git"
  },
  "scripts": {
    "test": "standard && airtap -- test/*.js",
    "test-local": "airtap --local -- test/*.js"
  },
  "funding": [
    {
      "type": "github",
      "url": "https://github.com/sponsors/feross"
    },
    {
      "type": "patreon",
      "url": "https://www.patreon.com/feross"
    },
    {
      "type": "consulting",
      "url": "https://feross.org/support"
    }
  ]
}


================================================
FILE: test/basic.js
================================================
var fs = require('fs')
var MediaElementWrapper = require('../')
var path = require('path')
var stream = require('stream')
var test = require('tape')

var FILE = fs.readFileSync(path.join(__dirname, 'test.mp4'))
var CODEC_TYPE = 'video/mp4; codecs="avc1.42e01e"'

if (!window.MediaSource) {
  test.only('MediaSource support', (t) => {
    t.pass('browser lacks support')
    t.end()
  })
}

test('basic test', function (t) {
  t.plan(2)

  var elem = createElem('video')
  var readable = new stream.PassThrough()
  var wrapper = new MediaElementWrapper(elem)
  var writable = wrapper.createWriteStream(CODEC_TYPE)

  readable.on('error', function (err) { t.fail(err) })
  writable.on('error', function (err) { t.fail(err) })
  elem.addEventListener('error', function (err) { t.fail(err) })

  elem.addEventListener('playing', function () {
    t.pass('got the "playing" event')
  })

  elem.addEventListener('progress', onProgress)

  function onProgress () {
    t.pass('got a "progress" event')
    elem.removeEventListener('progress', onProgress)
  }

  readable.pipe(writable)
  readable.write(FILE)
})

// Don't fail when createWriteStream() is called twice before mediasource opens
// See: https://github.com/feross/mediasource/pull/5
test('call createWriteStream() twice immediately', function (t) {
  t.plan(3)

  var elem = createElem('video')
  var readable = new stream.PassThrough()
  var wrapper = new MediaElementWrapper(elem)

  var writable = wrapper.createWriteStream(CODEC_TYPE)

  t.doesNotThrow(function () {
    writable = wrapper.createWriteStream(writable)
  })

  readable.on('error', function (err) { t.fail(err) })
  writable.on('error', function (err) { t.fail(err) })
  elem.addEventListener('error', function (err) { t.fail(err) })

  elem.addEventListener('playing', function () {
    t.pass('got the "playing" event')
  })

  elem.addEventListener('progress', onProgress)

  function onProgress () {
    t.pass('got a "progress" event')
    elem.removeEventListener('progress', onProgress)
  }

  readable.pipe(writable)
  readable.write(FILE)
})

function createElem (tagName) {
  var elem = document.createElement(tagName)
  elem.controls = true
  elem.muted = true // make autoplay work
  elem.autoplay = true // for chrome
  document.body.insertBefore(elem, document.body.firstChild)
  return elem
}


================================================
FILE: test/package.json
================================================
{
  "name": "test",
  "version": "0.0.0",
  "browserify": {
    "transform": ["brfs"]
  }
}
Download .txt
gitextract_5zrtq0qu/

├── .airtap.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── package.json
└── test/
    ├── basic.js
    └── package.json
Download .txt
SYMBOL INDEX (6 symbols across 2 files)

FILE: index.js
  function MediaElementWrapper (line 12) | function MediaElementWrapper (elem, opts) {
  function MediaSourceStream (line 85) | function MediaSourceStream (wrapper, obj) {
  function downloadBuffers (line 285) | function downloadBuffers (bufs, name) {

FILE: test/basic.js
  function onProgress (line 35) | function onProgress () {
  function onProgress (line 69) | function onProgress () {
  function createElem (line 78) | function createElem (tagName) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (20K chars).
[
  {
    "path": ".airtap.yml",
    "chars": 284,
    "preview": "sauce_connect: true\nloopback: airtap.local\nbrowsers:\n  - name: chrome\n    version: latest\n  - name: firefox\n    version:"
  },
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": ".npmignore",
    "chars": 30,
    "preview": ".airtap.yml\n.travis.yml\ntest/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1504,
    "preview": "language: node_js\nnode_js:\n  - lts/*\naddons:\n  sauce_connect: true\n  hosts:\n    - airtap.local\nenv:\n  global:\n  - secure"
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Feross Aboukhadijeh\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "README.md",
    "chars": 3642,
    "preview": "# mediasource [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloa"
  },
  {
    "path": "index.js",
    "chars": 8177,
    "preview": "/*! mediasource. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */\nmodule.exports = MediaElementWrappe"
  },
  {
    "path": "package.json",
    "chars": 1289,
    "preview": "{\n  \"name\": \"mediasource\",\n  \"description\": \"MediaSource API as a node.js Writable stream\",\n  \"version\": \"2.4.0\",\n  \"aut"
  },
  {
    "path": "test/basic.js",
    "chars": 2334,
    "preview": "var fs = require('fs')\nvar MediaElementWrapper = require('../')\nvar path = require('path')\nvar stream = require('stream'"
  },
  {
    "path": "test/package.json",
    "chars": 92,
    "preview": "{\n  \"name\": \"test\",\n  \"version\": \"0.0.0\",\n  \"browserify\": {\n    \"transform\": [\"brfs\"]\n  }\n}\n"
  }
]

About this extraction

This page contains the full source code of the feross/mediasource GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (18.0 KB), approximately 5.5k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!