@hevcjs/shaka-plugin 0.2.1 → 0.3.0
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 +28 -2
- package/dist/compute-aware.d.ts +28 -0
- package/dist/compute-aware.d.ts.map +1 -0
- package/dist/index.d.ts +69 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +139 -7
- package/dist/index.js.map +1 -1
- package/dist/transmuxer.d.ts +2 -0
- package/dist/transmuxer.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,18 +16,44 @@ npm install @hevcjs/shaka-plugin shaka-player
|
|
|
16
16
|
import shaka from 'shaka-player';
|
|
17
17
|
import { registerHevcTransmuxer } from '@hevcjs/shaka-plugin';
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const handle = registerHevcTransmuxer(shaka, {
|
|
20
20
|
workerUrl: '/transcode-worker.js',
|
|
21
21
|
wasmUrl: '/hevc-decode.js',
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
const player = new shaka.Player();
|
|
25
25
|
await player.attach(document.querySelector('video'));
|
|
26
|
+
handle.attachComputeAware(player); // compute-aware ABR (on by default)
|
|
26
27
|
await player.load('https://example.com/stream/manifest.mpd');
|
|
27
28
|
|
|
28
|
-
// later:
|
|
29
|
+
// later: handle(); // unregisters transmuxer AND detaches compute-aware
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
`handle` is callable for backward compatibility (`handle()` unregisters), and exposes `handle.unregister()` / `handle.attachComputeAware(player, options?)` for explicit control. The unified teardown tears down both the transmuxer registration and any active compute-aware listener.
|
|
33
|
+
|
|
34
|
+
## Compute-aware ABR
|
|
35
|
+
|
|
36
|
+
The plugin watches per-segment transcode `speedX` (`segDurMs / wallClockMs`). When the device can't keep up, it asks Shaka to narrow its variant ceiling via `player.configure({ abr: { restrictions } })` — Shaka's own bandwidth-based ABR keeps picking freely from what's left. **On by default.**
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// Tune the decider (defaults: measureWindow 2, lowerAfter 1, raiseAfter 6, targetSpeedX 1.3)
|
|
40
|
+
registerHevcTransmuxer(shaka, {
|
|
41
|
+
adaptiveCompute: { targetSpeedX: 1.5, lowerAfter: 2 },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Telemetry hook — fires per segment, not just on cap changes
|
|
45
|
+
handle.attachComputeAware(player, {
|
|
46
|
+
onObservation: (stat, avgSpeedX, capIndex, reason) => {
|
|
47
|
+
console.log(`speedX=${stat.speedX.toFixed(2)} cap=${capIndex} (${reason})`);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Opt out
|
|
52
|
+
registerHevcTransmuxer(shaka, { adaptiveCompute: false });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Options passed at `attachComputeAware` time merge on top of options passed at register time — convenient when `onObservation` is only known once the UI exists.
|
|
56
|
+
|
|
31
57
|
## How It Works
|
|
32
58
|
|
|
33
59
|
Shaka exposes a `TransmuxerEngine` that lets plugins convert one container/codec into another before MSE sees the bytes. This package follows the same pattern as Shaka's built-in `AacTransmuxer`:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComputeAwareConfig, SegmentPerfStat } from "@hevcjs/core";
|
|
2
|
+
type ShakaPlayer = any;
|
|
3
|
+
export interface ShakaComputeAwareOptions extends ComputeAwareConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Optional sink for telemetry — called on every observation, not just
|
|
6
|
+
* cap changes. Useful for plotting speedX over time in a demo.
|
|
7
|
+
*
|
|
8
|
+
* `reason` is the decider's verdict on this observation:
|
|
9
|
+
* `init` (window not full yet) | `hold` (no change) |
|
|
10
|
+
* `lower` (cap stepped down) | `raise` (cap stepped up).
|
|
11
|
+
*/
|
|
12
|
+
onObservation?: (stat: SegmentPerfStat, avgSpeedX: number, capIndex: number | null, reason: "init" | "hold" | "lower" | "raise") => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Attach the compute-aware ABR feedback loop to a Shaka player.
|
|
16
|
+
*
|
|
17
|
+
* Must be called after `new shaka.Player()`. Safe to call before
|
|
18
|
+
* `player.load()`: variants are looked up lazily as segments arrive.
|
|
19
|
+
*
|
|
20
|
+
* @returns cleanup function — unsubscribes the perf-bus listener.
|
|
21
|
+
* Does NOT clear any restriction already applied to the player. If you
|
|
22
|
+
* want to restore an unbounded ABR, call
|
|
23
|
+
* `player.configure({ abr: { restrictions: { maxHeight: Infinity, maxBandwidth: Infinity }}})`
|
|
24
|
+
* after detaching.
|
|
25
|
+
*/
|
|
26
|
+
export declare function attachShakaComputeAware(player: ShakaPlayer, options?: ShakaComputeAwareOptions): () => void;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=compute-aware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-aware.d.ts","sourceRoot":"","sources":["../src/compute-aware.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EAChB,MAAM,cAAc,CAAC;AAItB,KAAK,WAAW,GAAG,GAAG,CAAC;AAcvB,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,eAAe,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,KACxC,IAAI,CAAC;CACX;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,wBAA6B,GACrC,MAAM,IAAI,CAoCZ"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,24 @@
|
|
|
20
20
|
* });
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
|
+
* Compute-aware ABR is ON by default — Shaka's bandwidth-based ABR keeps
|
|
24
|
+
* choosing freely while we narrow the ceiling when the device can't keep
|
|
25
|
+
* up. The player is supplied later via `attachComputeAware`:
|
|
26
|
+
* ```ts
|
|
27
|
+
* const handle = registerHevcTransmuxer(shaka, {
|
|
28
|
+
* wasmUrl: '/hevc-decode.js',
|
|
29
|
+
* workerUrl: '/transcode-worker.js',
|
|
30
|
+
* // adaptiveCompute is ON by default.
|
|
31
|
+
* // To opt out: adaptiveCompute: false
|
|
32
|
+
* // To tune: adaptiveCompute: { targetSpeedX: 1.5, lowerAfter: 1 }
|
|
33
|
+
* });
|
|
34
|
+
* const player = new shaka.Player();
|
|
35
|
+
* handle.attachComputeAware(player); // wire the feedback loop
|
|
36
|
+
* await player.load(manifestUrl);
|
|
37
|
+
* // ...
|
|
38
|
+
* handle(); // unregister + detach (callable)
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
23
41
|
* To force the transmuxer even on browsers with native HEVC support
|
|
24
42
|
* (Safari, recent Chrome on macOS), use Shaka's built-in config rather
|
|
25
43
|
* than patching MSE yourself:
|
|
@@ -29,16 +47,61 @@
|
|
|
29
47
|
* ```
|
|
30
48
|
*/
|
|
31
49
|
import type { HevcTransmuxerConfig } from "./transmuxer.js";
|
|
50
|
+
import type { ShakaComputeAwareOptions } from "./compute-aware.js";
|
|
32
51
|
export { HevcTransmuxer } from "./transmuxer.js";
|
|
33
52
|
export type { TransmuxOutput, HevcTransmuxerConfig } from "./transmuxer.js";
|
|
53
|
+
export { attachShakaComputeAware } from "./compute-aware.js";
|
|
54
|
+
export type { ShakaComputeAwareOptions } from "./compute-aware.js";
|
|
55
|
+
export { subscribeSegmentStat } from "@hevcjs/core";
|
|
56
|
+
export type { SegmentPerfStat } from "@hevcjs/core";
|
|
34
57
|
type ShakaNamespace = any;
|
|
58
|
+
type ShakaPlayer = any;
|
|
35
59
|
/**
|
|
36
60
|
* Plugin configuration. Forwarded as-is to `HevcTransmuxer`. Supports the
|
|
37
61
|
* `SegmentTranscoderConfig` fields (`wasmUrl`, `wasmBinaryUrl`, `fps`,
|
|
38
62
|
* `bitrate`) plus an optional `workerUrl` that, when set, routes the
|
|
39
|
-
* HEVC decode + H.264 encode pipeline through a Web Worker
|
|
63
|
+
* HEVC decode + H.264 encode pipeline through a Web Worker, plus an
|
|
64
|
+
* optional `adaptiveCompute` flag/config to enable the compute-aware
|
|
65
|
+
* ABR feedback loop.
|
|
66
|
+
*/
|
|
67
|
+
export interface HevcShakaPluginConfig extends HevcTransmuxerConfig {
|
|
68
|
+
/**
|
|
69
|
+
* Compute-aware ABR feedback. The returned handle exposes
|
|
70
|
+
* `attachComputeAware(player)` that wires the host Shaka player to the
|
|
71
|
+
* transcode perf bus and caps variants when the device can't keep up.
|
|
72
|
+
*
|
|
73
|
+
* - **On by default** (undefined or `true`) — sensible defaults.
|
|
74
|
+
* - Pass an object to tune the decider knobs (`targetSpeedX`, etc.).
|
|
75
|
+
* - Pass `false` to opt out: `attachComputeAware` becomes a silent no-op.
|
|
76
|
+
*
|
|
77
|
+
* `attachComputeAware(player)` must still be called explicitly because
|
|
78
|
+
* the player instance isn't available at register time.
|
|
79
|
+
*/
|
|
80
|
+
adaptiveCompute?: boolean | ShakaComputeAwareOptions;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Return shape of `registerHevcTransmuxer`. Callable for backwards compat
|
|
84
|
+
* (`handle()` unregisters the transmuxer, same as before). Methods are
|
|
85
|
+
* attached as properties when `adaptiveCompute` is enabled so the existing
|
|
86
|
+
* `const cleanup = registerHevcTransmuxer(...)` pattern still works.
|
|
40
87
|
*/
|
|
41
|
-
export
|
|
88
|
+
export interface HevcShakaPluginHandle {
|
|
89
|
+
(): void;
|
|
90
|
+
/** Explicit alias for the callable form. */
|
|
91
|
+
unregister(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Attach the compute-aware feedback loop to a Shaka player.
|
|
94
|
+
* Active by default; becomes a silent no-op only when the registration
|
|
95
|
+
* config explicitly passed `adaptiveCompute: false`.
|
|
96
|
+
*
|
|
97
|
+
* Options passed here are merged on top of any options passed at
|
|
98
|
+
* register time, which is convenient when the telemetry sink
|
|
99
|
+
* (`onObservation`) is only available once the UI exists.
|
|
100
|
+
*
|
|
101
|
+
* @returns cleanup function — detaches the perf-bus listener.
|
|
102
|
+
*/
|
|
103
|
+
attachComputeAware(player: ShakaPlayer, options?: ShakaComputeAwareOptions): () => void;
|
|
104
|
+
}
|
|
42
105
|
/**
|
|
43
106
|
* Register the HEVC transmuxer with Shaka's TransmuxerEngine.
|
|
44
107
|
*
|
|
@@ -47,8 +110,9 @@ export type HevcShakaPluginConfig = HevcTransmuxerConfig;
|
|
|
47
110
|
* our transmuxer over any default fallback.
|
|
48
111
|
*
|
|
49
112
|
* @param shaka the global `shaka` namespace (import or window.shaka)
|
|
50
|
-
* @param config forwarded to `HevcTransmuxer` (wasmUrl, wasmBinaryUrl, fps, bitrate, workerUrl)
|
|
51
|
-
* @returns A
|
|
113
|
+
* @param config forwarded to `HevcTransmuxer` (wasmUrl, wasmBinaryUrl, fps, bitrate, workerUrl, adaptiveCompute)
|
|
114
|
+
* @returns A handle that is both callable (unregisters) and exposes
|
|
115
|
+
* `attachComputeAware(player)` when `adaptiveCompute` is enabled.
|
|
52
116
|
*/
|
|
53
|
-
export declare function registerHevcTransmuxer(shaka: ShakaNamespace, config?: HevcShakaPluginConfig):
|
|
117
|
+
export declare function registerHevcTransmuxer(shaka: ShakaNamespace, config?: HevcShakaPluginConfig): HevcShakaPluginHandle;
|
|
54
118
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAInE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,KAAK,cAAc,GAAG,GAAG,CAAC;AAE1B,KAAK,WAAW,GAAG,GAAG,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IACjE;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAC;CACtD;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,IAAI,CAAC;IACT,4CAA4C;IAC5C,UAAU,IAAI,IAAI,CAAC;IACnB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,IAAI,CAAC;CACzF;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,cAAc,EACrB,MAAM,GAAE,qBAA0B,GACjC,qBAAqB,CAwCvB"}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,15 @@ var HevcTransmuxer = class {
|
|
|
33
33
|
this.initPromise_ = null;
|
|
34
34
|
this.pendingHevcInit_ = null;
|
|
35
35
|
this.h264InitEmitted_ = false;
|
|
36
|
+
// Cache for the last HEVC init segment we processed and the H.264 init we
|
|
37
|
+
// produced for it. Shaka can call transmux() with the same init bytes
|
|
38
|
+
// multiple times during a session (variant probing, transmuxer re-checks);
|
|
39
|
+
// we must not tear down the live encoder on those redundant calls or
|
|
40
|
+
// playback stalls while the encoder rebuilds. A real representation change
|
|
41
|
+
// arrives with different bytes and goes through the normal `prepareInit`
|
|
42
|
+
// path.
|
|
43
|
+
this.lastHevcInitBytes_ = null;
|
|
44
|
+
this.cachedH264Init_ = null;
|
|
36
45
|
this.originalMimeType_ = mimeType;
|
|
37
46
|
this.transcoderConfig_ = config;
|
|
38
47
|
}
|
|
@@ -42,6 +51,8 @@ var HevcTransmuxer = class {
|
|
|
42
51
|
this.initPromise_ = null;
|
|
43
52
|
this.pendingHevcInit_ = null;
|
|
44
53
|
this.h264InitEmitted_ = false;
|
|
54
|
+
this.lastHevcInitBytes_ = null;
|
|
55
|
+
this.cachedH264Init_ = null;
|
|
45
56
|
}
|
|
46
57
|
isSupported(mimeType, _contentType) {
|
|
47
58
|
return HEVC_MIME_PATTERN.test(mimeType);
|
|
@@ -102,10 +113,21 @@ var HevcTransmuxer = class {
|
|
|
102
113
|
}
|
|
103
114
|
await this.initPromise_;
|
|
104
115
|
if (isInit) {
|
|
116
|
+
if (this.cachedH264Init_ && bytesEqual(bytes, this.lastHevcInitBytes_)) {
|
|
117
|
+
const copy2 = new Uint8Array(this.cachedH264Init_.byteLength);
|
|
118
|
+
copy2.set(this.cachedH264Init_);
|
|
119
|
+
return copy2;
|
|
120
|
+
}
|
|
121
|
+
const initBytesSnapshot = new Uint8Array(bytes.byteLength);
|
|
122
|
+
initBytesSnapshot.set(bytes);
|
|
105
123
|
const result = await this.transcoder_.prepareInit(bytes);
|
|
106
124
|
this.h264InitEmitted_ = true;
|
|
107
|
-
const
|
|
108
|
-
|
|
125
|
+
const h264InitCopy = new Uint8Array(result.initSegment.byteLength);
|
|
126
|
+
h264InitCopy.set(result.initSegment);
|
|
127
|
+
this.lastHevcInitBytes_ = initBytesSnapshot;
|
|
128
|
+
this.cachedH264Init_ = h264InitCopy;
|
|
129
|
+
const copy = new Uint8Array(h264InitCopy.byteLength);
|
|
130
|
+
copy.set(h264InitCopy);
|
|
109
131
|
return copy;
|
|
110
132
|
}
|
|
111
133
|
const h264Media = await this.transcoder_.processMediaSegment(bytes);
|
|
@@ -115,6 +137,13 @@ var HevcTransmuxer = class {
|
|
|
115
137
|
return h264Media;
|
|
116
138
|
}
|
|
117
139
|
};
|
|
140
|
+
function bytesEqual(a, b) {
|
|
141
|
+
if (!b || a.byteLength !== b.byteLength) return false;
|
|
142
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
143
|
+
if (a[i] !== b[i]) return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
118
147
|
function toUint8(data) {
|
|
119
148
|
if (data instanceof Uint8Array) return data;
|
|
120
149
|
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
@@ -125,7 +154,82 @@ function toUint8(data) {
|
|
|
125
154
|
);
|
|
126
155
|
}
|
|
127
156
|
|
|
157
|
+
// src/compute-aware.ts
|
|
158
|
+
import {
|
|
159
|
+
ComputeAwareDecider,
|
|
160
|
+
subscribeSegmentStat
|
|
161
|
+
} from "@hevcjs/core";
|
|
162
|
+
function attachShakaComputeAware(player, options = {}) {
|
|
163
|
+
const { onObservation, ...deciderConfig } = options;
|
|
164
|
+
const decider = new ComputeAwareDecider(deciderConfig);
|
|
165
|
+
const unsubscribe = subscribeSegmentStat((stat) => {
|
|
166
|
+
const ladder = readLadder(player);
|
|
167
|
+
if (ladder.length === 0) return;
|
|
168
|
+
decider.setLadderSize(ladder.length);
|
|
169
|
+
const currentIdx = findCurrentIndex(player, ladder);
|
|
170
|
+
const decision = decider.observe(stat.speedX, currentIdx);
|
|
171
|
+
if (onObservation) {
|
|
172
|
+
try {
|
|
173
|
+
onObservation(stat, decision.avgSpeedX, decision.capIndex, decision.reason);
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (decision.reason === "lower" || decision.reason === "raise") {
|
|
178
|
+
try {
|
|
179
|
+
applyCap(player, ladder, decision.capIndex);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
decider.revertLastDecision();
|
|
182
|
+
console.warn("[hevc.js/shaka] applyCap failed, reverted decider:", err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return unsubscribe;
|
|
187
|
+
}
|
|
188
|
+
function readLadder(player) {
|
|
189
|
+
const variants = player.getVariantTracks?.() ?? [];
|
|
190
|
+
const seen = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const v of variants) {
|
|
192
|
+
const hasVideo = v.height != null || v.videoBandwidth != null || v.videoCodec != null && v.videoCodec !== "";
|
|
193
|
+
if (!hasVideo) continue;
|
|
194
|
+
const bw = v.videoBandwidth ?? v.bandwidth ?? 0;
|
|
195
|
+
const key = v.height != null ? `h:${v.height}` : `b:${bw}`;
|
|
196
|
+
if (seen.has(key)) continue;
|
|
197
|
+
seen.set(key, {
|
|
198
|
+
height: v.height ?? void 0,
|
|
199
|
+
bandwidth: bw
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const ladder = Array.from(seen.values());
|
|
203
|
+
ladder.sort((a, b) => {
|
|
204
|
+
if (a.height != null && b.height != null) return a.height - b.height;
|
|
205
|
+
return a.bandwidth - b.bandwidth;
|
|
206
|
+
});
|
|
207
|
+
return ladder;
|
|
208
|
+
}
|
|
209
|
+
function findCurrentIndex(player, ladder) {
|
|
210
|
+
const variants = player.getVariantTracks?.() ?? [];
|
|
211
|
+
const active = variants.find((t) => t.active);
|
|
212
|
+
if (!active) return ladder.length - 1;
|
|
213
|
+
if (active.height != null) {
|
|
214
|
+
const idx2 = ladder.findIndex((v) => v.height === active.height);
|
|
215
|
+
if (idx2 >= 0) return idx2;
|
|
216
|
+
}
|
|
217
|
+
const bw = active.videoBandwidth ?? active.bandwidth ?? 0;
|
|
218
|
+
const idx = ladder.findIndex((v) => v.bandwidth === bw);
|
|
219
|
+
return idx >= 0 ? idx : ladder.length - 1;
|
|
220
|
+
}
|
|
221
|
+
function applyCap(player, ladder, capIndex) {
|
|
222
|
+
const cap = ladder[capIndex];
|
|
223
|
+
if (!cap || typeof player.configure !== "function") return;
|
|
224
|
+
const restrictions = {
|
|
225
|
+
maxBandwidth: cap.bandwidth
|
|
226
|
+
};
|
|
227
|
+
if (cap.height != null) restrictions.maxHeight = cap.height;
|
|
228
|
+
player.configure({ abr: { restrictions } });
|
|
229
|
+
}
|
|
230
|
+
|
|
128
231
|
// src/index.ts
|
|
232
|
+
import { subscribeSegmentStat as subscribeSegmentStat2 } from "@hevcjs/core";
|
|
129
233
|
var HEVC_MIME_TYPES = [
|
|
130
234
|
'video/mp4; codecs="hev1"',
|
|
131
235
|
'video/mp4; codecs="hvc1"'
|
|
@@ -136,27 +240,55 @@ function registerHevcTransmuxer(shaka, config = {}) {
|
|
|
136
240
|
console.warn(
|
|
137
241
|
"[hevc.js/shaka] shaka.transmuxer.TransmuxerEngine.registerTransmuxer not found. Make sure shaka-player >= 4.0 is loaded before calling registerHevcTransmuxer()."
|
|
138
242
|
);
|
|
139
|
-
return () => {
|
|
140
|
-
};
|
|
243
|
+
return makeHandle(() => {
|
|
244
|
+
}, void 0);
|
|
141
245
|
}
|
|
142
246
|
const priority = engine.PluginPriority?.APPLICATION ?? engine.PluginPriority?.PREFERRED ?? 4;
|
|
247
|
+
const { adaptiveCompute, ...transmuxerConfig } = config;
|
|
143
248
|
for (const mimeType of HEVC_MIME_TYPES) {
|
|
144
249
|
engine.registerTransmuxer(
|
|
145
250
|
mimeType,
|
|
146
|
-
() => new HevcTransmuxer(mimeType,
|
|
251
|
+
() => new HevcTransmuxer(mimeType, transmuxerConfig),
|
|
147
252
|
priority
|
|
148
253
|
);
|
|
149
254
|
}
|
|
150
|
-
|
|
255
|
+
const unregister = () => {
|
|
151
256
|
if (typeof engine.unregisterTransmuxer === "function") {
|
|
152
257
|
for (const mimeType of HEVC_MIME_TYPES) {
|
|
153
258
|
engine.unregisterTransmuxer(mimeType, priority);
|
|
154
259
|
}
|
|
155
260
|
}
|
|
156
261
|
};
|
|
262
|
+
return makeHandle(unregister, adaptiveCompute);
|
|
263
|
+
}
|
|
264
|
+
function makeHandle(unregister, adaptive) {
|
|
265
|
+
let activeDetach = null;
|
|
266
|
+
const tearDown = () => {
|
|
267
|
+
activeDetach?.();
|
|
268
|
+
activeDetach = null;
|
|
269
|
+
unregister();
|
|
270
|
+
};
|
|
271
|
+
const fn = (() => tearDown());
|
|
272
|
+
fn.unregister = tearDown;
|
|
273
|
+
fn.attachComputeAware = (player, runtimeOpts) => {
|
|
274
|
+
if (adaptive === false) return () => {
|
|
275
|
+
};
|
|
276
|
+
activeDetach?.();
|
|
277
|
+
const registerOpts = typeof adaptive === "object" ? adaptive : {};
|
|
278
|
+
const opts = { ...registerOpts, ...runtimeOpts ?? {} };
|
|
279
|
+
const detach = attachShakaComputeAware(player, opts);
|
|
280
|
+
activeDetach = detach;
|
|
281
|
+
return () => {
|
|
282
|
+
detach();
|
|
283
|
+
if (activeDetach === detach) activeDetach = null;
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
return fn;
|
|
157
287
|
}
|
|
158
288
|
export {
|
|
159
289
|
HevcTransmuxer,
|
|
160
|
-
|
|
290
|
+
attachShakaComputeAware,
|
|
291
|
+
registerHevcTransmuxer,
|
|
292
|
+
subscribeSegmentStat2 as subscribeSegmentStat
|
|
161
293
|
};
|
|
162
294
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transmuxer.ts","../src/index.ts"],"sourcesContent":["/**\n * HEVC Transmuxer for Shaka Player.\n *\n * Implements the `shaka.extern.Transmuxer` interface so Shaka can ingest\n * HEVC/H.265 fMP4 segments on browsers that lack native HEVC support.\n * Uses `@hevcjs/core` SegmentTranscoder to decode HEVC and re-encode to\n * H.264 fMP4 that the browser's MSE can play.\n *\n * Modeled after `lib/transmuxer/aac_transmuxer.js` in shaka-player.\n */\n\nimport {\n SegmentTranscoder,\n TranscodeWorkerClient,\n hevcMimeToH264Codec,\n} from \"@hevcjs/core\";\nimport type { SegmentTranscoderConfig } from \"@hevcjs/core\";\n\n/**\n * Config accepted by `HevcTransmuxer` (and forwarded by `registerHevcTransmuxer`).\n * When `workerUrl` is set, transcoding runs inside a Web Worker; otherwise\n * the HEVC decode + H.264 encode pipeline runs on the main thread.\n */\nexport interface HevcTransmuxerConfig extends SegmentTranscoderConfig {\n /** URL to the transcode worker script. When set, transcoding runs off main thread. */\n workerUrl?: string;\n}\n\n// Loose typing while we don't pull `shaka.extern.*` into the build.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaStream = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaSegmentReference = any;\n\n/**\n * Return type of `HevcTransmuxer.transmux`. Compatible with both Shaka 4.x\n * (which expects a raw `Uint8Array` and passes it straight to MSE) and 5+\n * (which checks `ArrayBuffer.isView` and falls back to `{data, init}`\n * when the value is a plain object). Returning a `Uint8Array` is the\n * common subset that works on every supported Shaka version.\n */\nexport type TransmuxOutput = Uint8Array;\n\nconst HEVC_MIME_PATTERN = /^video\\/mp4\\s*;.*codecs=\"?(hev1|hvc1)/i;\n\n/**\n * 8-byte ISO BMFF `free` box (size + type, no payload). Spec-compliant\n * padding that any MP4 parser ignores. Used as a stand-in when we need\n * to return *something* to Shaka but have nothing real to emit yet —\n * `appendBuffer(emptyUint8Array)` throws \"Overload resolution failed\"\n * on Chrome, so we can't return zero-length buffers.\n */\nconst FREE_BOX_8B = new Uint8Array([\n 0, 0, 0, 8, // size = 8\n 0x66, 0x72, 0x65, 0x65, // 'free'\n]);\n\n/**\n * Sniff whether a buffer starts with an ISO BMFF init segment.\n * Init segments begin with the `ftyp` box; media segments begin with\n * `moof` (or `styp` followed by `moof`).\n *\n * Box header layout: 4 bytes big-endian size, 4 bytes ASCII type.\n */\nexport function isInitSegment(bytes: Uint8Array): boolean {\n if (bytes.length < 8) return false;\n const boxType = String.fromCharCode(\n bytes[4]!,\n bytes[5]!,\n bytes[6]!,\n bytes[7]!,\n );\n return boxType === \"ftyp\";\n}\n\nexport class HevcTransmuxer {\n private readonly originalMimeType_: string;\n private readonly transcoderConfig_: HevcTransmuxerConfig;\n private transcoder_: SegmentTranscoder | TranscodeWorkerClient | null = null;\n private initPromise_: Promise<void> | null = null;\n private pendingHevcInit_: Uint8Array | null = null;\n private h264InitEmitted_ = false;\n\n constructor(mimeType: string, config: HevcTransmuxerConfig = {}) {\n this.originalMimeType_ = mimeType;\n this.transcoderConfig_ = config;\n }\n\n destroy(): void {\n this.transcoder_?.destroy();\n this.transcoder_ = null;\n this.initPromise_ = null;\n this.pendingHevcInit_ = null;\n this.h264InitEmitted_ = false;\n }\n\n isSupported(mimeType: string, _contentType?: string): boolean {\n return HEVC_MIME_PATTERN.test(mimeType);\n }\n\n /**\n * Output mime advertised to Shaka before any frame has been encoded.\n * Best-effort mapping based on the HEVC level declared in the input\n * (see `@hevcjs/core/codec-mapping`). The actual encoded stream may\n * use a slightly different profile/level if `H264Encoder` decides\n * differently from the encoded resolution.\n */\n convertCodecs(_contentType: string, mimeType: string): string {\n if (!HEVC_MIME_PATTERN.test(mimeType)) return mimeType;\n return `video/mp4; codecs=\"${hevcMimeToH264Codec(mimeType)}\"`;\n }\n\n getOriginalMimeType(): string {\n return this.originalMimeType_;\n }\n\n /**\n * Convert one HEVC fMP4 segment into an MSE-ready H.264 fMP4 segment.\n *\n * Shaka calls this once per segment with `reference === null` for the\n * init segment and a non-null `reference` for media segments.\n *\n * - Init segment: warm up the H.264 encoder eagerly (encodes a single\n * black frame to obtain a valid avcC) and return a complete H.264\n * init segment that MSE can immediately ingest.\n * - Media segment: decode HEVC, re-encode to H.264, mux fMP4, return.\n *\n * Returns a raw `Uint8Array` rather than `{data, init}` so the same\n * code path works on Shaka 4.x (which expects a `Uint8Array` directly)\n * and on Shaka 5+ (which accepts either via an `ArrayBuffer.isView`\n * check). Init/media segmentation is implicit in the call sequence.\n */\n async transmux(\n data: BufferSource,\n _stream: ShakaStream,\n reference: ShakaSegmentReference,\n _duration: number,\n _contentType: string,\n ): Promise<TransmuxOutput> {\n const bytes = toUint8(data);\n const isInit = reference == null || isInitSegment(bytes);\n\n if (!this.transcoder_) {\n const workerUrl = this.transcoderConfig_.workerUrl;\n if (workerUrl) {\n const worker = new TranscodeWorkerClient({\n ...this.transcoderConfig_,\n workerUrl,\n });\n this.transcoder_ = worker;\n this.initPromise_ = worker.waitReady();\n console.log(\n `[hevc.js/shaka] HEVC transcoding routed through Worker at ${workerUrl}`,\n );\n } else {\n const local = new SegmentTranscoder(this.transcoderConfig_);\n this.transcoder_ = local;\n this.initPromise_ = local.init();\n console.log(\n \"[hevc.js/shaka] HEVC transcoding runs on main thread (no workerUrl provided)\",\n );\n }\n }\n await this.initPromise_;\n\n if (isInit) {\n const result = await this.transcoder_!.prepareInit(bytes);\n this.h264InitEmitted_ = true;\n // Defensive copy: avoids any risk of the underlying ArrayBuffer being\n // detached or mutated between this return and the eventual MSE append.\n const copy = new Uint8Array(result.initSegment.byteLength);\n copy.set(result.initSegment);\n return copy;\n }\n\n const h264Media = await this.transcoder_!.processMediaSegment(bytes);\n if (!h264Media) {\n // No frames produced (e.g. drop frames in adaptive switching). Emit\n // a spec-valid `free` box of 8 bytes — empty buffers crash Chrome's\n // appendBuffer with \"Overload resolution failed\".\n return FREE_BOX_8B;\n }\n return h264Media;\n }\n}\n\nfunction toUint8(data: BufferSource): Uint8Array {\n if (data instanceof Uint8Array) return data;\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(\n (data as ArrayBufferView).buffer,\n (data as ArrayBufferView).byteOffset,\n (data as ArrayBufferView).byteLength,\n );\n}\n","/**\n * Shaka Player HEVC Plugin — public entry point.\n *\n * Usage (main thread, no Worker):\n * ```ts\n * import shaka from 'shaka-player';\n * import { registerHevcTransmuxer } from '@hevcjs/shaka-plugin';\n *\n * registerHevcTransmuxer(shaka, { wasmUrl: '/hevc-decode.js' });\n * const player = new shaka.Player();\n * await player.attach(videoElement);\n * await player.load(manifestUrl);\n * ```\n *\n * Usage (off-main-thread via Web Worker — recommended for 4K / smoothness):\n * ```ts\n * registerHevcTransmuxer(shaka, {\n * wasmUrl: '/hevc-decode.js',\n * workerUrl: '/transcode-worker.js',\n * });\n * ```\n *\n * To force the transmuxer even on browsers with native HEVC support\n * (Safari, recent Chrome on macOS), use Shaka's built-in config rather\n * than patching MSE yourself:\n *\n * ```ts\n * player.configure({ mediaSource: { forceTransmux: true } });\n * ```\n */\n\nimport { HevcTransmuxer } from \"./transmuxer.js\";\nimport type { HevcTransmuxerConfig } from \"./transmuxer.js\";\n\nexport { HevcTransmuxer } from \"./transmuxer.js\";\nexport type { TransmuxOutput, HevcTransmuxerConfig } from \"./transmuxer.js\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaNamespace = any;\n\n/**\n * Plugin configuration. Forwarded as-is to `HevcTransmuxer`. Supports the\n * `SegmentTranscoderConfig` fields (`wasmUrl`, `wasmBinaryUrl`, `fps`,\n * `bitrate`) plus an optional `workerUrl` that, when set, routes the\n * HEVC decode + H.264 encode pipeline through a Web Worker.\n */\nexport type HevcShakaPluginConfig = HevcTransmuxerConfig;\n\nconst HEVC_MIME_TYPES = [\n 'video/mp4; codecs=\"hev1\"',\n 'video/mp4; codecs=\"hvc1\"',\n];\n\n/**\n * Register the HEVC transmuxer with Shaka's TransmuxerEngine.\n *\n * Must be called before `player.load()`. Registers a factory for both\n * `hev1` and `hvc1` MIME types at APPLICATION priority so Shaka picks\n * our transmuxer over any default fallback.\n *\n * @param shaka the global `shaka` namespace (import or window.shaka)\n * @param config forwarded to `HevcTransmuxer` (wasmUrl, wasmBinaryUrl, fps, bitrate, workerUrl)\n * @returns A cleanup function that unregisters the transmuxer.\n */\nexport function registerHevcTransmuxer(\n shaka: ShakaNamespace,\n config: HevcShakaPluginConfig = {},\n): () => void {\n const engine = shaka?.transmuxer?.TransmuxerEngine;\n if (!engine || typeof engine.registerTransmuxer !== \"function\") {\n console.warn(\n \"[hevc.js/shaka] shaka.transmuxer.TransmuxerEngine.registerTransmuxer not found. \" +\n \"Make sure shaka-player >= 4.0 is loaded before calling registerHevcTransmuxer().\",\n );\n return () => {};\n }\n\n // External (application-supplied) plugins should register at the\n // APPLICATION priority so they override any built-in fallback. Values in\n // shaka.transmuxer.TransmuxerEngine.PluginPriority: FALLBACK=1,\n // PREFERRED_SECONDARY=2, PREFERRED=3, APPLICATION=4.\n const priority =\n engine.PluginPriority?.APPLICATION ??\n engine.PluginPriority?.PREFERRED ??\n 4;\n\n for (const mimeType of HEVC_MIME_TYPES) {\n engine.registerTransmuxer(\n mimeType,\n () => new HevcTransmuxer(mimeType, config),\n priority,\n );\n }\n\n return () => {\n if (typeof engine.unregisterTransmuxer === \"function\") {\n for (const mimeType of HEVC_MIME_TYPES) {\n // unregisterTransmuxer keys on `${mime}-${priority}` so the\n // priority used at register time must be passed back here.\n engine.unregisterTransmuxer(mimeType, priority);\n }\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/transmuxer.ts","../src/compute-aware.ts","../src/index.ts"],"sourcesContent":["/**\n * HEVC Transmuxer for Shaka Player.\n *\n * Implements the `shaka.extern.Transmuxer` interface so Shaka can ingest\n * HEVC/H.265 fMP4 segments on browsers that lack native HEVC support.\n * Uses `@hevcjs/core` SegmentTranscoder to decode HEVC and re-encode to\n * H.264 fMP4 that the browser's MSE can play.\n *\n * Modeled after `lib/transmuxer/aac_transmuxer.js` in shaka-player.\n */\n\nimport {\n SegmentTranscoder,\n TranscodeWorkerClient,\n hevcMimeToH264Codec,\n} from \"@hevcjs/core\";\nimport type { SegmentTranscoderConfig } from \"@hevcjs/core\";\n\n/**\n * Config accepted by `HevcTransmuxer` (and forwarded by `registerHevcTransmuxer`).\n * When `workerUrl` is set, transcoding runs inside a Web Worker; otherwise\n * the HEVC decode + H.264 encode pipeline runs on the main thread.\n */\nexport interface HevcTransmuxerConfig extends SegmentTranscoderConfig {\n /** URL to the transcode worker script. When set, transcoding runs off main thread. */\n workerUrl?: string;\n}\n\n// Loose typing while we don't pull `shaka.extern.*` into the build.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaStream = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaSegmentReference = any;\n\n/**\n * Return type of `HevcTransmuxer.transmux`. Compatible with both Shaka 4.x\n * (which expects a raw `Uint8Array` and passes it straight to MSE) and 5+\n * (which checks `ArrayBuffer.isView` and falls back to `{data, init}`\n * when the value is a plain object). Returning a `Uint8Array` is the\n * common subset that works on every supported Shaka version.\n */\nexport type TransmuxOutput = Uint8Array;\n\nconst HEVC_MIME_PATTERN = /^video\\/mp4\\s*;.*codecs=\"?(hev1|hvc1)/i;\n\n/**\n * 8-byte ISO BMFF `free` box (size + type, no payload). Spec-compliant\n * padding that any MP4 parser ignores. Used as a stand-in when we need\n * to return *something* to Shaka but have nothing real to emit yet —\n * `appendBuffer(emptyUint8Array)` throws \"Overload resolution failed\"\n * on Chrome, so we can't return zero-length buffers.\n */\nconst FREE_BOX_8B = new Uint8Array([\n 0, 0, 0, 8, // size = 8\n 0x66, 0x72, 0x65, 0x65, // 'free'\n]);\n\n/**\n * Sniff whether a buffer starts with an ISO BMFF init segment.\n * Init segments begin with the `ftyp` box; media segments begin with\n * `moof` (or `styp` followed by `moof`).\n *\n * Box header layout: 4 bytes big-endian size, 4 bytes ASCII type.\n */\nexport function isInitSegment(bytes: Uint8Array): boolean {\n if (bytes.length < 8) return false;\n const boxType = String.fromCharCode(\n bytes[4]!,\n bytes[5]!,\n bytes[6]!,\n bytes[7]!,\n );\n return boxType === \"ftyp\";\n}\n\nexport class HevcTransmuxer {\n private readonly originalMimeType_: string;\n private readonly transcoderConfig_: HevcTransmuxerConfig;\n private transcoder_: SegmentTranscoder | TranscodeWorkerClient | null = null;\n private initPromise_: Promise<void> | null = null;\n private pendingHevcInit_: Uint8Array | null = null;\n private h264InitEmitted_ = false;\n // Cache for the last HEVC init segment we processed and the H.264 init we\n // produced for it. Shaka can call transmux() with the same init bytes\n // multiple times during a session (variant probing, transmuxer re-checks);\n // we must not tear down the live encoder on those redundant calls or\n // playback stalls while the encoder rebuilds. A real representation change\n // arrives with different bytes and goes through the normal `prepareInit`\n // path.\n private lastHevcInitBytes_: Uint8Array | null = null;\n private cachedH264Init_: Uint8Array | null = null;\n\n constructor(mimeType: string, config: HevcTransmuxerConfig = {}) {\n this.originalMimeType_ = mimeType;\n this.transcoderConfig_ = config;\n }\n\n destroy(): void {\n this.transcoder_?.destroy();\n this.transcoder_ = null;\n this.initPromise_ = null;\n this.pendingHevcInit_ = null;\n this.h264InitEmitted_ = false;\n this.lastHevcInitBytes_ = null;\n this.cachedH264Init_ = null;\n }\n\n isSupported(mimeType: string, _contentType?: string): boolean {\n return HEVC_MIME_PATTERN.test(mimeType);\n }\n\n /**\n * Output mime advertised to Shaka before any frame has been encoded.\n * Best-effort mapping based on the HEVC level declared in the input\n * (see `@hevcjs/core/codec-mapping`). The actual encoded stream may\n * use a slightly different profile/level if `H264Encoder` decides\n * differently from the encoded resolution.\n */\n convertCodecs(_contentType: string, mimeType: string): string {\n if (!HEVC_MIME_PATTERN.test(mimeType)) return mimeType;\n return `video/mp4; codecs=\"${hevcMimeToH264Codec(mimeType)}\"`;\n }\n\n getOriginalMimeType(): string {\n return this.originalMimeType_;\n }\n\n /**\n * Convert one HEVC fMP4 segment into an MSE-ready H.264 fMP4 segment.\n *\n * Shaka calls this once per segment with `reference === null` for the\n * init segment and a non-null `reference` for media segments.\n *\n * - Init segment: warm up the H.264 encoder eagerly (encodes a single\n * black frame to obtain a valid avcC) and return a complete H.264\n * init segment that MSE can immediately ingest.\n * - Media segment: decode HEVC, re-encode to H.264, mux fMP4, return.\n *\n * Returns a raw `Uint8Array` rather than `{data, init}` so the same\n * code path works on Shaka 4.x (which expects a `Uint8Array` directly)\n * and on Shaka 5+ (which accepts either via an `ArrayBuffer.isView`\n * check). Init/media segmentation is implicit in the call sequence.\n */\n async transmux(\n data: BufferSource,\n _stream: ShakaStream,\n reference: ShakaSegmentReference,\n _duration: number,\n _contentType: string,\n ): Promise<TransmuxOutput> {\n const bytes = toUint8(data);\n const isInit = reference == null || isInitSegment(bytes);\n\n if (!this.transcoder_) {\n const workerUrl = this.transcoderConfig_.workerUrl;\n if (workerUrl) {\n const worker = new TranscodeWorkerClient({\n ...this.transcoderConfig_,\n workerUrl,\n });\n this.transcoder_ = worker;\n this.initPromise_ = worker.waitReady();\n console.log(\n `[hevc.js/shaka] HEVC transcoding routed through Worker at ${workerUrl}`,\n );\n } else {\n const local = new SegmentTranscoder(this.transcoderConfig_);\n this.transcoder_ = local;\n this.initPromise_ = local.init();\n console.log(\n \"[hevc.js/shaka] HEVC transcoding runs on main thread (no workerUrl provided)\",\n );\n }\n }\n await this.initPromise_;\n\n if (isInit) {\n // Short-circuit when Shaka resends the exact same init bytes (variant\n // probe, transmuxer re-check). Going through prepareInit again would\n // close the live H.264 encoder and the next media segment would stall\n // while a new one warms up — the visible \"stutter every segment\"\n // symptom that motivated this cache. Real ABR switches arrive with\n // different bytes and fall through to the full prepareInit path.\n if (this.cachedH264Init_ && bytesEqual(bytes, this.lastHevcInitBytes_)) {\n const copy = new Uint8Array(this.cachedH264Init_.byteLength);\n copy.set(this.cachedH264Init_);\n return copy;\n }\n\n // Snapshot the input bytes *before* prepareInit. The worker variant\n // transfers `bytes.buffer` to the worker, which detaches it on the\n // main thread — so reading from `bytes` after the await would throw\n // \"TypedArray.set on a detached ArrayBuffer\".\n const initBytesSnapshot = new Uint8Array(bytes.byteLength);\n initBytesSnapshot.set(bytes);\n\n const result = await this.transcoder_!.prepareInit(bytes);\n this.h264InitEmitted_ = true;\n\n // Snapshot the output immediately. Then commit both snapshots to the\n // cache fields atomically.\n const h264InitCopy = new Uint8Array(result.initSegment.byteLength);\n h264InitCopy.set(result.initSegment);\n this.lastHevcInitBytes_ = initBytesSnapshot;\n this.cachedH264Init_ = h264InitCopy;\n\n // Defensive copy for the return value — never hand MSE a view into\n // our cache.\n const copy = new Uint8Array(h264InitCopy.byteLength);\n copy.set(h264InitCopy);\n return copy;\n }\n\n const h264Media = await this.transcoder_!.processMediaSegment(bytes);\n if (!h264Media) {\n // No frames produced (e.g. drop frames in adaptive switching). Emit\n // a spec-valid `free` box of 8 bytes — empty buffers crash Chrome's\n // appendBuffer with \"Overload resolution failed\".\n return FREE_BOX_8B;\n }\n return h264Media;\n }\n}\n\nfunction bytesEqual(a: Uint8Array, b: Uint8Array | null): boolean {\n if (!b || a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction toUint8(data: BufferSource): Uint8Array {\n if (data instanceof Uint8Array) return data;\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return new Uint8Array(\n (data as ArrayBufferView).buffer,\n (data as ArrayBufferView).byteOffset,\n (data as ArrayBufferView).byteLength,\n );\n}\n","/**\n * Compute-aware ABR adapter for Shaka Player.\n *\n * Subscribes to the per-segment perf bus published by `@hevcjs/core` and,\n * when transcode throughput drifts away from a healthy `speedX`, narrows\n * the variants Shaka's own ABR controller is allowed to choose from. The\n * host ABR algorithm is never replaced — we just move the upper bound via\n * the public `player.configure({ abr: { restrictions } })` API.\n *\n * Why this exists: a variant that's reachable from a network-bandwidth\n * standpoint can still saturate the device's WASM-decode + WebCodecs-encode\n * budget, draining the buffer without Shaka's ABR ever noticing. Mainstream\n * ABR algorithms only look at network because fetch+parse+MSE-append is\n * essentially free in their world. With our transcode pipeline, it isn't.\n */\nimport {\n ComputeAwareDecider,\n subscribeSegmentStat,\n} from \"@hevcjs/core\";\nimport type {\n ComputeAwareConfig,\n SegmentPerfStat,\n} from \"@hevcjs/core\";\n\n// Loose typing while we don't pull `shaka.extern.*` into the build.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaPlayer = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaVariantTrack = any;\n\n/**\n * One entry of the ladder we feed to the decider. Heights are optional —\n * some manifests only differ in bandwidth (audio-only ladders excluded).\n */\ninterface LadderRank {\n height?: number;\n /** Per-variant video bandwidth in bits/sec (preferred), else total bandwidth. */\n bandwidth: number;\n}\n\nexport interface ShakaComputeAwareOptions extends ComputeAwareConfig {\n /**\n * Optional sink for telemetry — called on every observation, not just\n * cap changes. Useful for plotting speedX over time in a demo.\n *\n * `reason` is the decider's verdict on this observation:\n * `init` (window not full yet) | `hold` (no change) |\n * `lower` (cap stepped down) | `raise` (cap stepped up).\n */\n onObservation?: (\n stat: SegmentPerfStat,\n avgSpeedX: number,\n capIndex: number | null,\n reason: \"init\" | \"hold\" | \"lower\" | \"raise\",\n ) => void;\n}\n\n/**\n * Attach the compute-aware ABR feedback loop to a Shaka player.\n *\n * Must be called after `new shaka.Player()`. Safe to call before\n * `player.load()`: variants are looked up lazily as segments arrive.\n *\n * @returns cleanup function — unsubscribes the perf-bus listener.\n * Does NOT clear any restriction already applied to the player. If you\n * want to restore an unbounded ABR, call\n * `player.configure({ abr: { restrictions: { maxHeight: Infinity, maxBandwidth: Infinity }}})`\n * after detaching.\n */\nexport function attachShakaComputeAware(\n player: ShakaPlayer,\n options: ShakaComputeAwareOptions = {},\n): () => void {\n const { onObservation, ...deciderConfig } = options;\n const decider = new ComputeAwareDecider(deciderConfig);\n\n const unsubscribe = subscribeSegmentStat((stat: SegmentPerfStat) => {\n const ladder = readLadder(player);\n if (ladder.length === 0) return; // manifest not loaded yet, or audio-only\n\n decider.setLadderSize(ladder.length);\n const currentIdx = findCurrentIndex(player, ladder);\n const decision = decider.observe(stat.speedX, currentIdx);\n\n if (onObservation) {\n try {\n onObservation(stat, decision.avgSpeedX, decision.capIndex, decision.reason);\n } catch {\n // a buggy telemetry sink must not break ABR\n }\n }\n\n if (decision.reason === \"lower\" || decision.reason === \"raise\") {\n try {\n applyCap(player, ladder, decision.capIndex!);\n } catch (err) {\n // Player may be in a destroyed/invalid state. Rolling back the\n // decider keeps it in sync with what the player actually has\n // configured — otherwise the next decision thinks the cap is\n // already lower (or higher) than it really is.\n decider.revertLastDecision();\n // eslint-disable-next-line no-console\n console.warn(\"[hevc.js/shaka] applyCap failed, reverted decider:\", err);\n }\n }\n });\n\n return unsubscribe;\n}\n\n/**\n * Build a deduplicated, ascending ladder from `player.getVariantTracks()`.\n * Dedup key prefers `height` (the natural quality axis), falls back to\n * `videoBandwidth` for height-less manifests.\n */\nfunction readLadder(player: ShakaPlayer): LadderRank[] {\n const variants: ShakaVariantTrack[] = player.getVariantTracks?.() ?? [];\n const seen = new Map<string, LadderRank>();\n\n for (const v of variants) {\n // Skip audio-only / text variants. Anything with a height or a video\n // bandwidth/codec qualifies as a video variant.\n const hasVideo =\n v.height != null ||\n v.videoBandwidth != null ||\n (v.videoCodec != null && v.videoCodec !== \"\");\n if (!hasVideo) continue;\n\n const bw = (v.videoBandwidth ?? v.bandwidth ?? 0) as number;\n const key = v.height != null ? `h:${v.height}` : `b:${bw}`;\n if (seen.has(key)) continue;\n seen.set(key, {\n height: v.height ?? undefined,\n bandwidth: bw,\n });\n }\n\n const ladder = Array.from(seen.values());\n ladder.sort((a, b) => {\n if (a.height != null && b.height != null) return a.height - b.height;\n return a.bandwidth - b.bandwidth;\n });\n return ladder;\n}\n\nfunction findCurrentIndex(player: ShakaPlayer, ladder: LadderRank[]): number {\n const variants: ShakaVariantTrack[] = player.getVariantTracks?.() ?? [];\n const active = variants.find((t) => t.active);\n if (!active) return ladder.length - 1;\n\n if (active.height != null) {\n const idx = ladder.findIndex((v) => v.height === active.height);\n if (idx >= 0) return idx;\n }\n const bw = (active.videoBandwidth ?? active.bandwidth ?? 0) as number;\n const idx = ladder.findIndex((v) => v.bandwidth === bw);\n return idx >= 0 ? idx : ladder.length - 1;\n}\n\nfunction applyCap(player: ShakaPlayer, ladder: LadderRank[], capIndex: number): void {\n const cap = ladder[capIndex];\n if (!cap || typeof player.configure !== \"function\") return;\n\n // Always cap by bandwidth (universal). Add maxHeight when the manifest\n // exposes heights — gives Shaka a more direct signal than bytes/sec.\n const restrictions: Record<string, number> = {\n maxBandwidth: cap.bandwidth,\n };\n if (cap.height != null) restrictions.maxHeight = cap.height;\n\n player.configure({ abr: { restrictions } });\n}\n","/**\n * Shaka Player HEVC Plugin — public entry point.\n *\n * Usage (main thread, no Worker):\n * ```ts\n * import shaka from 'shaka-player';\n * import { registerHevcTransmuxer } from '@hevcjs/shaka-plugin';\n *\n * registerHevcTransmuxer(shaka, { wasmUrl: '/hevc-decode.js' });\n * const player = new shaka.Player();\n * await player.attach(videoElement);\n * await player.load(manifestUrl);\n * ```\n *\n * Usage (off-main-thread via Web Worker — recommended for 4K / smoothness):\n * ```ts\n * registerHevcTransmuxer(shaka, {\n * wasmUrl: '/hevc-decode.js',\n * workerUrl: '/transcode-worker.js',\n * });\n * ```\n *\n * Compute-aware ABR is ON by default — Shaka's bandwidth-based ABR keeps\n * choosing freely while we narrow the ceiling when the device can't keep\n * up. The player is supplied later via `attachComputeAware`:\n * ```ts\n * const handle = registerHevcTransmuxer(shaka, {\n * wasmUrl: '/hevc-decode.js',\n * workerUrl: '/transcode-worker.js',\n * // adaptiveCompute is ON by default.\n * // To opt out: adaptiveCompute: false\n * // To tune: adaptiveCompute: { targetSpeedX: 1.5, lowerAfter: 1 }\n * });\n * const player = new shaka.Player();\n * handle.attachComputeAware(player); // wire the feedback loop\n * await player.load(manifestUrl);\n * // ...\n * handle(); // unregister + detach (callable)\n * ```\n *\n * To force the transmuxer even on browsers with native HEVC support\n * (Safari, recent Chrome on macOS), use Shaka's built-in config rather\n * than patching MSE yourself:\n *\n * ```ts\n * player.configure({ mediaSource: { forceTransmux: true } });\n * ```\n */\n\nimport { HevcTransmuxer } from \"./transmuxer.js\";\nimport type { HevcTransmuxerConfig } from \"./transmuxer.js\";\nimport { attachShakaComputeAware } from \"./compute-aware.js\";\nimport type { ShakaComputeAwareOptions } from \"./compute-aware.js\";\n\nexport { HevcTransmuxer } from \"./transmuxer.js\";\nexport type { TransmuxOutput, HevcTransmuxerConfig } from \"./transmuxer.js\";\nexport { attachShakaComputeAware } from \"./compute-aware.js\";\nexport type { ShakaComputeAwareOptions } from \"./compute-aware.js\";\n// Re-export the perf-bus surface so consumers can subscribe to per-segment\n// transcode stats (speedX, frames, resolution) without depending on\n// @hevcjs/core directly.\nexport { subscribeSegmentStat } from \"@hevcjs/core\";\nexport type { SegmentPerfStat } from \"@hevcjs/core\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaNamespace = any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ShakaPlayer = any;\n\n/**\n * Plugin configuration. Forwarded as-is to `HevcTransmuxer`. Supports the\n * `SegmentTranscoderConfig` fields (`wasmUrl`, `wasmBinaryUrl`, `fps`,\n * `bitrate`) plus an optional `workerUrl` that, when set, routes the\n * HEVC decode + H.264 encode pipeline through a Web Worker, plus an\n * optional `adaptiveCompute` flag/config to enable the compute-aware\n * ABR feedback loop.\n */\nexport interface HevcShakaPluginConfig extends HevcTransmuxerConfig {\n /**\n * Compute-aware ABR feedback. The returned handle exposes\n * `attachComputeAware(player)` that wires the host Shaka player to the\n * transcode perf bus and caps variants when the device can't keep up.\n *\n * - **On by default** (undefined or `true`) — sensible defaults.\n * - Pass an object to tune the decider knobs (`targetSpeedX`, etc.).\n * - Pass `false` to opt out: `attachComputeAware` becomes a silent no-op.\n *\n * `attachComputeAware(player)` must still be called explicitly because\n * the player instance isn't available at register time.\n */\n adaptiveCompute?: boolean | ShakaComputeAwareOptions;\n}\n\n/**\n * Return shape of `registerHevcTransmuxer`. Callable for backwards compat\n * (`handle()` unregisters the transmuxer, same as before). Methods are\n * attached as properties when `adaptiveCompute` is enabled so the existing\n * `const cleanup = registerHevcTransmuxer(...)` pattern still works.\n */\nexport interface HevcShakaPluginHandle {\n (): void;\n /** Explicit alias for the callable form. */\n unregister(): void;\n /**\n * Attach the compute-aware feedback loop to a Shaka player.\n * Active by default; becomes a silent no-op only when the registration\n * config explicitly passed `adaptiveCompute: false`.\n *\n * Options passed here are merged on top of any options passed at\n * register time, which is convenient when the telemetry sink\n * (`onObservation`) is only available once the UI exists.\n *\n * @returns cleanup function — detaches the perf-bus listener.\n */\n attachComputeAware(player: ShakaPlayer, options?: ShakaComputeAwareOptions): () => void;\n}\n\nconst HEVC_MIME_TYPES = [\n 'video/mp4; codecs=\"hev1\"',\n 'video/mp4; codecs=\"hvc1\"',\n];\n\n/**\n * Register the HEVC transmuxer with Shaka's TransmuxerEngine.\n *\n * Must be called before `player.load()`. Registers a factory for both\n * `hev1` and `hvc1` MIME types at APPLICATION priority so Shaka picks\n * our transmuxer over any default fallback.\n *\n * @param shaka the global `shaka` namespace (import or window.shaka)\n * @param config forwarded to `HevcTransmuxer` (wasmUrl, wasmBinaryUrl, fps, bitrate, workerUrl, adaptiveCompute)\n * @returns A handle that is both callable (unregisters) and exposes\n * `attachComputeAware(player)` when `adaptiveCompute` is enabled.\n */\nexport function registerHevcTransmuxer(\n shaka: ShakaNamespace,\n config: HevcShakaPluginConfig = {},\n): HevcShakaPluginHandle {\n const engine = shaka?.transmuxer?.TransmuxerEngine;\n if (!engine || typeof engine.registerTransmuxer !== \"function\") {\n console.warn(\n \"[hevc.js/shaka] shaka.transmuxer.TransmuxerEngine.registerTransmuxer not found. \" +\n \"Make sure shaka-player >= 4.0 is loaded before calling registerHevcTransmuxer().\",\n );\n return makeHandle(() => {}, undefined);\n }\n\n // External (application-supplied) plugins should register at the\n // APPLICATION priority so they override any built-in fallback. Values in\n // shaka.transmuxer.TransmuxerEngine.PluginPriority: FALLBACK=1,\n // PREFERRED_SECONDARY=2, PREFERRED=3, APPLICATION=4.\n const priority =\n engine.PluginPriority?.APPLICATION ??\n engine.PluginPriority?.PREFERRED ??\n 4;\n\n const { adaptiveCompute, ...transmuxerConfig } = config;\n\n for (const mimeType of HEVC_MIME_TYPES) {\n engine.registerTransmuxer(\n mimeType,\n () => new HevcTransmuxer(mimeType, transmuxerConfig),\n priority,\n );\n }\n\n const unregister = () => {\n if (typeof engine.unregisterTransmuxer === \"function\") {\n for (const mimeType of HEVC_MIME_TYPES) {\n // unregisterTransmuxer keys on `${mime}-${priority}` so the\n // priority used at register time must be passed back here.\n engine.unregisterTransmuxer(mimeType, priority);\n }\n }\n };\n\n return makeHandle(unregister, adaptiveCompute);\n}\n\n/**\n * Build the callable+methods handle. Keeping the callable form preserves\n * the pre-existing `const cleanup = registerHevcTransmuxer(...); cleanup();`\n * pattern; the `attachComputeAware` property is added only when the feature\n * is enabled, but a no-op is always present so consumers can call it\n * unconditionally without a type guard.\n *\n * Unified cleanup: invoking the handle (or `unregister()`) tears down both\n * the transmuxer registration AND any active compute-aware listener, so the\n * caller doesn't have to remember a separate detach. Matches the dash.js\n * plugin's `attachHevcSupport` cleanup behaviour.\n */\nfunction makeHandle(\n unregister: () => void,\n adaptive: boolean | ShakaComputeAwareOptions | undefined,\n): HevcShakaPluginHandle {\n let activeDetach: (() => void) | null = null;\n\n const tearDown = () => {\n activeDetach?.();\n activeDetach = null;\n unregister();\n };\n\n const fn = (() => tearDown()) as HevcShakaPluginHandle;\n fn.unregister = tearDown;\n fn.attachComputeAware = (\n player: ShakaPlayer,\n runtimeOpts?: ShakaComputeAwareOptions,\n ): () => void => {\n // Explicit opt-out is the only path that disables the feature.\n // `undefined` (no flag) → on; `true` → on; object → on with options.\n if (adaptive === false) return () => {};\n // Re-attaching replaces any previous listener — keep the handle's\n // tearDown able to free the *current* one.\n activeDetach?.();\n // Merge register-time options with attach-time options; attach-time\n // wins on conflicts so the caller can override defaults set early\n // (typical case: onObservation is only known once the UI exists).\n const registerOpts = typeof adaptive === \"object\" ? adaptive : {};\n const opts = { ...registerOpts, ...(runtimeOpts ?? {}) };\n const detach = attachShakaComputeAware(player, opts);\n activeDetach = detach;\n return () => {\n detach();\n if (activeDetach === detach) activeDetach = null;\n };\n };\n return fn;\n}\n"],"mappings":"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","names":["copy","idx","subscribeSegmentStat"]}
|
package/dist/transmuxer.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export declare class HevcTransmuxer {
|
|
|
43
43
|
private initPromise_;
|
|
44
44
|
private pendingHevcInit_;
|
|
45
45
|
private h264InitEmitted_;
|
|
46
|
+
private lastHevcInitBytes_;
|
|
47
|
+
private cachedH264Init_;
|
|
46
48
|
constructor(mimeType: string, config?: HevcTransmuxerConfig);
|
|
47
49
|
destroy(): void;
|
|
48
50
|
isSupported(mimeType: string, _contentType?: string): boolean;
|
package/dist/transmuxer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transmuxer.d.ts","sourceRoot":"","sources":["../src/transmuxer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D;;;;GAIG;AACH,MAAM,WAAW,oBAAqB,SAAQ,uBAAuB;IACnE,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,KAAK,WAAW,GAAG,GAAG,CAAC;AAEvB,KAAK,qBAAqB,GAAG,GAAG,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAgBxC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CASxD;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAuB;IACzD,OAAO,CAAC,WAAW,CAA0D;IAC7E,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,gBAAgB,CAAS;
|
|
1
|
+
{"version":3,"file":"transmuxer.d.ts","sourceRoot":"","sources":["../src/transmuxer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D;;;;GAIG;AACH,MAAM,WAAW,oBAAqB,SAAQ,uBAAuB;IACnE,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,KAAK,WAAW,GAAG,GAAG,CAAC;AAEvB,KAAK,qBAAqB,GAAG,GAAG,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAgBxC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CASxD;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAuB;IACzD,OAAO,CAAC,WAAW,CAA0D;IAC7E,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,gBAAgB,CAAS;IAQjC,OAAO,CAAC,kBAAkB,CAA2B;IACrD,OAAO,CAAC,eAAe,CAA2B;gBAEtC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,oBAAyB;IAK/D,OAAO,IAAI,IAAI;IAUf,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO;IAI7D;;;;;;OAMG;IACH,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAK7D,mBAAmB,IAAI,MAAM;IAI7B;;;;;;;;;;;;;;;OAeG;IACG,QAAQ,CACZ,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,qBAAqB,EAChC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC;CAyE3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hevcjs/shaka-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shaka Player plugin for HEVC/H.265 playback — registers a Shaka Transmuxer that decodes HEVC streams via @hevcjs/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"url": "https://github.com/privaloops/hevc.js/issues"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@hevcjs/core": "1.
|
|
52
|
+
"@hevcjs/core": "1.3.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"shaka-player": ">=4.0.0"
|