peerpigeon
Version:
WebRTC-based peer-to-peer mesh networking library with intelligent routing and signaling server
389 lines (280 loc) • 10.4 kB
Markdown
# Broadcast Streaming API
The PeerPigeon Broadcast Streaming API allows you to stream data to all peers in the mesh network using the **gossip protocol**. This is useful for scenarios like:
- Broadcasting files to all participants across the entire mesh (multi-hop)
- Live streaming content to viewers anywhere in the network
- Distributing updates or media to all peers, even those not directly connected
## Features
- **Gossip-based propagation**: Streams reach all peers in the mesh, even those multiple hops away
- **Multi-hop delivery**: Messages automatically propagate through the network (TTL-based)
- **Efficient streaming**: Uses WritableStream API for memory-efficient chunked transfers
- **Automatic deduplication**: Gossip protocol prevents duplicate chunk delivery
- **Error resilience**: Network continues functioning even if individual peers fail
- **Progress tracking**: Emits events for monitoring broadcast progress and completion
- **Flexible input**: Supports Files, Blobs, and custom ReadableStreams
## How It Works
Unlike point-to-point streaming, broadcast streaming uses the gossip protocol to propagate stream chunks throughout the entire mesh network:
1. **Chunk broadcasting**: Each chunk is sent as a gossip message with TTL (time-to-live)
2. **Multi-hop propagation**: Peers relay chunks to their neighbors, reaching the entire network
3. **Ordered reassembly**: Receiving peers buffer and reorder chunks automatically
4. **Deduplication**: Gossip protocol ensures peers don't process duplicate chunks
**Network Reach**: Gossip broadcast reaches ALL peers in the mesh, not just directly connected ones.
## Basic Usage
### Broadcasting a File
```javascript
import { PeerPigeonMesh } from 'peerpigeon';
const mesh = new PeerPigeonMesh();
await mesh.init();
await mesh.connect('ws://signaling-server:3000');
// Broadcast a file to all connected peers
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
await mesh.broadcastFile(file);
```
### Broadcasting a Blob
```javascript
const text = 'Hello all peers!';
const blob = new Blob([text], { type: 'text/plain' });
await mesh.broadcastBlob(blob, {
filename: 'message.txt'
});
```
### Broadcasting a Custom Stream
```javascript
// Create a readable stream from any source
const readableStream = new ReadableStream({
start(controller) {
// Push chunks to the stream
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.close();
}
});
// Broadcast with metadata
await mesh.broadcastStream(readableStream, {
filename: 'data.bin',
mimeType: 'application/octet-stream',
totalSize: 1024
});
```
## Advanced Usage
### Creating a Broadcast Stream Manually
For more control, you can create a writable broadcast stream and pipe data to it:
```javascript
// Create a writable stream that broadcasts to all peers
const broadcastStream = mesh.createBroadcastStream({
filename: 'large-file.dat',
mimeType: 'application/octet-stream',
totalSize: 1024 * 1024 * 100 // 100MB
});
// Get a writer
const writer = broadcastStream.getWriter();
// Write chunks
await writer.write(chunk1);
await writer.write(chunk2);
await writer.write(chunk3);
// Close when done
await writer.close();
```
### Piping from a ReadableStream
```javascript
const response = await fetch('https://example.com/large-file.zip');
const readableStream = response.body;
const broadcastStream = mesh.createBroadcastStream({
filename: 'download.zip',
mimeType: 'application/zip'
});
// Pipe directly from fetch to all peers
await readableStream.pipeTo(broadcastStream);
```
## Event Handling
### Monitoring Broadcast Completion
```javascript
mesh.addEventListener('broadcastStreamComplete', (event) => {
console.log(`Broadcast complete!`);
console.log(`Total bytes sent: ${event.totalBytes}`);
console.log(`Successful peers: ${event.successCount}/${event.totalPeers}`);
console.log(`Metadata:`, event.metadata);
});
```
### Handling Broadcast Failures
```javascript
mesh.addEventListener('broadcastStreamAborted', (event) => {
console.error(`Broadcast aborted: ${event.reason}`);
console.log(`Stream ID: ${event.streamId}`);
});
```
### Receiving Broadcasts
Peers receiving broadcast streams get the standard `streamReceived` event:
```javascript
mesh.addEventListener('streamReceived', async (event) => {
console.log(`Receiving stream from ${event.peerId}`);
console.log(`Metadata:`, event.metadata);
// Read the stream
const reader = event.stream.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
// Process received data
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
console.log(`Received ${totalLength} bytes`);
// Create a blob if needed
if (event.metadata.filename) {
const blob = new Blob(chunks, { type: event.metadata.mimeType });
const url = URL.createObjectURL(blob);
// Download or display the file
}
});
```
## API Reference
### Methods
#### `createBroadcastStream(options)`
Creates a WritableStream that broadcasts data to all connected peers.
**Parameters:**
- `options` (object, optional):
- `streamId` (string): Custom stream ID (auto-generated if not provided)
- `type` (string): Stream type (default: 'broadcast')
- `filename` (string): Filename for the stream
- `mimeType` (string): MIME type of the content
- `totalSize` (number): Total size in bytes
- `chunkSize` (number): Chunk size for transmission
**Returns:** `WritableStream`
**Throws:** Error if no peers are connected
---
#### `broadcastStream(readableStream, options)`
Broadcasts a ReadableStream to all connected peers.
**Parameters:**
- `readableStream` (ReadableStream): The stream to broadcast
- `options` (object, optional): Same as `createBroadcastStream`
**Returns:** `Promise<void>`
---
#### `broadcastFile(file)`
Broadcasts a File to all connected peers.
**Parameters:**
- `file` (File): The file to broadcast
**Returns:** `Promise<void>`
---
#### `broadcastBlob(blob, options)`
Broadcasts a Blob to all connected peers.
**Parameters:**
- `blob` (Blob): The blob to broadcast
- `options` (object, optional): Additional options (filename, etc.)
**Returns:** `Promise<void>`
---
### Events
#### `broadcastStreamComplete`
Emitted when a broadcast stream completes successfully.
**Event Data:**
```javascript
{
streamId: string,
totalBytes: number,
totalChunks: number,
metadata: object
}
```
---
#### `broadcastStreamAborted`
Emitted when a broadcast stream is aborted.
**Event Data:**
```javascript
{
streamId: string,
reason: string,
metadata: object
}
```
---
#### `streamReceived`
Emitted on receiving peers when a gossip stream starts. This event fires on ALL peers in the mesh as chunks propagate via gossip.
**Event Data:**
```javascript
{
peerId: string,
streamId: string,
stream: ReadableStream,
metadata: object
}
```
---
#### `streamCompleted`
Emitted when a gossip stream is fully received.
**Event Data:**
```javascript
{
peerId: string,
streamId: string,
totalChunks: number,
totalBytes: number
}
```
## Error Handling
The broadcast streaming API uses gossip protocol for resilience:
- Chunks propagate through the network with TTL (time-to-live)
- If some peers are unreachable, chunks route around them
- Network continues functioning even if individual peers fail
- Gossip deduplication prevents processing the same chunk multiple times
```javascript
try {
await mesh.broadcastFile(file);
} catch (error) {
if (error.message.includes('All peer connections failed')) {
console.error('Broadcast failed: All peers disconnected');
} else {
console.error('Broadcast error:', error);
}
}
```
## Performance Considerations
### Chunk Size
The default chunk size is optimized for most use cases, but you can customize it:
```javascript
mesh.createBroadcastStream({
filename: 'large-file.dat',
chunkSize: 32768 // 32KB chunks
});
```
- Smaller chunks (4-16KB): Better for real-time streaming, more overhead
- Larger chunks (32-64KB): Better throughput, higher latency
### Memory Usage
Broadcast streams use memory efficiently by:
- Processing data in chunks
- Using backpressure to avoid overwhelming peers
- Cleaning up resources automatically
For very large files, the streaming API uses minimal memory as it doesn't load the entire file into memory.
### Network Bandwidth
Broadcasting consumes bandwidth proportional to: `file_size × number_of_peers`
For example, broadcasting a 10MB file to 5 peers consumes approximately 50MB of upload bandwidth.
## Examples
See the complete working example in `/examples/broadcast-stream-demo.html`.
## Comparison with Other Methods
| Method | Use Case | Network Reach | Behavior |
|--------|----------|---------------|----------|
| `broadcastStream()` | Large files/media | **Entire mesh (multi-hop)** | Gossip-based chunked streaming |
| `broadcastBinaryData()` | Small binary messages | **Direct peers only** | Single-shot binary broadcast |
| `sendMessage()` | Text messages | **Entire mesh (multi-hop)** | Gossip protocol with TTL |
| `createStreamToPeer()` | 1:1 file transfer | **Single peer** | Direct P2P streaming |
### Gossip vs Direct Streaming
**Broadcast Streaming (Gossip)**:
- ✅ Reaches **all peers** in the mesh (multi-hop)
- ✅ Survives **network partitions** and peer churn
- ✅ **Automatic deduplication** of chunks
- ✅ Works through **intermediate peers** as relays
- ⚠️ Higher **latency** due to multi-hop propagation
- ⚠️ More **bandwidth usage** (chunks propagate through multiple paths)
**Direct P2P Streaming** (createStreamToPeer):
- ✅ **Lower latency** - direct connection
- ✅ **Efficient bandwidth** usage
- ✅ Best for **1:1 file transfers**
- ❌ Only reaches **directly connected peer**
- ❌ Fails if **connection drops**
## Browser Compatibility
The broadcast streaming API requires:
- WebRTC support
- Streams API (ReadableStream, WritableStream)
- Modern browsers: Chrome 89+, Firefox 88+, Safari 14.1+, Edge 89+
## Next Steps
- See [STREAMING_API.md](./STREAMING_API.md) for peer-to-peer streaming
- See [BINARY_MESSAGES.md](./BINARY_MESSAGES.md) for binary messaging
- Check out the demo: `examples/broadcast-stream-demo.html`