@yume-chan/scrcpy 0.0.12 → 0.0.15

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 (78) hide show
  1. package/CHANGELOG.json +39 -0
  2. package/CHANGELOG.md +23 -1
  3. package/README.md +1 -0
  4. package/bin/scrcpy-server +0 -0
  5. package/bin/version.d.ts +1 -1
  6. package/bin/version.js +1 -1
  7. package/esm/client.d.ts +14 -2
  8. package/esm/client.d.ts.map +1 -1
  9. package/esm/client.js +277 -145
  10. package/esm/client.js.map +1 -1
  11. package/esm/connection.d.ts +4 -4
  12. package/esm/connection.d.ts.map +1 -1
  13. package/esm/connection.js +74 -71
  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.d.ts.map +1 -1
  20. package/esm/decoder/web-codecs/index.js +28 -22
  21. package/esm/decoder/web-codecs/index.js.map +1 -1
  22. package/esm/message.d.ts +12 -8
  23. package/esm/message.d.ts.map +1 -1
  24. package/esm/message.js +13 -8
  25. package/esm/message.js.map +1 -1
  26. package/esm/options/1_16/index.d.ts +8 -7
  27. package/esm/options/1_16/index.d.ts.map +1 -1
  28. package/esm/options/1_16/index.js +88 -82
  29. package/esm/options/1_16/index.js.map +1 -1
  30. package/esm/options/1_16/sps.js +3 -2
  31. package/esm/options/1_16/sps.js.map +1 -1
  32. package/esm/options/1_18.d.ts +3 -2
  33. package/esm/options/1_18.d.ts.map +1 -1
  34. package/esm/options/1_18.js +24 -1
  35. package/esm/options/1_18.js.map +1 -1
  36. package/esm/options/1_21.js +4 -1
  37. package/esm/options/1_21.js.map +1 -1
  38. package/esm/options/1_22.d.ts +3 -3
  39. package/esm/options/1_22.d.ts.map +1 -1
  40. package/esm/options/1_22.js +9 -6
  41. package/esm/options/1_22.js.map +1 -1
  42. package/esm/options/1_23.d.ts +12 -0
  43. package/esm/options/1_23.d.ts.map +1 -0
  44. package/esm/options/1_23.js +34 -0
  45. package/esm/options/1_23.js.map +1 -0
  46. package/esm/options/1_24.d.ts +9 -0
  47. package/esm/options/1_24.d.ts.map +1 -0
  48. package/esm/options/1_24.js +13 -0
  49. package/esm/options/1_24.js.map +1 -0
  50. package/esm/options/common.d.ts +6 -2
  51. package/esm/options/common.d.ts.map +1 -1
  52. package/esm/options/common.js.map +1 -1
  53. package/esm/options/index.d.ts +2 -0
  54. package/esm/options/index.d.ts.map +1 -1
  55. package/esm/options/index.js +2 -0
  56. package/esm/options/index.js.map +1 -1
  57. package/esm/push-server.d.ts +2 -2
  58. package/esm/push-server.d.ts.map +1 -1
  59. package/esm/push-server.js +6 -13
  60. package/esm/push-server.js.map +1 -1
  61. package/esm/utils.d.ts +0 -2
  62. package/esm/utils.d.ts.map +1 -1
  63. package/esm/utils.js +0 -8
  64. package/esm/utils.js.map +1 -1
  65. package/package.json +6 -6
  66. package/src/client.ts +185 -25
  67. package/src/connection.ts +40 -15
  68. package/src/decoder/web-codecs/index.ts +2 -1
  69. package/src/message.ts +10 -3
  70. package/src/options/1_16/index.ts +51 -43
  71. package/src/options/1_18.ts +21 -1
  72. package/src/options/1_22.ts +6 -8
  73. package/src/options/1_23.ts +44 -0
  74. package/src/options/1_24.ts +18 -0
  75. package/src/options/common.ts +7 -2
  76. package/src/options/index.ts +2 -0
  77. package/src/push-server.ts +6 -8
  78. package/src/utils.ts +0 -11
package/src/connection.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Adb, AdbBufferedStream, AdbSocket } from "@yume-chan/adb";
1
+ import type { Adb, AdbSocket } from "@yume-chan/adb";
2
2
  import type { Disposable } from "@yume-chan/event";
