@yume-chan/scrcpy-decoder-webcodecs 0.0.17 → 0.0.19
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.json +18 -0
- package/CHANGELOG.md +13 -1
- package/LICENSE +1 -1
- package/README.md +8 -6
- package/esm/index.d.ts +22 -20
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +96 -80
- package/esm/index.js.map +1 -1
- package/package.json +18 -13
- package/src/index.ts +62 -40
- package/tsconfig.build.json +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/.rush/temp/package-deps_build.json +0 -14
- package/.rush/temp/package-deps_build_watch.json +0 -14
- package/.rush/temp/shrinkwrap-deps.json +0 -288
- package/esm/tinyh264/index.d.ts +0 -18
- package/esm/tinyh264/index.d.ts.map +0 -1
- package/esm/tinyh264/index.js +0 -86
- package/esm/tinyh264/index.js.map +0 -1
- package/esm/tinyh264/worker.d.ts +0 -2
- package/esm/tinyh264/worker.d.ts.map +0 -1
- package/esm/tinyh264/worker.js +0 -3
- package/esm/tinyh264/worker.js.map +0 -1
- package/esm/tinyh264/wrapper.d.ts +0 -21
- package/esm/tinyh264/wrapper.d.ts.map +0 -1
- package/esm/tinyh264/wrapper.js +0 -74
- package/esm/tinyh264/wrapper.js.map +0 -1
- package/jest.config.js +0 -13
- package/scrcpy-decoder-tinyh264.build.log +0 -1
- package/scrcpy-decoder-webcodecs.build.log +0 -1
- package/tsconfig.json +0 -16
- package/tsconfig.test.json +0 -8
package/CHANGELOG.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yume-chan/scrcpy-decoder-webcodecs",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.0.19",
|
|
6
|
+
"tag": "@yume-chan/scrcpy-decoder-webcodecs_v0.0.19",
|
|
7
|
+
"date": "Sun, 09 Apr 2023 05:55:33 GMT",
|
|
8
|
+
"comments": {}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.0.18",
|
|
12
|
+
"tag": "@yume-chan/scrcpy-decoder-webcodecs_v0.0.18",
|
|
13
|
+
"date": "Wed, 25 Jan 2023 21:33:49 GMT",
|
|
14
|
+
"comments": {
|
|
15
|
+
"none": [
|
|
16
|
+
{
|
|
17
|
+
"comment": "Change to not use vertical sync to minimize latency"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
4
22
|
{
|
|
5
23
|
"version": "0.0.17",
|
|
6
24
|
"tag": "@yume-chan/scrcpy-decoder-webcodecs_v0.0.17",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Change Log - @yume-chan/scrcpy-decoder-webcodecs
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Sun, 09 Apr 2023 05:55:33 GMT and should not be manually modified.
|
|
4
|
+
|
|
5
|
+
## 0.0.19
|
|
6
|
+
Sun, 09 Apr 2023 05:55:33 GMT
|
|
7
|
+
|
|
8
|
+
_Version update only_
|
|
9
|
+
|
|
10
|
+
## 0.0.18
|
|
11
|
+
Wed, 25 Jan 2023 21:33:49 GMT
|
|
12
|
+
|
|
13
|
+
### Updates
|
|
14
|
+
|
|
15
|
+
- Change to not use vertical sync to minimize latency
|
|
4
16
|
|
|
5
17
|
## 0.0.17
|
|
6
18
|
Tue, 18 Oct 2022 09:32:30 GMT
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @yume-chan/scrcpy-decoder-webcodecs
|
|
2
2
|
|
|
3
|
-
Decode and render
|
|
3
|
+
Decode and render H.264 streams using the [WebCodecs API](https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API).
|
|
4
4
|
|
|
5
|
-
It has no dependencies and high performance, but
|
|
5
|
+
It has no dependencies and high performance, but is only available on recent versions of Chrome.
|
|
6
6
|
|
|
7
7
|
**WARNING:** The public API is UNSTABLE. If you have any questions, please open an issue.
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- [Compatibility](#compatibility)
|
|
10
|
+
- [Usage](#usage)
|
|
11
11
|
|
|
12
12
|
## Compatibility
|
|
13
13
|
|
|
@@ -17,11 +17,13 @@ It has no dependencies and high performance, but are only available on recent ve
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
+
It draws frames onto `decoder.renderer` (a `<canvas>` element), you can insert it anywhere you want to display the video.
|
|
21
|
+
|
|
20
22
|
```ts
|
|
21
23
|
const decoder = new WebCodecsDecoder();
|
|
22
|
-
document.body.appendChild(decoder.
|
|
24
|
+
document.body.appendChild(decoder.renderer);
|
|
23
25
|
|
|
24
26
|
videoPacketStream // from `@yume-chan/scrcpy`
|
|
25
27
|
.pipeTo(decoder.writable)
|
|
26
|
-
.catch(() => {
|
|
28
|
+
.catch(() => {});
|
|
27
29
|
```
|
package/esm/index.d.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import type { ScrcpyVideoStreamPacket } from
|
|
2
|
-
import { WritableStream } from
|
|
3
|
-
export declare class WebCodecsDecoder {
|
|
4
|
-
readonly maxProfile: undefined;
|
|
5
|
-
readonly maxLevel: undefined;
|
|
6
|
-
private _writable;
|
|
7
|
-
get writable(): WritableStream<ScrcpyVideoStreamPacket>;
|
|
8
|
-
private _renderer;
|
|
9
|
-
get renderer(): HTMLCanvasElement;
|
|
10
|
-
private _frameRendered;
|
|
11
|
-
get frameRendered(): number;
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import type { ScrcpyVideoStreamPacket } from "@yume-chan/scrcpy";
|
|
2
|
+
import { WritableStream } from "@yume-chan/stream-extra";
|
|
3
|
+
export declare class WebCodecsDecoder {
|
|
4
|
+
readonly maxProfile: undefined;
|
|
5
|
+
readonly maxLevel: undefined;
|
|
6
|
+
private _writable;
|
|
7
|
+
get writable(): WritableStream<ScrcpyVideoStreamPacket>;
|
|
8
|
+
private _renderer;
|
|
9
|
+
get renderer(): HTMLCanvasElement;
|
|
10
|
+
private _frameRendered;
|
|
11
|
+
get frameRendered(): number;
|
|
12
|
+
private _frameSkipped;
|
|
13
|
+
get frameSkipped(): number;
|
|
14
|
+
private context;
|
|
15
|
+
private decoder;
|
|
16
|
+
private currentFrameRendered;
|
|
17
|
+
private animationFrameId;
|
|
18
|
+
constructor();
|
|
19
|
+
private onFramePresented;
|
|
20
|
+
private configure;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
}
|
|
21
23
|
//# sourceMappingURL=index.d.ts.map
|
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAER,uBAAuB,EAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAMzD,qBAAa,gBAAgB;IAGzB,SAAgB,UAAU,YAAa;IACvC,SAAgB,QAAQ,YAAa;IAErC,OAAO,CAAC,SAAS,CAA0C;IAC3D,IAAW,QAAQ,4CAElB;IAED,OAAO,CAAC,SAAS,CAAoB;IACrC,IAAW,QAAQ,sBAElB;IAED,OAAO,CAAC,cAAc,CAAK;IAC3B,IAAW,aAAa,WAEvB;IAED,OAAO,CAAC,aAAa,CAAK;IAC1B,IAAW,YAAY,WAEtB;IAED,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAAe;IAE9B,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,gBAAgB,CAAK;;IAqD7B,OAAO,CAAC,gBAAgB,CAGtB;IAEF,OAAO,CAAC,SAAS;IAiBV,OAAO;CAIjB"}
|
package/esm/index.js
CHANGED
|
@@ -1,81 +1,97 @@
|
|
|
1
|
-
import { WritableStream } from
|
|
2
|
-
function toHex(value) {
|
|
3
|
-
return value.toString(16).padStart(2,
|
|
4
|
-
}
|
|
5
|
-
export class WebCodecsDecoder {
|
|
6
|
-
// Usually, browsers can decode most configurations,
|
|
7
|
-
// So let device choose best profile and level for itself.
|
|
8
|
-
maxProfile = undefined;
|
|
9
|
-
maxLevel = undefined;
|
|
10
|
-
_writable;
|
|
11
|
-
get writable() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
get
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { WritableStream } from "@yume-chan/stream-extra";
|
|
2
|
+
function toHex(value) {
|
|
3
|
+
return value.toString(16).padStart(2, "0").toUpperCase();
|
|
4
|
+
}
|
|
5
|
+
export class WebCodecsDecoder {
|
|
6
|
+
// Usually, browsers can decode most configurations,
|
|
7
|
+
// So let device choose best profile and level for itself.
|
|
8
|
+
maxProfile = undefined;
|
|
9
|
+
maxLevel = undefined;
|
|
10
|
+
_writable;
|
|
11
|
+
get writable() {
|
|
12
|
+
return this._writable;
|
|
13
|
+
}
|
|
14
|
+
_renderer;
|
|
15
|
+
get renderer() {
|
|
16
|
+
return this._renderer;
|
|
17
|
+
}
|
|
18
|
+
_frameRendered = 0;
|
|
19
|
+
get frameRendered() {
|
|
20
|
+
return this._frameRendered;
|
|
21
|
+
}
|
|
22
|
+
_frameSkipped = 0;
|
|
23
|
+
get frameSkipped() {
|
|
24
|
+
return this._frameSkipped;
|
|
25
|
+
}
|
|
26
|
+
context;
|
|
27
|
+
decoder;
|
|
28
|
+
currentFrameRendered = false;
|
|
29
|
+
animationFrameId = 0;
|
|
30
|
+
constructor() {
|
|
31
|
+
this._renderer = document.createElement("canvas");
|
|
32
|
+
this.context = this._renderer.getContext("2d");
|
|
33
|
+
this.decoder = new VideoDecoder({
|
|
34
|
+
output: (frame) => {
|
|
35
|
+
if (this.currentFrameRendered) {
|
|
36
|
+
this._frameSkipped += 1;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.currentFrameRendered = true;
|
|
40
|
+
this._frameRendered += 1;
|
|
41
|
+
}
|
|
42
|
+
// PERF: H.264 renderer may draw multiple frames in one vertical sync interval to minimize latency.
|
|
43
|
+
// When multiple frames are drawn in one vertical sync interval,
|
|
44
|
+
// only the last one is visible to users.
|
|
45
|
+
// But this ensures users can always see the most up-to-date screen.
|
|
46
|
+
// This is also the behavior of official Scrcpy client.
|
|
47
|
+
// https://github.com/Genymobile/scrcpy/issues/3679
|
|
48
|
+
this.context.drawImage(frame, 0, 0);
|
|
49
|
+
frame.close();
|
|
50
|
+
},
|
|
51
|
+
error(e) {
|
|
52
|
+
void e;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
this._writable = new WritableStream({
|
|
56
|
+
write: (packet) => {
|
|
57
|
+
switch (packet.type) {
|
|
58
|
+
case "configuration":
|
|
59
|
+
this.configure(packet.data);
|
|
60
|
+
break;
|
|
61
|
+
case "frame":
|
|
62
|
+
this.decoder.decode(new EncodedVideoChunk({
|
|
63
|
+
// Treat `undefined` as `key`, otherwise won't decode.
|
|
64
|
+
type: packet.keyframe === false ? "delta" : "key",
|
|
65
|
+
timestamp: 0,
|
|
66
|
+
data: packet.data,
|
|
67
|
+
}));
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
this.onFramePresented();
|
|
73
|
+
}
|
|
74
|
+
onFramePresented = () => {
|
|
75
|
+
this.currentFrameRendered = false;
|
|
76
|
+
this.animationFrameId = requestAnimationFrame(this.onFramePresented);
|
|
77
|
+
};
|
|
78
|
+
configure(config) {
|
|
79
|
+
const { profileIndex, constraintSet, levelIndex } = config;
|
|
80
|
+
this._renderer.width = config.croppedWidth;
|
|
81
|
+
this._renderer.height = config.croppedHeight;
|
|
82
|
+
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
|
83
|
+
// ISO Base Media File Format Name Space
|
|
84
|
+
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
|
|
85
|
+
.map(toHex)
|
|
86
|
+
.join("")}`;
|
|
87
|
+
this.decoder.configure({
|
|
88
|
+
codec: codec,
|
|
89
|
+
optimizeForLatency: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
dispose() {
|
|
93
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
94
|
+
this.decoder.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
81
97
|
//# sourceMappingURL=index.js.map
|
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yume-chan/scrcpy-decoder-webcodecs",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.19",
|
|
4
|
+
"description": "Raw H.264 stream decoder and renderer using WebCodecs API (requires modern browser).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adb",
|
|
7
7
|
"android-phone",
|
|
8
|
-
"scrcpy
|
|
8
|
+
"scrcpy",
|
|
9
|
+
"scrcpy-decoder"
|
|
9
10
|
],
|
|
10
11
|
"license": "MIT",
|
|
11
12
|
"author": {
|
|
@@ -26,22 +27,26 @@
|
|
|
26
27
|
"main": "esm/index.js",
|
|
27
28
|
"types": "esm/index.d.ts",
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@types/dom-webcodecs": "^0.1.
|
|
30
|
-
"@yume-chan/
|
|
31
|
-
"@yume-chan/
|
|
32
|
-
"tslib": "^2.4.
|
|
30
|
+
"@types/dom-webcodecs": "^0.1.6",
|
|
31
|
+
"@yume-chan/scrcpy": "^0.0.19",
|
|
32
|
+
"@yume-chan/stream-extra": "^0.0.19",
|
|
33
|
+
"tslib": "^2.4.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@jest/globals": "^
|
|
36
|
-
"@yume-chan/
|
|
36
|
+
"@jest/globals": "^29.5.0",
|
|
37
|
+
"@yume-chan/eslint-config": "^1.0.0",
|
|
38
|
+
"@yume-chan/tsconfig": "^1.0.0",
|
|
37
39
|
"cross-env": "^7.0.3",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
40
|
+
"eslint": "^8.36.0",
|
|
41
|
+
"jest": "^29.5.0",
|
|
42
|
+
"prettier": "^2.8.4",
|
|
43
|
+
"ts-jest": "^29.0.4",
|
|
44
|
+
"typescript": "^4.9.4"
|
|
41
45
|
},
|
|
42
46
|
"scripts": {
|
|
43
47
|
"build": "tsc -b tsconfig.build.json",
|
|
44
48
|
"build:watch": "tsc -b tsconfig.build.json",
|
|
45
|
-
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage"
|
|
49
|
+
"//test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
50
|
+
"lint": "eslint src/**/*.ts --fix && prettier src/**/*.ts --write --tab-width 4"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
H264Configuration,
|
|
3
|
+
ScrcpyVideoStreamPacket,
|
|
4
|
+
} from "@yume-chan/scrcpy";
|
|
5
|
+
import { WritableStream } from "@yume-chan/stream-extra";
|
|
3
6
|
|
|
4
7
|
function toHex(value: number) {
|
|
5
|
-
return value.toString(16).padStart(2,
|
|
8
|
+
return value.toString(16).padStart(2, "0").toUpperCase();
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export class WebCodecsDecoder {
|
|
@@ -12,68 +15,85 @@ export class WebCodecsDecoder {
|
|
|
12
15
|
public readonly maxLevel = undefined;
|
|
13
16
|
|
|
14
17
|
private _writable: WritableStream<ScrcpyVideoStreamPacket>;
|
|
15
|
-
public get writable() {
|
|
18
|
+
public get writable() {
|
|
19
|
+
return this._writable;
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
private _renderer: HTMLCanvasElement;
|
|
18
|
-
public get renderer() {
|
|
23
|
+
public get renderer() {
|
|
24
|
+
return this._renderer;
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
private _frameRendered = 0;
|
|
21
|
-
public get frameRendered() {
|
|
28
|
+
public get frameRendered() {
|
|
29
|
+
return this._frameRendered;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private _frameSkipped = 0;
|
|
33
|
+
public get frameSkipped() {
|
|
34
|
+
return this._frameSkipped;
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
private context: CanvasRenderingContext2D;
|
|
24
38
|
private decoder: VideoDecoder;
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
private animationFrame: number = 0;
|
|
40
|
+
private currentFrameRendered = false;
|
|
41
|
+
private animationFrameId = 0;
|
|
29
42
|
|
|
30
43
|
public constructor() {
|
|
31
|
-
this._renderer = document.createElement(
|
|
44
|
+
this._renderer = document.createElement("canvas");
|
|
32
45
|
|
|
33
|
-
this.context = this._renderer.getContext(
|
|
46
|
+
this.context = this._renderer.getContext("2d")!;
|
|
34
47
|
this.decoder = new VideoDecoder({
|
|
35
48
|
output: (frame) => {
|
|
36
|
-
if (this.
|
|
37
|
-
this.
|
|
49
|
+
if (this.currentFrameRendered) {
|
|
50
|
+
this._frameSkipped += 1;
|
|
51
|
+
} else {
|
|
52
|
+
this.currentFrameRendered = true;
|
|
53
|
+
this._frameRendered += 1;
|
|
38
54
|
}
|
|
39
|
-
this.lastFrame = frame;
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
// PERF: H.264 renderer may draw multiple frames in one vertical sync interval to minimize latency.
|
|
57
|
+
// When multiple frames are drawn in one vertical sync interval,
|
|
58
|
+
// only the last one is visible to users.
|
|
59
|
+
// But this ensures users can always see the most up-to-date screen.
|
|
60
|
+
// This is also the behavior of official Scrcpy client.
|
|
61
|
+
// https://github.com/Genymobile/scrcpy/issues/3679
|
|
62
|
+
this.context.drawImage(frame, 0, 0);
|
|
63
|
+
frame.close();
|
|
64
|
+
},
|
|
65
|
+
error(e) {
|
|
66
|
+
void e;
|
|
45
67
|
},
|
|
46
|
-
error() { },
|
|
47
68
|
});
|
|
48
69
|
|
|
49
70
|
this._writable = new WritableStream<ScrcpyVideoStreamPacket>({
|
|
50
|
-
write:
|
|
71
|
+
write: (packet) => {
|
|
51
72
|
switch (packet.type) {
|
|
52
|
-
case
|
|
73
|
+
case "configuration":
|
|
53
74
|
this.configure(packet.data);
|
|
54
75
|
break;
|
|
55
|
-
case
|
|
56
|
-
this.decoder.decode(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
case "frame":
|
|
77
|
+
this.decoder.decode(
|
|
78
|
+
new EncodedVideoChunk({
|
|
79
|
+
// Treat `undefined` as `key`, otherwise won't decode.
|
|
80
|
+
type:
|
|
81
|
+
packet.keyframe === false ? "delta" : "key",
|
|
82
|
+
timestamp: 0,
|
|
83
|
+
data: packet.data,
|
|
84
|
+
})
|
|
85
|
+
);
|
|
62
86
|
break;
|
|
63
87
|
}
|
|
64
|
-
}
|
|
88
|
+
},
|
|
65
89
|
});
|
|
66
|
-
}
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this._frameRendered += 1;
|
|
71
|
-
this.context.drawImage(this.lastFrame, 0, 0);
|
|
72
|
-
this.lastFrame.close();
|
|
73
|
-
this.lastFrame = undefined;
|
|
74
|
-
}
|
|
91
|
+
this.onFramePresented();
|
|
92
|
+
}
|
|
75
93
|
|
|
76
|
-
|
|
94
|
+
private onFramePresented = () => {
|
|
95
|
+
this.currentFrameRendered = false;
|
|
96
|
+
this.animationFrameId = requestAnimationFrame(this.onFramePresented);
|
|
77
97
|
};
|
|
78
98
|
|
|
79
99
|
private configure(config: H264Configuration) {
|
|
@@ -84,7 +104,9 @@ export class WebCodecsDecoder {
|
|
|
84
104
|
|
|
85
105
|
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
|
86
106
|
// ISO Base Media File Format Name Space
|
|
87
|
-
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
|
|
107
|
+
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
|
|
108
|
+
.map(toHex)
|
|
109
|
+
.join("")}`;
|
|
88
110
|
this.decoder.configure({
|
|
89
111
|
codec: codec,
|
|
90
112
|
optimizeForLatency: true,
|
|
@@ -92,7 +114,7 @@ export class WebCodecsDecoder {
|
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
public dispose() {
|
|
95
|
-
cancelAnimationFrame(this.
|
|
117
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
96
118
|
this.decoder.close();
|
|
97
119
|
}
|
|
98
120
|
}
|
package/tsconfig.build.json
CHANGED