-
Notifications
You must be signed in to change notification settings - Fork 22
/
script-libp2p.js
212 lines (189 loc) · 6.71 KB
/
script-libp2p.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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()
})
})