@yume-chan/scrcpy 0.0.13 → 0.0.16

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.
Files changed (80) hide show
  1. package/CHANGELOG.json +45 -0
  2. package/CHANGELOG.md +25 -1
  3. package/LICENSE +21 -21
  4. package/README.md +90 -89
  5. package/bin/scrcpy-server +0 -0
  6. package/bin/version.d.ts +1 -1
  7. package/bin/version.js +1 -1
  8. package/esm/client.d.ts +14 -2
  9. package/esm/client.d.ts.map +1 -1
  10. package/esm/client.js +275 -143
  11. package/esm/client.js.map +1 -1
  12. package/esm/connection.d.ts.map +1 -1
  13. package/esm/connection.js +74 -86
  14. package/esm/connection.js.map +1 -1
  15. package/esm/decoder/tinyh264/index.js +48 -48
  16. package/esm/decoder/tinyh264/index.js.map +1 -1
  17. package/esm/decoder/tinyh264/wrapper.js +3 -2
  18. package/esm/decoder/tinyh264/wrapper.js.map +1 -1
  19. package/esm/decoder/web-codecs/index.js +26 -21
  20. package/esm/decoder/web-codecs/index.js.map +1 -1
  21. package/esm/message.d.ts +13 -8
  22. package/esm/message.d.ts.map +1 -1
  23. package/esm/message.js +14 -8
  24. package/esm/message.js.map +1 -1
  25. package/esm/options/1_16/index.d.ts +8 -4
  26. package/esm/options/1_16/index.d.ts.map +1 -1
  27. package/esm/options/1_16/index.js +44 -18
  28. package/esm/options/1_16/index.js.map +1 -1
  29. package/esm/options/1_16/sps.js +3 -2
  30. package/esm/options/1_16/sps.js.map +1 -1
  31. package/esm/options/1_18.d.ts +3 -2
  32. package/esm/options/1_18.d.ts.map +1 -1
  33. package/esm/options/1_18.js +24 -1
  34. package/esm/options/1_18.js.map +1 -1
  35. package/esm/options/1_21.js +4 -1
  36. package/esm/options/1_21.js.map +1 -1
  37. package/esm/options/1_22.d.ts +3 -3
  38. package/esm/options/1_22.d.ts.map +1 -1
  39. package/esm/options/1_22.js +9 -6
  40. package/esm/options/1_22.js.map +1 -1
  41. package/esm/options/1_23.js +4 -1
  42. package/esm/options/1_23.js.map +1 -1
  43. package/esm/options/1_24.d.ts +9 -0
  44. package/esm/options/1_24.d.ts.map +1 -0
  45. package/esm/options/1_24.js +13 -0
  46. package/esm/options/1_24.js.map +1 -0
  47. package/esm/options/common.d.ts +2 -0
  48. package/esm/options/common.d.ts.map +1 -1
  49. package/esm/options/common.js.map +1 -1
  50. package/esm/options/index.d.ts +1 -0
  51. package/esm/options/index.d.ts.map +1 -1
  52. package/esm/options/index.js +1 -0
  53. package/esm/options/index.js.map +1 -1
  54. package/esm/push-server.js +5 -10
  55. package/esm/push-server.js.map +1 -1
  56. package/package.json +8 -8
  57. package/scrcpy.LICENSE +203 -203
  58. package/scripts/fetch-server.cjs +28 -28
  59. package/src/client.ts +435 -275
  60. package/src/codec.ts +35 -35
  61. package/src/connection.ts +148 -145
  62. package/src/decoder/index.ts +3 -3
  63. package/src/decoder/tinyh264/index.ts +112 -112
  64. package/src/decoder/tinyh264/types.d.ts +137 -137
  65. package/src/decoder/tinyh264/worker.ts +2 -2
  66. package/src/decoder/tinyh264/wrapper.ts +89 -89
  67. package/src/decoder/types.ts +35 -35
  68. package/src/decoder/web-codecs/index.ts +99 -99
  69. package/src/index.ts +8 -8
  70. package/src/message.ts +113 -105
  71. package/src/options/1_16/index.ts +345 -315
  72. package/src/options/1_16/sps.ts +300 -300
  73. package/src/options/1_18.ts +61 -41
  74. package/src/options/1_21.ts +34 -34
  75. package/src/options/1_22.ts +78 -80
  76. package/src/options/1_24.ts +18 -0
  77. package/src/options/common.ts +66 -63
  78. package/src/options/index.ts +7 -6
  79. package/src/push-server.ts +24 -24
  80. package/src/utils.ts +5 -5
