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
[](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"]
}
}
gitextract_5zrtq0qu/
├── .airtap.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── package.json
└── test/
├── basic.js
└── package.json
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.