3
3
  import type { ValueOrPromise } from "@yume-chan/struct";
4
4
  import { delay } from "./utils.js";
@@ -29,7 +29,7 @@ export abstract class ScrcpyClientConnection implements Disposable {
29
29
 
30
30
  public initialize(): ValueOrPromise<void> { }
31
31
 
32
- public abstract getStreams(): ValueOrPromise<[videoSteam: AdbBufferedStream, controlStream: AdbSocket | undefined]>;
32
+ public abstract getStreams(): ValueOrPromise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]>;
33
33
 
34
34
  public dispose(): void { }
35
35
  }
@@ -50,26 +50,39 @@ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
50
50
  throw new Error(`Can't connect to server after 100 retries`);
51
51
  }
52
52
 
53
- private async connectVideoStream(): Promise<AdbBufferedStream> {
54
- const socket = await this.connectAndRetry();
55
- const stream = new AdbBufferedStream(socket);
53
+ private async connectVideoStream(): Promise<AdbSocket> {
54
+ const stream = await this.connectAndRetry();
56
55
  if (this.options.sendDummyByte) {
56
+ const reader = stream.readable.getReader();
57
+ const { done, value } = await reader.read();
57
58
  // server will write a `0` to signal connection success
58
- await stream.read(1);
59
+ if (done || value.byteLength !== 1 || value[0] !== 0) {
60
+ throw new Error('Unexpected response from server');
61
+ }
62
+ reader.releaseLock();
59
63
  }
60
64
  return stream;
61
65
  }
62
66
 
63
- public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbSocket | undefined]> {
67
+ public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
64
68
  const videoStream = await this.connectVideoStream();
69
+
65
70
  let controlStream: AdbSocket | undefined;
66
71
  if (this.options.control) {
67
72
  controlStream = await this.connectAndRetry();
68
73
  }
74
+
75
+ // Server only writes device meta after control socket is connected (if enabled)
69
76
  if (this.options.sendDeviceMeta) {
77
+ const reader = videoStream.readable.getReader();
78
+ const { done, value } = await reader.read();
70
79
  // 64 bytes device name + 2 bytes video width + 2 bytes video height
71
- await videoStream.read(64 + 2 + 2);
80
+ if (done || value.byteLength !== 64 + 2 + 2) {
81
+ throw new Error('Unexpected response from server');
82
+ }
83
+ reader.releaseLock();
72
84
  }
85
+
73
86
  return [videoStream, controlStream];
74
87
  }
75
88
  }
@@ -90,27 +103,39 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
90
103
  const queue = new TransformStream<AdbSocket>();
91
104
  this.streams = queue.readable.getReader();
92
105
  const writer = queue.writable.getWriter();
93
- this.address = await this.device.reverse.add('localabstract:scrcpy', 27183, {
94
- onSocket: (packet, stream) => {
95
- writer.write(stream);
106
+ this.address = await this.device.reverse.add(
107
+ 'localabstract:scrcpy',
108
+ 'tcp:27183',
109
+ socket => {
110
+ writer.write(socket);
111
+ return true;
96
112
  },
97
- });
113
+ );
98
114
  }
99
115
 
100
116
  private async accept(): Promise<AdbSocket> {
101
117
  return (await this.streams.read()).value!;
102
118
  }
103
119
 
104
- public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbSocket | undefined]> {
105
- const videoStream = new AdbBufferedStream(await this.accept());
120
+ public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
121
+ const videoStream = await this.accept();
122
+
106
123
  let controlStream: AdbSocket | undefined;
107
124
  if (this.options.control) {
108
125
  controlStream = await this.accept();
109
126
  }
127
+
128
+ // Server only writes device meta after control socket is connected (if enabled)
110
129
  if (this.options.sendDeviceMeta) {
130
+ const reader = videoStream.readable.getReader();
131
+ const { done, value } = await reader.read();
111
132
  // 64 bytes device name + 2 bytes video width + 2 bytes video height
112
- await videoStream.read(64 + 2 + 2);
133
+ if (done || value.byteLength !== 64 + 2 + 2) {
134
+ throw new Error('Unexpected response from server');
135
+ }
136
+ reader.releaseLock();
113
137
  }
138
+
114
139
  return [videoStream, controlStream];
115
140
  }
