├── static ├── robots.txt └── favicon.ico ├── start.sh ├── src ├── favicon.ico ├── style │ └── main.scss ├── plugins │ └── vue-ipfs.js ├── App.vue ├── NotFound.vue ├── index.html ├── index.js ├── IpfsGatewayVideo.vue ├── IpfsLs.vue ├── components │ └── PathForm.vue ├── Home.vue └── IpfsVideo.vue ├── .parcelrc ├── recode_vp8_webm.sh ├── recode_vp9_webm.sh ├── .gitignore ├── README.md └── package.json /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | yarn build 4 | yarn start 5 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bneijt/ipfs-video-frontend/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bneijt/ipfs-video-frontend/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /src/style/main.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | // $scheme-main: #333; 3 | // $text: #6d6b61; 4 | // $grey-dark: #6d6b61; 5 | @import "../../node_modules/bulma/bulma.sass"; 6 | -------------------------------------------------------------------------------- /.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@parcel/config-default" 4 | ], 5 | "reporters": [ 6 | "...", 7 | "parcel-reporter-static-files-copy" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /recode_vp8_webm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Script that takes the input file and recodes to streamable VP8 Opus WEBM file 3 | set -e 4 | INPUT=$1 5 | if [ -z "$INPUT" ]; then 6 | echo "Requires a single file as input file" 7 | exit 1 8 | fi 9 | OUTPUT="${INPUT%%.*}".webm 10 | QUALITY=45 11 | ffmpeg -i "$INPUT" -c:v libvpx -crf $QUALITY -b:v 10M -r 30 -c:a libopus -b:a 96K "$OUTPUT" 12 | -------------------------------------------------------------------------------- /recode_vp9_webm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Script that takes the input file and recodes to streamable VP8 Opus WEBM file 3 | set -e 4 | INPUT=$1 5 | if [ -z "$INPUT" ]; then 6 | echo "Requires a single file as input file" 7 | exit 1 8 | fi 9 | OUTPUT="${INPUT%%.*}".webm 10 | QUALITY=45 11 | ffmpeg -i "$INPUT" -c:v libvpx-vp9 -crf $QUALITY -b:v 0 -r 30 -c:a libopus -b:a 96K "$OUTPUT" -------------------------------------------------------------------------------- /src/plugins/vue-ipfs.js: -------------------------------------------------------------------------------- 1 | import IPFS from 'ipfs' 2 | 3 | const plugin = { 4 | install: (app, options = {}) => { 5 | app.config.globalProperties.$ipfs = IPFS.create(options) 6 | 7 | app.provide('ipfs', options) 8 | } 9 | } 10 | 11 | // Auto-install 12 | if (typeof window !== 'undefined' && window.Vue) { 13 | window.Vue.use(plugin) 14 | } 15 | 16 | export default plugin 17 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | -------------------------------------------------------------------------------- /src/NotFound.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /examples 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | dist 26 | .parcel-cache 27 | .nuxt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IPFS video front-end 2 | ========= 3 | This is the IPFS video front-end hosted on https://ipfs.video 4 | 5 | The basic idea is being able to play a video from IPFS directly or using one of the gateways available. 6 | 7 | Development notes 8 | ================ 9 | 10 | To detect the type of MP4 embedded media, I did some reverse engineering to find the correct bytes for the different codecs embedded in the mp4 file. 11 | 12 | Commands to do that include: 13 | 14 | #!/bin/bash 15 | echo avc1 16 | mp4file --dump "$1" | grep AVCProfileIndication 17 | mp4file --dump "$1" | grep profile_compatibility 18 | mp4file --dump "$1" | grep AVCLevelIndication 19 | 20 | echo mp4a 21 | mp4file --dump "$1" | grep objectTypeId 22 | mp4file --dump "$1" | grep AVCProfileIndication -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | IPFS video player 10 | 11 | 12 | 13 | 14 | 18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipfs-video-frontend", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "private": true, 6 | "dependencies": { 7 | "bulma": "^0.9.2", 8 | "ipfs": "^0.55.3", 9 | "vue": "^3.0.11", 10 | "vue-meta": "^3.0.0-alpha.9", 11 | "vue-router": "^4.0.8" 12 | }, 13 | "source": "dist/index.html", 14 | "scripts": { 15 | "start": "parcel serve src/index.html", 16 | "build": "parcel build --no-scope-hoist src/index.html" 17 | }, 18 | "devDependencies": { 19 | "@parcel/transformer-sass": "^2.0.0-rc.0", 20 | "@parcel/transformer-vue": "^2.0.0-rc.0", 21 | "parcel": "^2.0.0-rc.0", 22 | "parcel-reporter-static-files-copy": "^1.3.0", 23 | "sass": "^1.32.13" 24 | }, 25 | "resolutions": { 26 | "trim-newlines": "^3.0.1", 27 | "electron": "^9.4.0" 28 | }, 29 | "browserslist": [ 30 | "last 2 versions and not dead and > 2%" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import { createMetaManager, plugin as metaPlugin } from 'vue-meta' 4 | 5 | import App from "./App"; 6 | import Home from "./Home"; 7 | import IpfsVideo from "./IpfsVideo"; 8 | import PathForm from "./components/PathForm"; 9 | import IpfsGatewayVideo from "./IpfsGatewayVideo"; 10 | import IpfsLs from './IpfsLs'; 11 | import NotFound from './NotFound'; 12 | import VueIpfs from './plugins/vue-ipfs'; 13 | 14 | const routes = [ 15 | { path: '/', component: Home }, 16 | { path: '/ipfs/:ipfsPath+', component: IpfsVideo }, 17 | { path: '/gw/:ipfsPath+', component: IpfsGatewayVideo }, 18 | { path: '/ls/:ipfsPath+', component: IpfsLs }, 19 | { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, 20 | ] 21 | const router = createRouter({ 22 | history: createWebHistory(), 23 | routes: routes 24 | }) 25 | 26 | const app = createApp(App) 27 | .use(router) 28 | .use(VueIpfs) 29 | .use(createMetaManager()) 30 | .use(metaPlugin) 31 | 32 | app.component('path-form', PathForm); 33 | app.mount("#app"); 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/IpfsGatewayVideo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 49 | -------------------------------------------------------------------------------- /src/IpfsLs.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 94 | -------------------------------------------------------------------------------- /src/components/PathForm.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 104 | -------------------------------------------------------------------------------- /src/Home.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 131 | -------------------------------------------------------------------------------- /src/IpfsVideo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 299 | --------------------------------------------------------------------------------