LogoPear Docs
How ToStream and share media

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.

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

LayerChange
DependenciesAdd hyperblobs, hypercore-blob-server, hypercore-id-encoding, and get-mime-type.
WorkerAdd a Hyperblobs core in the worker; store each video as one blob and attach a blob-server link to every entry.
Worker transportAdd an add-video ingest (an { type: 'add-video', filePath } control message) and a videos snapshot whose entries carry a playable link.
RendererRender 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-type

Publish 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:

workers/video-room.js
  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:

workers/video-room.js
  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 host

Drop 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

On this page