116
141
 
@@ -55,7 +55,8 @@ export class WebCodecsDecoder implements H264Decoder {
55
55
  break;
56
56
  case 'frame':
57
57
  this.decoder.decode(new EncodedVideoChunk({
58
- type: 'key',
58
+ // Treat `undefined` as `key`, otherwise won't decode.
59
+ type: packet.keyframe === false ? 'delta' : 'key',
59
60
  timestamp: 0,
60
61
  data: packet.data,
61
62
  }));
package/src/message.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import Struct, { placeholder } from '@yume-chan/struct';
2
2
 
3
+ // https://github.com/Genymobile/scrcpy/blob/fa5b2a29e983a46b49531def9cf3d80c40c3de37/app/src/control_msg.h#L23
4
+ // For their message bodies, see https://github.com/Genymobile/scrcpy/blob/5c62f3419d252d10cd8c9cbb7c918b358b81f2d0/app/src/control_msg.c#L92
3
5
  export enum ScrcpyControlMessageType {
4
6
  InjectKeycode,
5
7
  InjectText,
@@ -7,6 +9,7 @@ export enum ScrcpyControlMessageType {
7
9
  InjectScroll,
8
10
  BackOrScreenOn,
9
11
  ExpandNotificationPanel,
12
+ ExpandSettingPanel,
10
13
  CollapseNotificationPanel,
11
14
  GetClipboard,
12
15
  SetClipboard,
@@ -31,9 +34,13 @@ export enum AndroidMotionEventAction {
31
34
  ButtonRelease,
32
35
  }
33
36
 
37
+ export const ScrcpySimpleControlMessage =
38
+ new Struct()
39
+ .uint8('type');
40
+
34
41
  export const ScrcpyInjectTouchControlMessage =
35
42
  new Struct()
36
- .uint8('type', ScrcpyControlMessageType.InjectTouch as const)
43
+ .fields(ScrcpySimpleControlMessage)
37
44
  .uint8('action', placeholder<AndroidMotionEventAction>())
38
45
  .uint64('pointerId')
39
46
  .uint32('pointerX')
@@ -47,7 +54,7 @@ export type ScrcpyInjectTouchControlMessage = typeof ScrcpyInjectTouchControlMes
47
54
 
48
55
  export const ScrcpyInjectTextControlMessage =
49
56
  new Struct()
50
- .uint8('type', ScrcpyControlMessageType.InjectText as const)
57
+ .fields(ScrcpySimpleControlMessage)
51
58
  .uint32('length')
52
59
  .string('text', { lengthField: 'length' });
53
60
 
@@ -95,7 +102,7 @@ export enum AndroidKeyCode {
95
102
 
96
103
  export const ScrcpyInjectKeyCodeControlMessage =
97
104
  new Struct()
98
- .uint8('type', ScrcpyControlMessageType.InjectKeycode as const)
105
+ .fields(ScrcpySimpleControlMessage)
99
106
  .uint8('action', placeholder<AndroidKeyEventAction>())
100
107
  .uint32('keyCode')
101
108
  .uint32('repeat')
@@ -1,8 +1,8 @@
1
- import { BufferedStreamEndedError, ReadableStream, TransformStream, type Adb, type AdbBufferedStream } from "@yume-chan/adb";
2
- import Struct, { placeholder } from "@yume-chan/struct";
1
+ import { StructDeserializeStream, TransformStream, type Adb } from "@yume-chan/adb";
2
+ import Struct from "@yume-chan/struct";
3
3
  import type { AndroidCodecLevel, AndroidCodecProfile } from "../../codec.js";
4
4
  import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnectionOptions } from "../../connection.js";
5
- import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message.js";
5
+ import { AndroidKeyEventAction, ScrcpyControlMessageType, ScrcpySimpleControlMessage } from "../../message.js";
6
6
  import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18.js";
7
7
  import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22.js";
8
8
  import { toScrcpyOptionValue, type ScrcpyOptions, type ScrcpyOptionValue, type VideoStreamPacket } from "../common.js";
@@ -16,7 +16,7 @@ export enum ScrcpyLogLevel {
16
16
  Error = 'error',
17
17
  }
18
18
 
19
- export enum ScrcpyScreenOrientation {
19
+ export enum ScrcpyVideoOrientation {
20
20
  Initial = -2,
21
21
  Unlocked = -1,
22
22
  Portrait = 0,
@@ -75,7 +75,7 @@ export interface ScrcpyOptionsInit1_16 {
75
75
  * It will not keep the device screen in specific orientation,
76
76
  * only the captured video will in this orientation.
77
77
  */
78
- lockVideoOrientation: ScrcpyScreenOrientation;
78
+ lockVideoOrientation: ScrcpyVideoOrientation;
79
79
 
80
80
  tunnelForward: boolean;
81
81
 
@@ -112,19 +112,18 @@ export interface ScrcpyOptionsInit1_16 {
112
112
 
113
113
  export const VideoPacket =
114
114
  new Struct()
115
- .int64('pts')
115
+ .uint64('pts')
116
116
  .uint32('size')
117
117
  .uint8Array('data', { lengthField: 'size' });
118
118
 
119
- export const NoPts = BigInt(-1);
119
+ export const NO_PTS = BigInt(1) << BigInt(63);
120
120
 
121
121
  export const ScrcpyBackOrScreenOnEvent1_16 =
122
- new Struct()
123
- .uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
122
+ ScrcpySimpleControlMessage;
124
123
 
125
124
  export const ScrcpyInjectScrollControlMessage1_16 =
126
125
  new Struct()
127
- .uint8('type', ScrcpyControlMessageType.InjectScroll as const)
126
+ .fields(ScrcpySimpleControlMessage)
128
127
  .uint32('pointerX')
129
128
  .uint32('pointerY')
130
129
  .uint16('screenWidth')
@@ -142,8 +141,8 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
142
141
  }
143
142
 
144
143
  if (new.target === ScrcpyOptions1_16 &&
145
- value.lockVideoOrientation === ScrcpyScreenOrientation.Initial) {
146
- value.lockVideoOrientation = ScrcpyScreenOrientation.Unlocked;
144
+ value.lockVideoOrientation === ScrcpyVideoOrientation.Initial) {
145
+ value.lockVideoOrientation = ScrcpyVideoOrientation.Unlocked;
147
146
  }
148
147
 
149
148
  this.value = value as Partial<T>;
@@ -174,7 +173,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
174
173
  maxSize: 0,
175
174
  bitRate: 8_000_000,
176
175
  maxFps: 0,
177
- lockVideoOrientation: ScrcpyScreenOrientation.Unlocked,
176
+ lockVideoOrientation: ScrcpyVideoOrientation.Unlocked,
178
177
  tunnelForward: false,
179
178
  crop: '-',
180
179
  sendFrameMeta: true,
@@ -211,29 +210,28 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
211
210
  return /\s+scrcpy --encoder-name '(.*?)'/;
212
211
  }
213
212
 
214
- public parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket> {
213
+ public createVideoStreamTransformer(): TransformStream<Uint8Array, VideoStreamPacket> {
215
214
  // Optimized path for video frames only
216
215
  if (this.value.sendFrameMeta === false) {
217
- return stream
218
- .release()
219
- .pipeThrough(new TransformStream<Uint8Array, VideoStreamPacket>({
220
- transform(chunk, controller) {
221
- controller.enqueue({
222
- type: 'frame',
223
- data: chunk,
224
- });
225
- },
226
- }));
216
+ return new TransformStream({
217
+ transform(chunk, controller) {
218
+ controller.enqueue({
219
+ type: 'frame',
220
+ data: chunk,
221
+ });
222
+ },
223
+ });
227
224
  }
228
225
 
229
226
  let header: Uint8Array | undefined;
230
227
 
231
- return new ReadableStream<VideoStreamPacket>({
232
- async pull(controller) {
233
- try {
234
- const { pts, data } = await VideoPacket.deserialize(stream);
235
- if (pts === NoPts) {
236
- const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
228
+ let deserializeStream = new StructDeserializeStream(VideoPacket);
229
+ return {
230
+ writable: deserializeStream.writable,
231
+ readable: deserializeStream.readable.pipeThrough(new TransformStream({
232
+ transform(packet, controller) {
233
+ if (packet.pts === NO_PTS) {
234
+ const sequenceParameterSet = parse_sequence_parameter_set(packet.data.slice().buffer);
237
235
 
238
236
  const {
239
237
  profile_idc: profileIndex,
@@ -258,7 +256,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
258
256
  const croppedWidth = encodedWidth - cropLeft - cropRight;
259
257
  const croppedHeight = encodedHeight - cropTop - cropBottom;
260
258
 
261
- header = data;
259
+ header = packet.data;
262
260
  controller.enqueue({
263
261
  type: 'configuration',
264
262
  data: {
@@ -280,28 +278,38 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
280
278
 
281
279
  let frameData: Uint8Array;
282
280
  if (header) {
283
- frameData = new Uint8Array(header.byteLength + data.byteLength);
281
+ frameData = new Uint8Array(header.byteLength + packet.data.byteLength);
284
282
  frameData.set(header);
285
- frameData.set(data!, header.byteLength);
283
+ frameData.set(packet.data, header.byteLength);
286
284
  header = undefined;
287
285
  } else {
288
- frameData = data;
286
+ frameData = packet.data;
289
287
  }
290
288
 
291
289
  controller.enqueue({
292
290
  type: 'frame',
291
+ pts: packet.pts,
293
292
  data: frameData,
294
293
  });
295
- } catch (e) {
296
- if (e instanceof BufferedStreamEndedError) {
297
- controller.close();
298
- return;
299
- }
300
-
301
- throw e;
302
294
  }
303
- }
304
- });
295
+ }))
296
+ };
297
+ }
298
+
299
+ public getControlMessageTypes(): ScrcpyControlMessageType[] {
300
+ return [
301
+ /* 0 */ ScrcpyControlMessageType.InjectKeycode,
302
+ /* 1 */ ScrcpyControlMessageType.InjectText,
303
+ /* 2 */ ScrcpyControlMessageType.InjectTouch,
304
+ /* 3 */ ScrcpyControlMessageType.InjectScroll,
305
+ /* 4 */ ScrcpyControlMessageType.BackOrScreenOn,
306
+ /* 5 */ ScrcpyControlMessageType.ExpandNotificationPanel,
307
+ /* 6 */ ScrcpyControlMessageType.CollapseNotificationPanel,
308
+ /* 7 */ ScrcpyControlMessageType.GetClipboard,
309
+ /* 8 */ ScrcpyControlMessageType.SetClipboard,
310
+ /* 9 */ ScrcpyControlMessageType.SetScreenPowerMode,
311
+ /* 10 */ ScrcpyControlMessageType.RotateDevice,
312
+ ];
305
313
  }
306
314
 
307
315
  public serializeBackOrScreenOnControlMessage(
@@ -1,5 +1,5 @@
1
1
  import Struct, { placeholder } from "@yume-chan/struct";
2
- import type { AndroidKeyEventAction } from "../message.js";
2
+ import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../message.js";
3
3
  import { ScrcpyBackOrScreenOnEvent1_16, ScrcpyOptions1_16, type ScrcpyOptionsInit1_16 } from "./1_16/index.js";
4
4
 
5
5
  export interface ScrcpyOptionsInit1_18 extends ScrcpyOptionsInit1_16 {
@@ -33,6 +33,26 @@ export class ScrcpyOptions1_18<T extends ScrcpyOptionsInit1_18 = ScrcpyOptionsIn
33
33
  return /\s+scrcpy --encoder '(.*?)'/;
34
34
  }
35
35
 
36
+ public override getControlMessageTypes(): ScrcpyControlMessageType[] {
37
+ /**
38
+ * 0 InjectKeycode
39
+ * 1 InjectText
40
+ * 2 InjectTouch
41
+ * 3 InjectScroll
42
+ * 4 BackOrScreenOn
43
+ * 5 ExpandNotificationPanel
44
+ * 6 ExpandSettingsPanel
45
+ * 7 CollapseNotificationPanel
46
+ * 8 GetClipboard
47
+ * 9 SetClipboard
48
+ * 10 SetScreenPowerMode
49
+ * 11 RotateDevice
50
+ */
51
+ const types = super.getControlMessageTypes();
52
+ types.splice(6, 0, ScrcpyControlMessageType.ExpandSettingPanel);
53
+ return types;
54
+ }
55
+
36
56
  public override serializeBackOrScreenOnControlMessage(
37
57
  message: ScrcpyBackOrScreenOnEvent1_18,
38
58
  ) {
@@ -1,6 +1,6 @@
1
1
  import type { Adb } from "@yume-chan/adb";
2
2
  import Struct from "@yume-chan/struct";
3
- import { ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnection, type ScrcpyClientConnectionOptions } from "../connection.js";
3
+ import { ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnection } from "../connection.js";
4
4
  import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16/index.js";
5
5
  import { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21.js";
6
6
 
@@ -8,14 +8,14 @@ export interface ScrcpyOptionsInit1_22 extends ScrcpyOptionsInit1_21 {
8
8
  downsizeOnError: boolean;
9
9
 
10
10
  /**
11
- * Send device name and size
11
+ * Send device name and size at start of video stream.
12
12
  *
13
13
  * @default true
14
14
  */
15
15
  sendDeviceMeta: boolean;
16
16
 
17
17
  /**
18
- * Write a byte on start to detect connection issues
18
+ * Send a `0` byte on start of video stream to detect connection issues
19
19
  *
20
20
  * @default true
21
21
  */
@@ -59,11 +59,9 @@ export class ScrcpyOptions1_22<T extends ScrcpyOptionsInit1_22 = ScrcpyOptionsIn
59
59
  }
60
60
 
61
61
  public override createConnection(device: Adb): ScrcpyClientConnection {
62
- const defaultValue = this.getDefaultValue();
63
- const options: ScrcpyClientConnectionOptions = {
64
- control: this.value.control ?? defaultValue.control,
65
- sendDummyByte: this.value.sendDummyByte ?? defaultValue.sendDummyByte,
66
- sendDeviceMeta: this.value.sendDeviceMeta ?? defaultValue.sendDeviceMeta,
62
+ const options = {
63
+ ...this.getDefaultValue(),
64
+ ...this.value,
67
65
  };
68
66
  if (this.value.tunnelForward) {
69
67
  return new ScrcpyClientForwardConnection(device, options);
@@ -0,0 +1,44 @@
1
+ import { TransformStream } from "@yume-chan/adb";
2
+ import { ScrcpyOptions1_22, type ScrcpyOptionsInit1_22 } from './1_22.js';
3
+ import type { VideoStreamPacket } from "./common.js";
4
+
5
+ export interface ScrcpyOptionsInit1_23 extends ScrcpyOptionsInit1_22 {
6
+ cleanup: boolean;
7
+ }
8
+
9
+ const KEYFRAME_PTS = BigInt(1) << BigInt(62);
10
+
11
+ export class ScrcpyOptions1_23<T extends ScrcpyOptionsInit1_23 = ScrcpyOptionsInit1_23> extends ScrcpyOptions1_22<T> {
12
+ public constructor(init: Partial<ScrcpyOptionsInit1_23>) {
13
+ super(init);
14
+ }
15
+
16
+ protected override getDefaultValue(): T {
17
+ return {
18
+ ...super.getDefaultValue(),
19
+ cleanup: true,
20
+ };
21
+ }
22
+
23
+ public override createVideoStreamTransformer(): TransformStream<Uint8Array, VideoStreamPacket> {
24
+ const superStream = super.createVideoStreamTransformer();
25
+ return {
26
+ writable: superStream.writable,
27
+ readable: superStream.readable.pipeThrough(new TransformStream({
28
+ transform(packet, controller): void {
29
+ if (packet.type !== 'frame') {
30
+ controller.enqueue(packet);
31
+ return;
32
+ }
33
+
34
+ if (packet.pts !== undefined && packet.pts & KEYFRAME_PTS) {
35
+ packet.keyframe = true;
36
+ packet.pts &= ~KEYFRAME_PTS;
37
+ }
38
+
39
+ controller.enqueue(packet);
40
+ }
41
+ }))
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,18 @@
1
+ import { ScrcpyOptions1_23, type ScrcpyOptionsInit1_23 } from './1_23.js';
2
+
3
+ export interface ScrcpyOptionsInit1_24 extends ScrcpyOptionsInit1_23 {
4
+ powerOn: boolean;
5
+ }
6
+
7
+ export class ScrcpyOptions1_24<T extends ScrcpyOptionsInit1_24 = ScrcpyOptionsInit1_24> extends ScrcpyOptions1_23<T> {
8
+ public constructor(init: Partial<ScrcpyOptionsInit1_24>) {
9
+ super(init);
10
+ }
11
+
12
+ protected override getDefaultValue(): T {
13
+ return {
14
+ ...super.getDefaultValue(),
15
+ powerOn: true,
16
+ };
17
+ }
18
+ }
@@ -1,6 +1,7 @@
1
- import type { Adb, AdbBufferedStream, ReadableStream } from "@yume-chan/adb";
1
+ import type { Adb, TransformStream } from "@yume-chan/adb";
2
2
  import type { ScrcpyClientConnection } from "../connection.js";
3
3
  import type { H264Configuration } from "../decoder/index.js";
4
+ import type { ScrcpyControlMessageType } from "../message.js";
4
5
  import type { ScrcpyBackOrScreenOnEvent1_18 } from "./1_18.js";
5
6
  import type { ScrcpyInjectScrollControlMessage1_22 } from "./1_22.js";
6
7
 
@@ -35,6 +36,8 @@ export interface VideoStreamConfigurationPacket {
35
36
 
36
37
  export interface VideoStreamFramePacket {
37
38
  type: 'frame';
39
+ keyframe?: boolean | undefined;
40
+ pts?: bigint | undefined;
38
41
  data: Uint8Array;
39
42
  }
40
43
 
@@ -49,7 +52,9 @@ export interface ScrcpyOptions<T> {
49
52
 
50
53
  createConnection(adb: Adb): ScrcpyClientConnection;
51
54
 
52
- parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket>;
55
+ createVideoStreamTransformer(): TransformStream<Uint8Array, VideoStreamPacket>;
56
+
57
+ getControlMessageTypes(): ScrcpyControlMessageType[];
53
58
 
54
59
  serializeBackOrScreenOnControlMessage(
55
60
  message: ScrcpyBackOrScreenOnEvent1_18,
@@ -2,4 +2,6 @@ export * from './1_16/index.js';
2
2
  export * from './1_18.js';
3
3
  export * from './1_21.js';
4
4
  export * from './1_22.js';
5
+ export * from './1_23.js';
6
+ export * from './1_24.js';
5
7
  export * from './common.js';
@@ -1,4 +1,4 @@
1
- import { Adb, AdbSync, WrapWritableStream, WritableStream } from "@yume-chan/adb";
1
+ import { WrapWritableStream, type Adb, type AdbSync } from "@yume-chan/adb";
2
2
  import { DEFAULT_SERVER_PATH } from "./options/index.js";
3
3
 
4
4
  export interface PushServerOptions {
@@ -11,15 +11,13 @@ export function pushServer(
11
11
  ) {
12
12
  const { path = DEFAULT_SERVER_PATH } = options;
13
13
 
14
- return new WrapWritableStream<Uint8Array, WritableStream<Uint8Array>, AdbSync>({
14
+ let sync!: AdbSync;
15
+ return new WrapWritableStream<Uint8Array>({
15
16
  async start() {
16
- const sync = await device.sync();
17
- return {
18
- writable: sync.write(path),
19
- state: sync,
20
- };
17
+ sync = await device.sync();
18
+ return sync.write(path);
21
19
  },
22
- async close(sync) {
20
+ async close() {
23
21
  await sync.dispose();
24
22
  },
25
23
  });
package/src/utils.ts CHANGED
@@ -3,14 +3,3 @@ export function delay(time: number): Promise<void> {
3
3
  (globalThis as any).setTimeout(resolve, time);
4
4
  });
5
5
  }
6
-
7
- const Utf8Encoder = new TextEncoder();
8
- const Utf8Decoder = new TextDecoder();
9
-
10
- export function encodeUtf8(input: string): ArrayBuffer {
11
- return Utf8Encoder.encode(input).buffer;
12
- }
13
-
14
- export function decodeUtf8(buffer: ArrayBuffer): string {
15
- return Utf8Decoder.decode(buffer);
16
- }