Stream stored video in a peer-to-peer app
Serve stored video files over hypercore-blob-server so peers can stream them with range requests on top of the hello-pear-electron scaffold.
This guide shows you how to stream a stored video file peer-to-peer by adapting the hello-pear-electron scaffold to publish video blobs through Hyperblobs and serve them with hypercore-blob-server. The reference implementation is pear-video-stream.
This guide is about the Pear-end, not the shell. The code below lives in the Bare worker — the peer-to-peer logic, not the user interface. Because the Pear-end never imports DOM APIs and never assumes a UI framework, the same worker is portable across desktop (Electron), mobile (React Native via Bare iOS / Bare Android), and terminal. The example apps ship an Electron shell, but only the UI half changes per platform — the logic here stays the same. See Runtime and languages for the cross-platform model and current support.
This is a delta-only how-to. The shared scaffold is explained in the Start from the hello-pear-electron template tutorial — read it first.
- Stream a live camera in a peer-to-peer app — the live counterpart that builds on the same blob plumbing.
Before you begin
- A working clone of
hello-pear-electron(or your own app built from the getting-started path). - A stored video file (
.mp4,.webm) you want to share.
What changes
| Layer | Change |
|---|---|
| Dependencies | Add hyperblobs, hypercore-blob-server, hypercore-id-encoding, and get-mime-type. |
| Worker | Add a Hyperblobs core in the worker; store each video as one blob and attach a blob-server link to every entry. |
| Worker transport | Add an add-video ingest (an { type: 'add-video', filePath } control message) and a videos snapshot whose entries carry a playable link. |
| Renderer | Render a video player on the selected entry's link. |
The Electron shell, preload bridge, packaging, and graceful teardown stay as in hello-pear-electron.
Steps
Add the dependencies
npm install hyperblobs hypercore-blob-server hypercore-id-encoding get-mime-typePublish each video as a Hyperblob
workers/video-room.js (VideoRoom) keeps the tutorial's Autobase + pairing, and constructs a Hyperblobs core plus a hypercore-blob-server. addVideo reads the user-selected file off disk, checks the MIME type with get-mime-type, streams the bytes into the blobs core, then records { id, name, type, blob, info } in the view:
async addVideo (filePath, info) {
const name = path.basename(filePath)
const type = getMimeType(name)
if (!type || !type.startsWith('video/')) {
throw new Error('Only video files are allowed')
}
const rs = fs.createReadStream(filePath)
const ws = this.blobs.createWriteStream()
await new Promise((resolve, reject) => {
ws.on('error', reject)
ws.on('close', resolve)
rs.pipe(ws)
})
const blob = { key: idEnc.normalize(this.blobs.core.key), ...ws.id }
const id = Math.random().toString(16).slice(2)
await this.base.append(
VideoDispatch.encode('@pear-video-stream/add-video', { id, name, type, blob, info })
)
}Serve the blob locally
The server speaks HTTP range requests, so the player handles seeking without downloading the whole file. getVideos joins each blob's swarm topic on demand and attaches a playable info.link (from blobServer.getLink) to every entry it returns to the renderer:
async getVideos ({ reverse = true, limit = 100 } = {}) {
const videos = await this.view.find('@pear-video-stream/videos', { reverse, limit }).toArray()
for (const item of videos) {
if (!this.blobsCores[item.blob.key]) {
const blobsCore = this.store.get({ key: idEnc.decode(item.blob.key) })
this.blobsCores[item.blob.key] = blobsCore
await blobsCore.ready()
this.swarm.join(blobsCore.discoveryKey)
}
}
return videos.map(item => {
const link = this.blobServer.getLink(item.blob.key, { blob: item.blob, type: item.type })
return { ...item, info: { ...item.info, link } }
})
}Render a <video> element
In the renderer, create a <video controls> element and set its src to video.info.link for the selected entry. The first peer who clicks Play triggers a range request, the worker pulls the byte range from the Hyperblob, and the player streams.
Run it
npm run build
# host
npm start -- --storage /tmp/video-host --name hostDrop a video file into the window (the renderer forwards the path via webUtils.getPathForFile). In a second terminal:
npm start -- --storage /tmp/video-viewer --name viewer --invite <invite>The viewer sees the entry, clicks Play, and the player streams range by range over the replicated Hyperblobs.
Where to go next
- Store and serve large media with Hyperblobs — the Pear/Bare blob primitive behind this app, with no UI.
- Stream a live camera in a peer-to-peer app — same blob mechanics for live frames.
- Back up photos in a peer-to-peer app — same scaffold with
bare-ffmpeg/bare-mediafor media decoding. - Storage and distribution — why range-served blobs scale well in a P2P swarm.