@sarakusha/ebml 0.0.4 → 0.0.5
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 +9 -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/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,13 @@
|
|
|
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.5](https://github.com/sarakusha/ebml/compare/v0.0.4...v0.0.5) (2026-05-26)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* keep video frame metadata during fade transforms ([3230d4d](https://github.com/sarakusha/ebml/commit/3230d4d635453a91155ba0e06f376cfc9c518539))
|
|
11
|
+
* preserve decoded frames through flush ([1cd3bd5](https://github.com/sarakusha/ebml/commit/1cd3bd521a98456e5be29afb201c2ee2f30e6aa8))
|
|
12
|
+
* tests ([e03e665](https://github.com/sarakusha/ebml/commit/e03e6654ffdaac596afa85711ef13c5851e27d52))
|
|
13
|
+
|
|
5
14
|
### 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"}
|
|
@@ -52,77 +52,96 @@ var VideoFrameGenerator = class {
|
|
|
52
52
|
constructor(config, maxPreloadFrames = MAX_PRELOAD_FRAMES) {
|
|
53
53
|
this.config = config;
|
|
54
54
|
const pendingFrames = [];
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const capacity = new Semaphore(maxPreloadFrames);
|
|
56
|
+
let readableController;
|
|
57
57
|
let decoder;
|
|
58
|
-
let cancel;
|
|
59
|
-
let abort;
|
|
60
58
|
let finished = false;
|
|
61
|
-
|
|
59
|
+
const closeDecoder = () => {
|
|
60
|
+
if (decoder && decoder.state !== "closed") decoder.close();
|
|
61
|
+
};
|
|
62
62
|
const clear = () => {
|
|
63
|
-
clearing = true;
|
|
64
63
|
pendingFrames.splice(0).forEach((frame) => frame.close());
|
|
65
|
-
|
|
66
|
-
pull.purge();
|
|
64
|
+
capacity.purge();
|
|
67
65
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
push.release();
|
|
76
|
-
} catch (e) {
|
|
77
|
-
controller.error(e);
|
|
66
|
+
const drain = () => {
|
|
67
|
+
if (!readableController) return;
|
|
68
|
+
while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {
|
|
69
|
+
const frame = pendingFrames.shift();
|
|
70
|
+
if (frame) {
|
|
71
|
+
readableController.enqueue(frame);
|
|
72
|
+
capacity.release();
|
|
78
73
|
}
|
|
74
|
+
}
|
|
75
|
+
if (finished && pendingFrames.length === 0) {
|
|
76
|
+
readableController.close();
|
|
77
|
+
readableController = void 0;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const fail = (reason) => {
|
|
81
|
+
clear();
|
|
82
|
+
closeDecoder();
|
|
83
|
+
readableController?.error(reason);
|
|
84
|
+
readableController = void 0;
|
|
85
|
+
};
|
|
86
|
+
this.readable = new ReadableStream({
|
|
87
|
+
start: (controller) => {
|
|
88
|
+
readableController = controller;
|
|
89
|
+
},
|
|
90
|
+
pull: () => {
|
|
91
|
+
drain();
|
|
79
92
|
},
|
|
80
93
|
cancel: (reason) => {
|
|
81
|
-
|
|
82
|
-
clear();
|
|
94
|
+
fail(reason);
|
|
83
95
|
}
|
|
84
96
|
}, new CountQueuingStrategy({ highWaterMark: 1 }));
|
|
85
97
|
this.writable = new WritableStream({
|
|
86
98
|
start: async (controller) => {
|
|
87
99
|
decoder = new VideoDecoder({
|
|
88
|
-
output:
|
|
100
|
+
output: (frame) => {
|
|
89
101
|
pendingFrames.push(frame);
|
|
90
|
-
|
|
102
|
+
drain();
|
|
91
103
|
},
|
|
92
104
|
error: (err) => {
|
|
93
105
|
console.error("error while decode", err);
|
|
94
106
|
controller.error(err);
|
|
107
|
+
fail(err);
|
|
95
108
|
}
|
|
96
109
|
});
|
|
97
110
|
try {
|
|
98
111
|
decoder.configure(await this.config);
|
|
99
112
|
} catch (err) {
|
|
100
113
|
controller.error(err);
|
|
114
|
+
fail(err);
|
|
101
115
|
console.error("error while configure", err);
|
|
102
116
|
}
|
|
103
117
|
},
|
|
104
118
|
write: async (chunk, controller) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
try {
|
|
120
|
+
await capacity.acquire();
|
|
121
|
+
if (!decoder || decoder.state === "closed") {
|
|
122
|
+
controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
108
125
|
decoder.decode(chunk);
|
|
109
126
|
} catch (e) {
|
|
110
|
-
if (
|
|
127
|
+
if (decoder?.state !== "closed") {
|
|
111
128
|
console.error("error while decode chunk", e);
|
|
112
129
|
controller.error(e);
|
|
113
130
|
}
|
|
114
131
|
}
|
|
115
132
|
},
|
|
116
133
|
close: async () => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
try {
|
|
135
|
+
await decoder?.flush();
|
|
136
|
+
closeDecoder();
|
|
137
|
+
finished = true;
|
|
138
|
+
drain();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
fail(err);
|
|
141
|
+
}
|
|
121
142
|
},
|
|
122
143
|
abort: (reason) => {
|
|
123
|
-
|
|
124
|
-
decoder.close();
|
|
125
|
-
clear();
|
|
144
|
+
fail(reason);
|
|
126
145
|
}
|
|
127
146
|
});
|
|
128
147
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoFrameGenerator.cjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const
|
|
1
|
+
{"version":3,"file":"VideoFrameGenerator.cjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n fail(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n try {\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n controller.error(new Error('VideoDecoder is closed.'));\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n closeDecoder();\n finished = true;\n drain();\n } catch (err) {\n fail(err);\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -48,77 +48,96 @@ var VideoFrameGenerator = class {
|
|
|
48
48
|
constructor(config, maxPreloadFrames = MAX_PRELOAD_FRAMES) {
|
|
49
49
|
this.config = config;
|
|
50
50
|
const pendingFrames = [];
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const capacity = new Semaphore(maxPreloadFrames);
|
|
52
|
+
let readableController;
|
|
53
53
|
let decoder;
|
|
54
|
-
let cancel;
|
|
55
|
-
let abort;
|
|
56
54
|
let finished = false;
|
|
57
|
-
|
|
55
|
+
const closeDecoder = () => {
|
|
56
|
+
if (decoder && decoder.state !== "closed") decoder.close();
|
|
57
|
+
};
|
|
58
58
|
const clear = () => {
|
|
59
|
-
clearing = true;
|
|
60
59
|
pendingFrames.splice(0).forEach((frame) => frame.close());
|
|
61
|
-
|
|
62
|
-
pull.purge();
|
|
60
|
+
capacity.purge();
|
|
63
61
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
push.release();
|
|
72
|
-
} catch (e) {
|
|
73
|
-
controller.error(e);
|
|
62
|
+
const drain = () => {
|
|
63
|
+
if (!readableController) return;
|
|
64
|
+
while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {
|
|
65
|
+
const frame = pendingFrames.shift();
|
|
66
|
+
if (frame) {
|
|
67
|
+
readableController.enqueue(frame);
|
|
68
|
+
capacity.release();
|
|
74
69
|
}
|
|
70
|
+
}
|
|
71
|
+
if (finished && pendingFrames.length === 0) {
|
|
72
|
+
readableController.close();
|
|
73
|
+
readableController = void 0;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const fail = (reason) => {
|
|
77
|
+
clear();
|
|
78
|
+
closeDecoder();
|
|
79
|
+
readableController?.error(reason);
|
|
80
|
+
readableController = void 0;
|
|
81
|
+
};
|
|
82
|
+
this.readable = new ReadableStream({
|
|
83
|
+
start: (controller) => {
|
|
84
|
+
readableController = controller;
|
|
85
|
+
},
|
|
86
|
+
pull: () => {
|
|
87
|
+
drain();
|
|
75
88
|
},
|
|
76
89
|
cancel: (reason) => {
|
|
77
|
-
|
|
78
|
-
clear();
|
|
90
|
+
fail(reason);
|
|
79
91
|
}
|
|
80
92
|
}, new CountQueuingStrategy({ highWaterMark: 1 }));
|
|
81
93
|
this.writable = new WritableStream({
|
|
82
94
|
start: async (controller) => {
|
|
83
95
|
decoder = new VideoDecoder({
|
|
84
|
-
output:
|
|
96
|
+
output: (frame) => {
|
|
85
97
|
pendingFrames.push(frame);
|
|
86
|
-
|
|
98
|
+
drain();
|
|
87
99
|
},
|
|
88
100
|
error: (err) => {
|
|
89
101
|
console.error("error while decode", err);
|
|
90
102
|
controller.error(err);
|
|
103
|
+
fail(err);
|
|
91
104
|
}
|
|
92
105
|
});
|
|
93
106
|
try {
|
|
94
107
|
decoder.configure(await this.config);
|
|
95
108
|
} catch (err) {
|
|
96
109
|
controller.error(err);
|
|
110
|
+
fail(err);
|
|
97
111
|
console.error("error while configure", err);
|
|
98
112
|
}
|
|
99
113
|
},
|
|
100
114
|
write: async (chunk, controller) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
try {
|
|
116
|
+
await capacity.acquire();
|
|
117
|
+
if (!decoder || decoder.state === "closed") {
|
|
118
|
+
controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
104
121
|
decoder.decode(chunk);
|
|
105
122
|
} catch (e) {
|
|
106
|
-
if (
|
|
123
|
+
if (decoder?.state !== "closed") {
|
|
107
124
|
console.error("error while decode chunk", e);
|
|
108
125
|
controller.error(e);
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
128
|
},
|
|
112
129
|
close: async () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
130
|
+
try {
|
|
131
|
+
await decoder?.flush();
|
|
132
|
+
closeDecoder();
|
|
133
|
+
finished = true;
|
|
134
|
+
drain();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
fail(err);
|
|
137
|
+
}
|
|
117
138
|
},
|
|
118
139
|
abort: (reason) => {
|
|
119
|
-
|
|
120
|
-
decoder.close();
|
|
121
|
-
clear();
|
|
140
|
+
fail(reason);
|
|
122
141
|
}
|
|
123
142
|
});
|
|
124
143
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoFrameGenerator.mjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const
|
|
1
|
+
{"version":3,"file":"VideoFrameGenerator.mjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n fail(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n try {\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n controller.error(new Error('VideoDecoder is closed.'));\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n closeDecoder();\n finished = true;\n drain();\n } catch (err) {\n fail(err);\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":";AAEA,IAAqB,YAArB,MAA+B;CAKR;CAJrB,WAAW;CAEX,WAA6B,CAAC;CAE9B,YAAY,MAAe,GAAG;EAAT,KAAA,MAAA;CAAU;CAE/B,OAA0B;EACxB,MAAM,UAAU,KAAKA,SAAS,MAAM;EACpC,IAAI,SAAS;GACX,QAAQ,QAAQ;GAChB,OAAO;EACT;EACA,OAAO;CACT;CAEA,UAAyB;EACvB,IAAI,KAAKC,WAAW,KAAK,KAAK;GAC5B,KAAKA,YAAY;GACjB,OAAO,QAAQ,QAAQ;EACzB;EACA,OAAO,IAAI,SAAe,SAAS,WAAW;GAC5C,KAAKD,SAAS,KAAK;IACjB;IACA;GACF,CAAC;EACH,CAAC;CACH;CAEA,UAAgB;EACd,IAAI,KAAK,KAAK,GAAG,KAAKC,YAAY;CACpC;CAEA,QAAQ;EACN,MAAM,aAAa,KAAKD,SAAS,OAAO,CAAC;EACzC,WAAW,SAAS,EAAE,aAAa;GACjC,uBAAO,IAAI,MAAM,uBAAuB,CAAC;EAC3C,CAAC;EACD,KAAKC,WAAW;EAChB,OAAO,WAAW;CACpB;AACF;;;ACzCA,MAAM,qBAAqB;AAE3B,IAAqB,sBAArB,MAAmG;CAMtF;CALX;CAEA;CAEA,YACE,QACA,mBAA2B,oBAC3B;EAFS,KAAA,SAAA;EAGT,MAAM,gBAA8B,CAAC;EACrC,MAAM,WAAW,IAAI,UAAU,gBAAgB;EAC/C,IAAI;EACJ,IAAI;EACJ,IAAI,WAAW;EAEf,MAAM,qBAAqB;GACzB,IAAI,WAAW,QAAQ,UAAU,UAAU,QAAQ,MAAM;EAC3D;EAEA,MAAM,cAAc;GAClB,cAAc,OAAO,CAAC,EAAE,SAAS,UAAU,MAAM,MAAM,CAAC;GACxD,SAAS,MAAM;EACjB;EAEA,MAAM,cAAc;GAClB,IAAI,CAAC,oBAAoB;GACzB,OAAO,cAAc,SAAS,MAAM,mBAAmB,eAAe,KAAK,GAAG;IAC5E,MAAM,QAAQ,cAAc,MAAM;IAClC,IAAI,OAAO;KACT,mBAAmB,QAAQ,KAAK;KAChC,SAAS,QAAQ;IACnB;GACF;GACA,IAAI,YAAY,cAAc,WAAW,GAAG;IAC1C,mBAAmB,MAAM;IACzB,qBAAqB,KAAA;GACvB;EACF;EAEA,MAAM,QAAQ,WAAoB;GAChC,MAAM;GACN,aAAa;GACb,oBAAoB,MAAM,MAAM;GAChC,qBAAqB,KAAA;EACvB;EAEA,KAAK,WAAW,IAAI,eAClB;GACE,QAAQ,eAAe;IACrB,qBAAqB;GACvB;GACA,YAAY;IACV,MAAM;GACR;GACA,SAAS,WAAW;IAClB,KAAK,MAAM;GACb;EACF,GACA,IAAI,qBAAqB,EAAE,eAAe,EAAE,CAAC,CAC/C;EACA,KAAK,WAAW,IAAI,eAAe;GACjC,OAAO,OAAO,eAAe;IAC3B,UAAU,IAAI,aAAa;KACzB,SAAS,UAAU;MACjB,cAAc,KAAK,KAAK;MACxB,MAAM;KACR;KACA,QAAQ,QAAQ;MACd,QAAQ,MAAM,sBAAsB,GAAG;MACvC,WAAW,MAAM,GAAG;MACpB,KAAK,GAAG;KACV;IACF,CAAC;IACD,IAAI;KACF,QAAQ,UAAU,MAAM,KAAK,MAAM;IACrC,SAAS,KAAK;KACZ,WAAW,MAAM,GAAG;KACpB,KAAK,GAAG;KACR,QAAQ,MAAM,yBAAyB,GAAG;IAC5C;GACF;GACA,OAAO,OAAO,OAAO,eAAe;IAClC,IAAI;KACF,MAAM,SAAS,QAAQ;KACvB,IAAI,CAAC,WAAW,QAAQ,UAAU,UAAU;MAC1C,WAAW,sBAAM,IAAI,MAAM,yBAAyB,CAAC;MACrD;KACF;KACA,QAAQ,OAAO,KAAK;IACtB,SAAS,GAAG;KACV,IAAI,SAAS,UAAU,UAAU;MAC/B,QAAQ,MAAM,4BAA4B,CAAC;MAC3C,WAAW,MAAM,CAAC;KACpB;IACF;GACF;GACA,OAAO,YAAY;IACjB,IAAI;KACF,MAAM,SAAS,MAAM;KACrB,aAAa;KACb,WAAW;KACX,MAAM;IACR,SAAS,KAAK;KACZ,KAAK,GAAG;IACV;GACF;GACA,QAAQ,WAAW;IACjB,KAAK,MAAM;GACb;EACF,CAAC;CACH;AACF"}
|