From 6423992df4aa4ec5ec5b844d2f8c8366b6b51738 Mon Sep 17 00:00:00 2001 From: Joseph Werle Date: Mon, 29 Apr 2019 14:53:44 -0400 Subject: [PATCH] chore(.): init --- .gitignore | 1 + .npmrc | 2 + README.md | 72 ++++++++++++++++++- example.js | 38 ++++++++++ index.js | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 30 ++++++++ 6 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 .npmrc create mode 100644 example.js create mode 100644 index.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore index ad46b30..49b97c3 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ typings/ # next.js build output .next +package-lock.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ed67c5c --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +tag-version-prefix="" +message="chore(release): %s :tada:" diff --git a/README.md b/README.md index c7fbd70..e16caf1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ -# dat-shaka-plugin -A plugin for 'shaka-player' to load 'dat://' URI +dat-shaka-player-plugin +================ + +A plugin for 'shaka-player' to load `dat://` URI schemes. + +## Installation + +```sh +$ npm install dat-shaka-player-plugin +``` + +## Usage + +```js +const register = require('dat-shaka-player-plugin') +const shaka = require('shaka-player') + +register(shaka) +``` + +## API + +### `plugin = require('dat-shaka-player-plugin')(shaka, options)` + +A factory function to create and register the plugin with +`shaka-player` to handle `dat://` URI requests for MPEG-DASH +manifests (`*.mpd`) or HLS playlists (`*.m3u8`). `shaka` is the +`shaka-player` module and `options` can be: + +```js +{ + archive: Object, // an optional 'hyperdrive' instance that represents a DAT archive. This will override the resolved archive from the DAT link. + swarm: Object, // an optional 'discovery-swarm-web' or 'discovery-swarm' object to swarm for resources + discovery: Object, // an optional object passed to the swarm constructor +} +``` + +#### `plugin.requests` + +A `Set` that represents the currently active requests being made by the +`shaka-player` for resources stored in a DAT archive. + +#### `plugin.options` + +The options passed to the plugin factory. + +#### `plugin.swarms` + +A `Map` of swarms corresponding to a DAT link. + +#### `plugin.drives` + +A `Map` of hyperdrives corresponding to a DAT link. + +#### `plugin.destroyed` + +A boolean indicating that the plugin is destroyed. + +#### `plugin.unregister()` + +An alias to `plugin.destroy()` + +#### `plugin.destroy()` + +Destroy the plugin, close the underlying resources, and unregister as a +scheme for `shaka-player`. + +## License + +MIT diff --git a/example.js b/example.js new file mode 100644 index 0000000..26b145c --- /dev/null +++ b/example.js @@ -0,0 +1,38 @@ +const register = require('./') +const shaka = require('shaka-player') + +const BIG_BUCK_BUNNY_URI = 'dat://b2e7f16be8cf431310db322c2b8f8c97a39872de364295f251b76f40002f62bc/bunny.mpd' + +const video = document.createElement('video') +const player = new shaka.Player(video) + +Object.assign(video, { + crossOrigin: 'anonymous', + autoplay: true, + controls: true, + preload: true, +}) + +Object.assign(document.body.style, { + background: '#151515', + padding: 0, +}) + +Object.assign(video.style, { + display: 'block', + position: 'relative', + margin: '0 auto', + width: '98%', +}) + +const plugin = register(shaka, {}) +const uri = window.location.hash.slice(1) || BIG_BUCK_BUNNY_URI + +player.load(uri) + +video.addEventListener('ended', () => { + // you most likely never need to do this + plugin.destroy() +}) + +document.body.appendChild(video) diff --git a/index.js b/index.js new file mode 100644 index 0000000..0445a19 --- /dev/null +++ b/index.js @@ -0,0 +1,192 @@ +const resolveLink = require('dat-link-resolve') +const parseRange = require('range-parser') +const hyperdrive = require('hyperdrive') +const Discovery = require('discovery-swarm-web') +const collect = require('collect-stream') +const debug = require('debug')('dat-shaka-plugin') +const mime = require('mime') +const ram = require('random-access-memory') +const url = require('url') + +const SCHEME = 'dat' + +function createPlugin(shaka, options) { + if (!options || 'object' !== typeof options) { + options = {} + } + + const requests = new Set() + const swarms = new Map() + const drives = new Map() + + let destroyed = false + + shaka.net.NetworkingEngine.registerScheme(SCHEME, onscheme) + + return { + requests, + options, + swarms, + drives, + + get destroyed() { + return destroyed + }, + + unregister() { + return this.destroy() + }, + + destroy() { + for (const key of swarms.keys()) { + swarms.get(key).close() + swarms.delete(key) + } + + for (const key of drives.keys()) { + drives.get(key).close() + drives.delete(key) + } + + for (const request of requests.values()) { + request.abort() + requests.delete(request) + } + + shaka.net.NetworkingEngine.unregisterScheme(SCHEME) + }, + } + + function onscheme(uri, req, type, onprogress) { + if (destroyed) { + throw new Error("'dat-shaka-plugin' is destroyed. Please unregister.") + } + + let filename = null + let drive = null + let abort = null + let key = null + + const request = new Promise((resolve, reject) => { + abort = reject + + resolveLink(uri, onresolvelink) + + function onresolvelink(err, link) { + if (err) { + return reject(err) + } + + key = Buffer.from(link, 'hex') + drive = drives.get(link) || options.archive || options.drive + swarm = swarms.get(link) || options.swarm + + if (!drive) { + drive = hyperdrive(ram, key, Object.assign({}, options, { + latest: true, + sparse: true, + })) + } + + if (!swarm) { + swarm = new Discovery(Object.assign({}, options.discovery, { + stream: onstream + })) + } + + drives.set(link, drive) + swarms.set(link, swarm) + + const { pathname } = url.parse(uri.replace(link, 'dat.local')) + + filename = pathname + + drive.ready(() => { + swarm.join(drive.discoveryKey) + drive.download(filename) + drive.stat(filename, onstat) + }) + } + + function onread(err, buffer) { + if (err) { + return reject(err) + } + + resolve(makeResponse(uri, buffer, type)) + } + + function onstat(err, stats) { + if (err) { + return drive.once('update', () => { + drive.stat(filename, onstat) + }) + } + + if ('HEAD' === req.method) { + resolve(makeResponse(uri, stats, type)) + } + + if ('GET' === req.method) { + const { headers } = req + const { size } = stats + const range = headers.Range + ? parseRange(size, headers.Range)[0] + : null + + collect(drive.createReadStream(filename, range), onread) + } + } + }) + + const operation = new shaka.util.AbortableOperation(request, onabort) + + requests.add(request) + request.then(ondone).catch(ondone) + request.operation = operation + request.abort = () => operation.abort() + + return operation + + function ondone(err) { + if (err) { + debug(err) + } + requests.delete(request) + } + + function onabort() { + requests.delete(request) + return abort(new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.NETWORK, + shaka.util.Error.Code.OPERATION_ABORTED + )) + } + + function onstream() { + return drive.replicate({ timeout: 0 }) + } + } + + function makeResponse(uri, file, type) { + const contentType = mime.getType(uri) + const headers = {} + const status = 200 + const data = file && file.buffer ? file.buffer : null + + headers['content-type'] = contentType + + if (file && file.size) { + headers['content-length'] = file.size + } else if (file && file.buffer) { + headers['content-length'] = file.length + } + + const response = { uri, data, status, headers, fromCache: false } + + return response + } +} + +module.exports = createPlugin diff --git a/package.json b/package.json new file mode 100644 index 0000000..ac8f000 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "dat-shaka-plugin", + "version": "0.0.1", + "description": "A plugin for 'shaka-player' to load 'dat://' URIs", + "main": "index.js", + "scripts": { + "test": ":" + }, + "keywords": [ + "dat", + "shaka", + "player", + "plugin" + ], + "author": "Joseph Werle ", + "license": "MIT", + "devDependencies": { + "discovery-swarm-web": "^1.0.6", + "shaka-player": "^2.5.0-beta3" + }, + "dependencies": { + "collect-stream": "^1.2.1", + "dat-link-resolve": "^2.3.0", + "debug": "^4.1.1", + "hyperdrive": "^9.14.5", + "mime": "^2.4.2", + "random-access-memory": "^3.1.1", + "range-parser": "^1.2.0" + } +}