@yume-chan/scrcpy-decoder-webcodecs 0.0.24 → 1.0.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/CHANGELOG.md +31 -1
- package/README.md +49 -29
- package/esm/video/codec/h265.d.ts.map +1 -1
- package/esm/video/codec/h265.js +3 -0
- package/esm/video/codec/h265.js.map +1 -1
- package/esm/video/decoder.d.ts +14 -7
- package/esm/video/decoder.d.ts.map +1 -1
- package/esm/video/decoder.js +166 -67
- package/esm/video/decoder.js.map +1 -1
- package/esm/video/render/bitmap.d.ts +4 -4
- package/esm/video/render/bitmap.d.ts.map +1 -1
- package/esm/video/render/bitmap.js +9 -11
- package/esm/video/render/bitmap.js.map +1 -1
- package/esm/video/render/canvas.d.ts +9 -0
- package/esm/video/render/canvas.d.ts.map +1 -0
- package/esm/video/render/canvas.js +22 -0
- package/esm/video/render/canvas.js.map +1 -0
- package/esm/video/render/index.d.ts +2 -0
- package/esm/video/render/index.d.ts.map +1 -1
- package/esm/video/render/index.js +3 -0
- package/esm/video/render/index.js.map +1 -1
- package/esm/video/render/insertable-stream.d.ts +10 -0
- package/esm/video/render/insertable-stream.d.ts.map +1 -0
- package/esm/video/render/insertable-stream.js +45 -0
- package/esm/video/render/insertable-stream.js.map +1 -0
- package/esm/video/render/type.d.ts +4 -2
- package/esm/video/render/type.d.ts.map +1 -1
- package/esm/video/render/webgl.d.ts +5 -4
- package/esm/video/render/webgl.d.ts.map +1 -1
- package/esm/video/render/webgl.js +26 -14
- package/esm/video/render/webgl.js.map +1 -1
- package/package.json +11 -14
- package/src/video/codec/h265.ts +3 -0
- package/src/video/decoder.ts +197 -69
- package/src/video/render/bitmap.ts +11 -17
- package/src/video/render/canvas.ts +29 -0
- package/src/video/render/index.ts +4 -0
- package/src/video/render/insertable-stream.ts +62 -0
- package/src/video/render/type.ts +6 -2
- package/src/video/render/webgl.ts +47 -19
- package/tsconfig.build.json +4 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/CHANGELOG.json +0 -89
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createCanvas } from "@yume-chan/scrcpy-decoder-tinyh264";
|
|
2
|
+
export class CanvasWebCodecsDecoderRenderer {
|
|
3
|
+
#canvas;
|
|
4
|
+
get canvas() {
|
|
5
|
+
return this.#canvas;
|
|
6
|
+
}
|
|
7
|
+
constructor(canvas) {
|
|
8
|
+
if (canvas) {
|
|
9
|
+
this.#canvas = canvas;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
this.#canvas = createCanvas();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
setSize(width, height) {
|
|
16
|
+
if (this.#canvas.width !== width || this.#canvas.height !== height) {
|
|
17
|
+
this.#canvas.width = width;
|
|
18
|
+
this.#canvas.height = height;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../../src/video/render/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAIlE,MAAM,OAAgB,8BAA8B;IAGhD,OAAO,CAAsC;IAC7C,IAAI,MAAM;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IAED,YAAY,MAA4C;QACpD,IAAI,MAAM,EAAE,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,GAAG,YAAY,EAAE,CAAC;QAClC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,MAAc;QACjC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACjE,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;QACjC,CAAC;IACL,CAAC;CAGJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/video/render/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAE5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WebCodecsVideoDecoderRenderer } from "./type.js";
|
|
2
|
+
export declare class InsertableStreamWebCodecsDecoderRenderer implements WebCodecsVideoDecoderRenderer {
|
|
3
|
+
#private;
|
|
4
|
+
static get isSupported(): boolean;
|
|
5
|
+
get element(): HTMLVideoElement;
|
|
6
|
+
constructor(element?: HTMLVideoElement);
|
|
7
|
+
setSize(width: number, height: number): void;
|
|
8
|
+
draw(frame: VideoFrame): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=insertable-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insertable-stream.d.ts","sourceRoot":"","sources":["../../../src/video/render/insertable-stream.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAQ/D,qBAAa,wCACT,YAAW,6BAA6B;;IAExC,MAAM,KAAK,WAAW,YAErB;IAGD,IAAI,OAAO,qBAEV;gBAMW,OAAO,CAAC,EAAE,gBAAgB;IAyBtC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAOtC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// cspell: ignore insertable
|
|
2
|
+
export class InsertableStreamWebCodecsDecoderRenderer {
|
|
3
|
+
static get isSupported() {
|
|
4
|
+
return typeof MediaStreamTrackGenerator !== "undefined";
|
|
5
|
+
}
|
|
6
|
+
#element;
|
|
7
|
+
get element() {
|
|
8
|
+
return this.#element;
|
|
9
|
+
}
|
|
10
|
+
#generator;
|
|
11
|
+
#writer;
|
|
12
|
+
#stream;
|
|
13
|
+
constructor(element) {
|
|
14
|
+
if (element) {
|
|
15
|
+
this.#element = element;
|
|
16
|
+
}
|
|
17
|
+
else if (typeof document !== "undefined") {
|
|
18
|
+
this.#element = document.createElement("video");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw new Error("no video element input found nor any video element can be created");
|
|
22
|
+
}
|
|
23
|
+
this.#element.muted = true;
|
|
24
|
+
this.#element.autoplay = true;
|
|
25
|
+
this.#element.disablePictureInPicture = true;
|
|
26
|
+
this.#element.disableRemotePlayback = true;
|
|
27
|
+
// The spec replaced `MediaStreamTrackGenerator` with `VideoTrackGenerator`.
|
|
28
|
+
// But Chrome has not implemented it yet.
|
|
29
|
+
// https://issues.chromium.org/issues/40058895
|
|
30
|
+
this.#generator = new MediaStreamTrackGenerator({ kind: "video" });
|
|
31
|
+
this.#writer = this.#generator.writable.getWriter();
|
|
32
|
+
this.#stream = new MediaStream([this.#generator]);
|
|
33
|
+
this.#element.srcObject = this.#stream;
|
|
34
|
+
}
|
|
35
|
+
setSize(width, height) {
|
|
36
|
+
if (this.#element.width !== width || this.#element.height !== height) {
|
|
37
|
+
this.#element.width = width;
|
|
38
|
+
this.#element.height = height;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async draw(frame) {
|
|
42
|
+
await this.#writer.write(frame);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=insertable-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insertable-stream.js","sourceRoot":"","sources":["../../../src/video/render/insertable-stream.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAU5B,MAAM,OAAO,wCAAwC;IAGjD,MAAM,KAAK,WAAW;QAClB,OAAO,OAAO,yBAAyB,KAAK,WAAW,CAAC;IAC5D,CAAC;IAED,QAAQ,CAAmB;IAC3B,IAAI,OAAO;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,UAAU,CAA4B;IACtC,OAAO,CAA0C;IACjD,OAAO,CAAc;IAErB,YAAY,OAA0B;QAClC,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAC5B,CAAC;aAAM,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACJ,MAAM,IAAI,KAAK,CACX,mEAAmE,CACtE,CAAC;QACN,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,uBAAuB,GAAG,IAAI,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,qBAAqB,GAAG,IAAI,CAAC;QAE3C,4EAA4E;QAC5E,yCAAyC;QACzC,8CAA8C;QAC9C,IAAI,CAAC,UAAU,GAAG,IAAI,yBAAyB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QAEpD,IAAI,CAAC,OAAO,GAAG,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC;IAC3C,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,MAAc;QACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACnE,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;QAClC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAiB;QACxB,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;CACJ"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
2
|
+
export interface WebCodecsVideoDecoderRenderer {
|
|
3
|
+
setSize(width: number, height: number): void;
|
|
4
|
+
draw(frame: VideoFrame): MaybePromiseLike<void>;
|
|
3
5
|
}
|
|
4
6
|
//# sourceMappingURL=type.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/video/render/type.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/video/render/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,6BAA6B;IAC1C,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7C,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;CACnD"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare class
|
|
1
|
+
import { CanvasWebCodecsDecoderRenderer } from "./canvas.js";
|
|
2
|
+
export declare class WebGLWebCodecsDecoderRenderer extends CanvasWebCodecsDecoderRenderer {
|
|
3
3
|
#private;
|
|
4
4
|
static vertexShaderSource: string;
|
|
5
5
|
static fragmentShaderSource: string;
|
|
6
|
+
static get isSupported(): boolean;
|
|
6
7
|
/**
|
|
7
8
|
* Create a new WebGL frame renderer.
|
|
8
9
|
* @param canvas The canvas to render frames to.
|
|
@@ -10,7 +11,7 @@ export declare class WebGLFrameRenderer implements FrameRenderer {
|
|
|
10
11
|
* Whether to allow capturing the canvas content using APIs like `readPixels` and `toDataURL`.
|
|
11
12
|
* Enable this option may reduce performance.
|
|
12
13
|
*/
|
|
13
|
-
constructor(canvas
|
|
14
|
-
draw(frame: VideoFrame): void
|
|
14
|
+
constructor(canvas?: HTMLCanvasElement | OffscreenCanvas, enableCapture?: boolean);
|
|
15
|
+
draw(frame: VideoFrame): Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
//# sourceMappingURL=webgl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webgl.d.ts","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webgl.d.ts","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAwB7D,qBAAa,6BAA8B,SAAQ,8BAA8B;;IAC7E,MAAM,CAAC,kBAAkB,SAW3B;IAEE,MAAM,CAAC,oBAAoB,SAQ7B;IAEE,MAAM,KAAK,WAAW,YAGrB;IAID;;;;;;OAMG;gBAEC,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,EAC5C,aAAa,CAAC,EAAE,OAAO;IA6D3B,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBzC"}
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import { createCanvas } from "@yume-chan/scrcpy-decoder-tinyh264";
|
|
2
|
+
import { CanvasWebCodecsDecoderRenderer } from "./canvas.js";
|
|
3
|
+
const Resolved = Promise.resolve();
|
|
4
|
+
function createContext(canvas, enableCapture) {
|
|
5
|
+
const attributes = {
|
|
6
|
+
// Low-power GPU should be enough for video rendering.
|
|
7
|
+
powerPreference: "low-power",
|
|
8
|
+
alpha: false,
|
|
9
|
+
// Disallow software rendering.
|
|
10
|
+
// Other rendering methods are faster than software-based WebGL.
|
|
11
|
+
failIfMajorPerformanceCaveat: true,
|
|
12
|
+
preserveDrawingBuffer: !!enableCapture,
|
|
13
|
+
};
|
|
14
|
+
return (canvas.getContext("webgl2", attributes) ||
|
|
15
|
+
canvas.getContext("webgl", attributes));
|
|
16
|
+
}
|
|
17
|
+
export class WebGLWebCodecsDecoderRenderer extends CanvasWebCodecsDecoderRenderer {
|
|
2
18
|
static vertexShaderSource = `
|
|
3
19
|
attribute vec2 xy;
|
|
4
20
|
|
|
@@ -20,6 +36,10 @@ export class WebGLFrameRenderer {
|
|
|
20
36
|
gl_FragColor = texture2D(texture, uv);
|
|
21
37
|
}
|
|
22
38
|
`;
|
|
39
|
+
static get isSupported() {
|
|
40
|
+
const canvas = createCanvas();
|
|
41
|
+
return !!createContext(canvas);
|
|
42
|
+
}
|
|
23
43
|
#context;
|
|
24
44
|
/**
|
|
25
45
|
* Create a new WebGL frame renderer.
|
|
@@ -29,28 +49,20 @@ export class WebGLFrameRenderer {
|
|
|
29
49
|
* Enable this option may reduce performance.
|
|
30
50
|
*/
|
|
31
51
|
constructor(canvas, enableCapture) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
failIfMajorPerformanceCaveat: true,
|
|
35
|
-
preserveDrawingBuffer: enableCapture,
|
|
36
|
-
}) ||
|
|
37
|
-
canvas.getContext("webgl", {
|
|
38
|
-
alpha: false,
|
|
39
|
-
failIfMajorPerformanceCaveat: true,
|
|
40
|
-
preserveDrawingBuffer: enableCapture,
|
|
41
|
-
});
|
|
52
|
+
super(canvas);
|
|
53
|
+
const gl = createContext(this.canvas, enableCapture);
|
|
42
54
|
if (!gl) {
|
|
43
55
|
throw new Error("WebGL not supported");
|
|
44
56
|
}
|
|
45
57
|
this.#context = gl;
|
|
46
58
|
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
47
|
-
gl.shaderSource(vertexShader,
|
|
59
|
+
gl.shaderSource(vertexShader, WebGLWebCodecsDecoderRenderer.vertexShaderSource);
|
|
48
60
|
gl.compileShader(vertexShader);
|
|
49
61
|
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
50
62
|
throw new Error(gl.getShaderInfoLog(vertexShader));
|
|
51
63
|
}
|
|
52
64
|
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
53
|
-
gl.shaderSource(fragmentShader,
|
|
65
|
+
gl.shaderSource(fragmentShader, WebGLWebCodecsDecoderRenderer.fragmentShaderSource);
|
|
54
66
|
gl.compileShader(fragmentShader);
|
|
55
67
|
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
56
68
|
throw new Error(gl.getShaderInfoLog(fragmentShader));
|
|
@@ -81,9 +93,9 @@ export class WebGLFrameRenderer {
|
|
|
81
93
|
draw(frame) {
|
|
82
94
|
const gl = this.#context;
|
|
83
95
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
|
|
84
|
-
frame.close();
|
|
85
96
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
86
97
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
98
|
+
return Resolved;
|
|
87
99
|
}
|
|
88
100
|
}
|
|
89
101
|
//# sourceMappingURL=webgl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webgl.js","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webgl.js","sourceRoot":"","sources":["../../../src/video/render/webgl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE,OAAO,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;AAEnC,SAAS,aAAa,CAClB,MAA2C,EAC3C,aAAuB;IAEvB,MAAM,UAAU,GAA2B;QACvC,sDAAsD;QACtD,eAAe,EAAE,WAAW;QAC5B,KAAK,EAAE,KAAK;QACZ,+BAA+B;QAC/B,gEAAgE;QAChE,4BAA4B,EAAE,IAAI;QAClC,qBAAqB,EAAE,CAAC,CAAC,aAAa;KACzC,CAAC;IAEF,OAAO,CACH,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC;QACvC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CACzC,CAAC;AACN,CAAC;AAED,MAAM,OAAO,6BAA8B,SAAQ,8BAA8B;IAC7E,MAAM,CAAC,kBAAkB,GAAG;;;;;;;;;;;CAW/B,CAAC;IAEE,MAAM,CAAC,oBAAoB,GAAG;;;;;;;;CAQjC,CAAC;IAEE,MAAM,KAAK,WAAW;QAClB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,OAAO,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,QAAQ,CAAwB;IAEhC;;;;;;OAMG;IACH,YACI,MAA4C,EAC5C,aAAuB;QAEvB,KAAK,CAAC,MAAM,CAAC,CAAC;QAEd,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACrD,IAAI,CAAC,EAAE,EAAE,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QAEnB,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,CAAE,CAAC;QACxD,EAAE,CAAC,YAAY,CACX,YAAY,EACZ,6BAA6B,CAAC,kBAAkB,CACnD,CAAC;QACF,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,YAAY,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,YAAY,CAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,eAAe,CAAE,CAAC;QAC5D,EAAE,CAAC,YAAY,CACX,cAAc,EACd,6BAA6B,CAAC,oBAAoB,CACrD,CAAC;QACF,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,cAAc,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,cAAc,CAAE,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,aAAa,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;QACzC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAC7C,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC/C,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,aAAa,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAE,CAAC,CAAC;QAC1D,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAE7B,kDAAkD;QAClD,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC;QACvC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QAC7C,EAAE,CAAC,UAAU,CACT,EAAE,CAAC,YAAY,EACf,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,EAClE,EAAE,CAAC,WAAW,CACjB,CAAC;QAEF,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC7D,EAAE,CAAC,mBAAmB,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,EAAE,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;QAEvC,0CAA0C;QAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;QACnC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,KAAiB;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACzB,EAAE,CAAC,UAAU,CACT,EAAE,CAAC,UAAU,EACb,CAAC,EACD,EAAE,CAAC,IAAI,EACP,EAAE,CAAC,IAAI,EACP,EAAE,CAAC,aAAa,EAChB,KAAK,CACR,CAAC;QAEF,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC;QACjE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAErC,OAAO,QAAQ,CAAC;IACpB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yume-chan/scrcpy-decoder-webcodecs",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Raw H.264 stream decoder and renderer using WebCodecs API (requires modern browser).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adb",
|
|
@@ -26,26 +26,23 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"main": "esm/index.js",
|
|
28
28
|
"types": "esm/index.d.ts",
|
|
29
|
+
"sideEffects": false,
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@yume-chan/
|
|
31
|
-
"@yume-chan/no-data-view": "^0.0
|
|
32
|
-
"@yume-chan/
|
|
33
|
-
"@yume-chan/scrcpy
|
|
34
|
-
"@yume-chan/
|
|
31
|
+
"@yume-chan/async": "^4.0.2",
|
|
32
|
+
"@yume-chan/no-data-view": "^1.0.0",
|
|
33
|
+
"@yume-chan/stream-extra": "^1.0.0",
|
|
34
|
+
"@yume-chan/scrcpy": "^1.0.0",
|
|
35
|
+
"@yume-chan/scrcpy-decoder-tinyh264": "^1.0.0",
|
|
36
|
+
"@yume-chan/event": "^1.0.0"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
|
-
"
|
|
39
|
+
"prettier": "^3.4.1",
|
|
40
|
+
"typescript": "^5.7.2",
|
|
38
41
|
"@yume-chan/eslint-config": "^1.0.0",
|
|
39
|
-
"@yume-chan/tsconfig": "^1.0.0"
|
|
40
|
-
"cross-env": "^7.0.3",
|
|
41
|
-
"jest": "^30.0.0-alpha.4",
|
|
42
|
-
"prettier": "^3.3.2",
|
|
43
|
-
"ts-jest": "^29.1.4",
|
|
44
|
-
"typescript": "^5.4.5"
|
|
42
|
+
"@yume-chan/tsconfig": "^1.0.0"
|
|
45
43
|
},
|
|
46
44
|
"scripts": {
|
|
47
45
|
"build": "tsc -b tsconfig.build.json",
|
|
48
|
-
"build:watch": "tsc -b tsconfig.build.json",
|
|
49
46
|
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4"
|
|
50
47
|
}
|
|
51
48
|
}
|
package/src/video/codec/h265.ts
CHANGED
package/src/video/decoder.ts
CHANGED
|
@@ -10,11 +10,94 @@ import { WritableStream } from "@yume-chan/stream-extra";
|
|
|
10
10
|
|
|
11
11
|
import { Av1Codec, H264Decoder, H265Decoder } from "./codec/index.js";
|
|
12
12
|
import type { CodecDecoder } from "./codec/type.js";
|
|
13
|
-
import type {
|
|
14
|
-
|
|
13
|
+
import type { WebCodecsVideoDecoderRenderer } from "./render/index.js";
|
|
14
|
+
|
|
15
|
+
class Pool<T> {
|
|
16
|
+
#controller!: ReadableStreamDefaultController<T>;
|
|
17
|
+
#readable = new ReadableStream<T>(
|
|
18
|
+
{
|
|
19
|
+
start: (controller) => {
|
|
20
|
+
this.#controller = controller;
|
|
21
|
+
},
|
|
22
|
+
pull: (controller) => {
|
|
23
|
+
controller.enqueue(this.#initializer());
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{ highWaterMark: 0 },
|
|
27
|
+
);
|
|
28
|
+
#reader = this.#readable.getReader();
|
|
29
|
+
|
|
30
|
+
#initializer: () => T;
|
|
31
|
+
|
|
32
|
+
#size = 0;
|
|
33
|
+
#capacity: number;
|
|
34
|
+
|
|
35
|
+
constructor(initializer: () => T, capacity: number) {
|
|
36
|
+
this.#initializer = initializer;
|
|
37
|
+
this.#capacity = capacity;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async borrow() {
|
|
41
|
+
const result = await this.#reader.read();
|
|
42
|
+
return result.value!;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return(value: T) {
|
|
46
|
+
if (this.#size < this.#capacity) {
|
|
47
|
+
this.#controller.enqueue(value);
|
|
48
|
+
this.#size += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class VideoFrameCapturer {
|
|
54
|
+
#canvas: OffscreenCanvas | HTMLCanvasElement;
|
|
55
|
+
#context: ImageBitmapRenderingContext;
|
|
56
|
+
|
|
57
|
+
constructor() {
|
|
58
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
59
|
+
this.#canvas = new OffscreenCanvas(1, 1);
|
|
60
|
+
} else {
|
|
61
|
+
this.#canvas = document.createElement("canvas");
|
|
62
|
+
this.#canvas.width = 1;
|
|
63
|
+
this.#canvas.height = 1;
|
|
64
|
+
}
|
|
65
|
+
this.#context = this.#canvas.getContext("bitmaprenderer", {
|
|
66
|
+
alpha: false,
|
|
67
|
+
})!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async capture(frame: VideoFrame): Promise<Blob> {
|
|
71
|
+
this.#canvas.width = frame.displayWidth;
|
|
72
|
+
this.#canvas.height = frame.displayHeight;
|
|
73
|
+
|
|
74
|
+
const bitmap = await createImageBitmap(frame);
|
|
75
|
+
this.#context.transferFromImageBitmap(bitmap);
|
|
76
|
+
|
|
77
|
+
if (this.#canvas instanceof OffscreenCanvas) {
|
|
78
|
+
return await this.#canvas.convertToBlob({
|
|
79
|
+
type: "image/png",
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
(this.#canvas as HTMLCanvasElement).toBlob((blob) => {
|
|
84
|
+
if (!blob) {
|
|
85
|
+
reject(new Error("Failed to convert canvas to blob"));
|
|
86
|
+
} else {
|
|
87
|
+
resolve(blob);
|
|
88
|
+
}
|
|
89
|
+
}, "image/png");
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const VideoFrameCapturerPool =
|
|
96
|
+
/* #__PURE__ */
|
|
97
|
+
new Pool(() => new VideoFrameCapturer(), 4);
|
|
15
98
|
|
|
16
99
|
export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
17
|
-
static isSupported() {
|
|
100
|
+
static get isSupported() {
|
|
18
101
|
return typeof globalThis.VideoDecoder !== "undefined";
|
|
19
102
|
}
|
|
20
103
|
|
|
@@ -22,6 +105,7 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
|
22
105
|
{
|
|
23
106
|
h264: {},
|
|
24
107
|
h265: {},
|
|
108
|
+
av1: {},
|
|
25
109
|
};
|
|
26
110
|
|
|
27
111
|
#codec: ScrcpyVideoCodecId;
|
|
@@ -36,19 +120,23 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
|
36
120
|
return this.#writable;
|
|
37
121
|
}
|
|
38
122
|
|
|
39
|
-
#
|
|
123
|
+
#error: Error | undefined;
|
|
124
|
+
#controller!: WritableStreamDefaultController;
|
|
125
|
+
|
|
126
|
+
#renderer: WebCodecsVideoDecoderRenderer;
|
|
40
127
|
get renderer() {
|
|
41
|
-
return this.#
|
|
128
|
+
return this.#renderer;
|
|
42
129
|
}
|
|
43
130
|
|
|
44
|
-
#
|
|
131
|
+
#framesDraw = 0;
|
|
132
|
+
#framesPresented = 0;
|
|
45
133
|
get framesRendered() {
|
|
46
|
-
return this.#
|
|
134
|
+
return this.#framesPresented;
|
|
47
135
|
}
|
|
48
136
|
|
|
49
|
-
#
|
|
137
|
+
#framesSkipped = 0;
|
|
50
138
|
get framesSkipped() {
|
|
51
|
-
return this.#
|
|
139
|
+
return this.#framesSkipped;
|
|
52
140
|
}
|
|
53
141
|
|
|
54
142
|
#sizeChanged = new EventEmitter<{ width: number; height: number }>();
|
|
@@ -57,61 +145,40 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
|
57
145
|
}
|
|
58
146
|
|
|
59
147
|
#decoder: VideoDecoder;
|
|
60
|
-
#renderer: FrameRenderer;
|
|
61
148
|
|
|
62
|
-
#
|
|
149
|
+
#drawing = false;
|
|
150
|
+
#nextFrame: VideoFrame | undefined;
|
|
151
|
+
#captureFrame: VideoFrame | undefined;
|
|
152
|
+
|
|
63
153
|
#animationFrameId = 0;
|
|
64
154
|
|
|
65
155
|
/**
|
|
66
156
|
* Create a new WebCodecs video decoder.
|
|
67
|
-
* @param codec The video codec to decode
|
|
68
|
-
* @param enableCapture
|
|
69
|
-
* Whether to allow capturing the canvas content using APIs like `readPixels` and `toDataURL`.
|
|
70
|
-
* Enable this option may reduce performance.
|
|
71
157
|
*/
|
|
72
|
-
constructor(codec
|
|
158
|
+
constructor({ codec, renderer }: WebCodecsVideoDecoder.Options) {
|
|
73
159
|
this.#codec = codec;
|
|
74
160
|
|
|
75
|
-
this.#
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
this.#renderer = new WebGLFrameRenderer(
|
|
79
|
-
this.#canvas,
|
|
80
|
-
enableCapture,
|
|
81
|
-
);
|
|
82
|
-
} catch {
|
|
83
|
-
this.#renderer = new BitmapFrameRenderer(this.#canvas);
|
|
84
|
-
}
|
|
161
|
+
this.#renderer = renderer;
|
|
85
162
|
|
|
86
163
|
this.#decoder = new VideoDecoder({
|
|
87
164
|
output: (frame) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// When multiple frames are drawn in one vertical sync interval,
|
|
97
|
-
// only the last one is visible to users.
|
|
98
|
-
// But this ensures users can always see the most up-to-date screen.
|
|
99
|
-
// This is also the behavior of official Scrcpy client.
|
|
100
|
-
// https://github.com/Genymobile/scrcpy/issues/3679
|
|
101
|
-
this.#updateSize(frame.displayWidth, frame.displayHeight);
|
|
102
|
-
this.#renderer.draw(frame);
|
|
103
|
-
},
|
|
104
|
-
error(e) {
|
|
105
|
-
if (controller) {
|
|
106
|
-
try {
|
|
107
|
-
controller.error(e);
|
|
108
|
-
} catch {
|
|
109
|
-
// ignore
|
|
110
|
-
// `controller` may already in error state
|
|
165
|
+
this.#captureFrame?.close();
|
|
166
|
+
// PERF: `VideoFrame#clone` is cheap
|
|
167
|
+
this.#captureFrame = frame.clone();
|
|
168
|
+
|
|
169
|
+
if (this.#drawing) {
|
|
170
|
+
if (this.#nextFrame) {
|
|
171
|
+
this.#nextFrame.close();
|
|
172
|
+
this.#framesSkipped += 1;
|
|
111
173
|
}
|
|
112
|
-
|
|
113
|
-
|
|
174
|
+
this.#nextFrame = frame;
|
|
175
|
+
return;
|
|
114
176
|
}
|
|
177
|
+
|
|
178
|
+
void this.#draw(frame);
|
|
179
|
+
},
|
|
180
|
+
error: (error) => {
|
|
181
|
+
this.#setError(error);
|
|
115
182
|
},
|
|
116
183
|
});
|
|
117
184
|
|
|
@@ -134,16 +201,16 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
|
134
201
|
this.#updateSize,
|
|
135
202
|
);
|
|
136
203
|
break;
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unsupported codec: ${this.#codec as number}`);
|
|
137
206
|
}
|
|
138
207
|
|
|
139
|
-
let error: Error | undefined;
|
|
140
|
-
let controller: WritableStreamDefaultController | undefined;
|
|
141
208
|
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
|
142
|
-
start: (
|
|
143
|
-
if (error) {
|
|
144
|
-
|
|
209
|
+
start: (controller) => {
|
|
210
|
+
if (this.#error) {
|
|
211
|
+
controller.error(this.#error);
|
|
145
212
|
} else {
|
|
146
|
-
controller =
|
|
213
|
+
this.#controller = controller;
|
|
147
214
|
}
|
|
148
215
|
},
|
|
149
216
|
write: (packet) => {
|
|
@@ -151,29 +218,90 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
|
|
151
218
|
},
|
|
152
219
|
});
|
|
153
220
|
|
|
154
|
-
this.#
|
|
221
|
+
this.#onVerticalSync();
|
|
155
222
|
}
|
|
156
223
|
|
|
157
|
-
#
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
224
|
+
#setError(error: Error) {
|
|
225
|
+
if (this.#controller) {
|
|
226
|
+
try {
|
|
227
|
+
this.#controller.error(error);
|
|
228
|
+
} catch {
|
|
229
|
+
// ignore
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
this.#error = error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async #draw(frame: VideoFrame) {
|
|
237
|
+
try {
|
|
238
|
+
this.#drawing = true;
|
|
239
|
+
// PERF: Draw every frame to minimize latency at cost of performance.
|
|
240
|
+
// When multiple frames are drawn in one vertical sync interval,
|
|
241
|
+
// only the last one is visible to users.
|
|
242
|
+
// But this ensures users can always see the most up-to-date screen.
|
|
243
|
+
// This is also the behavior of official Scrcpy client.
|
|
244
|
+
// https://github.com/Genymobile/scrcpy/issues/3679
|
|
245
|
+
this.#updateSize(frame.displayWidth, frame.displayHeight);
|
|
246
|
+
await this.#renderer.draw(frame);
|
|
247
|
+
this.#framesDraw += 1;
|
|
248
|
+
frame.close();
|
|
249
|
+
|
|
250
|
+
if (this.#nextFrame) {
|
|
251
|
+
const frame = this.#nextFrame;
|
|
252
|
+
this.#nextFrame = undefined;
|
|
253
|
+
await this.#draw(frame);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.#drawing = false;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
this.#setError(error as Error);
|
|
165
259
|
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#updateSize = (width: number, height: number) => {
|
|
263
|
+
this.#renderer.setSize(width, height);
|
|
264
|
+
this.#sizeChanged.fire({ width, height });
|
|
166
265
|
};
|
|
167
266
|
|
|
168
|
-
#
|
|
169
|
-
this.#
|
|
170
|
-
|
|
267
|
+
#onVerticalSync = () => {
|
|
268
|
+
if (this.#framesDraw > 0) {
|
|
269
|
+
this.#framesPresented += 1;
|
|
270
|
+
this.#framesSkipped += this.#framesDraw - 1;
|
|
271
|
+
this.#framesDraw = 0;
|
|
272
|
+
}
|
|
273
|
+
this.#animationFrameId = requestAnimationFrame(this.#onVerticalSync);
|
|
171
274
|
};
|
|
172
275
|
|
|
276
|
+
async snapshot() {
|
|
277
|
+
const frame = this.#captureFrame;
|
|
278
|
+
if (!frame) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const capturer = await VideoFrameCapturerPool.borrow();
|
|
283
|
+
const result = await capturer.capture(frame);
|
|
284
|
+
VideoFrameCapturerPool.return(capturer);
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
|
|
173
288
|
dispose() {
|
|
174
289
|
cancelAnimationFrame(this.#animationFrameId);
|
|
175
290
|
if (this.#decoder.state !== "closed") {
|
|
176
291
|
this.#decoder.close();
|
|
177
292
|
}
|
|
293
|
+
this.#nextFrame?.close();
|
|
294
|
+
this.#captureFrame?.close();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export namespace WebCodecsVideoDecoder {
|
|
299
|
+
export interface Options {
|
|
300
|
+
/**
|
|
301
|
+
* The video codec to decode
|
|
302
|
+
*/
|
|
303
|
+
codec: ScrcpyVideoCodecId;
|
|
304
|
+
|
|
305
|
+
renderer: WebCodecsVideoDecoderRenderer;
|
|
178
306
|
}
|
|
179
307
|
}
|