@yume-chan/scrcpy 0.0.11

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 (111) hide show
  1. package/CHANGELOG.json +43 -0
  2. package/CHANGELOG.md +21 -0
  3. package/LICENSE +21 -0
  4. package/README.md +88 -0
  5. package/bin/scrcpy-server +0 -0
  6. package/bin/version.d.ts +1 -0
  7. package/bin/version.js +1 -0
  8. package/esm/client.d.ts +36 -0
  9. package/esm/client.d.ts.map +1 -0
  10. package/esm/client.js +209 -0
  11. package/esm/client.js.map +1 -0
  12. package/esm/codec.d.ts +34 -0
  13. package/esm/codec.d.ts.map +1 -0
  14. package/esm/codec.js +37 -0
  15. package/esm/codec.js.map +1 -0
  16. package/esm/connection.d.ts +37 -0
  17. package/esm/connection.d.ts.map +1 -0
  18. package/esm/connection.js +103 -0
  19. package/esm/connection.js.map +1 -0
  20. package/esm/decoder/index.d.ts +4 -0
  21. package/esm/decoder/index.d.ts.map +1 -0
  22. package/esm/decoder/index.js +4 -0
  23. package/esm/decoder/index.js.map +1 -0
  24. package/esm/decoder/tinyh264/index.d.ts +20 -0
  25. package/esm/decoder/tinyh264/index.d.ts.map +1 -0
  26. package/esm/decoder/tinyh264/index.js +86 -0
  27. package/esm/decoder/tinyh264/index.js.map +1 -0
  28. package/esm/decoder/tinyh264/worker.d.ts +2 -0
  29. package/esm/decoder/tinyh264/worker.d.ts.map +1 -0
  30. package/esm/decoder/tinyh264/worker.js +3 -0
  31. package/esm/decoder/tinyh264/worker.js.map +1 -0
  32. package/esm/decoder/tinyh264/wrapper.d.ts +21 -0
  33. package/esm/decoder/tinyh264/wrapper.d.ts.map +1 -0
  34. package/esm/decoder/tinyh264/wrapper.js +67 -0
  35. package/esm/decoder/tinyh264/wrapper.js.map +1 -0
  36. package/esm/decoder/types.d.ts +28 -0
  37. package/esm/decoder/types.d.ts.map +1 -0
  38. package/esm/decoder/types.js +2 -0
  39. package/esm/decoder/types.js.map +1 -0
  40. package/esm/decoder/web-codecs/index.d.ts +22 -0
  41. package/esm/decoder/web-codecs/index.d.ts.map +1 -0
  42. package/esm/decoder/web-codecs/index.js +75 -0
  43. package/esm/decoder/web-codecs/index.js.map +1 -0
  44. package/esm/index.d.ts +9 -0
  45. package/esm/index.d.ts.map +1 -0
  46. package/esm/index.js +9 -0
  47. package/esm/index.js.map +1 -0
  48. package/esm/message.d.ts +93 -0
  49. package/esm/message.d.ts.map +1 -0
  50. package/esm/message.js +92 -0
  51. package/esm/message.js.map +1 -0
  52. package/esm/options/1_16/index.d.ts +107 -0
  53. package/esm/options/1_16/index.d.ts.map +1 -0
  54. package/esm/options/1_16/index.js +211 -0
  55. package/esm/options/1_16/index.js.map +1 -0
  56. package/esm/options/1_16/sps.d.ts +22 -0
  57. package/esm/options/1_16/sps.d.ts.map +1 -0
  58. package/esm/options/1_16/sps.js +254 -0
  59. package/esm/options/1_16/sps.js.map +1 -0
  60. package/esm/options/1_18.d.ts +19 -0
  61. package/esm/options/1_18.d.ts.map +1 -0
  62. package/esm/options/1_18.js +23 -0
  63. package/esm/options/1_18.js.map +1 -0
  64. package/esm/options/1_21.d.ts +10 -0
  65. package/esm/options/1_21.d.ts.map +1 -0
  66. package/esm/options/1_21.js +23 -0
  67. package/esm/options/1_21.js.map +1 -0
  68. package/esm/options/1_22.d.ts +43 -0
  69. package/esm/options/1_22.d.ts.map +1 -0
  70. package/esm/options/1_22.js +40 -0
  71. package/esm/options/1_22.js.map +1 -0
  72. package/esm/options/common.d.ts +30 -0
  73. package/esm/options/common.d.ts.map +1 -0
  74. package/esm/options/common.js +16 -0
  75. package/esm/options/common.js.map +1 -0
  76. package/esm/options/index.d.ts +6 -0
  77. package/esm/options/index.d.ts.map +1 -0
  78. package/esm/options/index.js +6 -0
  79. package/esm/options/index.js.map +1 -0
  80. package/esm/push-server.d.ts +6 -0
  81. package/esm/push-server.d.ts.map +1 -0
  82. package/esm/push-server.js +23 -0
  83. package/esm/push-server.js.map +1 -0
  84. package/esm/utils.d.ts +4 -0
  85. package/esm/utils.d.ts.map +1 -0
  86. package/esm/utils.js +14 -0
  87. package/esm/utils.js.map +1 -0
  88. package/package.json +61 -0
  89. package/scrcpy.LICENSE +203 -0
  90. package/scripts/fetch-server.cjs +28 -0
  91. package/src/client.ts +275 -0
  92. package/src/codec.ts +35 -0
  93. package/src/connection.ts +123 -0
  94. package/src/decoder/index.ts +3 -0
  95. package/src/decoder/tinyh264/index.ts +112 -0
  96. package/src/decoder/tinyh264/types.d.ts +137 -0
  97. package/src/decoder/tinyh264/worker.ts +2 -0
  98. package/src/decoder/tinyh264/wrapper.ts +89 -0
  99. package/src/decoder/types.ts +35 -0
  100. package/src/decoder/web-codecs/index.ts +98 -0
  101. package/src/index.ts +8 -0
  102. package/src/message.ts +105 -0
  103. package/src/options/1_16/index.ts +322 -0
  104. package/src/options/1_16/sps.ts +300 -0
  105. package/src/options/1_18.ts +41 -0
  106. package/src/options/1_21.ts +34 -0
  107. package/src/options/1_22.ts +80 -0
  108. package/src/options/common.ts +61 -0
  109. package/src/options/index.ts +5 -0
  110. package/src/push-server.ts +26 -0
  111. package/src/utils.ts +16 -0
