P2P Media Loader
repository·main·Indexed 23 days ago
https://github.com/novage/p2p-media-loaderA JavaScript library for peer-to-peer media delivery of HLS and MPEG-DASH streams using WebRTC. It enables the creation of hybrid CDN/P2P networks to reduce bandwidth costs and origin server load. The library supports Hls.js and Shaka Player engines and integrates with players such as Vidstack, Clappr, MediaElement, Plyr, DPlayer, OpenPlayerJS, and PlayerJS.
What's inside p2p-media-loader
- P2P Media Loader is an open-source JavaScript library that enables peer-to-peer (P2P) media delivery using modern browser technologies like HTML5 video and WebRTC. It allows users watching the same HLS or MPEG-DASH streams (live or VOD) to share traffic in real time, creating a hybrid CDN/P2P mesh network. This reduces origin server load and CDN bandwidth costs while increasing overall network capacity.
How the `claimPeer` hook handles peer deduplication
mainTo prevent redundant connections when multiple
WebTorrentClientinstances discover the same remote peer, the client uses an injectedclaimPeer(peer_id)hook for aggressive deduplication:- When receiving an Offer: The client calls
claimPeer(peer_id)using thepeer_idfrom the tracker JSON. If it returnsfalse, the client ignores the message and does not create anRTCPeerConnection. - When receiving an Answer: The client calls
claimPeer(peer_id)using thepeer_idrevealed in the answer. If it returnsfalse, the client immediately closes the pendingRTCPeerConnectionand discards the answer.
Developers implementing a Peer Manager must ensure
claimPeercorrectly identifies if apeer_idis already being managed by another instance.- When receiving an Offer: The client calls
Hand-off of WebRTC connections to the Peer Manager
mainThe
WebTorrentClientis responsible only for the signaling and connection establishment phase. Its lifecycle ends immediately after the WebRTC data channel is successfully opened.Upon successful connection, the client emits the
peerConnectedevent with the following payload:peerId: The remote peer's ID.connection: TheRTCPeerConnectioninstance.channel: TheRTCDataChannelinstance.
Responsibility Shift: Once the event is emitted, the
WebTorrentClientdrops its internal references. The consuming Peer Manager must take full responsibility for:- Monitoring
channel.oncloseandconnection.oniceconnectionstatechangeto detect drops. - Implementing the BitTorrent wire protocol over the data channel.
- Cleaning up internal state when the connection closes.
How WebTorrentSocketPool manages connections
mainThe
WebTorrentSocketPooluses a reference counting mechanism to manage and reuseWebSocketClientconnections for WebTorrent trackers. This prevents redundant socket connections by sharing a single connection per unique URL across multiple clients.Lifecycle Flow:
- Acquisition: When
acquire(url)is called, the pool checks for an existing connection to that URL. If found, it increments the reference count. If not, it creates a newWebSocketClientand sets the count to1. - Release: Every acquisition provides a
releasecallback. Calling this decrements the reference count. - Disposal: When the reference count reaches
0, the pool automatically callsdisposeon the socket and removes it from the pool.
- Acquisition: When
How WebTorrentManager manages peer uniqueness
mainThe
WebTorrentManagerensures that the same remote peer is not connected multiple times, even if discovered across multiple trackers simultaneously.It achieves this by checking its internal collections of
connectingPeersandconnectedPeersbefore allowing a new connection. If a connection attempt for apeer_idis already in progress or established, the manager rejects the new connection attempt from that specific tracker.Lifecycle Notes:
- If a WebRTC connection fails (e.g., ICE gathering or data channel timeout), the manager automatically removes the peer from the
connectingPeerscollection to allow future reconnection attempts. - If a connection is established and then rejected by the upper layer, the upper layer must call the
close()callback provided in thepeerConnectedpayload to ensure the peer is removed from the internal collections.
- If a WebRTC connection fails (e.g., ICE gathering or data channel timeout), the manager automatically removes the peer from the
How P2P Media Loader works
mainThe library operates by combining traditional HTTP(S) downloads with WebRTC-based peer sharing:
- Initial Playback: The library initially downloads media segments via HTTP(S) from the source CDN to ensure fast startup.
- Peer Discovery: It transmits media stream details and connection info (like ICE candidates) to WebTorrent trackers. These trackers return a list of other peers watching the same stream.
- P2P Swarming: The library connects to these peers to download segments from them while simultaneously sharing segments it has already downloaded.
- Hybrid Model: If no peers are available, it falls back to standard HTTP(S) downloads. Periodically, random peers in the swarm download new segments via HTTP(S) to distribute them to the rest of the P2P network.
Understand WebSocketClient reconnection and binary behavior
mainThe
WebSocketClientfollows two key architectural principles:Exponential Backoff & Jitter: To avoid overwhelming servers, reconnection delays increase exponentially. The delay is calculated using the formula:
baseDelay = min(initialDelay * 2^backoffCount, maxDelay)jitter = baseDelay * jitterMultiplierdelay = max(0, baseDelay + random(-jitter, jitter))
Binary Compatibility: The client sets
binaryType = 'arraybuffer'on the underlying native WebSocket. This ensures efficient and compatible handling of arbitrary binary protocols.
P2P Network components and requirements
mainTo function, the P2P network relies on several components:
- WebRTC Data Channels: Used to exchange data between peers.
- Media Source Extensions (MSE) / Managed Media Source: Required by Hls.js and Shaka Player engines for playback.
- STUN Servers: Used by WebRTC to gather ICE candidates. The library uses public STUN servers by default.
- WebTorrent Trackers: Used for WebRTC signaling and creating peer swarms. The library uses public trackers (e.g.,
https://tracker.novage.com.ua/) by default, meaning no server-side software is required for simple use cases.
WebRTC Connection Flow and ICE Gathering
mainThe
WebTorrentClientuses a non-trickle ICE approach where all ICE candidates are bundled into a single SDP before sending to the tracker.ICE Gathering Timeout
To prevent indefinite stalling, the client enforces a 5-second timeout on ICE gathering. If gathering does not complete within 5 seconds, the client proceeds with whatever candidates have been gathered so far.
Initiating Connections (Sending Offers)
Offers are generated in parallel using
Promise.allSettledto minimize latency.- For each slot, a new
RTCPeerConnectionandRTCDataChannelare created. - An SDP offer is created and ICE gathering is waited for.
- A random 20-character alphanumeric
offer_idis generated. - The connection is stored in an internal Map keyed by
offer_id. - A default
offerTimeoutof 50s is set. If no answer arrives, the connection is closed and removed to prevent memory leaks.
Receiving Offers
- The client calls
claimPeer(peer_id)to deduplicate. - It creates an
RTCPeerConnection, callssetRemoteDescription, generates an SDP answer, and waits for ICE gathering. - Once the data channel opens, it emits the
peerConnectedevent.
Receiving Answers
- The client calls
claimPeer(peer_id)to deduplicate. - If
claimPeerisfalse, the pending connection is closed and discarded. - If
true, it appliessetRemoteDescription(answer)to the pending connection. - Once the data channel opens, it emits the
peerConnectedevent.
- For each slot, a new
Integrate P2P with DPlayer and Shaka Player
mainTo use P2P with DPlayer using Shaka Player:
- Call
ShakaP2PEngine.registerPlugins(). - Instantiate
ShakaP2PEnginewith yourcoreconfiguration. - In the DPlayer
video.customTypeconfiguration (using a type likecustomHlsOrDash), manually create ashaka.Playerinstance, attach it to the video element, and callshakaP2PEngine.bindShakaPlayer(shakaPlayer)before loading the stream.
<script type="module"> import { ShakaP2PEngine } from "p2p-media-loader-shaka"; const container = document.getElementById("container"); ShakaP2PEngine.registerPlugins(); const shakaP2PEngine = new ShakaP2PEngine({ core: { swarmId: "Optional custom swarm ID for stream", // Other P2P Media Loader Core options }, }); const player = new DPlayer({ container, video: { url: "", type: "customHlsOrDash", customType: { customHlsOrDash: (video) => { const shakaPlayer = new shaka.Player(); void shakaPlayer.attach(video); shakaP2PEngine.bindShakaPlayer(shakaPlayer); void shakaPlayer.load(streamUrl); }, }, }, }); </script>- Call
Integrate P2P with a standalone Hls.js player (IIFE)
mainFor legacy environments or Smart TVs that do not support ES modules, use the IIFE builds. The Hls.js P2P engine is exposed via the global
window.p2pml.hlsjsnamespace.To integrate, use
HlsJsP2PEngine.injectMixin(window.Hls)to create a new Hls constructor that includes P2P capabilities. You can then instantiate this constructor with a configuration object containingp2p.coresettings (such asswarmId).<script src="https://cdn.jsdelivr.net/npm/hls.js@~1/dist/hls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@latest/dist/p2p-media-loader-hlsjs.iife.min.js"></script> <script> document.addEventListener("DOMContentLoaded", function () { var videoElement = document.getElementById("video"); var streamUrl = "https://example.com/stream.m3u8"; if (Hls.isSupported()) { // Access the engine from the global p2pml object var HlsJsP2PEngine = window.p2pml.hlsjs.HlsJsP2PEngine; var HlsWithP2P = HlsJsP2PEngine.injectMixin(window.Hls); var hls = new HlsWithP2P({ p2p: { core: { swarmId: "Optional custom swarm ID for stream", // Other P2P engine configuration parameters go here }, }, }); hls.attachMedia(videoElement); hls.loadSource(streamUrl); } }); </script>Install P2P Media Loader via npm
mainYou can include P2P Media Loader in your project using npm. Choose the package that matches your video player integration:
- For Hls.js integration, install
p2p-media-loader-hlsjs. - For Shaka Player integration, install
p2p-media-loader-shaka.
- For Hls.js integration, install