@meshstream/p2p-web 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +501 -501
- package/dist/adapters.js +1 -1
- package/dist/advanced.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/version.d.ts +3 -1
- package/package.json +57 -53
package/README.md
CHANGED
|
@@ -1,501 +1,501 @@
|
|
|
1
|
-
# MeshStream P2P Web SDK
|
|
2
|
-
|
|
3
|
-
SDK-owned console diagnostics respect the client `logLevel` (default `none`), with redaction and
|
|
4
|
-
repeated-error limiting. Web does not write local log files. See the [logging guide](../../docs/sdk-logging.md).
|
|
5
|
-
|
|
6
|
-
Browser HLS and DASH P2P delivery SDK for MeshStream P2P-CDN. The SDK wraps
|
|
7
|
-
`hls.js`, Shaka Player, and `p2p-media-loader`, fetches runtime configuration
|
|
8
|
-
from Config API, joins a WebRTC tracker swarm, reports playback/P2P statistics,
|
|
9
|
-
and keeps CDN playback as the fallback path.
|
|
10
|
-
|
|
11
|
-
## Install
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
# SDK-managed player: MeshStream bundles its HLS, DASH, and P2P runtimes.
|
|
15
|
-
npm install @meshstream/p2p-web
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
For an application-managed `hls.js` instance, install the player in the
|
|
19
|
-
application as well. MeshStream will bind P2P to that instance without taking
|
|
20
|
-
over its configuration or lifecycle:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install @meshstream/p2p-web hls.js
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
In this monorepo the SDK is consumed as a workspace package:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm run build:web-sdk
|
|
30
|
-
npm run build -w @p2pcdn/web-demo
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Quick Start
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
import { attach } from "@meshstream/p2p-web";
|
|
37
|
-
|
|
38
|
-
const player = attach({
|
|
39
|
-
key: "your_sdk_key",
|
|
40
|
-
url: "https://cdn.example.com/live/index.m3u8", // or .mpd / .mp4 / .mp3
|
|
41
|
-
video: document.querySelector("video"),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
await player.start();
|
|
45
|
-
|
|
46
|
-
window.addEventListener("pagehide", () => player.dispose(), { once: true });
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
The historic `video` option accepts any `HTMLMediaElement`; pass an `<audio>`
|
|
50
|
-
element for progressive MP3 playback.
|
|
51
|
-
|
|
52
|
-
The SDK fetches SaaS bootstrap configuration, selects trackers/reporting
|
|
53
|
-
endpoints, detects browser capability, starts WebRTC P2P when possible, and
|
|
54
|
-
keeps CDN playback as the automatic fallback path.
|
|
55
|
-
|
|
56
|
-
When MeshStream creates hls.js, use `hlsConfig` to retain normal player tuning
|
|
57
|
-
such as `maxBufferLength`, ABR limits, or low-latency settings:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
const player = attach({
|
|
61
|
-
key,
|
|
62
|
-
url,
|
|
63
|
-
video,
|
|
64
|
-
hlsConfig: { maxBufferLength: 45, lowLatencyMode: true },
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
MeshStream always reapplies its P2P loader and safe origin request settings
|
|
69
|
-
after the instance is created. For `attachToHls`, configure the caller-owned
|
|
70
|
-
hls.js instance directly instead.
|
|
71
|
-
|
|
72
|
-
To display compatibility information before playback, call `getCapability()`.
|
|
73
|
-
It returns only `mseSupported`, `webrtcSupported`, and `serviceWorkerSupported`;
|
|
74
|
-
it does not override the SDK's playback or fallback decision.
|
|
75
|
-
|
|
76
|
-
The web transport selects one content-affine Tracker for a playback session.
|
|
77
|
-
Tracker nodes keep independent in-memory peer maps; switching or retrying a
|
|
78
|
-
Tracker does not copy peers between nodes. If signaling becomes unavailable,
|
|
79
|
-
the player continues through CDN rather than registering the same session with
|
|
80
|
-
every shard. Configure tested hot-content buckets and same-region fallback
|
|
81
|
-
nodes when a stream needs faster recovery.
|
|
82
|
-
|
|
83
|
-
`player.statistics()` always returns the same complete counter shape. During
|
|
84
|
-
startup and CDN fallback, P2P counters are `0` instead of being omitted, so
|
|
85
|
-
application dashboards can use one schema for HLS, DASH, MP4/MP3, and RxPlayer.
|
|
86
|
-
|
|
87
|
-
## Optional Service Worker Transport
|
|
88
|
-
|
|
89
|
-
Service Worker support is an optional transport layer. MSE P2P remains the
|
|
90
|
-
default and is preferred whenever it is available. To deploy the SW runtime,
|
|
91
|
-
copy the published `dist/meshstream-sw.js` file to a same-origin HTTPS path
|
|
92
|
-
such as `/meshstream-sw.js`, then opt in:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import { attach } from "@meshstream/p2p-web";
|
|
96
|
-
|
|
97
|
-
const player = attach({
|
|
98
|
-
key,
|
|
99
|
-
url,
|
|
100
|
-
video,
|
|
101
|
-
transportPreference: "auto",
|
|
102
|
-
serviceWorker: {
|
|
103
|
-
url: "/meshstream-sw.js",
|
|
104
|
-
scope: "/",
|
|
105
|
-
autoRegister: true,
|
|
106
|
-
p2pTimeoutMs: 5000,
|
|
107
|
-
onSegmentStats: ({ source, bytes, url }) => {
|
|
108
|
-
console.debug("segment", source, bytes, url);
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
await player.start();
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`auto` uses MSE P2P first. If MSE is unavailable and the page is controlled by
|
|
116
|
-
the registered SW, playback uses a virtual manifest URL under
|
|
117
|
-
`/__meshstream__/v1/`; failed or unavailable page-side P2P requests fall back
|
|
118
|
-
to the original CDN URL. `transportPreference: "service-worker"` is available
|
|
119
|
-
for explicit testing. When bootstrap returns a tracker configuration, the SDK
|
|
120
|
-
also starts a page-side `p2p-media-loader-core` engine that works without MSE,
|
|
121
|
-
indexes HLS master/media playlists, and answers SW segment requests from the
|
|
122
|
-
WebRTC swarm with CDN fallback. A custom `segmentProvider` can still be passed
|
|
123
|
-
to replace that engine for application-specific routing.
|
|
124
|
-
|
|
125
|
-
For manual registration, import `registerMeshStreamServiceWorker()` and
|
|
126
|
-
`createMeshStreamServiceWorkerSession()` from `@meshstream/p2p-web/service-worker`.
|
|
127
|
-
Registration requires HTTPS (localhost is allowed by browsers), and the SW script
|
|
128
|
-
must be same-origin with the page. Session metadata and rewritten resource mappings
|
|
129
|
-
are kept in IndexedDB so a browser-restarted SW can recover a registered session;
|
|
130
|
-
unregistering the player removes that metadata. `onSegmentStats` reports whether
|
|
131
|
-
each media request was served by the page provider (`p2p`) or CDN (`cdn`).
|
|
132
|
-
|
|
133
|
-
For a local browser smoke check after building, run
|
|
134
|
-
`node sdks/web/test/sw-browser-fixture.mjs` and open the printed URL. The page
|
|
135
|
-
reports the SW controller state, rewritten segment count, response bodies, and
|
|
136
|
-
per-segment source statistics.
|
|
137
|
-
|
|
138
|
-
For the real two-page WebRTC check, run the automated smoke from the repository
|
|
139
|
-
root:
|
|
140
|
-
|
|
141
|
-
```powershell
|
|
142
|
-
npm run test:web-sw-real
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
It builds the SDK and Tracker, opens seed/follower Chromium pages, and fails
|
|
146
|
-
unless the follower receives a non-empty segment through P2P. For manual
|
|
147
|
-
inspection, build the SDK and start
|
|
148
|
-
`node sdks/web/test/sw-real-p2p-browser-fixture.mjs`, then open the seed URL
|
|
149
|
-
first (`?role=seed`) and the follower URL (`?role=follower`).
|
|
150
|
-
|
|
151
|
-
### Staging rollout checklist
|
|
152
|
-
|
|
153
|
-
1. Build the browser package with `npm run build:web-sdk:release` and publish the
|
|
154
|
-
generated npm tarball. Copy `dist/meshstream-sw.js` from the package to a stable
|
|
155
|
-
same-origin HTTPS URL such as `/meshstream-sw.js`; do not serve it from a CDN
|
|
156
|
-
origin or a path outside the intended registration scope.
|
|
157
|
-
2. Deploy the page with `transportPreference: "auto"` and the SW `url` configured.
|
|
158
|
-
Confirm the normal browser path reports `p2p-hls`; test Safari/iOS or a forced
|
|
159
|
-
`transportPreference: "service-worker"` path and confirm `sw-hls-cdn`.
|
|
160
|
-
3. Check the browser Network panel for the virtual manifest under
|
|
161
|
-
`/__meshstream__/v1/` and verify segment requests return either `p2p` or `cdn`
|
|
162
|
-
in the SDK `onSegmentStats` callback.
|
|
163
|
-
4. Verify the configured report endpoint receives `reportType: "stats"` with
|
|
164
|
-
`p2pDownloaded`, `httpDownloaded`, `connectedPeers`, and tracker diagnostics.
|
|
165
|
-
The SDK sends an initial report, periodic reports, and a final `pagehide`
|
|
166
|
-
report; report failures must not interrupt playback.
|
|
167
|
-
5. Roll back by removing the `serviceWorker` option or setting
|
|
168
|
-
`transportPreference: "mse"`; the SDK then keeps the existing MSE/CDN path.
|
|
169
|
-
|
|
170
|
-
## Advanced Player Adapters
|
|
171
|
-
|
|
172
|
-
Use `@meshstream/p2p-web/adapters` for an existing player instance. Controlled
|
|
173
|
-
test environments that need to construct `MeshStreamHlsPlayer` directly can
|
|
174
|
-
import it from `@meshstream/p2p-web/advanced`; standard integrations should
|
|
175
|
-
continue to use root-level `attach`.
|
|
176
|
-
|
|
177
|
-
For a plain video element:
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
import { attach } from "@meshstream/p2p-web";
|
|
181
|
-
|
|
182
|
-
const player = attach({ key, url, video });
|
|
183
|
-
await player.start();
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Choosing an HLS integration
|
|
187
|
-
|
|
188
|
-
Both HLS modes use the same P2P scheduling, authorization, and telemetry.
|
|
189
|
-
Use `attach` when the SDK should create and manage hls.js. Use `attachToHls`
|
|
190
|
-
when the application already owns an hls.js instance and must retain its
|
|
191
|
-
buffering, ABR, event, or plugin configuration. In the latter mode, the
|
|
192
|
-
application remains responsible for `loadSource`, `attachMedia`, and
|
|
193
|
-
`destroy`; call `session.start()` before `loadSource`. Do not use both modes
|
|
194
|
-
for the same media element or playback URL.
|
|
195
|
-
|
|
196
|
-
For an existing hls.js instance, bind MeshStream before the application starts
|
|
197
|
-
media loading. This is a native HLS P2P integration: the SDK keeps the caller's
|
|
198
|
-
hls.js configuration and P2P-enables that same instance. It never calls
|
|
199
|
-
`loadSource`, `attachMedia`, or `destroy` on the caller-owned player.
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
import Hls from "hls.js";
|
|
203
|
-
import { attachToHls } from "@meshstream/p2p-web";
|
|
204
|
-
|
|
205
|
-
const hls = new Hls({ maxBufferLength: 30 });
|
|
206
|
-
const session = attachToHls({ key, url, video, player: hls });
|
|
207
|
-
|
|
208
|
-
await session.start(); // Bind P2P before hls.js makes media requests.
|
|
209
|
-
hls.loadSource(url);
|
|
210
|
-
hls.attachMedia(video);
|
|
211
|
-
|
|
212
|
-
window.addEventListener("pagehide", () => {
|
|
213
|
-
session.dispose(); // Does not destroy hls.
|
|
214
|
-
hls.destroy(); // The application owns this lifecycle.
|
|
215
|
-
}, { once: true });
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
For rotating signed URLs, pass a stable business identity so viewers join the
|
|
219
|
-
same swarm. `mimeType` is useful when the media URL has no `.m3u8`, `.mpd`, `.mp4`, or `.mp3`
|
|
220
|
-
suffix. `originHeaders` are sent only to same-origin manifest, segment, and key requests on the
|
|
221
|
-
configured media origin. SDK-controlled headers such as `Range` and hop-by-hop headers are ignored;
|
|
222
|
-
the browser still applies its own forbidden-header and CORS rules. For Shaka and dash.js
|
|
223
|
-
adapter objects, the player must expose its request interception API; otherwise
|
|
224
|
-
the SDK fails explicitly instead of starting without the requested headers.
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
const player = attach({
|
|
228
|
-
key,
|
|
229
|
-
url: signedManifestUrl,
|
|
230
|
-
video,
|
|
231
|
-
contentId: "live-channel-123",
|
|
232
|
-
mimeType: "m3u8",
|
|
233
|
-
originHeaders: { Authorization: "Bearer ...", "X-Playback-Session": "..." },
|
|
234
|
-
});
|
|
235
|
-
await player.start();
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
For tokens that rotate during a long playback session, provide the current
|
|
239
|
-
value with a synchronous callback. The callback runs immediately before an
|
|
240
|
-
eligible CDN-origin request; it must not refresh a token or return a Promise.
|
|
241
|
-
Refresh credentials in application state first. Dynamic values override static
|
|
242
|
-
headers with the same name and, like static values, are never sent to peers.
|
|
243
|
-
|
|
244
|
-
```ts
|
|
245
|
-
let playbackToken = initialToken;
|
|
246
|
-
|
|
247
|
-
const player = attach({
|
|
248
|
-
key,
|
|
249
|
-
url: signedManifestUrl,
|
|
250
|
-
video,
|
|
251
|
-
originHeaders: { "X-Playback-Session": playbackSession },
|
|
252
|
-
originHeadersProvider: ({ kind }) =>
|
|
253
|
-
kind === "segment" ? { Authorization: `Bearer ${playbackToken}` } : undefined,
|
|
254
|
-
});
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
For Video.js:
|
|
258
|
-
|
|
259
|
-
```ts
|
|
260
|
-
import { attachToVideoJs } from "@meshstream/p2p-web/adapters";
|
|
261
|
-
|
|
262
|
-
const player = attachToVideoJs({
|
|
263
|
-
key,
|
|
264
|
-
url,
|
|
265
|
-
player: videojs("player"),
|
|
266
|
-
});
|
|
267
|
-
await player.start();
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
This adapter follows the Video.js instance lifecycle and disposes the
|
|
271
|
-
MeshStream session automatically when Video.js is destroyed. It is a
|
|
272
|
-
media-element bridge: MeshStream owns protocol-compatible playback for that
|
|
273
|
-
element. For HLS, do not also load the same URL through Video.js VHS.
|
|
274
|
-
|
|
275
|
-
For Video.js HLS playback that must retain VHS and Video.js ABR ownership, use
|
|
276
|
-
the native VHS adapter instead. It gives VHS a same-origin virtual manifest and
|
|
277
|
-
routes the resulting segment traffic through the Service Worker P2P path. The
|
|
278
|
-
SDK releases its session automatically when Video.js is disposed; if the
|
|
279
|
-
Service Worker cannot start, VHS continues with the original CDN manifest.
|
|
280
|
-
|
|
281
|
-
```ts
|
|
282
|
-
import { attachToVhs } from "@meshstream/p2p-web/adapters";
|
|
283
|
-
|
|
284
|
-
const session = attachToVhs({
|
|
285
|
-
key,
|
|
286
|
-
url: "https://cdn.example.com/live/index.m3u8",
|
|
287
|
-
player: videojs("player"),
|
|
288
|
-
serviceWorker: { url: "/meshstream-sw.js" },
|
|
289
|
-
});
|
|
290
|
-
await session.start();
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
For players built on top of a standard `<video>` element:
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
import { attachToArtPlayer, attachToDPlayer, attachToPlyr } from "@meshstream/p2p-web/adapters";
|
|
297
|
-
|
|
298
|
-
await attachToPlyr({ key, url, player: plyr }).start();
|
|
299
|
-
await attachToDPlayer({ key, url, player: dplayer }).start();
|
|
300
|
-
await attachToArtPlayer({ key, url, player: art }).start();
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
The named adapters resolve common `video` or `media` fields, or a `<video>` nested
|
|
304
|
-
inside the player's root element, and then use the
|
|
305
|
-
same automatic HLS, DASH, and progressive-media path as the plain video adapter. They are media-element bridges,
|
|
306
|
-
not native Video.js, Plyr, DPlayer, or ArtPlayer engine plugins: MeshStream
|
|
307
|
-
owns the playback instance used for the supplied media element.
|
|
308
|
-
|
|
309
|
-
For DASH with the standard SDK path:
|
|
310
|
-
|
|
311
|
-
```ts
|
|
312
|
-
import { attach } from "@meshstream/p2p-web";
|
|
313
|
-
|
|
314
|
-
const player = attach({
|
|
315
|
-
key,
|
|
316
|
-
url: "https://cdn.example.com/live/index.mpd",
|
|
317
|
-
video,
|
|
318
|
-
});
|
|
319
|
-
await player.start();
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
`attach` selects DASH for `.mpd` URLs and HLS for `.m3u8` URLs. Customers
|
|
323
|
-
do not pass a protocol flag. DASH P2P uses Shaka Player internally for the
|
|
324
|
-
standard SDK path.
|
|
325
|
-
|
|
326
|
-
For an existing Shaka Player:
|
|
327
|
-
|
|
328
|
-
```ts
|
|
329
|
-
import { attachToShaka } from "@meshstream/p2p-web/adapters";
|
|
330
|
-
|
|
331
|
-
const player = attachToShaka({
|
|
332
|
-
key,
|
|
333
|
-
url: "https://cdn.example.com/live/index.mpd",
|
|
334
|
-
player: shakaPlayer,
|
|
335
|
-
});
|
|
336
|
-
await player.start();
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
`attachToShaka()` binds and releases only the MeshStream P2P session. The
|
|
340
|
-
application retains ownership of the supplied Shaka instance and must destroy
|
|
341
|
-
it when appropriate.
|
|
342
|
-
|
|
343
|
-
For an existing dash.js player:
|
|
344
|
-
|
|
345
|
-
```ts
|
|
346
|
-
import { attachToDashJs } from "@meshstream/p2p-web/adapters";
|
|
347
|
-
|
|
348
|
-
const player = attachToDashJs({
|
|
349
|
-
key,
|
|
350
|
-
url: "https://cdn.example.com/live/index.mpd",
|
|
351
|
-
video,
|
|
352
|
-
player: dashJsPlayer,
|
|
353
|
-
});
|
|
354
|
-
await player.start();
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
`attachToDashJs` provides native DASH P2P for dash.js `>= 3.1.0 < 6`. Create
|
|
358
|
-
the dash.js instance first, but do not initialize it or load the MPD before
|
|
359
|
-
`session.start()`: MeshStream installs its `XHRLoader` extension first, then
|
|
360
|
-
initializes the supplied instance. Regular audio/video initialization, index,
|
|
361
|
-
and media segments use P2P with CDN fallback. MPD, DRM licenses, subtitles,
|
|
362
|
-
low-latency partial segments, and credentialed cookie requests remain on the
|
|
363
|
-
customer's dash.js/CDN path. When passing
|
|
364
|
-
`originHeaders` to an existing dash.js or Shaka instance, its request interception
|
|
365
|
-
API is required; native Safari HLS cannot accept arbitrary custom headers, so use
|
|
366
|
-
hls.js or signed URLs for that case.
|
|
367
|
-
|
|
368
|
-
### Adapter capability matrix
|
|
369
|
-
|
|
370
|
-
| Player path | P2P integration | Notes |
|
|
371
|
-
| --- | --- | --- |
|
|
372
|
-
| `attach` with HLS | Native hls.js P2P | MeshStream creates and owns hls.js. |
|
|
373
|
-
| `attachToHls` | Native existing hls.js P2P | Application keeps the hls.js lifecycle. |
|
|
374
|
-
| `attach` with DASH; `attachToShaka` with HLS/DASH | Native Shaka P2P | Uses Shaka's networking integration. |
|
|
375
|
-
| `attachToRxPlayer` with DASH | Native RxPlayer segment P2P | Regular DASH segments; same-origin origin headers are supported. |
|
|
376
|
-
| `attachToDashJs` with dash.js `>= 3.1.0 < 6` | Native dash.js P2P | Installs a supported `XHRLoader` extension before dash.js initialization. |
|
|
377
|
-
| `attachToVhs` with Video.js VHS | Native VHS via Service Worker | VHS retains player and ABR ownership; requires a same-origin Service Worker. |
|
|
378
|
-
| `attachToVideoJs`, Plyr, DPlayer, ArtPlayer | Media-element bridge | Not a native plugin for the player's own media engine. |
|
|
379
|
-
|
|
380
|
-
For an existing RxPlayer DASH instance:
|
|
381
|
-
|
|
382
|
-
```ts
|
|
383
|
-
import RxPlayer from "rx-player";
|
|
384
|
-
import { attachToRxPlayer } from "@meshstream/p2p-web";
|
|
385
|
-
|
|
386
|
-
const rxPlayer = new RxPlayer({ videoElement: video });
|
|
387
|
-
const player = attachToRxPlayer({ key, url: dashMpdUrl, contentId: "asset-123", player: rxPlayer });
|
|
388
|
-
await player.start();
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
`attachToRxPlayer` intercepts RxPlayer's actual DASH audio/video segment loader and sends those
|
|
392
|
-
requests through MeshStream P2P. When `originHeaders` or
|
|
393
|
-
`originHeadersProvider` are configured, same-origin MPD and regular
|
|
394
|
-
audio/video fallback requests retain those headers; dynamic values override
|
|
395
|
-
static values. Cross-origin media, text, DRM, non-contiguous ranges, and
|
|
396
|
-
RxPlayer low-latency chunked loading remain on the customer's RxPlayer/CDN
|
|
397
|
-
path without SDK-supplied headers.
|
|
398
|
-
|
|
399
|
-
Telemetry keeps the playback host and path for diagnostics but strips query and fragment data,
|
|
400
|
-
so signed playback credentials are not included in reports.
|
|
401
|
-
|
|
402
|
-
## Runtime Contract
|
|
403
|
-
|
|
404
|
-
- `key` is the customer-facing SDK Key and can be embedded in Web pages.
|
|
405
|
-
- `homeRegion` is resolved from the SDK Key and identifies the tenant's registered data-home
|
|
406
|
-
region. It is returned by bootstrap for diagnostics and future regional routing; customer code
|
|
407
|
-
cannot override it.
|
|
408
|
-
- Request-network GeoIP is used only for access-side Tracker/ICE locality and is not a data-residency
|
|
409
|
-
selection.
|
|
410
|
-
- Customer integrations should only need `key`, the media `url`, and a `video`
|
|
411
|
-
element for the standard Web path.
|
|
412
|
-
- The SaaS bootstrap endpoint, tracker pool, ICE servers, reporting route, and
|
|
413
|
-
scheduling policy are internal service configuration.
|
|
414
|
-
- P2P, tracker, authorization, and reporting failures must not block `hls.js`
|
|
415
|
-
playback.
|
|
416
|
-
- `detectWebP2pCapability` should be called before constructing the P2P client.
|
|
417
|
-
iOS Safari HLS without MSE is routed to native HLS CDN playback.
|
|
418
|
-
- Fallback reporting uses the service-owned endpoint returned by bootstrap.
|
|
419
|
-
- Signed CDN URL query fields such as `token`, `signature`, `expires`,
|
|
420
|
-
`x-amz-*`, `x-oss-*`, and `x-goog-*` are excluded from URL-derived swarm IDs.
|
|
421
|
-
- `contentId` is optional. When supplied, the SDK derives the internal swarm ID
|
|
422
|
-
from it so rotating signed URL paths remain in the same swarm; otherwise it
|
|
423
|
-
derives identity from the canonical playback URL.
|
|
424
|
-
- `swarmId` and rendition/quality identifiers are internal runtime values. They
|
|
425
|
-
are intentionally not part of the customer-facing configuration; the SDK
|
|
426
|
-
derives and manages them from the content and manifest.
|
|
427
|
-
- Direct progressive `.mp4` and `.mp3` files use the same P2P Service Worker transport as a
|
|
428
|
-
byte-range stream. The SDK registers the Worker by default for this path and divides the file
|
|
429
|
-
into deterministic 1 MiB pieces. Large and open-ended ranges are returned as a backpressure-aware
|
|
430
|
-
stream, so they do not require buffering the whole response in the page. Full-file requests
|
|
431
|
-
without `Range`, suffix ranges, multi-range requests, and metadata remain on CDN.
|
|
432
|
-
Use a stable `contentId` when signed URLs have per-viewer path differences.
|
|
433
|
-
- The direct-file origin must support byte ranges, expose `Content-Length`, `Content-Type`, and
|
|
434
|
-
`Accept-Ranges` through CORS, and allow `GET`, `HEAD`, `OPTIONS`, and the `Range` request header.
|
|
435
|
-
`HEAD` is recommended; if the origin explicitly returns `405` or `501`, extensionless URL detection
|
|
436
|
-
uses a one-byte range `GET` instead. Missing metadata or any P2P error falls back to the original CDN URL.
|
|
437
|
-
|
|
438
|
-
## Public API
|
|
439
|
-
|
|
440
|
-
```ts
|
|
441
|
-
// Standard customer-facing entry point
|
|
442
|
-
attach(options)
|
|
443
|
-
|
|
444
|
-
// Advanced player adapters
|
|
445
|
-
import { attachToShaka, attachToDashJs, attachToVhs, attachToVideoJs } from "@meshstream/p2p-web/adapters";
|
|
446
|
-
|
|
447
|
-
// P2P runtime controls
|
|
448
|
-
player.start()
|
|
449
|
-
player.dispose()
|
|
450
|
-
player.setP2pEnabled(enabled)
|
|
451
|
-
player.addEventListener(listener) // returns unsubscribe
|
|
452
|
-
player.statistics()
|
|
453
|
-
player.diagnostics()
|
|
454
|
-
player.qoe()
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
`setP2pEnabled(false)` may be called before `start()` to force a CDN-only
|
|
458
|
-
session, or during playback to stop P2P without interrupting CDN delivery.
|
|
459
|
-
Repeated `start()` calls share the active session; call `dispose()` before
|
|
460
|
-
starting a fresh session for a different source or player lifecycle.
|
|
461
|
-
|
|
462
|
-
`start()` returns `{ mode, capability, fallbackReason? }`. On a CDN fallback,
|
|
463
|
-
`fallbackReason` is one of `CAPABILITY_UNSUPPORTED`, `P2P_DISABLED`,
|
|
464
|
-
`SDK_CONFIG_UNAVAILABLE`, `TRACKER_UNAVAILABLE`, or
|
|
465
|
-
`P2P_INITIALIZATION_FAILED`. It is intended for customer telemetry and does
|
|
466
|
-
not expose the server bootstrap configuration.
|
|
467
|
-
|
|
468
|
-
`addEventListener()` accepts `onPlaybackStateChanged`, `onP2pReady`,
|
|
469
|
-
`onP2pUnavailable`, `onPeerCountChanged`, `onTrackerError`,
|
|
470
|
-
`onPeerConnectError`, `onCdnFallback`, and `onStatistics`. Register before `start()` when the
|
|
471
|
-
initial connection state matters. Startup CDN fallbacks emit `UNSUPPORTED`,
|
|
472
|
-
`DISABLED`, `CONFIG_UNAVAILABLE`, `TRACKER_ERROR`, or
|
|
473
|
-
`INITIALIZATION_FAILED`; call the returned function during cleanup.
|
|
474
|
-
|
|
475
|
-
`onPlaybackStateChanged` reports `idle`, `initializing`, `connecting`,
|
|
476
|
-
`p2p-ready`, `cdn-fallback`, and `disposed`. `qoe()` returns a safe
|
|
477
|
-
application-telemetry snapshot: total/P2P/CDN download bytes, P2P download
|
|
478
|
-
ratio, connected peers, first-peer latency when available, cold-start CDN
|
|
479
|
-
bytes, and fallback count. It intentionally excludes Tracker, ICE, and peer
|
|
480
|
-
identifiers.
|
|
481
|
-
|
|
482
|
-
Low-latency HLS/DASH partial CMAF segment requests remain on the player CDN
|
|
483
|
-
path. P2P only distributes complete segments: sharing a response before its
|
|
484
|
-
partial body is complete breaks player cancellation semantics and causes
|
|
485
|
-
latency jitter. Playback continues normally through CDN for those parts.
|
|
486
|
-
|
|
487
|
-
## Release Checks
|
|
488
|
-
|
|
489
|
-
```bash
|
|
490
|
-
npm test -w @meshstream/p2p-web
|
|
491
|
-
npm run build:web-sdk:release
|
|
492
|
-
npm run pack:dry-run -w @meshstream/p2p-web
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
`prepack` runs the SDK test suite, bundles the runtime dependencies into the
|
|
496
|
-
public entry points, and then minifies the release output before package
|
|
497
|
-
creation. The published package contains only bundled JavaScript, `.d.ts`
|
|
498
|
-
types, the Service Worker, README, and licenses; customers do not need to
|
|
499
|
-
install the internal player or P2P packages separately. Public exports and
|
|
500
|
-
object property names remain stable, while readable formatting and source maps
|
|
501
|
-
are omitted from the published JavaScript.
|
|
1
|
+
# MeshStream P2P Web SDK
|
|
2
|
+
|
|
3
|
+
SDK-owned console diagnostics respect the client `logLevel` (default `none`), with redaction and
|
|
4
|
+
repeated-error limiting. Web does not write local log files. See the [logging guide](../../docs/sdk-logging.md).
|
|
5
|
+
|
|
6
|
+
Browser HLS and DASH P2P delivery SDK for MeshStream P2P-CDN. The SDK wraps
|
|
7
|
+
`hls.js`, Shaka Player, and `p2p-media-loader`, fetches runtime configuration
|
|
8
|
+
from Config API, joins a WebRTC tracker swarm, reports playback/P2P statistics,
|
|
9
|
+
and keeps CDN playback as the fallback path.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# SDK-managed player: MeshStream bundles its HLS, DASH, and P2P runtimes.
|
|
15
|
+
npm install @meshstream/p2p-web
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For an application-managed `hls.js` instance, install the player in the
|
|
19
|
+
application as well. MeshStream will bind P2P to that instance without taking
|
|
20
|
+
over its configuration or lifecycle:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @meshstream/p2p-web hls.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
In this monorepo the SDK is consumed as a workspace package:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run build:web-sdk
|
|
30
|
+
npm run build -w @p2pcdn/web-demo
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { attach } from "@meshstream/p2p-web";
|
|
37
|
+
|
|
38
|
+
const player = attach({
|
|
39
|
+
key: "your_sdk_key",
|
|
40
|
+
url: "https://cdn.example.com/live/index.m3u8", // or .mpd / .mp4 / .mp3
|
|
41
|
+
video: document.querySelector("video"),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await player.start();
|
|
45
|
+
|
|
46
|
+
window.addEventListener("pagehide", () => player.dispose(), { once: true });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The historic `video` option accepts any `HTMLMediaElement`; pass an `<audio>`
|
|
50
|
+
element for progressive MP3 playback.
|
|
51
|
+
|
|
52
|
+
The SDK fetches SaaS bootstrap configuration, selects trackers/reporting
|
|
53
|
+
endpoints, detects browser capability, starts WebRTC P2P when possible, and
|
|
54
|
+
keeps CDN playback as the automatic fallback path.
|
|
55
|
+
|
|
56
|
+
When MeshStream creates hls.js, use `hlsConfig` to retain normal player tuning
|
|
57
|
+
such as `maxBufferLength`, ABR limits, or low-latency settings:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const player = attach({
|
|
61
|
+
key,
|
|
62
|
+
url,
|
|
63
|
+
video,
|
|
64
|
+
hlsConfig: { maxBufferLength: 45, lowLatencyMode: true },
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
MeshStream always reapplies its P2P loader and safe origin request settings
|
|
69
|
+
after the instance is created. For `attachToHls`, configure the caller-owned
|
|
70
|
+
hls.js instance directly instead.
|
|
71
|
+
|
|
72
|
+
To display compatibility information before playback, call `getCapability()`.
|
|
73
|
+
It returns only `mseSupported`, `webrtcSupported`, and `serviceWorkerSupported`;
|
|
74
|
+
it does not override the SDK's playback or fallback decision.
|
|
75
|
+
|
|
76
|
+
The web transport selects one content-affine Tracker for a playback session.
|
|
77
|
+
Tracker nodes keep independent in-memory peer maps; switching or retrying a
|
|
78
|
+
Tracker does not copy peers between nodes. If signaling becomes unavailable,
|
|
79
|
+
the player continues through CDN rather than registering the same session with
|
|
80
|
+
every shard. Configure tested hot-content buckets and same-region fallback
|
|
81
|
+
nodes when a stream needs faster recovery.
|
|
82
|
+
|
|
83
|
+
`player.statistics()` always returns the same complete counter shape. During
|
|
84
|
+
startup and CDN fallback, P2P counters are `0` instead of being omitted, so
|
|
85
|
+
application dashboards can use one schema for HLS, DASH, MP4/MP3, and RxPlayer.
|
|
86
|
+
|
|
87
|
+
## Optional Service Worker Transport
|
|
88
|
+
|
|
89
|
+
Service Worker support is an optional transport layer. MSE P2P remains the
|
|
90
|
+
default and is preferred whenever it is available. To deploy the SW runtime,
|
|
91
|
+
copy the published `dist/meshstream-sw.js` file to a same-origin HTTPS path
|
|
92
|
+
such as `/meshstream-sw.js`, then opt in:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { attach } from "@meshstream/p2p-web";
|
|
96
|
+
|
|
97
|
+
const player = attach({
|
|
98
|
+
key,
|
|
99
|
+
url,
|
|
100
|
+
video,
|
|
101
|
+
transportPreference: "auto",
|
|
102
|
+
serviceWorker: {
|
|
103
|
+
url: "/meshstream-sw.js",
|
|
104
|
+
scope: "/",
|
|
105
|
+
autoRegister: true,
|
|
106
|
+
p2pTimeoutMs: 5000,
|
|
107
|
+
onSegmentStats: ({ source, bytes, url }) => {
|
|
108
|
+
console.debug("segment", source, bytes, url);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
await player.start();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`auto` uses MSE P2P first. If MSE is unavailable and the page is controlled by
|
|
116
|
+
the registered SW, playback uses a virtual manifest URL under
|
|
117
|
+
`/__meshstream__/v1/`; failed or unavailable page-side P2P requests fall back
|
|
118
|
+
to the original CDN URL. `transportPreference: "service-worker"` is available
|
|
119
|
+
for explicit testing. When bootstrap returns a tracker configuration, the SDK
|
|
120
|
+
also starts a page-side `p2p-media-loader-core` engine that works without MSE,
|
|
121
|
+
indexes HLS master/media playlists, and answers SW segment requests from the
|
|
122
|
+
WebRTC swarm with CDN fallback. A custom `segmentProvider` can still be passed
|
|
123
|
+
to replace that engine for application-specific routing.
|
|
124
|
+
|
|
125
|
+
For manual registration, import `registerMeshStreamServiceWorker()` and
|
|
126
|
+
`createMeshStreamServiceWorkerSession()` from `@meshstream/p2p-web/service-worker`.
|
|
127
|
+
Registration requires HTTPS (localhost is allowed by browsers), and the SW script
|
|
128
|
+
must be same-origin with the page. Session metadata and rewritten resource mappings
|
|
129
|
+
are kept in IndexedDB so a browser-restarted SW can recover a registered session;
|
|
130
|
+
unregistering the player removes that metadata. `onSegmentStats` reports whether
|
|
131
|
+
each media request was served by the page provider (`p2p`) or CDN (`cdn`).
|
|
132
|
+
|
|
133
|
+
For a local browser smoke check after building, run
|
|
134
|
+
`node sdks/web/test/sw-browser-fixture.mjs` and open the printed URL. The page
|
|
135
|
+
reports the SW controller state, rewritten segment count, response bodies, and
|
|
136
|
+
per-segment source statistics.
|
|
137
|
+
|
|
138
|
+
For the real two-page WebRTC check, run the automated smoke from the repository
|
|
139
|
+
root:
|
|
140
|
+
|
|
141
|
+
```powershell
|
|
142
|
+
npm run test:web-sw-real
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
It builds the SDK and Tracker, opens seed/follower Chromium pages, and fails
|
|
146
|
+
unless the follower receives a non-empty segment through P2P. For manual
|
|
147
|
+
inspection, build the SDK and start
|
|
148
|
+
`node sdks/web/test/sw-real-p2p-browser-fixture.mjs`, then open the seed URL
|
|
149
|
+
first (`?role=seed`) and the follower URL (`?role=follower`).
|
|
150
|
+
|
|
151
|
+
### Staging rollout checklist
|
|
152
|
+
|
|
153
|
+
1. Build the browser package with `npm run build:web-sdk:release` and publish the
|
|
154
|
+
generated npm tarball. Copy `dist/meshstream-sw.js` from the package to a stable
|
|
155
|
+
same-origin HTTPS URL such as `/meshstream-sw.js`; do not serve it from a CDN
|
|
156
|
+
origin or a path outside the intended registration scope.
|
|
157
|
+
2. Deploy the page with `transportPreference: "auto"` and the SW `url` configured.
|
|
158
|
+
Confirm the normal browser path reports `p2p-hls`; test Safari/iOS or a forced
|
|
159
|
+
`transportPreference: "service-worker"` path and confirm `sw-hls-cdn`.
|
|
160
|
+
3. Check the browser Network panel for the virtual manifest under
|
|
161
|
+
`/__meshstream__/v1/` and verify segment requests return either `p2p` or `cdn`
|
|
162
|
+
in the SDK `onSegmentStats` callback.
|
|
163
|
+
4. Verify the configured report endpoint receives `reportType: "stats"` with
|
|
164
|
+
`p2pDownloaded`, `httpDownloaded`, `connectedPeers`, and tracker diagnostics.
|
|
165
|
+
The SDK sends an initial report, periodic reports, and a final `pagehide`
|
|
166
|
+
report; report failures must not interrupt playback.
|
|
167
|
+
5. Roll back by removing the `serviceWorker` option or setting
|
|
168
|
+
`transportPreference: "mse"`; the SDK then keeps the existing MSE/CDN path.
|
|
169
|
+
|
|
170
|
+
## Advanced Player Adapters
|
|
171
|
+
|
|
172
|
+
Use `@meshstream/p2p-web/adapters` for an existing player instance. Controlled
|
|
173
|
+
test environments that need to construct `MeshStreamHlsPlayer` directly can
|
|
174
|
+
import it from `@meshstream/p2p-web/advanced`; standard integrations should
|
|
175
|
+
continue to use root-level `attach`.
|
|
176
|
+
|
|
177
|
+
For a plain video element:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { attach } from "@meshstream/p2p-web";
|
|
181
|
+
|
|
182
|
+
const player = attach({ key, url, video });
|
|
183
|
+
await player.start();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Choosing an HLS integration
|
|
187
|
+
|
|
188
|
+
Both HLS modes use the same P2P scheduling, authorization, and telemetry.
|
|
189
|
+
Use `attach` when the SDK should create and manage hls.js. Use `attachToHls`
|
|
190
|
+
when the application already owns an hls.js instance and must retain its
|
|
191
|
+
buffering, ABR, event, or plugin configuration. In the latter mode, the
|
|
192
|
+
application remains responsible for `loadSource`, `attachMedia`, and
|
|
193
|
+
`destroy`; call `session.start()` before `loadSource`. Do not use both modes
|
|
194
|
+
for the same media element or playback URL.
|
|
195
|
+
|
|
196
|
+
For an existing hls.js instance, bind MeshStream before the application starts
|
|
197
|
+
media loading. This is a native HLS P2P integration: the SDK keeps the caller's
|
|
198
|
+
hls.js configuration and P2P-enables that same instance. It never calls
|
|
199
|
+
`loadSource`, `attachMedia`, or `destroy` on the caller-owned player.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import Hls from "hls.js";
|
|
203
|
+
import { attachToHls } from "@meshstream/p2p-web";
|
|
204
|
+
|
|
205
|
+
const hls = new Hls({ maxBufferLength: 30 });
|
|
206
|
+
const session = attachToHls({ key, url, video, player: hls });
|
|
207
|
+
|
|
208
|
+
await session.start(); // Bind P2P before hls.js makes media requests.
|
|
209
|
+
hls.loadSource(url);
|
|
210
|
+
hls.attachMedia(video);
|
|
211
|
+
|
|
212
|
+
window.addEventListener("pagehide", () => {
|
|
213
|
+
session.dispose(); // Does not destroy hls.
|
|
214
|
+
hls.destroy(); // The application owns this lifecycle.
|
|
215
|
+
}, { once: true });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
For rotating signed URLs, pass a stable business identity so viewers join the
|
|
219
|
+
same swarm. `mimeType` is useful when the media URL has no `.m3u8`, `.mpd`, `.mp4`, or `.mp3`
|
|
220
|
+
suffix. `originHeaders` are sent only to same-origin manifest, segment, and key requests on the
|
|
221
|
+
configured media origin. SDK-controlled headers such as `Range` and hop-by-hop headers are ignored;
|
|
222
|
+
the browser still applies its own forbidden-header and CORS rules. For Shaka and dash.js
|
|
223
|
+
adapter objects, the player must expose its request interception API; otherwise
|
|
224
|
+
the SDK fails explicitly instead of starting without the requested headers.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const player = attach({
|
|
228
|
+
key,
|
|
229
|
+
url: signedManifestUrl,
|
|
230
|
+
video,
|
|
231
|
+
contentId: "live-channel-123",
|
|
232
|
+
mimeType: "m3u8",
|
|
233
|
+
originHeaders: { Authorization: "Bearer ...", "X-Playback-Session": "..." },
|
|
234
|
+
});
|
|
235
|
+
await player.start();
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
For tokens that rotate during a long playback session, provide the current
|
|
239
|
+
value with a synchronous callback. The callback runs immediately before an
|
|
240
|
+
eligible CDN-origin request; it must not refresh a token or return a Promise.
|
|
241
|
+
Refresh credentials in application state first. Dynamic values override static
|
|
242
|
+
headers with the same name and, like static values, are never sent to peers.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
let playbackToken = initialToken;
|
|
246
|
+
|
|
247
|
+
const player = attach({
|
|
248
|
+
key,
|
|
249
|
+
url: signedManifestUrl,
|
|
250
|
+
video,
|
|
251
|
+
originHeaders: { "X-Playback-Session": playbackSession },
|
|
252
|
+
originHeadersProvider: ({ kind }) =>
|
|
253
|
+
kind === "segment" ? { Authorization: `Bearer ${playbackToken}` } : undefined,
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
For Video.js:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { attachToVideoJs } from "@meshstream/p2p-web/adapters";
|
|
261
|
+
|
|
262
|
+
const player = attachToVideoJs({
|
|
263
|
+
key,
|
|
264
|
+
url,
|
|
265
|
+
player: videojs("player"),
|
|
266
|
+
});
|
|
267
|
+
await player.start();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This adapter follows the Video.js instance lifecycle and disposes the
|
|
271
|
+
MeshStream session automatically when Video.js is destroyed. It is a
|
|
272
|
+
media-element bridge: MeshStream owns protocol-compatible playback for that
|
|
273
|
+
element. For HLS, do not also load the same URL through Video.js VHS.
|
|
274
|
+
|
|
275
|
+
For Video.js HLS playback that must retain VHS and Video.js ABR ownership, use
|
|
276
|
+
the native VHS adapter instead. It gives VHS a same-origin virtual manifest and
|
|
277
|
+
routes the resulting segment traffic through the Service Worker P2P path. The
|
|
278
|
+
SDK releases its session automatically when Video.js is disposed; if the
|
|
279
|
+
Service Worker cannot start, VHS continues with the original CDN manifest.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { attachToVhs } from "@meshstream/p2p-web/adapters";
|
|
283
|
+
|
|
284
|
+
const session = attachToVhs({
|
|
285
|
+
key,
|
|
286
|
+
url: "https://cdn.example.com/live/index.m3u8",
|
|
287
|
+
player: videojs("player"),
|
|
288
|
+
serviceWorker: { url: "/meshstream-sw.js" },
|
|
289
|
+
});
|
|
290
|
+
await session.start();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
For players built on top of a standard `<video>` element:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { attachToArtPlayer, attachToDPlayer, attachToPlyr } from "@meshstream/p2p-web/adapters";
|
|
297
|
+
|
|
298
|
+
await attachToPlyr({ key, url, player: plyr }).start();
|
|
299
|
+
await attachToDPlayer({ key, url, player: dplayer }).start();
|
|
300
|
+
await attachToArtPlayer({ key, url, player: art }).start();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The named adapters resolve common `video` or `media` fields, or a `<video>` nested
|
|
304
|
+
inside the player's root element, and then use the
|
|
305
|
+
same automatic HLS, DASH, and progressive-media path as the plain video adapter. They are media-element bridges,
|
|
306
|
+
not native Video.js, Plyr, DPlayer, or ArtPlayer engine plugins: MeshStream
|
|
307
|
+
owns the playback instance used for the supplied media element.
|
|
308
|
+
|
|
309
|
+
For DASH with the standard SDK path:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import { attach } from "@meshstream/p2p-web";
|
|
313
|
+
|
|
314
|
+
const player = attach({
|
|
315
|
+
key,
|
|
316
|
+
url: "https://cdn.example.com/live/index.mpd",
|
|
317
|
+
video,
|
|
318
|
+
});
|
|
319
|
+
await player.start();
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`attach` selects DASH for `.mpd` URLs and HLS for `.m3u8` URLs. Customers
|
|
323
|
+
do not pass a protocol flag. DASH P2P uses Shaka Player internally for the
|
|
324
|
+
standard SDK path.
|
|
325
|
+
|
|
326
|
+
For an existing Shaka Player:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { attachToShaka } from "@meshstream/p2p-web/adapters";
|
|
330
|
+
|
|
331
|
+
const player = attachToShaka({
|
|
332
|
+
key,
|
|
333
|
+
url: "https://cdn.example.com/live/index.mpd",
|
|
334
|
+
player: shakaPlayer,
|
|
335
|
+
});
|
|
336
|
+
await player.start();
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`attachToShaka()` binds and releases only the MeshStream P2P session. The
|
|
340
|
+
application retains ownership of the supplied Shaka instance and must destroy
|
|
341
|
+
it when appropriate.
|
|
342
|
+
|
|
343
|
+
For an existing dash.js player:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import { attachToDashJs } from "@meshstream/p2p-web/adapters";
|
|
347
|
+
|
|
348
|
+
const player = attachToDashJs({
|
|
349
|
+
key,
|
|
350
|
+
url: "https://cdn.example.com/live/index.mpd",
|
|
351
|
+
video,
|
|
352
|
+
player: dashJsPlayer,
|
|
353
|
+
});
|
|
354
|
+
await player.start();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`attachToDashJs` provides native DASH P2P for dash.js `>= 3.1.0 < 6`. Create
|
|
358
|
+
the dash.js instance first, but do not initialize it or load the MPD before
|
|
359
|
+
`session.start()`: MeshStream installs its `XHRLoader` extension first, then
|
|
360
|
+
initializes the supplied instance. Regular audio/video initialization, index,
|
|
361
|
+
and media segments use P2P with CDN fallback. MPD, DRM licenses, subtitles,
|
|
362
|
+
low-latency partial segments, and credentialed cookie requests remain on the
|
|
363
|
+
customer's dash.js/CDN path. When passing
|
|
364
|
+
`originHeaders` to an existing dash.js or Shaka instance, its request interception
|
|
365
|
+
API is required; native Safari HLS cannot accept arbitrary custom headers, so use
|
|
366
|
+
hls.js or signed URLs for that case.
|
|
367
|
+
|
|
368
|
+
### Adapter capability matrix
|
|
369
|
+
|
|
370
|
+
| Player path | P2P integration | Notes |
|
|
371
|
+
| --- | --- | --- |
|
|
372
|
+
| `attach` with HLS | Native hls.js P2P | MeshStream creates and owns hls.js. |
|
|
373
|
+
| `attachToHls` | Native existing hls.js P2P | Application keeps the hls.js lifecycle. |
|
|
374
|
+
| `attach` with DASH; `attachToShaka` with HLS/DASH | Native Shaka P2P | Uses Shaka's networking integration. |
|
|
375
|
+
| `attachToRxPlayer` with DASH | Native RxPlayer segment P2P | Regular DASH segments; same-origin origin headers are supported. |
|
|
376
|
+
| `attachToDashJs` with dash.js `>= 3.1.0 < 6` | Native dash.js P2P | Installs a supported `XHRLoader` extension before dash.js initialization. |
|
|
377
|
+
| `attachToVhs` with Video.js VHS | Native VHS via Service Worker | VHS retains player and ABR ownership; requires a same-origin Service Worker. |
|
|
378
|
+
| `attachToVideoJs`, Plyr, DPlayer, ArtPlayer | Media-element bridge | Not a native plugin for the player's own media engine. |
|
|
379
|
+
|
|
380
|
+
For an existing RxPlayer DASH instance:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import RxPlayer from "rx-player";
|
|
384
|
+
import { attachToRxPlayer } from "@meshstream/p2p-web";
|
|
385
|
+
|
|
386
|
+
const rxPlayer = new RxPlayer({ videoElement: video });
|
|
387
|
+
const player = attachToRxPlayer({ key, url: dashMpdUrl, contentId: "asset-123", player: rxPlayer });
|
|
388
|
+
await player.start();
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`attachToRxPlayer` intercepts RxPlayer's actual DASH audio/video segment loader and sends those
|
|
392
|
+
requests through MeshStream P2P. When `originHeaders` or
|
|
393
|
+
`originHeadersProvider` are configured, same-origin MPD and regular
|
|
394
|
+
audio/video fallback requests retain those headers; dynamic values override
|
|
395
|
+
static values. Cross-origin media, text, DRM, non-contiguous ranges, and
|
|
396
|
+
RxPlayer low-latency chunked loading remain on the customer's RxPlayer/CDN
|
|
397
|
+
path without SDK-supplied headers.
|
|
398
|
+
|
|
399
|
+
Telemetry keeps the playback host and path for diagnostics but strips query and fragment data,
|
|
400
|
+
so signed playback credentials are not included in reports.
|
|
401
|
+
|
|
402
|
+
## Runtime Contract
|
|
403
|
+
|
|
404
|
+
- `key` is the customer-facing SDK Key and can be embedded in Web pages.
|
|
405
|
+
- `homeRegion` is resolved from the SDK Key and identifies the tenant's registered data-home
|
|
406
|
+
region. It is returned by bootstrap for diagnostics and future regional routing; customer code
|
|
407
|
+
cannot override it.
|
|
408
|
+
- Request-network GeoIP is used only for access-side Tracker/ICE locality and is not a data-residency
|
|
409
|
+
selection.
|
|
410
|
+
- Customer integrations should only need `key`, the media `url`, and a `video`
|
|
411
|
+
element for the standard Web path.
|
|
412
|
+
- The SaaS bootstrap endpoint, tracker pool, ICE servers, reporting route, and
|
|
413
|
+
scheduling policy are internal service configuration.
|
|
414
|
+
- P2P, tracker, authorization, and reporting failures must not block `hls.js`
|
|
415
|
+
playback.
|
|
416
|
+
- `detectWebP2pCapability` should be called before constructing the P2P client.
|
|
417
|
+
iOS Safari HLS without MSE is routed to native HLS CDN playback.
|
|
418
|
+
- Fallback reporting uses the service-owned endpoint returned by bootstrap.
|
|
419
|
+
- Signed CDN URL query fields such as `token`, `signature`, `expires`,
|
|
420
|
+
`x-amz-*`, `x-oss-*`, and `x-goog-*` are excluded from URL-derived swarm IDs.
|
|
421
|
+
- `contentId` is optional. When supplied, the SDK derives the internal swarm ID
|
|
422
|
+
from it so rotating signed URL paths remain in the same swarm; otherwise it
|
|
423
|
+
derives identity from the canonical playback URL.
|
|
424
|
+
- `swarmId` and rendition/quality identifiers are internal runtime values. They
|
|
425
|
+
are intentionally not part of the customer-facing configuration; the SDK
|
|
426
|
+
derives and manages them from the content and manifest.
|
|
427
|
+
- Direct progressive `.mp4` and `.mp3` files use the same P2P Service Worker transport as a
|
|
428
|
+
byte-range stream. The SDK registers the Worker by default for this path and divides the file
|
|
429
|
+
into deterministic 1 MiB pieces. Large and open-ended ranges are returned as a backpressure-aware
|
|
430
|
+
stream, so they do not require buffering the whole response in the page. Full-file requests
|
|
431
|
+
without `Range`, suffix ranges, multi-range requests, and metadata remain on CDN.
|
|
432
|
+
Use a stable `contentId` when signed URLs have per-viewer path differences.
|
|
433
|
+
- The direct-file origin must support byte ranges, expose `Content-Length`, `Content-Type`, and
|
|
434
|
+
`Accept-Ranges` through CORS, and allow `GET`, `HEAD`, `OPTIONS`, and the `Range` request header.
|
|
435
|
+
`HEAD` is recommended; if the origin explicitly returns `405` or `501`, extensionless URL detection
|
|
436
|
+
uses a one-byte range `GET` instead. Missing metadata or any P2P error falls back to the original CDN URL.
|
|
437
|
+
|
|
438
|
+
## Public API
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
// Standard customer-facing entry point
|
|
442
|
+
attach(options)
|
|
443
|
+
|
|
444
|
+
// Advanced player adapters
|
|
445
|
+
import { attachToShaka, attachToDashJs, attachToVhs, attachToVideoJs } from "@meshstream/p2p-web/adapters";
|
|
446
|
+
|
|
447
|
+
// P2P runtime controls
|
|
448
|
+
player.start()
|
|
449
|
+
player.dispose()
|
|
450
|
+
player.setP2pEnabled(enabled)
|
|
451
|
+
player.addEventListener(listener) // returns unsubscribe
|
|
452
|
+
player.statistics()
|
|
453
|
+
player.diagnostics()
|
|
454
|
+
player.qoe()
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
`setP2pEnabled(false)` may be called before `start()` to force a CDN-only
|
|
458
|
+
session, or during playback to stop P2P without interrupting CDN delivery.
|
|
459
|
+
Repeated `start()` calls share the active session; call `dispose()` before
|
|
460
|
+
starting a fresh session for a different source or player lifecycle.
|
|
461
|
+
|
|
462
|
+
`start()` returns `{ mode, capability, fallbackReason? }`. On a CDN fallback,
|
|
463
|
+
`fallbackReason` is one of `CAPABILITY_UNSUPPORTED`, `P2P_DISABLED`,
|
|
464
|
+
`SDK_CONFIG_UNAVAILABLE`, `TRACKER_UNAVAILABLE`, or
|
|
465
|
+
`P2P_INITIALIZATION_FAILED`. It is intended for customer telemetry and does
|
|
466
|
+
not expose the server bootstrap configuration.
|
|
467
|
+
|
|
468
|
+
`addEventListener()` accepts `onPlaybackStateChanged`, `onP2pReady`,
|
|
469
|
+
`onP2pUnavailable`, `onPeerCountChanged`, `onTrackerError`,
|
|
470
|
+
`onPeerConnectError`, `onCdnFallback`, and `onStatistics`. Register before `start()` when the
|
|
471
|
+
initial connection state matters. Startup CDN fallbacks emit `UNSUPPORTED`,
|
|
472
|
+
`DISABLED`, `CONFIG_UNAVAILABLE`, `TRACKER_ERROR`, or
|
|
473
|
+
`INITIALIZATION_FAILED`; call the returned function during cleanup.
|
|
474
|
+
|
|
475
|
+
`onPlaybackStateChanged` reports `idle`, `initializing`, `connecting`,
|
|
476
|
+
`p2p-ready`, `cdn-fallback`, and `disposed`. `qoe()` returns a safe
|
|
477
|
+
application-telemetry snapshot: total/P2P/CDN download bytes, P2P download
|
|
478
|
+
ratio, connected peers, first-peer latency when available, cold-start CDN
|
|
479
|
+
bytes, and fallback count. It intentionally excludes Tracker, ICE, and peer
|
|
480
|
+
identifiers.
|
|
481
|
+
|
|
482
|
+
Low-latency HLS/DASH partial CMAF segment requests remain on the player CDN
|
|
483
|
+
path. P2P only distributes complete segments: sharing a response before its
|
|
484
|
+
partial body is complete breaks player cancellation semantics and causes
|
|
485
|
+
latency jitter. Playback continues normally through CDN for those parts.
|
|
486
|
+
|
|
487
|
+
## Release Checks
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npm test -w @meshstream/p2p-web
|
|
491
|
+
npm run build:web-sdk:release
|
|
492
|
+
npm run pack:dry-run -w @meshstream/p2p-web
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
`prepack` runs the SDK test suite, bundles the runtime dependencies into the
|
|
496
|
+
public entry points, and then minifies the release output before package
|
|
497
|
+
creation. The published package contains only bundled JavaScript, `.d.ts`
|
|
498
|
+
types, the Service Worker, README, and licenses; customers do not need to
|
|
499
|
+
install the internal player or P2P packages separately. Public exports and
|
|
500
|
+
object property names remain stable, while readable formatting and source maps
|
|
501
|
+
are omitted from the published JavaScript.
|