package/src/message.ts ADDED
@@ -0,0 +1,105 @@
1
+ import Struct, { placeholder } from '@yume-chan/struct';
2
+
3
+ export enum ScrcpyControlMessageType {
4
+ InjectKeycode,
5
+ InjectText,
6
+ InjectTouch,
7
+ InjectScroll,
8
+ BackOrScreenOn,
9
+ ExpandNotificationPanel,
10
+ CollapseNotificationPanel,
11
+ GetClipboard,
12
+ SetClipboard,
13
+ SetScreenPowerMode,
14
+ RotateDevice,
15
+ }
16
+
17
+ // https://developer.android.com/reference/android/view/MotionEvent#constants_1
18
+ export enum AndroidMotionEventAction {
19
+ Down,
20
+ Up,
21
+ Move,
22
+ Cancel,
23
+ Outside,
24
+ PointerDown,
25
+ PointerUp,
26
+ HoverMove,
27
+ Scroll,
28
+ HoverEnter,
29
+ HoverExit,
30
+ ButtonPress,
31
+ ButtonRelease,
32
+ }
33
+
34
+ export const ScrcpyInjectTouchControlMessage =
35
+ new Struct()
36
+ .uint8('type', ScrcpyControlMessageType.InjectTouch as const)
37
+ .uint8('action', placeholder<AndroidMotionEventAction>())
38
+ .uint64('pointerId')
39
+ .uint32('pointerX')
40
+ .uint32('pointerY')
41
+ .uint16('screenWidth')
42
+ .uint16('screenHeight')
43
+ .uint16('pressure')
44
+ .uint32('buttons');
45
+
46
+ export type ScrcpyInjectTouchControlMessage = typeof ScrcpyInjectTouchControlMessage['TInit'];
47
+
48
+ export const ScrcpyInjectTextControlMessage =
49
+ new Struct()
50
+ .uint8('type', ScrcpyControlMessageType.InjectText as const)
51
+ .uint32('length')
52
+ .string('text', { lengthField: 'length' });
53
+
54
+ export type ScrcpyInjectTextControlMessage =
55
+ typeof ScrcpyInjectTextControlMessage['TInit'];
56
+
57
+ export enum AndroidKeyEventAction {
58
+ Down = 0,
59
+ Up = 1,
60
+ }
61
+
62
+ export enum AndroidKeyCode {
63
+ Home = 3,
64
+ Back = 4,
65
+ A = 29,
66
+ B,
67
+ C,
68
+ D,
69
+ E,
70
+ F,
71
+ G,
72
+ H,
73
+ I,
74
+ J,
75
+ K,
76
+ L,
77
+ M,
78
+ N,
79
+ O,
80
+ P,
81
+ Q,
82
+ R,
83
+ S,
84
+ T,
85
+ U,
86
+ V,
87
+ W,
88
+ X,
89
+ Y,
90
+ Z,
91
+ Space = 62,
92
+ Delete = 67,
93
+ AppSwitch = 187,
94
+ }
95
+
96
+ export const ScrcpyInjectKeyCodeControlMessage =
97
+ new Struct()
98
+ .uint8('type', ScrcpyControlMessageType.InjectKeycode as const)
99
+ .uint8('action', placeholder<AndroidKeyEventAction>())
100
+ .uint32('keyCode')
101
+ .uint32('repeat')
102
+ .uint32('metaState');
103
+
104
+ export type ScrcpyInjectKeyCodeControlMessage =
105
+ typeof ScrcpyInjectKeyCodeControlMessage['TInit'];
@@ -0,0 +1,322 @@
1
+ import { BufferedStreamEndedError, ReadableStream, TransformStream, type Adb, type AdbBufferedStream } from "@yume-chan/adb";
2
+ import Struct, { placeholder } from "@yume-chan/struct";
3
+ import type { AndroidCodecLevel, AndroidCodecProfile } from "../../codec.js";
4
+ import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnectionOptions } from "../../connection.js";
5
+ import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message.js";
6
+ import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18.js";
7
+ import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22.js";
8
+ import { toScrcpyOptionValue, type ScrcpyOptions, type ScrcpyOptionValue, type VideoStreamPacket } from "../common.js";
9
+ import { parse_sequence_parameter_set } from "./sps.js";
10
+
11
+ export enum ScrcpyLogLevel {
12
+ Verbose = 'verbose',
13
+ Debug = 'debug',
14
+ Info = 'info',
15
+ Warn = 'warn',
16
+ Error = 'error',
17
+ }
18
+
19
+ export enum ScrcpyScreenOrientation {
20
+ Initial = -2,
21
+ Unlocked = -1,
22
+ Portrait = 0,
23
+ Landscape = 1,
24
+ PortraitFlipped = 2,
25
+ LandscapeFlipped = 3,
26
+ }
27
+
28
+ export interface CodecOptionsInit {
29
+ profile: AndroidCodecProfile;
30
+
31
+ level: AndroidCodecLevel;
32
+ }
33
+
34
+ export class CodecOptions implements ScrcpyOptionValue {
35
+ public value: Partial<CodecOptionsInit>;
36
+
37
+ public constructor(value: Partial<CodecOptionsInit>) {
38
+ this.value = value;
39
+ }
40
+
41
+ public toOptionValue(): string | undefined {
42
+ const entries = Object.entries(this.value)
43
+ .filter(([key, value]) => value !== undefined);
44
+
45
+ if (entries.length === 0) {
46
+ return undefined;
47
+ }
48
+
49
+ return entries
50
+ .map(([key, value]) => `${key}=${value}`)
51
+ .join(',');
52
+ }
53
+ }
54
+
55
+ export interface ScrcpyOptionsInit1_16 {
56
+ logLevel: ScrcpyLogLevel;
57
+
58
+ /**
59
+ * The maximum value of both width and height.
60
+ */
61
+ maxSize: number;
62
+
63
+ bitRate: number;
64
+
65
+ /**
66
+ * 0 for unlimited.
67
+ *
68
+ * @default 0
69
+ */
70
+ maxFps: number;
71
+
72
+ /**
73
+ * The orientation of the video stream.
74
+ *
75
+ * It will not keep the device screen in specific orientation,
76
+ * only the captured video will in this orientation.
77
+ */
78
+ lockVideoOrientation: ScrcpyScreenOrientation;
79
+
80
+ tunnelForward: boolean;
81
+
82
+ crop: string;
83
+
84
+ /**
85
+ * Send PTS so that the client may record properly
86
+ *
87
+ * Note: When `sendFrameMeta: false` is specified,
88
+ * `onChangeEncoding` event won't fire and `onVideoData` event doesn't
89
+ * merge sps/pps frame and first video frame. Which means you can't use
90
+ * the shipped decoders to render the video
91
+ * (You can still record the stream into a file).
92
+ *
93
+ * @default true
94
+ */
95
+ sendFrameMeta: boolean;
96
+
97
+ /**
98
+ * @default true
99
+ */
100
+ control: boolean;
101
+
102
+ displayId: number;
103
+
104
+ showTouches: boolean;
105
+
106
+ stayAwake: boolean;
107
+
108
+ codecOptions: CodecOptions;
109
+
110
+ encoderName: string;
111
+ }
112
+
113
+ export const VideoPacket =
114
+ new Struct()
115
+ .int64('pts')
116
+ .uint32('size')
117
+ .uint8Array('data', { lengthField: 'size' });
118
+
119
+ export const NoPts = BigInt(-1);
120
+
121
+ export const ScrcpyBackOrScreenOnEvent1_16 =
122
+ new Struct()
123
+ .uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
124
+
125
+ export const ScrcpyInjectScrollControlMessage1_16 =
126
+ new Struct()
127
+ .uint8('type', ScrcpyControlMessageType.InjectScroll as const)
128
+ .uint32('pointerX')
129
+ .uint32('pointerY')
130
+ .uint16('screenWidth')
131
+ .uint16('screenHeight')
132
+ .int32('scrollX')
133
+ .int32('scrollY');
134
+
135
+ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
136
+ public value: Partial<T>;
137
+
138
+ public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
139
+ if (new.target === ScrcpyOptions1_16 &&
140
+ value.logLevel === ScrcpyLogLevel.Verbose) {
141
+ value.logLevel = ScrcpyLogLevel.Debug;
142
+ }
143
+
144
+ if (new.target === ScrcpyOptions1_16 &&
145
+ value.lockVideoOrientation === ScrcpyScreenOrientation.Initial) {
146
+ value.lockVideoOrientation = ScrcpyScreenOrientation.Unlocked;
147
+ }
148
+
149
+ this.value = value as Partial<T>;
150
+ }
151
+
152
+ protected getArgumentOrder(): (keyof T)[] {
153
+ return [
154
+ 'logLevel',
155
+ 'maxSize',
156
+ 'bitRate',
157
+ 'maxFps',
158
+ 'lockVideoOrientation',
159
+ 'tunnelForward',
160
+ 'crop',
161
+ 'sendFrameMeta',
162
+ 'control',
163
+ 'displayId',
164
+ 'showTouches',
165
+ 'stayAwake',
166
+ 'codecOptions',
167
+ 'encoderName',
168
+ ];
169
+ }
170
+
171
+ protected getDefaultValue(): T {
172
+ return {
173
+ logLevel: ScrcpyLogLevel.Debug,
174
+ maxSize: 0,
175
+ bitRate: 8_000_000,
176
+ maxFps: 0,
177
+ lockVideoOrientation: ScrcpyScreenOrientation.Unlocked,
178
+ tunnelForward: false,
179
+ crop: '-',
180
+ sendFrameMeta: true,
181
+ control: true,
182
+ displayId: 0,
183
+ showTouches: false,
184
+ stayAwake: false,
185
+ codecOptions: new CodecOptions({}),
186
+ encoderName: '-',
187
+ } as T;
188
+ }
189
+
190
+ public formatServerArguments(): string[] {
191
+ const defaults = this.getDefaultValue();
192
+ return this.getArgumentOrder()
193
+ .map(key => toScrcpyOptionValue(this.value[key] || defaults[key], '-'));
194
+ }
195
+
196
+ public createConnection(adb: Adb): ScrcpyClientConnection {
197
+ const options: ScrcpyClientConnectionOptions = {
198
+ // Old scrcpy connection always have control stream no matter what the option is
199
+ control: true,
200
+ sendDummyByte: true,
201
+ sendDeviceMeta: true,
202
+ };
203
+ if (this.value.tunnelForward) {
204
+ return new ScrcpyClientForwardConnection(adb, options);
205
+ } else {
206
+ return new ScrcpyClientReverseConnection(adb, options);
207
+ }
208
+ }
209
+
210
+ public getOutputEncoderNameRegex(): RegExp {
211
+ return /\s+scrcpy --encoder-name '(.*?)'/;
212
+ }
213
+
214
+ public parseVideoStream(stream: AdbBufferedStream): ReadableStream<VideoStreamPacket> {
215
+ // Optimized path for video frames only
216
+ 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
+ }));
227
+ }
228
+
229
+ let header: Uint8Array | undefined;
230
+
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);
237
+
238
+ const {
239
+ profile_idc: profileIndex,
240
+ constraint_set: constraintSet,
241
+ level_idc: levelIndex,
242
+ pic_width_in_mbs_minus1,
243
+ pic_height_in_map_units_minus1,
244
+ frame_mbs_only_flag,
245
+ frame_crop_left_offset,
246
+ frame_crop_right_offset,
247
+ frame_crop_top_offset,
248
+ frame_crop_bottom_offset,
249
+ } = sequenceParameterSet;
250
+
251
+ const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
252
+ const encodedHeight = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
253
+ const cropLeft = frame_crop_left_offset * 2;
254
+ const cropRight = frame_crop_right_offset * 2;
255
+ const cropTop = frame_crop_top_offset * 2;
256
+ const cropBottom = frame_crop_bottom_offset * 2;
257
+
258
+ const croppedWidth = encodedWidth - cropLeft - cropRight;
259
+ const croppedHeight = encodedHeight - cropTop - cropBottom;
260
+
261
+ header = data;
262
+ controller.enqueue({
263
+ type: 'configuration',
264
+ data: {
265
+ profileIndex,
266
+ constraintSet,
267
+ levelIndex,
268
+ encodedWidth,
269
+ encodedHeight,
270
+ cropLeft,
271
+ cropRight,
272
+ cropTop,
273
+ cropBottom,
274
+ croppedWidth,
275
+ croppedHeight,
276
+ }
277
+ });
278
+ return;
279
+ }
280
+
281
+ let frameData: Uint8Array;
282
+ if (header) {
283
+ frameData = new Uint8Array(header.byteLength + data.byteLength);
284
+ frameData.set(header);
285
+ frameData.set(data!, header.byteLength);
286
+ header = undefined;
287
+ } else {
288
+ frameData = data;
289
+ }
290
+
291
+ controller.enqueue({
292
+ type: 'frame',
293
+ data: frameData,
294
+ });
295
+ } catch (e) {
296
+ if (e instanceof BufferedStreamEndedError) {
297
+ controller.close();
298
+ return;
299
+ }
300
+
301
+ throw e;
302
+ }
303
+ }
304
+ });
305
+ }
306
+
307
+ public serializeBackOrScreenOnControlMessage(
308
+ message: ScrcpyBackOrScreenOnEvent1_18,
309
+ ) {
310
+ if (message.action === AndroidKeyEventAction.Down) {
311
+ return ScrcpyBackOrScreenOnEvent1_16.serialize(message);
312
+ }
313
+
314
+ return undefined;
315
+ }
316
+
317
+ public serializeInjectScrollControlMessage(
318
+ message: ScrcpyInjectScrollControlMessage1_22,
319
+ ): Uint8Array {
320
+ return ScrcpyInjectScrollControlMessage1_16.serialize(message);
321
+ }
322
+ }