package/src/codec.ts CHANGED
@@ -1,35 +1,35 @@
1
- // From https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel
2
- export enum AndroidCodecProfile {
3
- Baseline = 0x01,
4
- Main = 0x02,
5
- Extended = 0x04,
6
- High = 0x08,
7
- High10 = 0x10,
8
- High422 = 0x20,
9
- High444 = 0x40,
10
- ConstrainedBaseline = 0x10000,
11
- ConstrainedHigh = 0x80000,
12
- }
13
-
14
- export enum AndroidCodecLevel {
15
- Level1 = 0x01,
16
- Level1b = 0x02,
17
- Level11 = 0x04,
18
- Level12 = 0x08,
19
- Level13 = 0x10,
20
- Level2 = 0x20,
21
- Level21 = 0x40,
22
- Level22 = 0x80,
23
- Level3 = 0x100,
24
- Level31 = 0x200,
25
- Level32 = 0x400,
26
- Level4 = 0x800,
27
- Level41 = 0x1000,
28
- Level42 = 0x2000,
29
- Level5 = 0x4000,
30
- Level51 = 0x8000,
31
- Level52 = 0x10000,
32
- Level6 = 0x20000,
33
- Level61 = 0x40000,
34
- Level62 = 0x80000,
35
- }
1
+ // From https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel
2
+ export enum AndroidCodecProfile {
3
+ Baseline = 0x01,
4
+ Main = 0x02,
5
+ Extended = 0x04,
6
+ High = 0x08,
7
+ High10 = 0x10,
8
+ High422 = 0x20,
9
+ High444 = 0x40,
10
+ ConstrainedBaseline = 0x10000,
11
+ ConstrainedHigh = 0x80000,
12
+ }
13
+
14
+ export enum AndroidCodecLevel {
15
+ Level1 = 0x01,
16
+ Level1b = 0x02,
17
+ Level11 = 0x04,
18
+ Level12 = 0x08,
19
+ Level13 = 0x10,
20
+ Level2 = 0x20,
21
+ Level21 = 0x40,
22
+ Level22 = 0x80,
23
+ Level3 = 0x100,
24
+ Level31 = 0x200,
25
+ Level32 = 0x400,
26
+ Level4 = 0x800,
27
+ Level41 = 0x1000,
28
+ Level42 = 0x2000,
29
+ Level5 = 0x4000,
30
+ Level51 = 0x8000,
31
+ Level52 = 0x10000,
32
+ Level6 = 0x20000,
33
+ Level61 = 0x40000,
34
+ Level62 = 0x80000,
35
+ }
package/src/connection.ts CHANGED
@@ -1,145 +1,148 @@
1
- import type { Adb, AdbSocket } from "@yume-chan/adb";
2
- import type { Disposable } from "@yume-chan/event";
3
- import type { ValueOrPromise } from "@yume-chan/struct";
4
- import { delay } from "./utils.js";
5
-
6
- export interface ScrcpyClientConnectionOptions {
7
- control: boolean;
8
-
9
- /**
10
- * Write a byte on start to detect connection issues
11
- */
12
- sendDummyByte: boolean;
13
-
14
- /**
15
- * Send device name and size
16
- */
17
- sendDeviceMeta: boolean;
18
- }
19
-
20
- export abstract class ScrcpyClientConnection implements Disposable {
21
- protected device: Adb;
22
-
23
- protected options: ScrcpyClientConnectionOptions;
24
-
25
- public constructor(device: Adb, options: ScrcpyClientConnectionOptions) {
26
- this.device = device;
27
- this.options = options;
28
- }
29
-
30
- public initialize(): ValueOrPromise<void> { }
31
-
32
- public abstract getStreams(): ValueOrPromise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]>;
33
-
34
- public dispose(): void { }
35
- }
36
-
37
- export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
38
- private async connect(): Promise<AdbSocket> {
39
- return await this.device.createSocket('localabstract:scrcpy');
40
- }
41
-
42
- private async connectAndRetry(): Promise<AdbSocket> {
43
- for (let i = 0; i < 100; i++) {
44
- try {
45
- return await this.connect();
46
- } catch (e) {
47
- await delay(100);
48
- }
49
- }
50
- throw new Error(`Can't connect to server after 100 retries`);
51
- }
52
-
53
- private async connectVideoStream(): Promise<AdbSocket> {
54
- const stream = await this.connectAndRetry();
55
- if (this.options.sendDummyByte) {
56
- const reader = stream.readable.getReader();
57
- const { done, value } = await reader.read();
58
- // server will write a `0` to signal connection success
59
- if (done || value.byteLength !== 1 || value[0] !== 0) {
60
- throw new Error('Unexpected response from server');
61
- }
62
- reader.releaseLock();
63
- }
64
- return stream;
65
- }
66
-
67
- public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
68
- const videoStream = await this.connectVideoStream();
69
-
70
- let controlStream: AdbSocket | undefined;
71
- if (this.options.control) {
72
- controlStream = await this.connectAndRetry();
73
- }
74
-
75
- // Server only writes device meta after control socket is connected (if enabled)
76
- if (this.options.sendDeviceMeta) {
77
- const reader = videoStream.readable.getReader();
78
- const { done, value } = await reader.read();
79
- // 64 bytes device name + 2 bytes video width + 2 bytes video height
80
- if (done || value.byteLength !== 64 + 2 + 2) {
81
- throw new Error('Unexpected response from server');
82
- }
83
- reader.releaseLock();
84
- }
85
-
86
- return [videoStream, controlStream];
87
- }
88
- }
89
-
90
- export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
91
- private streams!: ReadableStreamDefaultReader<AdbSocket>;
92
-
93
- private address!: string;
94
-
95
- public override async initialize(): Promise<void> {
96
- try {
97
- // try to unbind first
98
- await this.device.reverse.remove('localabstract:scrcpy');
99
- } catch {
100
- // ignore error
101
- }
102
-
103
- const queue = new TransformStream<AdbSocket>();
104
- this.streams = queue.readable.getReader();
105
- const writer = queue.writable.getWriter();
106
- this.address = await this.device.reverse.add('localabstract:scrcpy', 27183, {
107
- onSocket: (packet, stream) => {
108
- writer.write(stream);
109
- },
110
- });
111
- }
112
-
113
- private async accept(): Promise<AdbSocket> {
114
- return (await this.streams.read()).value!;
115
- }
116
-
117
- public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
118
- const videoStream = await this.accept();
119
-
120
- let controlStream: AdbSocket | undefined;
121
- if (this.options.control) {
122
- controlStream = await this.accept();
123
- }
124
-
125
- // Server only writes device meta after control socket is connected (if enabled)
126
- if (this.options.sendDeviceMeta) {
127
- const reader = videoStream.readable.getReader();
128
- const { done, value } = await reader.read();
129
- // 64 bytes device name + 2 bytes video width + 2 bytes video height
130
- if (done || value.byteLength !== 64 + 2 + 2) {
131
- throw new Error('Unexpected response from server');
132
- }
133
- reader.releaseLock();
134
- }
135
-
136
- return [videoStream, controlStream];
137
- }
138
-
139
- public override dispose() {
140
- // Don't await this!
141
- // `reverse.remove`'s response will never arrive
142
- // before we read all pending data from `videoStream`
143
- this.device.reverse.remove(this.address);
144
- }
145
- }
1
+ import type { Adb, AdbSocket } from "@yume-chan/adb";
2
+ import type { Disposable } from "@yume-chan/event";
3
+ import type { ValueOrPromise } from "@yume-chan/struct";
4
+ import { delay } from "./utils.js";
5
+
6
+ export interface ScrcpyClientConnectionOptions {
7
+ control: boolean;
8
+
9
+ /**
10
+ * Write a byte on start to detect connection issues
11
+ */
12
+ sendDummyByte: boolean;
13
+
14
+ /**
15
+ * Send device name and size
16
+ */
17
+ sendDeviceMeta: boolean;
18
+ }
19
+
20
+ export abstract class ScrcpyClientConnection implements Disposable {
21
+ protected device: Adb;
22
+
23
+ protected options: ScrcpyClientConnectionOptions;
24
+
25
+ public constructor(device: Adb, options: ScrcpyClientConnectionOptions) {
26
+ this.device = device;
27
+ this.options = options;
28
+ }
29
+
30
+ public initialize(): ValueOrPromise<void> { }
31
+
32
+ public abstract getStreams(): ValueOrPromise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]>;
33
+
34
+ public dispose(): void { }
35
+ }
36
+
37
+ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
38
+ private async connect(): Promise<AdbSocket> {
39
+ return await this.device.createSocket('localabstract:scrcpy');
40
+ }
41
+
42
+ private async connectAndRetry(): Promise<AdbSocket> {
43
+ for (let i = 0; i < 100; i++) {
44
+ try {
45
+ return await this.connect();
46
+ } catch (e) {
47
+ await delay(100);
48
+ }
49
+ }
50
+ throw new Error(`Can't connect to server after 100 retries`);
51
+ }
52
+
53
+ private async connectVideoStream(): Promise<AdbSocket> {
54
+ const stream = await this.connectAndRetry();
55
+ if (this.options.sendDummyByte) {
56
+ const reader = stream.readable.getReader();
57
+ const { done, value } = await reader.read();
58
+ // server will write a `0` to signal connection success
59
+ if (done || value.byteLength !== 1 || value[0] !== 0) {
60
+ throw new Error('Unexpected response from server');
61
+ }
62
+ reader.releaseLock();
63
+ }
64
+ return stream;
65
+ }
66
+
67
+ public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
68
+ const videoStream = await this.connectVideoStream();
69
+
70
+ let controlStream: AdbSocket | undefined;
71
+ if (this.options.control) {
72
+ controlStream = await this.connectAndRetry();
73
+ }
74
+
75
+ // Server only writes device meta after control socket is connected (if enabled)
76
+ if (this.options.sendDeviceMeta) {
77
+ const reader = videoStream.readable.getReader();
78
+ const { done, value } = await reader.read();
79
+ // 64 bytes device name + 2 bytes video width + 2 bytes video height
80
+ if (done || value.byteLength !== 64 + 2 + 2) {
81
+ throw new Error('Unexpected response from server');
82
+ }
83
+ reader.releaseLock();
84
+ }
85
+
86
+ return [videoStream, controlStream];
87
+ }
88
+ }
89
+
90
+ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
91
+ private streams!: ReadableStreamDefaultReader<AdbSocket>;
92
+
93
+ private address!: string;
94
+
95
+ public override async initialize(): Promise<void> {
96
+ try {
97
+ // try to unbind first
98
+ await this.device.reverse.remove('localabstract:scrcpy');
99
+ } catch {
100
+ // ignore error
101
+ }
102
+
103
+ const queue = new TransformStream<AdbSocket>();
104
+ this.streams = queue.readable.getReader();
105
+ const writer = queue.writable.getWriter();
106
+ this.address = await this.device.reverse.add(
107
+ 'localabstract:scrcpy',
108
+ 'tcp:27183',
109
+ socket => {
110
+ writer.write(socket);
111
+ return true;
112
+ },
113
+ );
114
+ }
115
+
116
+ private async accept(): Promise<AdbSocket> {
117
+ return (await this.streams.read()).value!;
118
+ }
119
+
120
+ public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
121
+ const videoStream = await this.accept();
122
+
123
+ let controlStream: AdbSocket | undefined;
124
+ if (this.options.control) {
125
+ controlStream = await this.accept();
126
+ }
127
+
128
+ // Server only writes device meta after control socket is connected (if enabled)
129
+ if (this.options.sendDeviceMeta) {
130
+ const reader = videoStream.readable.getReader();
131
+ const { done, value } = await reader.read();
132
+ // 64 bytes device name + 2 bytes video width + 2 bytes video height
133
+ if (done || value.byteLength !== 64 + 2 + 2) {
134
+ throw new Error('Unexpected response from server');
135
+ }
136
+ reader.releaseLock();
137
+ }
138
+
139
+ return [videoStream, controlStream];
140
+ }
141
+
142
+ public override dispose() {
143
+ // Don't await this!
144
+ // `reverse.remove`'s response will never arrive
145
+ // before we read all pending data from `videoStream`
146
+ this.device.reverse.remove(this.address);
147
+ }
148
+ }
@@ -1,3 +1,3 @@
1
- export * from './types.js';
2
- export * from './tinyh264/index.js';
3
- export * from './web-codecs/index.js';
1
+ export * from './types.js';
2
+ export * from './tinyh264/index.js';
3
+ export * from './web-codecs/index.js';
@@ -1,112 +1,112 @@
1
- import { WritableStream } from "@yume-chan/adb";
2
- import { PromiseResolver } from "@yume-chan/async";
3
- import { AndroidCodecLevel, AndroidCodecProfile } from "../../codec.js";
4
- import type { VideoStreamPacket } from "../../options/index.js";
5
- import type { H264Configuration, H264Decoder } from "../types.js";
6
- import { createTinyH264Wrapper, type TinyH264Wrapper } from "./wrapper.js";
7
-
8
- let cachedInitializePromise: Promise<{ YuvBuffer: typeof import('yuv-buffer'), YuvCanvas: typeof import('yuv-canvas').default; }> | undefined;
9
- function initialize() {
10
- if (!cachedInitializePromise) {
11
- cachedInitializePromise = Promise.all(
12
- [import('yuv-buffer'), import('yuv-canvas')]
13
- ).then(([YuvBuffer, { default: YuvCanvas }]) => ({
14
- YuvBuffer, YuvCanvas
15
- }));
16
- }
17
-
18
- return cachedInitializePromise;
19
- }
20
-
21
- export class TinyH264Decoder implements H264Decoder {
22
- public readonly maxProfile = AndroidCodecProfile.Baseline;
23
-
24
- public readonly maxLevel = AndroidCodecLevel.Level4;
25
-
26
- private _renderer: HTMLCanvasElement;
27
- public get renderer() { return this._renderer; }
28
-
29
- private _frameRendered = 0;
30
- public get frameRendered() { return this._frameRendered; }
31
-
32
- private _writable: WritableStream<VideoStreamPacket>;
33
- public get writable() { return this._writable; }
34
-
35
- private _yuvCanvas: import('yuv-canvas').default | undefined;
36
- private _initializer: PromiseResolver<TinyH264Wrapper> | undefined;
37
-
38
- public constructor() {
39
- initialize();
40
-
41
- this._renderer = document.createElement('canvas');
42
-
43
- this._writable = new WritableStream<VideoStreamPacket>({
44
- write: async (packet) => {
45
- switch (packet.type) {
46
- case 'configuration':
47
- this.configure(packet.data);
48
- break;
49
- case 'frame':
50
- if (!this._initializer) {
51
- throw new Error('Decoder not initialized');
52
- }
53
-
54
- const wrapper = await this._initializer.promise;
55
- wrapper.feed(packet.data.slice().buffer);
56
- break;
57
- }
58
- }
59
- });
60
- }
61
-
62
- private async configure(config: H264Configuration) {
63
- this.dispose();
64
-
65
- this._initializer = new PromiseResolver<TinyH264Wrapper>();
66
- const { YuvBuffer, YuvCanvas } = await initialize();
67
-
68
- if (!this._yuvCanvas) {
69
- this._yuvCanvas = YuvCanvas.attach(this._renderer);;
70
- }
71
-
72
- const { encodedWidth, encodedHeight } = config;
73
- const chromaWidth = encodedWidth / 2;
74
- const chromaHeight = encodedHeight / 2;
75
-
76
- this._renderer.width = config.croppedWidth;
77
- this._renderer.height = config.croppedHeight;
78
- const format = YuvBuffer.format({
79
- width: encodedWidth,
80
- height: encodedHeight,
81
- chromaWidth,
82
- chromaHeight,
83
- cropLeft: config.cropLeft,
84
- cropTop: config.cropTop,
85
- cropWidth: config.croppedWidth,
86
- cropHeight: config.croppedHeight,
87
- displayWidth: config.croppedWidth,
88
- displayHeight: config.croppedHeight,
89
- });
90
-
91
- const wrapper = await createTinyH264Wrapper();
92
- this._initializer.resolve(wrapper);
93
-
94
- const uPlaneOffset = encodedWidth * encodedHeight;
95
- const vPlaneOffset = uPlaneOffset + chromaWidth * chromaHeight;
96
- wrapper.onPictureReady(({ data }) => {
97
- this._frameRendered += 1;
98
- const array = new Uint8Array(data);
99
- const frame = YuvBuffer.frame(format,
100
- YuvBuffer.lumaPlane(format, array, encodedWidth, 0),
101
- YuvBuffer.chromaPlane(format, array, chromaWidth, uPlaneOffset),
102
- YuvBuffer.chromaPlane(format, array, chromaWidth, vPlaneOffset)
103
- );
104
- this._yuvCanvas!.drawFrame(frame);
105
- });
106
- }
107
-
108
- public dispose(): void {
109
- this._initializer?.promise.then(wrapper => wrapper.dispose());
110
- this._initializer = undefined;
111
- }
112
- }
1
+ import { WritableStream } from "@yume-chan/adb";
2
+ import { PromiseResolver } from "@yume-chan/async";
3
+ import { AndroidCodecLevel, AndroidCodecProfile } from "../../codec.js";
4
+ import type { VideoStreamPacket } from "../../options/index.js";
5
+ import type { H264Configuration, H264Decoder } from "../types.js";
6
+ import { createTinyH264Wrapper, type TinyH264Wrapper } from "./wrapper.js";
7
+
8
+ let cachedInitializePromise: Promise<{ YuvBuffer: typeof import('yuv-buffer'), YuvCanvas: typeof import('yuv-canvas').default; }> | undefined;
9
+ function initialize() {
10
+ if (!cachedInitializePromise) {
11
+ cachedInitializePromise = Promise.all(
12
+ [import('yuv-buffer'), import('yuv-canvas')]
13
+ ).then(([YuvBuffer, { default: YuvCanvas }]) => ({
14
+ YuvBuffer, YuvCanvas
15
+ }));
16
+ }
17
+
18
+ return cachedInitializePromise;
19
+ }
20
+
21
+ export class TinyH264Decoder implements H264Decoder {
22
+ public readonly maxProfile = AndroidCodecProfile.Baseline;
23
+
24
+ public readonly maxLevel = AndroidCodecLevel.Level4;
25
+
26
+ private _renderer: HTMLCanvasElement;
27
+ public get renderer() { return this._renderer; }
28
+
29
+ private _frameRendered = 0;
30
+ public get frameRendered() { return this._frameRendered; }
31
+
32
+ private _writable: WritableStream<VideoStreamPacket>;
33
+ public get writable() { return this._writable; }
34
+
35
+ private _yuvCanvas: import('yuv-canvas').default | undefined;
36
+ private _initializer: PromiseResolver<TinyH264Wrapper> | undefined;
37
+
38
+ public constructor() {
39
+ initialize();
40
+
41
+ this._renderer = document.createElement('canvas');
42
+
43
+ this._writable = new WritableStream<VideoStreamPacket>({
44
+ write: async (packet) => {
45
+ switch (packet.type) {
46
+ case 'configuration':
47
+ this.configure(packet.data);
48
+ break;
49
+ case 'frame':
50
+ if (!this._initializer) {
51
+ throw new Error('Decoder not initialized');
52
+ }
53
+
54
+ const wrapper = await this._initializer.promise;
55
+ wrapper.feed(packet.data.slice().buffer);
56
+ break;
57
+ }
58
+ }
59
+ });
60
+ }
61
+
62
+ private async configure(config: H264Configuration) {
63
+ this.dispose();
64
+
65
+ this._initializer = new PromiseResolver<TinyH264Wrapper>();
66
+ const { YuvBuffer, YuvCanvas } = await initialize();
67
+
68
+ if (!this._yuvCanvas) {
69
+ this._yuvCanvas = YuvCanvas.attach(this._renderer);;
70
+ }
71
+
72
+ const { encodedWidth, encodedHeight } = config;
73
+ const chromaWidth = encodedWidth / 2;
74
+ const chromaHeight = encodedHeight / 2;
75
+
76
+ this._renderer.width = config.croppedWidth;
77
+ this._renderer.height = config.croppedHeight;
78
+ const format = YuvBuffer.format({
79
+ width: encodedWidth,
80
+ height: encodedHeight,
81
+ chromaWidth,
82
+ chromaHeight,
83
+ cropLeft: config.cropLeft,
84
+ cropTop: config.cropTop,
85
+ cropWidth: config.croppedWidth,
86
+ cropHeight: config.croppedHeight,
87
+ displayWidth: config.croppedWidth,
88
+ displayHeight: config.croppedHeight,
89
+ });
90
+
91
+ const wrapper = await createTinyH264Wrapper();
92
+ this._initializer.resolve(wrapper);
93
+
94
+ const uPlaneOffset = encodedWidth * encodedHeight;
95
+ const vPlaneOffset = uPlaneOffset + chromaWidth * chromaHeight;
96
+ wrapper.onPictureReady(({ data }) => {
97
+ this._frameRendered += 1;
98
+ const array = new Uint8Array(data);
99
+ const frame = YuvBuffer.frame(format,
100
+ YuvBuffer.lumaPlane(format, array, encodedWidth, 0),
101
+ YuvBuffer.chromaPlane(format, array, chromaWidth, uPlaneOffset),
102
+ YuvBuffer.chromaPlane(format, array, chromaWidth, vPlaneOffset)
103
+ );
104
+ this._yuvCanvas!.drawFrame(frame);
105
+ });
106
+ }
107
+
108
+ public dispose(): void {
109
+ this._initializer?.promise.then(wrapper => wrapper.dispose());
110
+ this._initializer = undefined;
111
+ }
112
+ }