Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 5e6d710

Browse files
committed
docs: Add browser example for ReadableStreams
1 parent a249669 commit 5e6d710

File tree

7 files changed

+228
-3
lines changed

7 files changed

+228
-3
lines changed

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Let us know if you find any issue or if you want to contribute and add a new tut
2121
- [js-ipfs in the browser with a `<script>` tag](./browser-script-tag)
2222
- [js-ipfs in electron](./run-in-electron)
2323
- [Using streams to add a directory of files to ipfs](./browser-add-readable-stream)
24+
- [Streaming video from ipfs to the browser using `ReadableStream`s](./browser-readablestream)
2425

2526
## Understanding the IPFS Stack
2627

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Streaming video from IPFS using ReadableStreams
2+
3+
We can use the execllent [`videostream`](https://www.npmjs.com/package/videostream) to stream video from IPFS to the browser. All we need to do is return a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)-like object that contains the requested byte ranges.
4+
5+
Take a look at [`index.js`](./index.js) to see a working example.
6+
7+
## Running the demo
8+
9+
In this directory:
10+
11+
```
12+
$ npm install
13+
$ npm start
14+
```
15+
16+
Then open [http://localhost:8888](http://localhost:8888) in your browser.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+
<title><%= htmlWebpackPlugin.options.title %></title>
6+
<style type="text/css">
7+
8+
body {
9+
margin: 0;
10+
padding: 0;
11+
}
12+
13+
#container {
14+
display: flex;
15+
height: 100vh;
16+
}
17+
18+
pre {
19+
flex-grow: 2;
20+
padding: 10px;
21+
height: calc(100vh - 45px);
22+
overflow: auto;
23+
}
24+
25+
#form-wrapper {
26+
padding: 20px;
27+
}
28+
29+
form {
30+
padding-bottom: 10px;
31+
display: flex;
32+
}
33+
34+
#hash {
35+
display: inline-block;
36+
margin: 0 10px 10px 0;
37+
font-size: 16px;
38+
flex-grow: 2;
39+
padding: 5px;
40+
}
41+
42+
button {
43+
display: inline-block;
44+
font-size: 16px;
45+
height: 32px;
46+
}
47+
48+
</style>
49+
</head>
50+
<body>
51+
<div id="container">
52+
<div id="form-wrapper">
53+
<form>
54+
<input type="text" id="hash" placeholder="Hash" disabled />
55+
<button id="gobutton" disabled>Go!</button>
56+
</form>
57+
<video id="video" controls></video>
58+
</div>
59+
<pre id="output" style="display: inline-block"></pre>
60+
</div>
61+
</body>
62+
</html>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use strict'
2+
3+
/* eslint-env browser */
4+
5+
const Ipfs = require('../../')
6+
const videoStream = require('videostream')
7+
const repoPath = 'ipfs-' + Math.random()
8+
const ipfs = new Ipfs({ repo: repoPath })
9+
const through = require('through')
10+
const unixFs = require('ipfs-unixfs')
11+
const pull = require('pull-stream')
12+
const drain = require('pull-stream/sinks/drain')
13+
14+
const log = (line) => {
15+
document.getElementById('output').appendChild(document.createTextNode(`${line}\r\n`))
16+
}
17+
18+
log('Initialising IPFS')
19+
20+
let stream
21+
22+
const cleanUp = () => {
23+
if (stream && stream.destroy) {
24+
stream.destroy()
25+
}
26+
}
27+
28+
ipfs.on('ready', () => {
29+
const videoElement = createVideoElement()
30+
31+
log('Adding video file')
32+
33+
addVideoFile('/video.mp4')
34+
.then(hash => {
35+
log(`Added file with hash ${hash}`)
36+
37+
const hashInput = document.getElementById('hash')
38+
const goButton = document.getElementById('gobutton')
39+
40+
hashInput.value = hash
41+
42+
goButton.onclick = function () {
43+
videoStream({
44+
createReadStream: function (opts) {
45+
// Return a readable stream that provides the bytes
46+
// between offsets "start" and "end" inclusive
47+
const start = opts.start
48+
const end = opts.end ? start + opts.end + 1 : undefined
49+
50+
log(`Asked for data starting at byte ${start}`)
51+
52+
cleanUp()
53+
54+
// We will write the requested bytes into this stream
55+
stream = ipfs.files.catReadableStream(hashInput.value.trim(), start, end)
56+
57+
return stream
58+
}
59+
}, videoElement)
60+
61+
return false
62+
}
63+
64+
hashInput.disabled = false
65+
goButton.disabled = false
66+
})
67+
})
68+
69+
const addVideoFile = (path) => {
70+
return fetch(path)
71+
.then(response => response.arrayBuffer())
72+
.then(buffer => ipfs.files.add(Buffer.from(buffer)))
73+
.then(result => result.pop().hash)
74+
}
75+
76+
const createVideoElement = () => {
77+
const videoElement = document.getElementById('video')
78+
videoElement.addEventListener('loadedmetadata', () => {
79+
log('Video metadata loaded')
80+
81+
videoElement.play()
82+
})
83+
videoElement.addEventListener('loadeddata', () => {
84+
log('First video frame loaded')
85+
})
86+
videoElement.addEventListener('loadstart', () => {
87+
log('Started loading video')
88+
})
89+
90+
return videoElement
91+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "browser-videostream",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"build": "webpack",
9+
"start": "npm run build && curl https://www.html5rocks.com/en/tutorials/video/basics/devstories.mp4 -o dist/video.mp4 && http-server dist -a 127.0.0.1 -p 8888"
10+
},
11+
"author": "",
12+
"license": "ISC",
13+
"devDependencies": {
14+
"html-webpack-plugin": "^2.30.1",
15+
"http-server": "^0.11.1",
16+
"uglifyjs-webpack-plugin": "^1.2.0",
17+
"webpack": "^3.11.0"
18+
},
19+
"dependencies": {
20+
"ipfs": "^0.27.7",
21+
"ipfs-unixfs": "^0.1.14",
22+
"through": "^2.3.8",
23+
"videostream": "^2.4.2"
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
5+
const HtmlWebpackPlugin = require('html-webpack-plugin')
6+
7+
module.exports = {
8+
devtool: 'source-map',
9+
entry: [
10+
'./index.js'
11+
],
12+
plugins: [
13+
new UglifyJsPlugin({
14+
sourceMap: true,
15+
uglifyOptions: {
16+
mangle: false,
17+
compress: false
18+
}
19+
}),
20+
new HtmlWebpackPlugin({
21+
title: 'IPFS Videostream example',
22+
template: 'index.html'
23+
})
24+
],
25+
output: {
26+
path: path.join(__dirname, 'dist'),
27+
filename: 'bundle.js'
28+
}
29+
}

src/core/components/files.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const unixfsEngine = require('ipfs-unixfs-engine')
44
const importer = unixfsEngine.importer
55
const exporter = unixfsEngine.exporter
6+
const reader = unixfsEngine.reader
67
const promisify = require('promisify-es6')
78
const pull = require('pull-stream')
89
const sort = require('pull-sort')
@@ -126,7 +127,7 @@ module.exports = function files (self) {
126127
)
127128
}
128129

129-
function _catPullStream (ipfsPath) {
130+
function _catPullStream (ipfsPath, ipldResolver, offset, length) {
130131
if (typeof ipfsPath === 'function') {
131132
throw new Error('You must supply an ipfsPath')
132133
}
@@ -240,9 +241,9 @@ module.exports = function files (self) {
240241
)
241242
}),
242243

243-
catReadableStream: (ipfsPath) => toStream.source(_catPullStream(ipfsPath)),
244+
catReadableStream: (ipfsPath, begin, end) => toStream.source(reader(ipfsPath, self._ipldResolver, begin, end)),
244245

245-
catPullStream: _catPullStream,
246+
catPullStream: (ipfsPath, begin, end) => reader(ipfsPath, self._ipldResolver, begin, end),
246247

247248
get: promisify((ipfsPath, callback) => {
248249
pull(

0 commit comments

Comments
 (0)