mirror of
https://github.com/seigler/HLS-over-IPFS-video-player
synced 2025-07-26 01:06:15 +00:00
first version
This commit is contained in:
commit
f5e7c9abbf
17 changed files with 169594 additions and 0 deletions
15
.github/pull_request_template.md
vendored
Normal file
15
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
**IMPORTANT: Please do not create a Pull Request for this repository.**
|
||||
|
||||
The contents of this repository are automatically synced from the parent [IPFS Examples Project](https://github.com/ipfs-examples/js-ipfs-examples) so any changes made to the standalone repository will be lost after the next sync.
|
||||
|
||||
Please open a PR against [IPFS Examples](https://github.com/ipfs-examples/js-ipfs-examples) instead.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
1. Fork the [IPFS Examples Project](https://github.com/ipfs-examples/js-ipfs-examples)
|
||||
2. Create your Feature Branch (`git checkout -b feature/amazing-example`)
|
||||
3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`)
|
||||
4. Push to the Branch (`git push origin feature/amazing-example`)
|
||||
5. Open a Pull Request
|
20
.github/workflows/sync.yml
vendored
Normal file
20
.github/workflows/sync.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
name: Sync
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Pull from another repository
|
||||
uses: ipfs-examples/actions-pull-directory-from-repo@main
|
||||
with:
|
||||
source-repo: "ipfs-examples/js-ipfs-examples"
|
||||
source-folder-path: "examples/browser-video-streaming"
|
||||
source-branch: "master"
|
||||
target-branch: "main"
|
||||
git-username: github-actions
|
||||
git-email: github-actions@github.com
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
.parcel-cache
|
161
README.md
Normal file
161
README.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
<p align="center">
|
||||
<a href="https://js.ipfs.io" title="JS IPFS">
|
||||
<img src="https://ipfs.io/ipfs/Qme6KJdKcp85TYbLxuLV7oQzMiLremD7HMoXLZEmgo6Rnh/js-ipfs-sticker.png" alt="IPFS in JavaScript logo" width="244" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center"><b>js-ipfs video streaming with hls.js</b></h3>
|
||||
|
||||
<p align="center">
|
||||
<b><i>Streaming video in the browser with js-ipfs and hls.js</i></b>
|
||||
<br />
|
||||
<br />
|
||||
<img src="https://raw.githubusercontent.com/jlord/forkngo/gh-pages/badges/cobalt.png" width="200">
|
||||
<br>
|
||||
<a href="https://github.com/ipfs/js-ipfs/tree/master/docs">Explore the docs</a>
|
||||
·
|
||||
<a href="https://codesandbox.io/">View Demo</a>
|
||||
·
|
||||
<a href="https://github.com/ipfs-examples/js-ipfs-examples/issues">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/ipfs-examples/js-ipfs-examples/issues">Request Feature/Example</a>
|
||||
</p>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [About The Project](#about-the-project)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation and Running example](#installation-and-running-example)
|
||||
- [Usage](#usage)
|
||||
- [Why use HLS?](#why-use-hls)
|
||||
- [hlsjs-ipfs-loader](#hlsjs-ipfs-loader)
|
||||
- [Generating HLS content](#generating-hls-content)
|
||||
- [References](#references)
|
||||
- [Documentation](#documentation)
|
||||
- [Contributing](#contributing)
|
||||
- [Want to hack on IPFS?](#want-to-hack-on-ipfs)
|
||||
|
||||
## About The Project
|
||||
|
||||
- Read the [docs](https://github.com/ipfs/js-ipfs/tree/master/docs)
|
||||
- Look into other [examples](https://github.com/ipfs-examples/js-ipfs-examples) to learn how to spawn an IPFS node in Node.js and in the Browser
|
||||
- Consult the [Core API docs](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api) to see what you can do with an IPFS node
|
||||
- Visit https://dweb-primer.ipfs.io to learn about IPFS and the concepts that underpin it
|
||||
- Head over to https://proto.school to take interactive tutorials that cover core IPFS APIs
|
||||
- Check out https://docs.ipfs.io for tips, how-tos and more
|
||||
- See https://blog.ipfs.io for news and more
|
||||
- Need help? Please ask 'How do I?' questions on https://discuss.ipfs.io
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure you have installed all of the following prerequisites on your development machine:
|
||||
|
||||
- Git - [Download & Install Git](https://git-scm.com/downloads). OSX and Linux machines typically have this already installed.
|
||||
- Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager.
|
||||
|
||||
### Installation and Running example
|
||||
|
||||
```console
|
||||
> npm install
|
||||
> npm start
|
||||
```
|
||||
|
||||
Now open your browser at `http://localhost:8888`
|
||||
|
||||
## Usage
|
||||
|
||||
This example shows a method for video/audio streaming in the browser over IPFS.
|
||||
|
||||
_Note:_ If you try to run the example straight from disk, some browsers (e.g Chrome) might, for security reasons, prevent some resources from loading correctly. To get around this, simply cd into the directory of this example and use http-server from npm:
|
||||
|
||||
```console
|
||||
$ npm install -g http-server
|
||||
$ http-server
|
||||
```
|
||||
|
||||
You should then be able to stream Big Buck Bunny by pointing your browser at http://localhost:8080.
|
||||
|
||||
In addition to video streaming, plain audio streaming works fine as well. Simply use the same ffmpeg + ipfs procedure as described above, but with your audio file as input. You may also want to change the video tag to `audio` (video tags will play plain audio as well, but the player looks a bit strange).
|
||||
|
||||
On a final note, without diving too deep into what the specific ffmpeg HLS options above mean, it's worth mentioning the `hls_time` option, which defines the length of each HLS chunk (in seconds) and is potentially interesting for performance tuning (see for example [this article](https://bitmovin.com/mpeg-dash-hls-segment-length/)).
|
||||
|
||||
_For more examples, please refer to the [Documentation](#documentation)_
|
||||
|
||||
### Why use HLS?
|
||||
|
||||
HLS (Apple's HTTP Live Streaming) is one of several protocols currently available for adaptive bitrate streaming.
|
||||
|
||||
One of the advantages of HLS over some other streaming technologies is that the content can be hosted on a plain old web server without any special server-side support. The way this works is that the original content (the stream or video/audio file) is split up into small MPEG2-TS segments before being uploaded to the server. The segments are then fetched by the HLS player on the fly (using regular HTTP GET requests) and get spliced together to a continuous stream.
|
||||
|
||||
In addition to the segments there are also so-called manifests (m3u8 files) which contain metadata about segments and their bitrates. A stream can contain segments of multiple bitrates and the HLS player will automatically switch to the optimal bitrate based on client performance.
|
||||
|
||||
The fact that HLS content is just "a bunch of files" makes it a good choice for IPFS (another protocol that works this way is MPEG-DASH, which could certainly be a good choice as well). Furthermore, the [hls.js](https://github.com/video-dev/hls.js) library enables straightforward integration with the HTML5 video element.
|
||||
|
||||
### hlsjs-ipfs-loader
|
||||
|
||||
The hls.js library ships with an HTTP based content loader only, but it's fortunately possible to configure custom content loaders as well, which is what makes IPFS streaming possible in this case. A loader implementation that fetches content using js-ipfs can be found [here](https://www.npmjs.com/package/hlsjs-ipfs-loader), and is easy to use on a regular HTML page:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/hlsjs-ipfs-loader@0.1.2"></script>
|
||||
```
|
||||
|
||||
### Generating HLS content
|
||||
|
||||
In order for any of the above to be useful, we also need to have a way to actually generate HLS manifests and MPEG2-TS segments from an arbitrary video/audio file. Luckily, most new builds of `ffmpeg` are compiled with this capability.
|
||||
|
||||
For example, say we have a directory containing a video file `BigBuckBunny_320x180.mp4`. We can then create a sub directory and generate the HLS data there, and finally add it to IPFS:
|
||||
|
||||
```console
|
||||
$ mkdir hls-bunny
|
||||
$ cd hls-bunny
|
||||
$ ffmpeg -i ../BigBuckBunny_320x180.mp4 -profile:v baseline -level 3.0 -start_number 0 -hls_time 5 -hls_list_size 0 -f hls master.m3u8
|
||||
$ ipfs add -Qr .
|
||||
```
|
||||
|
||||
The most important piece of information to note down is the name you choose for the HLS manifest (master.m3u8 in this example, but you're free to use any name), and the hash returned by `ipfs add`. Consult [streaming.js](streaming.js) for a full example of how these values are used.
|
||||
|
||||
## References
|
||||
|
||||
- Documentation:
|
||||
- [IPFS CONFIG](https://github.com/ipfs/js-ipfs/blob/master/docs/CONFIG.md)
|
||||
- [MISCELLANEOUS](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/MISCELLANEOUS.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Config](https://docs.ipfs.io/)
|
||||
- [Core API](https://github.com/ipfs/js-ipfs/tree/master/docs/core-api)
|
||||
- [Examples](https://github.com/ipfs-examples/js-ipfs-examples)
|
||||
- [Development](https://github.com/ipfs/js-ipfs/blob/master/docs/DEVELOPMENT.md)
|
||||
- [Tutorials](https://proto.school)
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
1. Fork the IPFS Project
|
||||
2. Create your Feature Branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your Changes (`git commit -a -m 'feat: add some amazing feature'`)
|
||||
4. Push to the Branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## Want to hack on IPFS?
|
||||
|
||||
[](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)
|
||||
|
||||
The IPFS implementation in JavaScript needs your help! There are a few things you can do right now to help out:
|
||||
|
||||
Read the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md).
|
||||
|
||||
- **Check out existing issues** The [issue list](https://github.com/ipfs/js-ipfs/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/js-ipfs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge
|
||||
- **Look at the [IPFS Roadmap](https://github.com/ipfs/roadmap)** This are the high priority items being worked on right now
|
||||
- **Perform code reviews** More eyes will help
|
||||
a. speed the project along
|
||||
b. ensure quality, and
|
||||
c. reduce possible future bugs.
|
||||
- **Add tests**. There can never be enough tests.
|
||||
- **Join the [Weekly Core Implementations Call](https://github.com/ipfs/team-mgmt/issues/992)** it's where everyone discusses what's going on with IPFS and what's next
|
BIN
dist/favicon.5c4f16e7.ico
vendored
Normal file
BIN
dist/favicon.5c4f16e7.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
dist/favicon.e7ae9020.ico
vendored
Normal file
BIN
dist/favicon.e7ae9020.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
35
dist/index.199f11ff.js
vendored
Normal file
35
dist/index.199f11ff.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.199f11ff.js.map
vendored
Normal file
1
dist/index.199f11ff.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
145687
dist/index.379dd93c.js
vendored
Normal file
145687
dist/index.379dd93c.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.379dd93c.js.map
vendored
Normal file
1
dist/index.379dd93c.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.html
vendored
Normal file
1
dist/index.html
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>HLS over IPFS video player</title><link rel="icon" href="/favicon.e7ae9020.ico"><style>*{box-sizing:border-box}body,html{margin:0;padding:0}body{align-items:center;background-color:#000;color:#fff;display:flex;font-family:sans-serif;height:100vh;justify-content:center}code{white-space:nowrap}#status{bottom:100%;font-size:2em;height:3ex;left:0;margin-bottom:-3ex;position:fixed;text-align:center;transition:transform .3s ease 1s;width:100%}.is-hiding{transform:translateY(-3ex)}#video{display:none;height:100%;width:100%}#help{max-width:80ch;padding:5em}</style><script defer src="/index.199f11ff.js"></script></head><body> <video id="video" controls autoplay></video> <div id="status"></div> <section id="help"> <h1>HLS over IPFS video player</h1> <p>This is a tool for viewing videos encoded as <a href="https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/browser-video-streaming">HLS</a>. The folder containing this playlist and all its files is uploaded to IPFS, and the hash is provided in the URL as a parameter.</p> <pre>?hash=QmYzdc44xBkVgp8aWJW57KprjDs5j2hmN8g7eDqm5pvY8L</pre> <p>It also accepts parameters for <code>source</code> filename (default is <code>master.m3u8</code>) and <code>title</code>. </p> <p>Given at least a valid hash, it should play a video in the browser using <a href="https://github.com/ipfs/js-ipfs">JS-IPFS</a> to connect directly to the IPFS network.</p> <noscript> <p><strong>Since your browser is not executing JavaScript, none of this will work.</strong></p> </noscript> </section> </body></html>
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
70
index.html
Normal file
70
index.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>HLS over IPFS video player</title>
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
height: 100vh;
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
background-color: black;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#status {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
transition: transform 0.3s 1s ease;
|
||||
position: fixed;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3ex;
|
||||
margin-bottom: -3ex;
|
||||
}
|
||||
.is-hiding {
|
||||
transform: translateY(-3ex)
|
||||
}
|
||||
#video {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#help {
|
||||
max-width: 80ch;
|
||||
padding: 5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module" defer="" src="./src/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video" controls="" autoplay=""></video>
|
||||
<div id="status"></div>
|
||||
<section id="help">
|
||||
<h1>HLS over IPFS video player</h1>
|
||||
<p>This is a tool for viewing videos encoded as <a href="https://github.com/ipfs-examples/js-ipfs-examples/tree/master/examples/browser-video-streaming">HLS</a>. The folder containing this playlist and all its files is uploaded to IPFS, and the hash is provided in the URL as a parameter.</p>
|
||||
<pre>?hash=QmYzdc44xBkVgp8aWJW57KprjDs5j2hmN8g7eDqm5pvY8L</pre>
|
||||
<p>It also accepts parameters for <code>source</code> filename (default is <code>master.m3u8</code>) and <code>title</code>.
|
||||
</p>
|
||||
<p>Given at least a valid hash, it should play a video in the browser using <a href="https://github.com/ipfs/js-ipfs">JS-IPFS</a> to connect directly to the IPFS network.</p>
|
||||
<noscript>
|
||||
<p><strong>Since your browser is not executing JavaScript, none of this will work.</strong></p>
|
||||
</noscript>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
23496
package-lock.json
generated
Normal file
23496
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "example-browser-video-streaming",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "IPFS browser video streaming example",
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./dist ./.cache ./.parcel-cache",
|
||||
"build": "parcel build index.html --no-scope-hoist",
|
||||
"serve": "parcel serve index.html --open -p 8888",
|
||||
"start": "npm run serve",
|
||||
"test": "npm run build && playwright test tests --browser=firefox --retries=3"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"hls.js": "^0.14.17",
|
||||
"hlsjs-ipfs-loader": "^0.3.0",
|
||||
"ipfs-core": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.8",
|
||||
"@playwright/test": "^1.12.3",
|
||||
"parcel": "latest",
|
||||
"playwright": "^1.12.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"test-util-ipfs-example": "^1.0.2"
|
||||
}
|
||||
}
|
52
src/index.js
Normal file
52
src/index.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
'use strict'
|
||||
|
||||
import { create } from 'ipfs-core'
|
||||
import Hls from 'hls.js'
|
||||
import HlsjsIpfsLoader from 'hlsjs-ipfs-loader'
|
||||
|
||||
let node
|
||||
|
||||
function getUrlParameter(name) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
|
||||
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)')
|
||||
var results = regex.exec(location.search)
|
||||
return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '))
|
||||
}
|
||||
|
||||
function showStatus(message, hide = false) {
|
||||
const status = document.getElementById('status')
|
||||
status.classList.toggle("is-hiding", hide)
|
||||
status.innerText = message
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const hash = getUrlParameter("hash")
|
||||
const source = getUrlParameter("source") || 'master.m3u8'
|
||||
const title = getUrlParameter("title")
|
||||
if (title) {
|
||||
document.title = title
|
||||
}
|
||||
if (hash) {
|
||||
document.getElementById("help").style.display = "none"
|
||||
const video = document.getElementById('video')
|
||||
video.style.display = "block"
|
||||
const repoPath = 'ipfs-' + Math.random()
|
||||
showStatus("Connecting to IPFS")
|
||||
node = await create({ repo: repoPath })
|
||||
showStatus("Connected")
|
||||
Hls.DefaultConfig.loader = HlsjsIpfsLoader
|
||||
Hls.DefaultConfig.debug = false
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls()
|
||||
hls.config.ipfs = node
|
||||
hls.config.ipfsHash = hash
|
||||
showStatus("Video loading")
|
||||
hls.loadSource(source)
|
||||
hls.attachMedia(video)
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
showStatus("Video loaded", true)
|
||||
video.play()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
24
tests/test.js
Normal file
24
tests/test.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
'use strict'
|
||||
|
||||
'use strict'
|
||||
|
||||
const { test } = require('@playwright/test');
|
||||
const { playwright } = require('test-util-ipfs-example');
|
||||
|
||||
// Setup
|
||||
const play = test.extend({
|
||||
...playwright.servers(),
|
||||
});
|
||||
|
||||
play.describe('browser videostream:', () => {
|
||||
// DOM
|
||||
const status = "#status"
|
||||
|
||||
play.beforeEach(async ({servers, page}) => {
|
||||
await page.goto(`http://localhost:${servers[0].port}/`);
|
||||
})
|
||||
|
||||
play('should properly initialized a IPFS node and stream a video', async ({ page }) => {
|
||||
await page.waitForSelector(`${status}:has-text('Video ready')`)
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue