Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b3898c2
Showing
5 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
!.gitkeep | ||
node_modules/ | ||
package-lock.json | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width"> | ||
<title>JS Bin</title> | ||
<style> | ||
body { | ||
background-color: #03A678; | ||
color: white; | ||
} | ||
body, html, h1, h3, p { | ||
margin: 0px; | ||
padding: 0px; | ||
font-family: Verdana, sanf-serif; | ||
} | ||
|
||
h1, h3 { | ||
text-align: center; | ||
margin-bottom: 10px; | ||
} | ||
|
||
h1 { | ||
margin-top: 10px; | ||
} | ||
|
||
a { | ||
text-decoration: none; | ||
color: white; | ||
padding-bottom: 1px; | ||
border-bottom: 2px solid white; | ||
} | ||
|
||
p { | ||
line-height: 25px; | ||
font-size: 16px; | ||
} | ||
|
||
#video { | ||
display: none; | ||
} | ||
|
||
canvas { | ||
background-color: white; | ||
box-shadow: 1px 1px 1px 1px rgba(0,0,0,0.1); | ||
border-radius: 50%; | ||
margin: 10px; | ||
} | ||
|
||
.footer { | ||
position: absolute; | ||
padding: 10px; | ||
bottom: 0px; | ||
left: 0px; | ||
right: 0px; | ||
text-align: center; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h1>Resort</h1> | ||
<h3> | ||
A experiment with showing live video feeds with | ||
<a href="https://ipfs.io/" target="_blank">IPFS</a> | ||
and | ||
<a href="https://libp2p.io/" target="_blank">libp2p</a> | ||
</h3> | ||
<div id="feeds"></div> | ||
<video id="video" width="128" height="128" autoplay></video> | ||
<script src="build/index.js"></script> | ||
<div class="footer"> | ||
<p>The source code for this experiment can be found here: | ||
<a href="https://github.com/victorbjelkholm/resort">https://github.com/victorbjelkholm/resort</a></p> | ||
<p> | ||
If you can only see yourself, ask a friend to go to the same URL, or open | ||
up the same URL in a new, incognito window. | ||
</p> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "ipfs-resort", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"license": "MIT", | ||
"dependencies": { | ||
"browserify": "^14.4.0", | ||
"externs-generator": "^0.3.2" | ||
}, | ||
"scripts": { | ||
"build": "browserify -t [ babelify ] script-libp2p.js -o build/index.js", | ||
"dev": "watchify -t [ babelify ] script-libp2p.js -o build/index.js" | ||
}, | ||
"devDependencies": { | ||
"babelify": "^7.3.0", | ||
"libp2p": "^0.12.4", | ||
"libp2p-multiplex": "^0.5.0", | ||
"libp2p-secio": "^0.8.1", | ||
"libp2p-webrtc-star": "^0.13.2", | ||
"libp2p-websockets": "^0.10.1", | ||
"multiaddr": "^3.0.1", | ||
"peer-book": "^0.5.1", | ||
"peer-id": "^0.10.1", | ||
"peer-info": "^0.11.0", | ||
"pull-pushable": "^2.1.1", | ||
"pull-stream": "^3.6.1", | ||
"pull-stream-to-stream": "^1.3.4", | ||
"typedarray-to-buffer": "^3.1.2", | ||
"watchify": "^3.9.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Resort - WebCam over IPFS | ||
|
||
## Install & Run | ||
|
||
- `npm install` | ||
- `npm run dev` | ||
- Open a webserver in the root directory | ||
- Navigate to page and accept sharing webcam | ||
- Open a new incognito window with same page | ||
- Enjoy seeing yourself, TWICE! | ||
|
||
|
||
## TODO | ||
|
||
- Layout | ||
- Timeout | ||
- Use IPFS to send hash of image instead of image itself | ||
- Measure latency | ||
- Debug output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
// This version uses libp2p only and sends the data directly to peers | ||
// - Works great for low amount of peers and requires less abstractions | ||
// - However, large images not possible to send | ||
// - Peers can not help to "seed" each others image, making it less efficient | ||
const WS = require('libp2p-websockets') | ||
const WebRTCStar = require('libp2p-webrtc-star') | ||
const Multiplex = require('libp2p-multiplex') | ||
const SECIO = require('libp2p-secio') | ||
const libp2p = require('libp2p') | ||
|
||
const PeerInfo = require('peer-info') | ||
const PeerId = require('peer-id') | ||
const PeerBook = require('peer-book') | ||
const Multiaddr = require('multiaddr') | ||
const toBuffer = require('typedarray-to-buffer') | ||
const pull = require('pull-stream') | ||
const pushable = require('pull-pushable') | ||
|
||
class Node extends libp2p { | ||
constructor (peerInfo, peerBook, options) { | ||
options = options || {} | ||
const wstar = new WebRTCStar() | ||
|
||
const modules = { | ||
transport: [new WS(), wstar], | ||
connection: { | ||
muxer: [Multiplex], | ||
crypto: [SECIO] | ||
}, | ||
discovery: [wstar.discovery] | ||
} | ||
|
||
super(modules, peerInfo, peerBook, options) | ||
} | ||
} | ||
|
||
// CONSTANTS | ||
// size of webcam video feed | ||
const SIZES = { | ||
width: 128, | ||
height: 128 | ||
} | ||
// Delay for sending | ||
const SEND_EACH_MS = 100 | ||
// Protocol for our application | ||
const PROTOCOL = '/resort/0.1.0' | ||
// Which signal server to use, overwrite default while in dev | ||
// const SIGNAL_SERVER = '/ip4/127.0.0.1/ws/p2p-webrtc-star/ipfs/:peer-id' | ||
const SIGNAL_SERVER = '/dns4/star-signal.cloud.ipfs.team/wss/p2p-webrtc-star/ipfs/:peer-id' | ||
|
||
// Create and start a libp2p Peer to use | ||
const startOwnPeer = (callback) => { | ||
PeerId.create((err, peerId) => { | ||
if (err) callback(err) | ||
const peerInfo = new PeerInfo(peerId) | ||
const peerBook = new PeerBook() | ||
const mh1 = Multiaddr(SIGNAL_SERVER.replace(':peer-id', peerId.toB58String())) | ||
peerInfo.multiaddrs.add(mh1) | ||
const node = new Node(peerInfo, peerBook, { | ||
webRTCStar: true | ||
}) | ||
node.start((err) => { | ||
if (err) callback(err) | ||
callback(null, node) | ||
}) | ||
}) | ||
} | ||
// Mutable global of all open connections, containing pushable pull-stream with | ||
// Peer ID as key | ||
const _OPEN_CONNECTIONS = {} | ||
|
||
const listenAndConnectToPeers = (node) => { | ||
node.on('peer:discovery', (peer) => { | ||
const id = peer.id._idB58String | ||
if (_OPEN_CONNECTIONS[id] === undefined) { | ||
node.dial(peer, PROTOCOL, (err, conn) => { | ||
// Ignore errors, as not everyone supports our protocol | ||
if (err) return | ||
// Create a pushable pull-stream that we can write to | ||
const p = pushable() | ||
pull(p, conn) | ||
// Assign the pull-stream to our global | ||
_OPEN_CONNECTIONS[id] = p | ||
// Create a canvas for this peer | ||
createCanvas(id) | ||
}) | ||
} else { | ||
console.log('Already connected to peer', id) | ||
} | ||
}) | ||
// When connection to a peer closes, remove the canvas | ||
node.swarm.on('peer-mux-closed', (peer) => { | ||
removeCanvas(peer.id._idB58String) | ||
}) | ||
// TODO maybe should listen for peer errors as well | ||
// TODO should setup a check and see when we last received data, if more than | ||
// X seconds, kill feed | ||
} | ||
|
||
// Mutable global with references to all canvases we have created | ||
const _CANVASES = {} | ||
|
||
const createCanvas = (id) => { | ||
if (id === undefined) throw new Error('ID needs to be defined') | ||
if (_CANVASES[id] !== undefined) throw new Error('Already had a canvas for ID') | ||
|
||
const canvas = document.createElement('canvas') | ||
canvas.setAttribute('width', SIZES.width) | ||
canvas.setAttribute('height', SIZES.height) | ||
const ctx = canvas.getContext('2d') | ||
_CANVASES[id] = ctx | ||
|
||
// Write some text on the canvas while we get the first frame | ||
ctx.font='15px Verdana' | ||
ctx.fillText('Loading video', 15, 70) | ||
|
||
document.getElementById('feeds').append(canvas) | ||
} | ||
const getCanvas = (id) => { | ||
if (id === undefined) throw new Error('ID needs to be defined') | ||
|
||
return _CANVASES[id] | ||
} | ||
const removeCanvas = (id) => { | ||
if (id === undefined) throw new Error('ID needs to be defined') | ||
|
||
_CANVASES[id].canvas.remove() | ||
} | ||
|
||
window.addEventListener('load', () => { | ||
// Our feed from the camera | ||
const video = document.getElementById('video'); | ||
|
||
startOwnPeer((err, node) => { | ||
if (err) throw err | ||
const myID = node.peerInfo.id._idB58String | ||
console.log('Your ID: ' + myID) | ||
createCanvas(myID) | ||
|
||
listenAndConnectToPeers(node) | ||
setInterval(() => { | ||
const mhs = node.peerBook.getAllArray() | ||
console.log('Connected to ' + mhs.length + ' other peers') | ||
}, 2000) | ||
|
||
// Handle incoming connections from peers | ||
// TODO get peer id together with first data somehow? | ||
// Right now we cannot get a live feed until getPeerInfo returns | ||
node.handle(PROTOCOL, (protocol, conn) => { | ||
console.log('Incoming connection') | ||
let peerID | ||
conn.getPeerInfo((err, info) => { | ||
if (err) throw err | ||
peerID = info.id._idB58String | ||
}) | ||
pull( | ||
conn, | ||
pull.through((data) => { | ||
// getPeerInfo might not have given us the Peer ID yet... | ||
if (peerID !== undefined) { | ||
const canvas = getCanvas(peerID) | ||
// We might not created the canvas yet... | ||
if (canvas !== undefined) { | ||
const dataToRender = new ImageData(new Uint8ClampedArray(data), SIZES.width, SIZES.height) | ||
canvas.putImageData(dataToRender, 0, 0); | ||
} | ||
} | ||
}), | ||
pull.collect((err) => { | ||
if (err) throw err | ||
}) | ||
) | ||
}) | ||
// Check if webcam is supported | ||
// TODO provide fallback if it isn't | ||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | ||
// Request webcam permissions | ||
navigator.mediaDevices.getUserMedia({ video: true }).then(function (stream) { | ||
// Write webcam data to video element | ||
video.src = window.URL.createObjectURL(stream) | ||
video.play() | ||
}) | ||
} | ||
|
||
function drawAndSend() { | ||
// If we don't have our own canvas yet, is because camera has yet to init | ||
const myCanvas = getCanvas(myID) | ||
if (myCanvas !== undefined) { | ||
|
||
// Write video feed into canvas | ||
myCanvas.drawImage(video, 0, 0, SIZES.width, SIZES.height) | ||
|
||
// Get image data from canvas | ||
const data = myCanvas.getImageData(0, 0, SIZES.width, SIZES.height) | ||
|
||
// Convert data into buffer and send to each open connection | ||
const bufferToWrite = toBuffer(data.data) | ||
|
||
// For each open connection we have | ||
Object.keys(_OPEN_CONNECTIONS).forEach((id) => { | ||
const stream = _OPEN_CONNECTIONS[id] | ||
// Write image data to the pull-stream | ||
stream.push(bufferToWrite) | ||
}) | ||
} | ||
// Repeat this as fast as possible but with delay of 100ms | ||
setTimeout(drawAndSend, SEND_EACH_MS) | ||
} | ||
// Start recursive function | ||
drawAndSend() | ||
}) | ||
}) |