@sarakusha/ebml 0.0.4 → 0.0.6
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/CHANGELOG.md +17 -0
- package/build/FadeTransform.cjs +36 -22
- package/build/FadeTransform.cjs.map +1 -1
- package/build/FadeTransform.d.mts +1 -0
- package/build/FadeTransform.d.mts.map +1 -1
- package/build/FadeTransform.mjs +36 -22
- package/build/FadeTransform.mjs.map +1 -1
- package/build/VideoChunkGenerator.cjs +42 -14
- package/build/VideoChunkGenerator.cjs.map +1 -1
- package/build/VideoChunkGenerator.d.mts.map +1 -1
- package/build/VideoChunkGenerator.mjs +43 -15
- package/build/VideoChunkGenerator.mjs.map +1 -1
- package/build/VideoFrameGenerator.cjs +52 -33
- package/build/VideoFrameGenerator.cjs.map +1 -1
- package/build/VideoFrameGenerator.mjs +52 -33
- package/build/VideoFrameGenerator.mjs.map +1 -1
- package/build/index.cjs.map +1 -1
- package/build/index.mjs.map +1 -1
- package/package.json +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,4 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.6](https://github.com/sarakusha/ebml/compare/v0.0.5...v0.0.6) (2026-06-25)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* не работал на смешанных (аудио+видео) данных ([c85d24f](https://github.com/sarakusha/ebml/commit/c85d24fa0424785af35849c430a57c238f2fd07e))
|
|
11
|
+
* parse video track by TrackNumber in VideoChunkGenerator ([ba83550](https://github.com/sarakusha/ebml/commit/ba8355026467ed8b3e1a2d5d29b68ff32bfc8b16))
|
|
12
|
+
|
|
13
|
+
### [0.0.5](https://github.com/sarakusha/ebml/compare/v0.0.4...v0.0.5) (2026-05-26)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* keep video frame metadata during fade transforms ([3230d4d](https://github.com/sarakusha/ebml/commit/3230d4d635453a91155ba0e06f376cfc9c518539))
|
|
19
|
+
* preserve decoded frames through flush ([1cd3bd5](https://github.com/sarakusha/ebml/commit/1cd3bd521a98456e5be29afb201c2ee2f30e6aa8))
|
|
20
|
+
* tests ([e03e665](https://github.com/sarakusha/ebml/commit/e03e6654ffdaac596afa85711ef13c5851e27d52))
|
|
21
|
+
|
|
5
22
|
### 0.0.4 (2026-05-25)
|
package/build/FadeTransform.cjs
CHANGED
|
@@ -4,27 +4,34 @@ Object.defineProperties(exports, {
|
|
|
4
4
|
});
|
|
5
5
|
const require_Deferred = require("./Deferred-CB69mtbF.cjs");
|
|
6
6
|
//#region src/FadeTransform.ts
|
|
7
|
-
const easeOutCubic = (t, b, c, d) =>
|
|
7
|
+
const easeOutCubic = (t, b, c, d) => {
|
|
8
|
+
const normalized = t / d - 1;
|
|
9
|
+
return c * (normalized * normalized * normalized + 1) + b;
|
|
10
|
+
};
|
|
8
11
|
var FadeTransform = class extends TransformStream {
|
|
9
12
|
setDisableFadeOut;
|
|
13
|
+
setDisableFadeIn;
|
|
10
14
|
constructor({ disableIn, disableOut, duration: fadeDuration = 200 } = {}) {
|
|
11
15
|
let deferred;
|
|
12
16
|
let last;
|
|
13
17
|
let ctx = null;
|
|
14
|
-
let init
|
|
18
|
+
let init;
|
|
19
|
+
let disableFadeIn = disableIn;
|
|
15
20
|
let disableFadeOut = disableOut;
|
|
16
21
|
const initialize = (chunk) => {
|
|
17
|
-
if (!ctx)
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
displayWidth,
|
|
22
|
-
displayHeight,
|
|
23
|
-
visibleRect: visibleRect ?? void 0,
|
|
24
|
-
duration: duration ?? void 0,
|
|
25
|
-
timestamp: timestamp ?? void 0
|
|
26
|
-
};
|
|
22
|
+
if (!ctx) {
|
|
23
|
+
const width = chunk.displayWidth || chunk.codedWidth;
|
|
24
|
+
const height = chunk.displayHeight || chunk.codedHeight;
|
|
25
|
+
ctx = new OffscreenCanvas(width, height).getContext("2d");
|
|
27
26
|
}
|
|
27
|
+
const { displayWidth, displayHeight, visibleRect, duration, timestamp } = chunk;
|
|
28
|
+
init = {
|
|
29
|
+
displayWidth,
|
|
30
|
+
displayHeight,
|
|
31
|
+
visibleRect: visibleRect ?? void 0,
|
|
32
|
+
duration: duration ?? void 0,
|
|
33
|
+
timestamp: timestamp ?? 0
|
|
34
|
+
};
|
|
28
35
|
};
|
|
29
36
|
super({
|
|
30
37
|
transform(chunk, controller) {
|
|
@@ -32,16 +39,18 @@ var FadeTransform = class extends TransformStream {
|
|
|
32
39
|
last?.close();
|
|
33
40
|
last = chunk.clone();
|
|
34
41
|
}
|
|
35
|
-
const timestamp = chunk.timestamp
|
|
36
|
-
if (!
|
|
37
|
-
if (!ctx) initialize(chunk);
|
|
38
|
-
if (ctx) {
|
|
42
|
+
const timestamp = chunk.timestamp ?? 0;
|
|
43
|
+
if (!disableFadeIn && timestamp / 1e3 < fadeDuration) {
|
|
44
|
+
if (!ctx || !init) initialize(chunk);
|
|
45
|
+
if (ctx && init) {
|
|
39
46
|
const brightness = easeOutCubic(timestamp / 1e3, 0, 100, fadeDuration);
|
|
40
47
|
ctx.filter = `brightness(${brightness}%)`;
|
|
41
48
|
ctx.drawImage(chunk, 0, 0);
|
|
42
49
|
chunk.close();
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const frame = new VideoFrame(ctx.canvas, {
|
|
51
|
+
...init,
|
|
52
|
+
timestamp
|
|
53
|
+
});
|
|
45
54
|
controller.enqueue(frame);
|
|
46
55
|
return;
|
|
47
56
|
}
|
|
@@ -50,8 +59,8 @@ var FadeTransform = class extends TransformStream {
|
|
|
50
59
|
},
|
|
51
60
|
flush: async (controller) => {
|
|
52
61
|
if (last && last.timestamp && !disableFadeOut) {
|
|
53
|
-
if (!ctx) initialize(last);
|
|
54
|
-
if (ctx) {
|
|
62
|
+
if (!ctx || !init) initialize(last);
|
|
63
|
+
if (ctx && init) {
|
|
55
64
|
deferred = new require_Deferred.Deferred();
|
|
56
65
|
const context = ctx;
|
|
57
66
|
const orig = last;
|
|
@@ -60,8 +69,10 @@ var FadeTransform = class extends TransformStream {
|
|
|
60
69
|
const fadeOut = (time) => {
|
|
61
70
|
context.filter = `brightness(${easeOutCubic(Math.max(0, fadeDuration - time), 0, 100, fadeDuration)}%)`;
|
|
62
71
|
context.drawImage(orig, 0, 0);
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
const frame = new VideoFrame(context.canvas, {
|
|
73
|
+
...init,
|
|
74
|
+
timestamp: start + time * 1e3
|
|
75
|
+
});
|
|
65
76
|
controller.enqueue(frame);
|
|
66
77
|
const next = time + duration;
|
|
67
78
|
if (next > fadeDuration) {
|
|
@@ -82,6 +93,9 @@ var FadeTransform = class extends TransformStream {
|
|
|
82
93
|
last = void 0;
|
|
83
94
|
}
|
|
84
95
|
};
|
|
96
|
+
this.setDisableFadeIn = (value) => {
|
|
97
|
+
disableFadeIn = value;
|
|
98
|
+
};
|
|
85
99
|
}
|
|
86
100
|
};
|
|
87
101
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FadeTransform.cjs","names":["Deferred"],"sources":["../src/FadeTransform.ts"],"sourcesContent":["import Deferred from './Deferred';\n\nexport type FadeOptions = {\n disableIn?: boolean;\n disableOut?: boolean;\n duration?: number;\n};\n\nconst easeOutCubic = (t: number, b: number, c: number, d: number)
|
|
1
|
+
{"version":3,"file":"FadeTransform.cjs","names":["Deferred"],"sources":["../src/FadeTransform.ts"],"sourcesContent":["import Deferred from './Deferred';\n\nexport type FadeOptions = {\n disableIn?: boolean;\n disableOut?: boolean;\n duration?: number;\n};\n\nconst easeOutCubic = (t: number, b: number, c: number, d: number) => {\n const normalized = t / d - 1;\n return c * (normalized * normalized * normalized + 1) + b;\n};\n\nexport default class FadeTransform extends TransformStream<VideoFrame, VideoFrame> {\n setDisableFadeOut: (value?: boolean) => void;\n\n setDisableFadeIn: (value?: boolean) => void;\n\n constructor({ disableIn, disableOut, duration: fadeDuration = 200 }: FadeOptions = {}) {\n let deferred: Deferred | undefined;\n let last: VideoFrame | undefined;\n let ctx: OffscreenCanvasRenderingContext2D | null = null;\n let init: VideoFrameInit | undefined;\n let disableFadeIn = disableIn;\n let disableFadeOut = disableOut;\n const initialize = (chunk: VideoFrame): void => {\n if (!ctx) {\n const width = chunk.displayWidth || chunk.codedWidth;\n const height = chunk.displayHeight || chunk.codedHeight;\n const offscreen = new OffscreenCanvas(width, height);\n ctx = offscreen.getContext('2d');\n }\n const { displayWidth, displayHeight, visibleRect, duration, timestamp } = chunk;\n init = {\n displayWidth,\n displayHeight,\n visibleRect: visibleRect ?? undefined,\n duration: duration ?? undefined,\n timestamp: timestamp ?? 0,\n };\n };\n super({\n transform(chunk, controller) {\n if (!disableFadeOut) {\n last?.close();\n last = chunk.clone();\n }\n const timestamp = chunk.timestamp ?? 0;\n if (!disableFadeIn && timestamp / 1000 < fadeDuration) {\n if (!ctx || !init) initialize(chunk);\n if (ctx && init) {\n const brightness = easeOutCubic(timestamp / 1000, 0, 100, fadeDuration);\n ctx.filter = `brightness(${brightness}%)`;\n ctx.drawImage(chunk, 0, 0);\n chunk.close();\n const frame = new VideoFrame(ctx.canvas as unknown as ImageBitmap, {\n ...init,\n timestamp,\n });\n controller.enqueue(frame);\n return;\n }\n }\n controller.enqueue(chunk);\n },\n flush: async (controller) => {\n if (last && last.timestamp && !disableFadeOut) {\n if (!ctx || !init) initialize(last);\n if (ctx && init) {\n deferred = new Deferred();\n const context = ctx;\n const orig = last;\n const { timestamp: start } = last;\n const duration = last.duration ? last.duration / 1000 : 33;\n const fadeOut = (time: number) => {\n const brightness = easeOutCubic(\n Math.max(0, fadeDuration - time),\n 0,\n 100,\n fadeDuration,\n );\n context.filter = `brightness(${brightness}%)`;\n context.drawImage(orig, 0, 0);\n const frame = new VideoFrame(context.canvas as unknown as ImageBitmap, {\n ...init,\n timestamp: start + time * 1000,\n });\n controller.enqueue(frame);\n const next = time + duration;\n if (next > fadeDuration) {\n orig.close();\n deferred?.resolve();\n } else setTimeout(() => fadeOut(next), 0);\n };\n fadeOut(duration);\n }\n }\n await deferred?.promise;\n },\n });\n this.setDisableFadeOut = (value?: boolean) => {\n disableFadeOut = value;\n if (disableFadeOut) {\n last?.close();\n last = undefined;\n }\n };\n this.setDisableFadeIn = (value?: boolean) => {\n disableFadeIn = value;\n };\n }\n}\n"],"mappings":";;;;;;AAQA,MAAM,gBAAgB,GAAW,GAAW,GAAW,MAAc;CACnE,MAAM,aAAa,IAAI,IAAI;CAC3B,OAAO,KAAK,aAAa,aAAa,aAAa,KAAK;AAC1D;AAEA,IAAqB,gBAArB,cAA2C,gBAAwC;CACjF;CAEA;CAEA,YAAY,EAAE,WAAW,YAAY,UAAU,eAAe,QAAqB,CAAC,GAAG;EACrF,IAAI;EACJ,IAAI;EACJ,IAAI,MAAgD;EACpD,IAAI;EACJ,IAAI,gBAAgB;EACpB,IAAI,iBAAiB;EACrB,MAAM,cAAc,UAA4B;GAC9C,IAAI,CAAC,KAAK;IACR,MAAM,QAAQ,MAAM,gBAAgB,MAAM;IAC1C,MAAM,SAAS,MAAM,iBAAiB,MAAM;IAE5C,MAAM,IADgB,gBAAgB,OAAO,MAC/B,EAAE,WAAW,IAAI;GACjC;GACA,MAAM,EAAE,cAAc,eAAe,aAAa,UAAU,cAAc;GAC1E,OAAO;IACL;IACA;IACA,aAAa,eAAe,KAAA;IAC5B,UAAU,YAAY,KAAA;IACtB,WAAW,aAAa;GAC1B;EACF;EACA,MAAM;GACJ,UAAU,OAAO,YAAY;IAC3B,IAAI,CAAC,gBAAgB;KACnB,MAAM,MAAM;KACZ,OAAO,MAAM,MAAM;IACrB;IACA,MAAM,YAAY,MAAM,aAAa;IACrC,IAAI,CAAC,iBAAiB,YAAY,MAAO,cAAc;KACrD,IAAI,CAAC,OAAO,CAAC,MAAM,WAAW,KAAK;KACnC,IAAI,OAAO,MAAM;MACf,MAAM,aAAa,aAAa,YAAY,KAAM,GAAG,KAAK,YAAY;MACtE,IAAI,SAAS,cAAc,WAAW;MACtC,IAAI,UAAU,OAAO,GAAG,CAAC;MACzB,MAAM,MAAM;MACZ,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAkC;OACjE,GAAG;OACH;MACF,CAAC;MACD,WAAW,QAAQ,KAAK;MACxB;KACF;IACF;IACA,WAAW,QAAQ,KAAK;GAC1B;GACA,OAAO,OAAO,eAAe;IAC3B,IAAI,QAAQ,KAAK,aAAa,CAAC,gBAAgB;KAC7C,IAAI,CAAC,OAAO,CAAC,MAAM,WAAW,IAAI;KAClC,IAAI,OAAO,MAAM;MACf,WAAW,IAAIA,iBAAAA,SAAS;MACxB,MAAM,UAAU;MAChB,MAAM,OAAO;MACb,MAAM,EAAE,WAAW,UAAU;MAC7B,MAAM,WAAW,KAAK,WAAW,KAAK,WAAW,MAAO;MACxD,MAAM,WAAW,SAAiB;OAOhC,QAAQ,SAAS,cANE,aACjB,KAAK,IAAI,GAAG,eAAe,IAAI,GAC/B,GACA,KACA,YAEsC,EAAE;OAC1C,QAAQ,UAAU,MAAM,GAAG,CAAC;OAC5B,MAAM,QAAQ,IAAI,WAAW,QAAQ,QAAkC;QACrE,GAAG;QACH,WAAW,QAAQ,OAAO;OAC5B,CAAC;OACD,WAAW,QAAQ,KAAK;OACxB,MAAM,OAAO,OAAO;OACpB,IAAI,OAAO,cAAc;QACvB,KAAK,MAAM;QACX,UAAU,QAAQ;OACpB,OAAO,iBAAiB,QAAQ,IAAI,GAAG,CAAC;MAC1C;MACA,QAAQ,QAAQ;KAClB;IACF;IACA,MAAM,UAAU;GAClB;EACF,CAAC;EACD,KAAK,qBAAqB,UAAoB;GAC5C,iBAAiB;GACjB,IAAI,gBAAgB;IAClB,MAAM,MAAM;IACZ,OAAO,KAAA;GACT;EACF;EACA,KAAK,oBAAoB,UAAoB;GAC3C,gBAAgB;EAClB;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FadeTransform.d.mts","names":[],"sources":["../src/FadeTransform.ts"],"mappings":";KAEY,WAAA;EACV,SAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"FadeTransform.d.mts","names":[],"sources":["../src/FadeTransform.ts"],"mappings":";KAEY,WAAA;EACV,SAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,cAQmB,aAAA,SAAsB,eAAA,CAAgB,UAAA,EAAY,UAAA;EACrE,iBAAA,GAAoB,KAAA;EAEpB,gBAAA,GAAmB,KAAA;;IAEL,SAAA;IAAW,UAAA;IAAY,QAAA,EAAU;EAAA,IAAsB,WAAA;AAAA"}
|
package/build/FadeTransform.mjs
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { t as Deferred } from "./Deferred-7Cu0KIje.mjs";
|
|
2
2
|
//#region src/FadeTransform.ts
|
|
3
|
-
const easeOutCubic = (t, b, c, d) =>
|
|
3
|
+
const easeOutCubic = (t, b, c, d) => {
|
|
4
|
+
const normalized = t / d - 1;
|
|
5
|
+
return c * (normalized * normalized * normalized + 1) + b;
|
|
6
|
+
};
|
|
4
7
|
var FadeTransform = class extends TransformStream {
|
|
5
8
|
setDisableFadeOut;
|
|
9
|
+
setDisableFadeIn;
|
|
6
10
|
constructor({ disableIn, disableOut, duration: fadeDuration = 200 } = {}) {
|
|
7
11
|
let deferred;
|
|
8
12
|
let last;
|
|
9
13
|
let ctx = null;
|
|
10
|
-
let init
|
|
14
|
+
let init;
|
|
15
|
+
let disableFadeIn = disableIn;
|
|
11
16
|
let disableFadeOut = disableOut;
|
|
12
17
|
const initialize = (chunk) => {
|
|
13
|
-
if (!ctx)
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
displayWidth,
|
|
18
|
-
displayHeight,
|
|
19
|
-
visibleRect: visibleRect ?? void 0,
|
|
20
|
-
duration: duration ?? void 0,
|
|
21
|
-
timestamp: timestamp ?? void 0
|
|
22
|
-
};
|
|
18
|
+
if (!ctx) {
|
|
19
|
+
const width = chunk.displayWidth || chunk.codedWidth;
|
|
20
|
+
const height = chunk.displayHeight || chunk.codedHeight;
|
|
21
|
+
ctx = new OffscreenCanvas(width, height).getContext("2d");
|
|
23
22
|
}
|
|
23
|
+
const { displayWidth, displayHeight, visibleRect, duration, timestamp } = chunk;
|
|
24
|
+
init = {
|
|
25
|
+
displayWidth,
|
|
26
|
+
displayHeight,
|
|
27
|
+
visibleRect: visibleRect ?? void 0,
|
|
28
|
+
duration: duration ?? void 0,
|
|
29
|
+
timestamp: timestamp ?? 0
|
|
30
|
+
};
|
|
24
31
|
};
|
|
25
32
|
super({
|
|
26
33
|
transform(chunk, controller) {
|
|
@@ -28,16 +35,18 @@ var FadeTransform = class extends TransformStream {
|
|
|
28
35
|
last?.close();
|
|
29
36
|
last = chunk.clone();
|
|
30
37
|
}
|
|
31
|
-
const timestamp = chunk.timestamp
|
|
32
|
-
if (!
|
|
33
|
-
if (!ctx) initialize(chunk);
|
|
34
|
-
if (ctx) {
|
|
38
|
+
const timestamp = chunk.timestamp ?? 0;
|
|
39
|
+
if (!disableFadeIn && timestamp / 1e3 < fadeDuration) {
|
|
40
|
+
if (!ctx || !init) initialize(chunk);
|
|
41
|
+
if (ctx && init) {
|
|
35
42
|
const brightness = easeOutCubic(timestamp / 1e3, 0, 100, fadeDuration);
|
|
36
43
|
ctx.filter = `brightness(${brightness}%)`;
|
|
37
44
|
ctx.drawImage(chunk, 0, 0);
|
|
38
45
|
chunk.close();
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
const frame = new VideoFrame(ctx.canvas, {
|
|
47
|
+
...init,
|
|
48
|
+
timestamp
|
|
49
|
+
});
|
|
41
50
|
controller.enqueue(frame);
|
|
42
51
|
return;
|
|
43
52
|
}
|
|
@@ -46,8 +55,8 @@ var FadeTransform = class extends TransformStream {
|
|
|
46
55
|
},
|
|
47
56
|
flush: async (controller) => {
|
|
48
57
|
if (last && last.timestamp && !disableFadeOut) {
|
|
49
|
-
if (!ctx) initialize(last);
|
|
50
|
-
if (ctx) {
|
|
58
|
+
if (!ctx || !init) initialize(last);
|
|
59
|
+
if (ctx && init) {
|
|
51
60
|
deferred = new Deferred();
|
|
52
61
|
const context = ctx;
|
|
53
62
|
const orig = last;
|
|
@@ -56,8 +65,10 @@ var FadeTransform = class extends TransformStream {
|
|
|
56
65
|
const fadeOut = (time) => {
|
|
57
66
|
context.filter = `brightness(${easeOutCubic(Math.max(0, fadeDuration - time), 0, 100, fadeDuration)}%)`;
|
|
58
67
|
context.drawImage(orig, 0, 0);
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
const frame = new VideoFrame(context.canvas, {
|
|
69
|
+
...init,
|
|
70
|
+
timestamp: start + time * 1e3
|
|
71
|
+
});
|
|
61
72
|
controller.enqueue(frame);
|
|
62
73
|
const next = time + duration;
|
|
63
74
|
if (next > fadeDuration) {
|
|
@@ -78,6 +89,9 @@ var FadeTransform = class extends TransformStream {
|
|
|
78
89
|
last = void 0;
|
|
79
90
|
}
|
|
80
91
|
};
|
|
92
|
+
this.setDisableFadeIn = (value) => {
|
|
93
|
+
disableFadeIn = value;
|
|
94
|
+
};
|
|
81
95
|
}
|
|
82
96
|
};
|
|
83
97
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FadeTransform.mjs","names":[],"sources":["../src/FadeTransform.ts"],"sourcesContent":["import Deferred from './Deferred';\n\nexport type FadeOptions = {\n disableIn?: boolean;\n disableOut?: boolean;\n duration?: number;\n};\n\nconst easeOutCubic = (t: number, b: number, c: number, d: number)
|
|
1
|
+
{"version":3,"file":"FadeTransform.mjs","names":[],"sources":["../src/FadeTransform.ts"],"sourcesContent":["import Deferred from './Deferred';\n\nexport type FadeOptions = {\n disableIn?: boolean;\n disableOut?: boolean;\n duration?: number;\n};\n\nconst easeOutCubic = (t: number, b: number, c: number, d: number) => {\n const normalized = t / d - 1;\n return c * (normalized * normalized * normalized + 1) + b;\n};\n\nexport default class FadeTransform extends TransformStream<VideoFrame, VideoFrame> {\n setDisableFadeOut: (value?: boolean) => void;\n\n setDisableFadeIn: (value?: boolean) => void;\n\n constructor({ disableIn, disableOut, duration: fadeDuration = 200 }: FadeOptions = {}) {\n let deferred: Deferred | undefined;\n let last: VideoFrame | undefined;\n let ctx: OffscreenCanvasRenderingContext2D | null = null;\n let init: VideoFrameInit | undefined;\n let disableFadeIn = disableIn;\n let disableFadeOut = disableOut;\n const initialize = (chunk: VideoFrame): void => {\n if (!ctx) {\n const width = chunk.displayWidth || chunk.codedWidth;\n const height = chunk.displayHeight || chunk.codedHeight;\n const offscreen = new OffscreenCanvas(width, height);\n ctx = offscreen.getContext('2d');\n }\n const { displayWidth, displayHeight, visibleRect, duration, timestamp } = chunk;\n init = {\n displayWidth,\n displayHeight,\n visibleRect: visibleRect ?? undefined,\n duration: duration ?? undefined,\n timestamp: timestamp ?? 0,\n };\n };\n super({\n transform(chunk, controller) {\n if (!disableFadeOut) {\n last?.close();\n last = chunk.clone();\n }\n const timestamp = chunk.timestamp ?? 0;\n if (!disableFadeIn && timestamp / 1000 < fadeDuration) {\n if (!ctx || !init) initialize(chunk);\n if (ctx && init) {\n const brightness = easeOutCubic(timestamp / 1000, 0, 100, fadeDuration);\n ctx.filter = `brightness(${brightness}%)`;\n ctx.drawImage(chunk, 0, 0);\n chunk.close();\n const frame = new VideoFrame(ctx.canvas as unknown as ImageBitmap, {\n ...init,\n timestamp,\n });\n controller.enqueue(frame);\n return;\n }\n }\n controller.enqueue(chunk);\n },\n flush: async (controller) => {\n if (last && last.timestamp && !disableFadeOut) {\n if (!ctx || !init) initialize(last);\n if (ctx && init) {\n deferred = new Deferred();\n const context = ctx;\n const orig = last;\n const { timestamp: start } = last;\n const duration = last.duration ? last.duration / 1000 : 33;\n const fadeOut = (time: number) => {\n const brightness = easeOutCubic(\n Math.max(0, fadeDuration - time),\n 0,\n 100,\n fadeDuration,\n );\n context.filter = `brightness(${brightness}%)`;\n context.drawImage(orig, 0, 0);\n const frame = new VideoFrame(context.canvas as unknown as ImageBitmap, {\n ...init,\n timestamp: start + time * 1000,\n });\n controller.enqueue(frame);\n const next = time + duration;\n if (next > fadeDuration) {\n orig.close();\n deferred?.resolve();\n } else setTimeout(() => fadeOut(next), 0);\n };\n fadeOut(duration);\n }\n }\n await deferred?.promise;\n },\n });\n this.setDisableFadeOut = (value?: boolean) => {\n disableFadeOut = value;\n if (disableFadeOut) {\n last?.close();\n last = undefined;\n }\n };\n this.setDisableFadeIn = (value?: boolean) => {\n disableFadeIn = value;\n };\n }\n}\n"],"mappings":";;AAQA,MAAM,gBAAgB,GAAW,GAAW,GAAW,MAAc;CACnE,MAAM,aAAa,IAAI,IAAI;CAC3B,OAAO,KAAK,aAAa,aAAa,aAAa,KAAK;AAC1D;AAEA,IAAqB,gBAArB,cAA2C,gBAAwC;CACjF;CAEA;CAEA,YAAY,EAAE,WAAW,YAAY,UAAU,eAAe,QAAqB,CAAC,GAAG;EACrF,IAAI;EACJ,IAAI;EACJ,IAAI,MAAgD;EACpD,IAAI;EACJ,IAAI,gBAAgB;EACpB,IAAI,iBAAiB;EACrB,MAAM,cAAc,UAA4B;GAC9C,IAAI,CAAC,KAAK;IACR,MAAM,QAAQ,MAAM,gBAAgB,MAAM;IAC1C,MAAM,SAAS,MAAM,iBAAiB,MAAM;IAE5C,MAAM,IADgB,gBAAgB,OAAO,MAC/B,EAAE,WAAW,IAAI;GACjC;GACA,MAAM,EAAE,cAAc,eAAe,aAAa,UAAU,cAAc;GAC1E,OAAO;IACL;IACA;IACA,aAAa,eAAe,KAAA;IAC5B,UAAU,YAAY,KAAA;IACtB,WAAW,aAAa;GAC1B;EACF;EACA,MAAM;GACJ,UAAU,OAAO,YAAY;IAC3B,IAAI,CAAC,gBAAgB;KACnB,MAAM,MAAM;KACZ,OAAO,MAAM,MAAM;IACrB;IACA,MAAM,YAAY,MAAM,aAAa;IACrC,IAAI,CAAC,iBAAiB,YAAY,MAAO,cAAc;KACrD,IAAI,CAAC,OAAO,CAAC,MAAM,WAAW,KAAK;KACnC,IAAI,OAAO,MAAM;MACf,MAAM,aAAa,aAAa,YAAY,KAAM,GAAG,KAAK,YAAY;MACtE,IAAI,SAAS,cAAc,WAAW;MACtC,IAAI,UAAU,OAAO,GAAG,CAAC;MACzB,MAAM,MAAM;MACZ,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAkC;OACjE,GAAG;OACH;MACF,CAAC;MACD,WAAW,QAAQ,KAAK;MACxB;KACF;IACF;IACA,WAAW,QAAQ,KAAK;GAC1B;GACA,OAAO,OAAO,eAAe;IAC3B,IAAI,QAAQ,KAAK,aAAa,CAAC,gBAAgB;KAC7C,IAAI,CAAC,OAAO,CAAC,MAAM,WAAW,IAAI;KAClC,IAAI,OAAO,MAAM;MACf,WAAW,IAAI,SAAS;MACxB,MAAM,UAAU;MAChB,MAAM,OAAO;MACb,MAAM,EAAE,WAAW,UAAU;MAC7B,MAAM,WAAW,KAAK,WAAW,KAAK,WAAW,MAAO;MACxD,MAAM,WAAW,SAAiB;OAOhC,QAAQ,SAAS,cANE,aACjB,KAAK,IAAI,GAAG,eAAe,IAAI,GAC/B,GACA,KACA,YAEsC,EAAE;OAC1C,QAAQ,UAAU,MAAM,GAAG,CAAC;OAC5B,MAAM,QAAQ,IAAI,WAAW,QAAQ,QAAkC;QACrE,GAAG;QACH,WAAW,QAAQ,OAAO;OAC5B,CAAC;OACD,WAAW,QAAQ,KAAK;OACxB,MAAM,OAAO,OAAO;OACpB,IAAI,OAAO,cAAc;QACvB,KAAK,MAAM;QACX,UAAU,QAAQ;OACpB,OAAO,iBAAiB,QAAQ,IAAI,GAAG,CAAC;MAC1C;MACA,QAAQ,QAAQ;KAClB;IACF;IACA,MAAM,UAAU;GAClB;EACF,CAAC;EACD,KAAK,qBAAqB,UAAoB;GAC5C,iBAAiB;GACjB,IAAI,gBAAgB;IAClB,MAAM,MAAM;IACZ,OAAO,KAAA;GACT;EACF;EACA,KAAK,oBAAoB,UAAoB;GAC3C,gBAAgB;EAClB;CACF;AACF"}
|
|
@@ -23,20 +23,23 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
23
23
|
}
|
|
24
24
|
id = Date.now().toString(16).slice(-6);
|
|
25
25
|
constructor() {
|
|
26
|
+
let inTrackEntry = false;
|
|
27
|
+
let trackNumber;
|
|
26
28
|
let trackType;
|
|
29
|
+
let videoTrackNumber;
|
|
27
30
|
let offset = 0;
|
|
28
31
|
let block;
|
|
29
32
|
let hasReference = false;
|
|
30
33
|
let total = 0;
|
|
31
|
-
const
|
|
32
|
-
const elements = {};
|
|
33
|
-
const required = {
|
|
34
|
+
const createRequired = () => ({
|
|
34
35
|
PixelWidth: "codedWidth",
|
|
35
36
|
PixelHeight: "codedHeight",
|
|
36
37
|
CodecPrivate: "codec",
|
|
37
38
|
CodecID: "codec"
|
|
38
|
-
};
|
|
39
|
-
|
|
39
|
+
});
|
|
40
|
+
let trackConfig = {};
|
|
41
|
+
let trackRequired = createRequired();
|
|
42
|
+
const parseConfig = (meta, config, required) => {
|
|
40
43
|
switch (meta.name) {
|
|
41
44
|
case "CodecID": {
|
|
42
45
|
const codec = getCodec(meta);
|
|
@@ -58,19 +61,44 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
58
61
|
if (name) config[name] = meta.value;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
delete required[meta.name];
|
|
61
65
|
};
|
|
66
|
+
const isTrackNumber = (element) => element.name === "TrackNumber" && require_Element.isContentElement(element);
|
|
67
|
+
const isTrackType = (element) => element.name === "TrackType" && require_Element.isContentElement(element);
|
|
68
|
+
const isVideoBlock = (candidate) => videoTrackNumber != null && candidate.track === videoTrackNumber;
|
|
62
69
|
super({
|
|
63
70
|
transform: async (element, controller) => {
|
|
64
|
-
elements[element.name] = (elements[element.name] ?? 0) + 1;
|
|
65
71
|
try {
|
|
66
|
-
if (require_Element.
|
|
67
|
-
|
|
72
|
+
if (require_Element.isMasterElement(element) && element.name === "TrackEntry") {
|
|
73
|
+
if (element.isClosing) {
|
|
74
|
+
if (trackType === VIDEO && trackNumber != null) {
|
|
75
|
+
videoTrackNumber = trackNumber;
|
|
76
|
+
if (Object.keys(trackRequired).length === 0) this.#config.resolve(trackConfig);
|
|
77
|
+
}
|
|
78
|
+
inTrackEntry = false;
|
|
79
|
+
trackNumber = void 0;
|
|
80
|
+
trackType = void 0;
|
|
81
|
+
trackConfig = {};
|
|
82
|
+
trackRequired = createRequired();
|
|
83
|
+
} else {
|
|
84
|
+
inTrackEntry = true;
|
|
85
|
+
trackNumber = void 0;
|
|
86
|
+
trackType = void 0;
|
|
87
|
+
trackConfig = {};
|
|
88
|
+
trackRequired = createRequired();
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (inTrackEntry && isTrackNumber(element)) {
|
|
93
|
+
trackNumber = Number(element.value);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (inTrackEntry && isTrackType(element)) {
|
|
97
|
+
trackType = Number(element.value);
|
|
68
98
|
return;
|
|
69
99
|
}
|
|
70
|
-
if (require_Element.isContentElement(element) &&
|
|
71
|
-
parseConfig(element);
|
|
72
|
-
delete required[element.name];
|
|
73
|
-
if (Object.keys(required).length === 0) this.#config.resolve(config);
|
|
100
|
+
if (inTrackEntry && require_Element.isContentElement(element) && trackRequired[element.name]) {
|
|
101
|
+
parseConfig(element, trackConfig, trackRequired);
|
|
74
102
|
return;
|
|
75
103
|
}
|
|
76
104
|
if (require_Element.isTimestamp(element)) {
|
|
@@ -82,7 +110,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
82
110
|
return;
|
|
83
111
|
}
|
|
84
112
|
if (require_Element.isSimpleBlockElement(element)) {
|
|
85
|
-
if (
|
|
113
|
+
if (isVideoBlock(element)) {
|
|
86
114
|
controller.enqueue(new EncodedVideoChunk({
|
|
87
115
|
type: element.keyframe ? "key" : "delta",
|
|
88
116
|
timestamp: (offset + element.value) * 1e3,
|
|
@@ -101,7 +129,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
101
129
|
return;
|
|
102
130
|
}
|
|
103
131
|
if (require_Element.isBlockGroupElement(element) && element.isClosing) {
|
|
104
|
-
if (block) {
|
|
132
|
+
if (block && isVideoBlock(block)) {
|
|
105
133
|
controller.enqueue(new EncodedVideoChunk({
|
|
106
134
|
type: hasReference ? "delta" : "key",
|
|
107
135
|
timestamp: (block.value + offset) * 1e3,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.cjs","names":["Deferred","#config","readHexString","
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.cjs","names":["Deferred","#config","readHexString","isContentElement","isMasterElement","isTimestamp","isDuration","isSimpleBlockElement","isBlockElement","isBlockGroupElement"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n} from './Element';\nimport type { BlockElement, ContentElement, Element } from './Element';\nimport { readHexString } from './tools';\n\nconst VIDEO = 1;\n\nconst getCodec = (element: ContentElement<'string' | 'utf-8'>): string | undefined => {\n switch (element.value) {\n case 'V_MPEG4/ISO/AVC':\n return 'avc1.';\n case 'V_VP9':\n return 'vp09.00.50.08';\n case 'V_VP8':\n return 'vp8'; // 'vp08.00.41.08';\n default:\n return undefined;\n }\n};\n\nexport default class VideoChunkGenerator extends TransformStream<Element, EncodedVideoChunk> {\n #config = new Deferred<VideoDecoderConfig>();\n\n readonly clusters: number[] = [];\n\n get config(): Promise<VideoDecoderConfig> {\n return this.#config.promise;\n }\n\n readonly id: string = Date.now().toString(16).slice(-6);\n\n constructor() {\n let inTrackEntry = false;\n let trackNumber: number | undefined;\n let trackType: number | undefined;\n let videoTrackNumber: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const createRequired = (): Record<string, keyof VideoDecoderConfig> => ({\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n });\n let trackConfig: Partial<VideoDecoderConfig> = {};\n let trackRequired = createRequired();\n\n const parseConfig = (\n meta: ContentElement,\n config: Partial<VideoDecoderConfig>,\n required: Record<string, keyof VideoDecoderConfig>,\n ): void => {\n switch (meta.name) {\n case 'CodecID': {\n const codec = getCodec(meta as ContentElement<'string'>);\n if (codec) {\n config.codec = codec;\n if (!codec.endsWith('.')) {\n delete required.CodecPrivate;\n }\n }\n break;\n }\n case 'CodecPrivate':\n if (config.codec === 'avc1.') {\n const codecPrivate = meta as ContentElement<'binary'>;\n config.codec += readHexString(codecPrivate.data.subarray(1, 4));\n config.description = codecPrivate.data.slice();\n }\n break;\n default: {\n const name = required[meta.name];\n if (name) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config[name] = meta.value as any;\n }\n }\n }\n delete required[meta.name];\n };\n const isTrackNumber = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackNumber' && isContentElement(element);\n\n const isTrackType = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackType' && isContentElement(element);\n\n const isVideoBlock = (candidate: { track: number }): boolean =>\n videoTrackNumber != null && candidate.track === videoTrackNumber;\n\n super({\n transform: async (element, controller) => {\n try {\n if (isMasterElement(element) && element.name === 'TrackEntry') {\n if (element.isClosing) {\n if (trackType === VIDEO && trackNumber != null) {\n videoTrackNumber = trackNumber;\n if (Object.keys(trackRequired).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(trackConfig)}`);\n this.#config.resolve(trackConfig as VideoDecoderConfig);\n }\n }\n inTrackEntry = false;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n } else {\n inTrackEntry = true;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n }\n return;\n }\n\n if (inTrackEntry && isTrackNumber(element)) {\n trackNumber = Number(element.value);\n return;\n }\n\n if (inTrackEntry && isTrackType(element)) {\n trackType = Number(element.value);\n return;\n }\n if (\n inTrackEntry &&\n isContentElement(element) &&\n trackRequired[element.name]\n ) {\n parseConfig(element, trackConfig, trackRequired);\n return;\n }\n\n if (isTimestamp(element)) {\n offset = element.value as number;\n return;\n }\n\n if (isDuration(element)) {\n postMessage({ duration: element.value / 1000 });\n return;\n }\n\n if (isSimpleBlockElement(element)) {\n if (isVideoBlock(element)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: element.keyframe ? 'key' : 'delta',\n timestamp: (offset + element.value) * 1000,\n data: element.payload,\n }),\n );\n total += 1;\n }\n return;\n }\n\n if (isBlockElement(element)) {\n block = element;\n return;\n }\n\n if (element.name === 'ReferenceBlock') {\n hasReference = true;\n return;\n }\n\n if (isBlockGroupElement(element) && element.isClosing) {\n if (block && isVideoBlock(block)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: hasReference ? 'delta' : 'key',\n timestamp: (block.value + offset) * 1000,\n data: block.payload,\n }),\n );\n total += 1;\n }\n block = undefined;\n hasReference = false;\n }\n if (isMasterElement(element) && element.name === 'Cluster' && !element.isClosing)\n this.clusters.push(total);\n } catch (err) {\n console.error(`VideoChunkGenerator#${this.id} error while ELEMENT: ${err}`);\n controller.error(err);\n this.#config.reject(err as Error);\n }\n },\n flush: (controller) => {\n // console.info(\n // `VideoChunkGenerator#${this.id}:flush total: ${total}, clusters: ${this.clusters.join()}`\n // );\n controller.terminate();\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.d.mts","names":[],"sources":["../src/VideoChunkGenerator.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.d.mts","names":[],"sources":["../src/VideoChunkGenerator.ts"],"mappings":";;;cA4BqB,mBAAA,SAA4B,eAAA,CAAgB,OAAA,EAAS,iBAAA;EAAA;WAG/D,QAAA;EAAA,IAEL,MAAA,CAAA,GAAU,OAAA,CAAQ,kBAAA;EAAA,SAIb,EAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isDuration, c as isTimestamp, i as isContentElement,
|
|
1
|
+
import { a as isDuration, c as isTimestamp, i as isContentElement, n as isBlockElement, o as isMasterElement, r as isBlockGroupElement, s as isSimpleBlockElement } from "./Element-ChEar-fo.mjs";
|
|
2
2
|
import { o as readHexString } from "./tools-CYLlbo9J.mjs";
|
|
3
3
|
import { t as Deferred } from "./Deferred-7Cu0KIje.mjs";
|
|
4
4
|
//#region src/VideoChunkGenerator.ts
|
|
@@ -19,20 +19,23 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
19
19
|
}
|
|
20
20
|
id = Date.now().toString(16).slice(-6);
|
|
21
21
|
constructor() {
|
|
22
|
+
let inTrackEntry = false;
|
|
23
|
+
let trackNumber;
|
|
22
24
|
let trackType;
|
|
25
|
+
let videoTrackNumber;
|
|
23
26
|
let offset = 0;
|
|
24
27
|
let block;
|
|
25
28
|
let hasReference = false;
|
|
26
29
|
let total = 0;
|
|
27
|
-
const
|
|
28
|
-
const elements = {};
|
|
29
|
-
const required = {
|
|
30
|
+
const createRequired = () => ({
|
|
30
31
|
PixelWidth: "codedWidth",
|
|
31
32
|
PixelHeight: "codedHeight",
|
|
32
33
|
CodecPrivate: "codec",
|
|
33
34
|
CodecID: "codec"
|
|
34
|
-
};
|
|
35
|
-
|
|
35
|
+
});
|
|
36
|
+
let trackConfig = {};
|
|
37
|
+
let trackRequired = createRequired();
|
|
38
|
+
const parseConfig = (meta, config, required) => {
|
|
36
39
|
switch (meta.name) {
|
|
37
40
|
case "CodecID": {
|
|
38
41
|
const codec = getCodec(meta);
|
|
@@ -54,19 +57,44 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
54
57
|
if (name) config[name] = meta.value;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
60
|
+
delete required[meta.name];
|
|
57
61
|
};
|
|
62
|
+
const isTrackNumber = (element) => element.name === "TrackNumber" && isContentElement(element);
|
|
63
|
+
const isTrackType = (element) => element.name === "TrackType" && isContentElement(element);
|
|
64
|
+
const isVideoBlock = (candidate) => videoTrackNumber != null && candidate.track === videoTrackNumber;
|
|
58
65
|
super({
|
|
59
66
|
transform: async (element, controller) => {
|
|
60
|
-
elements[element.name] = (elements[element.name] ?? 0) + 1;
|
|
61
67
|
try {
|
|
62
|
-
if (
|
|
63
|
-
|
|
68
|
+
if (isMasterElement(element) && element.name === "TrackEntry") {
|
|
69
|
+
if (element.isClosing) {
|
|
70
|
+
if (trackType === VIDEO && trackNumber != null) {
|
|
71
|
+
videoTrackNumber = trackNumber;
|
|
72
|
+
if (Object.keys(trackRequired).length === 0) this.#config.resolve(trackConfig);
|
|
73
|
+
}
|
|
74
|
+
inTrackEntry = false;
|
|
75
|
+
trackNumber = void 0;
|
|
76
|
+
trackType = void 0;
|
|
77
|
+
trackConfig = {};
|
|
78
|
+
trackRequired = createRequired();
|
|
79
|
+
} else {
|
|
80
|
+
inTrackEntry = true;
|
|
81
|
+
trackNumber = void 0;
|
|
82
|
+
trackType = void 0;
|
|
83
|
+
trackConfig = {};
|
|
84
|
+
trackRequired = createRequired();
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (inTrackEntry && isTrackNumber(element)) {
|
|
89
|
+
trackNumber = Number(element.value);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (inTrackEntry && isTrackType(element)) {
|
|
93
|
+
trackType = Number(element.value);
|
|
64
94
|
return;
|
|
65
95
|
}
|
|
66
|
-
if (isContentElement(element) &&
|
|
67
|
-
parseConfig(element);
|
|
68
|
-
delete required[element.name];
|
|
69
|
-
if (Object.keys(required).length === 0) this.#config.resolve(config);
|
|
96
|
+
if (inTrackEntry && isContentElement(element) && trackRequired[element.name]) {
|
|
97
|
+
parseConfig(element, trackConfig, trackRequired);
|
|
70
98
|
return;
|
|
71
99
|
}
|
|
72
100
|
if (isTimestamp(element)) {
|
|
@@ -78,7 +106,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
78
106
|
return;
|
|
79
107
|
}
|
|
80
108
|
if (isSimpleBlockElement(element)) {
|
|
81
|
-
if (
|
|
109
|
+
if (isVideoBlock(element)) {
|
|
82
110
|
controller.enqueue(new EncodedVideoChunk({
|
|
83
111
|
type: element.keyframe ? "key" : "delta",
|
|
84
112
|
timestamp: (offset + element.value) * 1e3,
|
|
@@ -97,7 +125,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
97
125
|
return;
|
|
98
126
|
}
|
|
99
127
|
if (isBlockGroupElement(element) && element.isClosing) {
|
|
100
|
-
if (block) {
|
|
128
|
+
if (block && isVideoBlock(block)) {
|
|
101
129
|
controller.enqueue(new EncodedVideoChunk({
|
|
102
130
|
type: hasReference ? "delta" : "key",
|
|
103
131
|
timestamp: (block.value + offset) * 1e3,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.mjs","names":["#config"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.mjs","names":["#config"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n} from './Element';\nimport type { BlockElement, ContentElement, Element } from './Element';\nimport { readHexString } from './tools';\n\nconst VIDEO = 1;\n\nconst getCodec = (element: ContentElement<'string' | 'utf-8'>): string | undefined => {\n switch (element.value) {\n case 'V_MPEG4/ISO/AVC':\n return 'avc1.';\n case 'V_VP9':\n return 'vp09.00.50.08';\n case 'V_VP8':\n return 'vp8'; // 'vp08.00.41.08';\n default:\n return undefined;\n }\n};\n\nexport default class VideoChunkGenerator extends TransformStream<Element, EncodedVideoChunk> {\n #config = new Deferred<VideoDecoderConfig>();\n\n readonly clusters: number[] = [];\n\n get config(): Promise<VideoDecoderConfig> {\n return this.#config.promise;\n }\n\n readonly id: string = Date.now().toString(16).slice(-6);\n\n constructor() {\n let inTrackEntry = false;\n let trackNumber: number | undefined;\n let trackType: number | undefined;\n let videoTrackNumber: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const createRequired = (): Record<string, keyof VideoDecoderConfig> => ({\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n });\n let trackConfig: Partial<VideoDecoderConfig> = {};\n let trackRequired = createRequired();\n\n const parseConfig = (\n meta: ContentElement,\n config: Partial<VideoDecoderConfig>,\n required: Record<string, keyof VideoDecoderConfig>,\n ): void => {\n switch (meta.name) {\n case 'CodecID': {\n const codec = getCodec(meta as ContentElement<'string'>);\n if (codec) {\n config.codec = codec;\n if (!codec.endsWith('.')) {\n delete required.CodecPrivate;\n }\n }\n break;\n }\n case 'CodecPrivate':\n if (config.codec === 'avc1.') {\n const codecPrivate = meta as ContentElement<'binary'>;\n config.codec += readHexString(codecPrivate.data.subarray(1, 4));\n config.description = codecPrivate.data.slice();\n }\n break;\n default: {\n const name = required[meta.name];\n if (name) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config[name] = meta.value as any;\n }\n }\n }\n delete required[meta.name];\n };\n const isTrackNumber = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackNumber' && isContentElement(element);\n\n const isTrackType = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackType' && isContentElement(element);\n\n const isVideoBlock = (candidate: { track: number }): boolean =>\n videoTrackNumber != null && candidate.track === videoTrackNumber;\n\n super({\n transform: async (element, controller) => {\n try {\n if (isMasterElement(element) && element.name === 'TrackEntry') {\n if (element.isClosing) {\n if (trackType === VIDEO && trackNumber != null) {\n videoTrackNumber = trackNumber;\n if (Object.keys(trackRequired).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(trackConfig)}`);\n this.#config.resolve(trackConfig as VideoDecoderConfig);\n }\n }\n inTrackEntry = false;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n } else {\n inTrackEntry = true;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n }\n return;\n }\n\n if (inTrackEntry && isTrackNumber(element)) {\n trackNumber = Number(element.value);\n return;\n }\n\n if (inTrackEntry && isTrackType(element)) {\n trackType = Number(element.value);\n return;\n }\n if (\n inTrackEntry &&\n isContentElement(element) &&\n trackRequired[element.name]\n ) {\n parseConfig(element, trackConfig, trackRequired);\n return;\n }\n\n if (isTimestamp(element)) {\n offset = element.value as number;\n return;\n }\n\n if (isDuration(element)) {\n postMessage({ duration: element.value / 1000 });\n return;\n }\n\n if (isSimpleBlockElement(element)) {\n if (isVideoBlock(element)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: element.keyframe ? 'key' : 'delta',\n timestamp: (offset + element.value) * 1000,\n data: element.payload,\n }),\n );\n total += 1;\n }\n return;\n }\n\n if (isBlockElement(element)) {\n block = element;\n return;\n }\n\n if (element.name === 'ReferenceBlock') {\n hasReference = true;\n return;\n }\n\n if (isBlockGroupElement(element) && element.isClosing) {\n if (block && isVideoBlock(block)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: hasReference ? 'delta' : 'key',\n timestamp: (block.value + offset) * 1000,\n data: block.payload,\n }),\n );\n total += 1;\n }\n block = undefined;\n hasReference = false;\n }\n if (isMasterElement(element) && element.name === 'Cluster' && !element.isClosing)\n this.clusters.push(total);\n } catch (err) {\n console.error(`VideoChunkGenerator#${this.id} error while ELEMENT: ${err}`);\n controller.error(err);\n this.#config.reject(err as Error);\n }\n },\n flush: (controller) => {\n // console.info(\n // `VideoChunkGenerator#${this.id}:flush total: ${total}, clusters: ${this.clusters.join()}`\n // );\n controller.terminate();\n },\n });\n }\n}\n"],"mappings":"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"}
|