@yume-chan/scrcpy 0.0.16 → 0.0.17
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 +15 -0
- package/CHANGELOG.md +9 -1
- package/README.md +275 -38
- package/esm/adb/client.d.ts +40 -0
- package/esm/adb/client.d.ts.map +1 -0
- package/esm/adb/client.js +218 -0
- package/esm/adb/client.js.map +1 -0
- package/esm/adb/connection.d.ts +38 -0
- package/esm/adb/connection.d.ts.map +1 -0
- package/esm/{connection.js → adb/connection.js} +20 -24
- package/esm/adb/connection.js.map +1 -0
- package/esm/adb/index.d.ts +4 -0
- package/esm/adb/index.d.ts.map +1 -0
- package/esm/adb/index.js +4 -0
- package/esm/adb/index.js.map +1 -0
- package/esm/adb/options/1_16.d.ts +8 -0
- package/esm/adb/options/1_16.d.ts.map +1 -0
- package/esm/adb/options/1_16.js +20 -0
- package/esm/adb/options/1_16.js.map +1 -0
- package/esm/adb/options/1_22.d.ts +8 -0
- package/esm/adb/options/1_22.d.ts.map +1 -0
- package/esm/adb/options/1_22.js +17 -0
- package/esm/adb/options/1_22.js.map +1 -0
- package/esm/adb/options/index.d.ts +4 -0
- package/esm/adb/options/index.d.ts.map +1 -0
- package/esm/adb/options/index.js +4 -0
- package/esm/adb/options/index.js.map +1 -0
- package/esm/adb/options/types.d.ts +23 -0
- package/esm/adb/options/types.d.ts.map +1 -0
- package/esm/adb/options/types.js +30 -0
- package/esm/adb/options/types.js.map +1 -0
- package/esm/codec.js +2 -2
- package/esm/codec.js.map +1 -1
- package/esm/control/index.d.ts +8 -0
- package/esm/control/index.d.ts.map +1 -0
- package/esm/control/index.js +8 -0
- package/esm/control/index.js.map +1 -0
- package/esm/control/inject-keycode.d.ts +53 -0
- package/esm/control/inject-keycode.d.ts.map +1 -0
- package/esm/control/inject-keycode.js +54 -0
- package/esm/control/inject-keycode.js.map +1 -0
- package/esm/control/inject-text.d.ts +9 -0
- package/esm/control/inject-text.d.ts.map +1 -0
- package/esm/control/inject-text.js +7 -0
- package/esm/control/inject-text.js.map +1 -0
- package/esm/control/inject-touch.d.ts +30 -0
- package/esm/control/inject-touch.d.ts.map +1 -0
- package/esm/control/inject-touch.js +30 -0
- package/esm/control/inject-touch.js.map +1 -0
- package/esm/control/rotate-device.d.ts +7 -0
- package/esm/control/rotate-device.d.ts.map +1 -0
- package/esm/control/rotate-device.js +5 -0
- package/esm/control/rotate-device.js.map +1 -0
- package/esm/control/serializer.d.ts +23 -0
- package/esm/control/serializer.d.ts.map +1 -0
- package/esm/control/serializer.js +73 -0
- package/esm/control/serializer.js.map +1 -0
- package/esm/control/set-screen-power-mode.d.ts +12 -0
- package/esm/control/set-screen-power-mode.d.ts.map +1 -0
- package/esm/control/set-screen-power-mode.js +12 -0
- package/esm/control/set-screen-power-mode.js.map +1 -0
- package/esm/control/type.d.ts +15 -0
- package/esm/control/type.d.ts.map +1 -0
- package/esm/control/type.js +19 -0
- package/esm/control/type.js.map +1 -0
- package/esm/decoder/index.d.ts +27 -3
- package/esm/decoder/index.d.ts.map +1 -1
- package/esm/decoder/index.js +1 -3
- package/esm/decoder/index.js.map +1 -1
- package/esm/decoder/tinyh264/index.d.ts +5 -5
- package/esm/decoder/tinyh264/index.d.ts.map +1 -1
- package/esm/decoder/tinyh264/index.js +4 -4
- package/esm/decoder/tinyh264/index.js.map +1 -1
- package/esm/decoder/tinyh264/worker.js.map +1 -1
- package/esm/decoder/tinyh264/wrapper.d.ts.map +1 -1
- package/esm/decoder/tinyh264/wrapper.js +14 -8
- package/esm/decoder/tinyh264/wrapper.js.map +1 -1
- package/esm/decoder/types.d.ts +1 -27
- package/esm/decoder/types.d.ts.map +1 -1
- package/esm/decoder/web-codecs/index.d.ts +4 -4
- package/esm/decoder/web-codecs/index.d.ts.map +1 -1
- package/esm/decoder/web-codecs/index.js +1 -1
- package/esm/decoder/web-codecs/index.js.map +1 -1
- package/esm/device-message/clipboard.d.ts +10 -0
- package/esm/device-message/clipboard.d.ts.map +1 -0
- package/esm/device-message/clipboard.js +7 -0
- package/esm/device-message/clipboard.js.map +1 -0
- package/esm/device-message/index.d.ts +4 -0
- package/esm/device-message/index.d.ts.map +1 -0
- package/esm/device-message/index.js +4 -0
- package/esm/device-message/index.js.map +1 -0
- package/esm/device-message/stream.d.ts +7 -0
- package/esm/device-message/stream.d.ts.map +1 -0
- package/esm/device-message/stream.js +16 -0
- package/esm/device-message/stream.js.map +1 -0
- package/esm/device-message/type.d.ts +5 -0
- package/esm/device-message/type.d.ts.map +1 -0
- package/esm/device-message/type.js +7 -0
- package/esm/device-message/type.js.map +1 -0
- package/esm/index.d.ts +3 -6
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -6
- package/esm/index.js.map +1 -1
- package/esm/options/1_16/codec-options.d.ts +19 -0
- package/esm/options/1_16/codec-options.d.ts.map +1 -0
- package/esm/options/1_16/codec-options.js +27 -0
- package/esm/options/1_16/codec-options.js.map +1 -0
- package/esm/options/1_16/index.d.ts +3 -110
- package/esm/options/1_16/index.d.ts.map +1 -1
- package/esm/options/1_16/index.js +3 -226
- package/esm/options/1_16/index.js.map +1 -1
- package/esm/options/1_16/options.d.ts +97 -0
- package/esm/options/1_16/options.d.ts.map +1 -0
- package/esm/options/1_16/options.js +188 -0
- package/esm/options/1_16/options.js.map +1 -0
- package/esm/options/1_18.d.ts +8 -8
- package/esm/options/1_18.d.ts.map +1 -1
- package/esm/options/1_18.js +6 -6
- package/esm/options/1_18.js.map +1 -1
- package/esm/options/1_21.d.ts +1 -1
- package/esm/options/1_21.d.ts.map +1 -1
- package/esm/options/1_21.js +1 -1
- package/esm/options/1_21.js.map +1 -1
- package/esm/options/1_22.d.ts +5 -8
- package/esm/options/1_22.d.ts.map +1 -1
- package/esm/options/1_22.js +4 -17
- package/esm/options/1_22.js.map +1 -1
- package/esm/options/1_23.d.ts +4 -4
- package/esm/options/1_23.d.ts.map +1 -1
- package/esm/options/1_23.js +1 -1
- package/esm/options/1_23.js.map +1 -1
- package/esm/options/1_24.d.ts +1 -1
- package/esm/options/1_24.d.ts.map +1 -1
- package/esm/options/1_24.js.map +1 -1
- package/esm/options/index.d.ts +1 -1
- package/esm/options/index.d.ts.map +1 -1
- package/esm/options/index.js +1 -1
- package/esm/options/index.js.map +1 -1
- package/esm/options/types.d.ts +45 -0
- package/esm/options/types.d.ts.map +1 -0
- package/esm/options/{common.js → types.js} +1 -1
- package/esm/options/types.js.map +1 -0
- package/package.json +24 -24
- package/src/adb/client.ts +292 -0
- package/src/{connection.ts → adb/connection.ts} +35 -38
- package/src/adb/index.ts +3 -0
- package/src/adb/options/1_16.ts +23 -0
- package/src/adb/options/1_22.ts +20 -0
- package/src/adb/options/index.ts +3 -0
- package/src/adb/options/types.ts +55 -0
- package/src/control/index.ts +7 -0
- package/src/control/inject-keycode.ts +59 -0
- package/src/control/inject-text.ts +12 -0
- package/src/control/inject-touch.ts +34 -0
- package/src/control/rotate-device.ts +10 -0
- package/src/control/serializer.ts +86 -0
- package/src/control/set-screen-power-mode.ts +17 -0
- package/src/control/type.ts +17 -0
- package/src/device-message/clipboard.ts +11 -0
- package/src/device-message/index.ts +3 -0
- package/src/device-message/stream.ts +19 -0
- package/src/device-message/type.ts +5 -0
- package/src/index.ts +3 -6
- package/src/options/1_16/codec-options.ts +48 -0
- package/src/options/1_16/index.ts +3 -345
- package/src/options/1_16/options.ts +294 -0
- package/src/options/1_18.ts +12 -10
- package/src/options/1_21.ts +16 -6
- package/src/options/1_22.ts +10 -21
- package/src/options/1_23.ts +7 -5
- package/src/options/1_24.ts +3 -2
- package/src/options/index.ts +1 -1
- package/src/options/{common.ts → types.ts} +32 -13
- package/tsconfig.build.json +3 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.test.tsbuildinfo +1 -0
- package/esm/client.d.ts +0 -48
- package/esm/client.d.ts.map +0 -1
- package/esm/client.js +0 -341
- package/esm/client.js.map +0 -1
- package/esm/connection.d.ts +0 -37
- package/esm/connection.d.ts.map +0 -1
- package/esm/connection.js.map +0 -1
- package/esm/message.d.ts +0 -98
- package/esm/message.d.ts.map +0 -1
- package/esm/message.js +0 -98
- package/esm/message.js.map +0 -1
- package/esm/options/common.d.ts +0 -34
- package/esm/options/common.d.ts.map +0 -1
- package/esm/options/common.js.map +0 -1
- package/esm/push-server.d.ts +0 -6
- package/esm/push-server.d.ts.map +0 -1
- package/esm/push-server.js +0 -16
- package/esm/push-server.js.map +0 -1
- package/esm/utils.d.ts +0 -2
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -6
- package/esm/utils.js.map +0 -1
- package/src/client.ts +0 -435
- package/src/decoder/index.ts +0 -3
- package/src/decoder/tinyh264/index.ts +0 -112
- package/src/decoder/tinyh264/types.d.ts +0 -137
- package/src/decoder/tinyh264/worker.ts +0 -2
- package/src/decoder/tinyh264/wrapper.ts +0 -89
- package/src/decoder/types.ts +0 -35
- package/src/decoder/web-codecs/index.ts +0 -99
- package/src/message.ts +0 -113
- package/src/push-server.ts +0 -24
- package/src/utils.ts +0 -5
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Struct from '@yume-chan/struct';
|
|
2
|
+
|
|
3
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
4
|
+
|
|
5
|
+
export const ScrcpyRotateDeviceControlMessage =
|
|
6
|
+
new Struct()
|
|
7
|
+
.uint8('type', ScrcpyControlMessageType.RotateDevice as const)
|
|
8
|
+
|
|
9
|
+
export type ScrcpyRotateDeviceControlMessage =
|
|
10
|
+
typeof ScrcpyRotateDeviceControlMessage['TInit'];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { WritableStream, WritableStreamDefaultWriter } from '@yume-chan/stream-extra';
|
|
2
|
+
|
|
3
|
+
import type { ScrcpyInjectScrollControlMessage1_22, ScrcpyOptions } from '../options/index.js';
|
|
4
|
+
import { AndroidKeyEventAction, ScrcpyInjectKeyCodeControlMessage } from './inject-keycode.js';
|
|
5
|
+
import { ScrcpyInjectTextControlMessage } from './inject-text.js';
|
|
6
|
+
import { ScrcpyInjectTouchControlMessage } from './inject-touch.js';
|
|
7
|
+
import { ScrcpyRotateDeviceControlMessage } from './rotate-device.js';
|
|
8
|
+
import { AndroidScreenPowerMode, ScrcpySetScreenPowerModeControlMessage } from './set-screen-power-mode.js';
|
|
9
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
10
|
+
|
|
11
|
+
export class ScrcpyControlMessageSerializer {
|
|
12
|
+
private options: ScrcpyOptions<any>;
|
|
13
|
+
/** Control message type values for current version of server */
|
|
14
|
+
private types: ScrcpyControlMessageType[];
|
|
15
|
+
private writer: WritableStreamDefaultWriter<Uint8Array>;
|
|
16
|
+
|
|
17
|
+
public constructor(stream: WritableStream<Uint8Array>, options: ScrcpyOptions<any>) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
this.types = options.getControlMessageTypes();
|
|
20
|
+
this.writer = stream.getWriter();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getTypeValue(type: ScrcpyControlMessageType): number {
|
|
24
|
+
const value = this.types.indexOf(type);
|
|
25
|
+
if (value === -1) {
|
|
26
|
+
throw new Error('Not supported');
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, 'type'>) {
|
|
32
|
+
return this.writer.write(ScrcpyInjectKeyCodeControlMessage.serialize({
|
|
33
|
+
...message,
|
|
34
|
+
type: this.getTypeValue(ScrcpyControlMessageType.InjectKeyCode),
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public injectText(text: string) {
|
|
39
|
+
return this.writer.write(ScrcpyInjectTextControlMessage.serialize({
|
|
40
|
+
text,
|
|
41
|
+
type: this.getTypeValue(ScrcpyControlMessageType.InjectText),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, 'type'>) {
|
|
46
|
+
return this.writer.write(ScrcpyInjectTouchControlMessage.serialize({
|
|
47
|
+
...message,
|
|
48
|
+
type: this.getTypeValue(ScrcpyControlMessageType.InjectTouch),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public injectScroll(message: Omit<ScrcpyInjectScrollControlMessage1_22, 'type'>) {
|
|
53
|
+
return this.writer.write(this.options.serializeInjectScrollControlMessage({
|
|
54
|
+
...message,
|
|
55
|
+
type: this.getTypeValue(ScrcpyControlMessageType.InjectScroll),
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async backOrScreenOn(action: AndroidKeyEventAction) {
|
|
60
|
+
const buffer = this.options.serializeBackOrScreenOnControlMessage({
|
|
61
|
+
action,
|
|
62
|
+
type: this.getTypeValue(ScrcpyControlMessageType.BackOrScreenOn),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (buffer) {
|
|
66
|
+
return await this.writer.write(buffer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public setScreenPowerMode(mode: AndroidScreenPowerMode) {
|
|
71
|
+
return this.writer.write(ScrcpySetScreenPowerModeControlMessage.serialize({
|
|
72
|
+
mode,
|
|
73
|
+
type: this.getTypeValue(ScrcpyControlMessageType.SetScreenPowerMode),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public rotateDevice() {
|
|
78
|
+
return this.writer.write(ScrcpyRotateDeviceControlMessage.serialize({
|
|
79
|
+
type: this.getTypeValue(ScrcpyControlMessageType.RotateDevice),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public close() {
|
|
84
|
+
return this.writer.close();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Struct, { placeholder } from '@yume-chan/struct';
|
|
2
|
+
|
|
3
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
4
|
+
|
|
5
|
+
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/SurfaceControl.java;l=659;drc=20303e05bf73796124ab70a279cf849b61b97905
|
|
6
|
+
export enum AndroidScreenPowerMode {
|
|
7
|
+
Off = 0,
|
|
8
|
+
Normal = 2
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ScrcpySetScreenPowerModeControlMessage =
|
|
12
|
+
new Struct()
|
|
13
|
+
.uint8('type', ScrcpyControlMessageType.SetScreenPowerMode as const)
|
|
14
|
+
.uint8('mode', placeholder<AndroidScreenPowerMode>());
|
|
15
|
+
|
|
16
|
+
export type ScrcpySetScreenPowerModeControlMessage =
|
|
17
|
+
typeof ScrcpySetScreenPowerModeControlMessage['TInit'];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// https://github.com/Genymobile/scrcpy/blob/fa5b2a29e983a46b49531def9cf3d80c40c3de37/app/src/control_msg.h#L23
|
|
2
|
+
// For their message bodies, see https://github.com/Genymobile/scrcpy/blob/5c62f3419d252d10cd8c9cbb7c918b358b81f2d0/app/src/control_msg.c#L92
|
|
3
|
+
// Their IDs change between versions, so always use `options.getControlMessageTypes()`
|
|
4
|
+
export enum ScrcpyControlMessageType {
|
|
5
|
+
InjectKeyCode,
|
|
6
|
+
InjectText,
|
|
7
|
+
InjectTouch,
|
|
8
|
+
InjectScroll,
|
|
9
|
+
BackOrScreenOn,
|
|
10
|
+
ExpandNotificationPanel,
|
|
11
|
+
ExpandSettingPanel,
|
|
12
|
+
CollapseNotificationPanel,
|
|
13
|
+
GetClipboard,
|
|
14
|
+
SetClipboard,
|
|
15
|
+
SetScreenPowerMode,
|
|
16
|
+
RotateDevice,
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Struct from '@yume-chan/struct';
|
|
2
|
+
import { ScrcpyDeviceMessageType } from './type.js';
|
|
3
|
+
|
|
4
|
+
export const ScrcpyClipboardDeviceMessage =
|
|
5
|
+
new Struct()
|
|
6
|
+
.uint32('length')
|
|
7
|
+
.string('content', { lengthField: 'length' })
|
|
8
|
+
.extra({ type: ScrcpyDeviceMessageType.Clipboard as const });
|
|
9
|
+
|
|
10
|
+
export type ScrcpyClipboardDeviceMessage =
|
|
11
|
+
typeof ScrcpyClipboardDeviceMessage['TDeserializeResult'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BufferedTransformStream } from '@yume-chan/stream-extra';
|
|
2
|
+
import { ScrcpyClipboardDeviceMessage } from './clipboard.js';
|
|
3
|
+
|
|
4
|
+
export type ScrcpyDeviceMessage =
|
|
5
|
+
| ScrcpyClipboardDeviceMessage;
|
|
6
|
+
|
|
7
|
+
export class ScrcpyDeviceMessageDeserializeStream extends BufferedTransformStream<ScrcpyDeviceMessage> {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(async (stream) => {
|
|
10
|
+
const type = await stream.read(1);
|
|
11
|
+
switch (type[0]) {
|
|
12
|
+
case 0:
|
|
13
|
+
return await ScrcpyClipboardDeviceMessage.deserialize(stream);
|
|
14
|
+
default:
|
|
15
|
+
throw new Error('unknown control message type');
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './adb/index.js';
|
|
2
2
|
export * from './codec.js';
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './message.js';
|
|
3
|
+
export * from './control/index.js';
|
|
4
|
+
export * from './device-message/index.js';
|
|
6
5
|
export * from './options/index.js';
|
|
7
|
-
export * from './push-server.js';
|
|
8
|
-
export * from './utils.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AndroidCodecLevel, AndroidCodecProfile } from '../../codec.js';
|
|
2
|
+
import type { ScrcpyOptionValue } from '../types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* If the option you need is not in this type,
|
|
6
|
+
* please file an issue on GitHub.
|
|
7
|
+
*/
|
|
8
|
+
export interface CodecOptionsInit {
|
|
9
|
+
profile: AndroidCodecProfile;
|
|
10
|
+
level: AndroidCodecLevel;
|
|
11
|
+
|
|
12
|
+
iFrameInterval: number;
|
|
13
|
+
repeatPreviousFrameAfter: number;
|
|
14
|
+
maxPtsGapToEncoder: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toDashCase(input: string) {
|
|
18
|
+
return input.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CODEC_OPTION_TYPES: Partial<Record<keyof CodecOptionsInit, 'long' | 'float' | 'string'>> = {
|
|
22
|
+
repeatPreviousFrameAfter: 'long',
|
|
23
|
+
maxPtsGapToEncoder: 'long',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class CodecOptions implements ScrcpyOptionValue {
|
|
27
|
+
public value: Partial<CodecOptionsInit>;
|
|
28
|
+
|
|
29
|
+
public constructor(value: Partial<CodecOptionsInit>) {
|
|
30
|
+
this.value = value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public toOptionValue(): string | undefined {
|
|
34
|
+
const entries = Object.entries(this.value)
|
|
35
|
+
.filter(([key, value]) => value !== undefined);
|
|
36
|
+
|
|
37
|
+
if (entries.length === 0) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return entries
|
|
42
|
+
.map(([key, value]) => {
|
|
43
|
+
const type = CODEC_OPTION_TYPES[key as keyof CodecOptionsInit];
|
|
44
|
+
return `${toDashCase(key)}${type ? `:${type}` : ''}=${value}`;
|
|
45
|
+
})
|
|
46
|
+
.join(',');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,345 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnectionOptions } from "../../connection.js";
|
|
5
|
-
import { AndroidKeyEventAction, ScrcpyControlMessageType, ScrcpySimpleControlMessage } 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 ScrcpyVideoOrientation {
|
|
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
|
-
level: AndroidCodecLevel;
|
|
31
|
-
|
|
32
|
-
iFrameInterval: number;
|
|
33
|
-
repeatPreviousFrameAfter: number;
|
|
34
|
-
maxPtsGapToEncoder: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function toDashCase(input: string) {
|
|
38
|
-
return input.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const CODEC_OPTION_TYPES: Partial<Record<keyof CodecOptionsInit, 'long' | 'float' | 'string'>> = {
|
|
42
|
-
repeatPreviousFrameAfter: 'long',
|
|
43
|
-
maxPtsGapToEncoder: 'long',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export class CodecOptions implements ScrcpyOptionValue {
|
|
47
|
-
public value: Partial<CodecOptionsInit>;
|
|
48
|
-
|
|
49
|
-
public constructor(value: Partial<CodecOptionsInit>) {
|
|
50
|
-
this.value = value;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public toOptionValue(): string | undefined {
|
|
54
|
-
const entries = Object.entries(this.value)
|
|
55
|
-
.filter(([key, value]) => value !== undefined);
|
|
56
|
-
|
|
57
|
-
if (entries.length === 0) {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return entries
|
|
62
|
-
.map(([key, value]) => {
|
|
63
|
-
const type = CODEC_OPTION_TYPES[key as keyof CodecOptionsInit];
|
|
64
|
-
return `${toDashCase(key)}${type ? `:${type}` : ''}=${value}`;
|
|
65
|
-
})
|
|
66
|
-
.join(',');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ScrcpyOptionsInit1_16 {
|
|
71
|
-
logLevel: ScrcpyLogLevel;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* The maximum value of both width and height.
|
|
75
|
-
*/
|
|
76
|
-
maxSize: number;
|
|
77
|
-
|
|
78
|
-
bitRate: number;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 0 for unlimited.
|
|
82
|
-
*
|
|
83
|
-
* @default 0
|
|
84
|
-
*/
|
|
85
|
-
maxFps: number;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The orientation of the video stream.
|
|
89
|
-
*
|
|
90
|
-
* It will not keep the device screen in specific orientation,
|
|
91
|
-
* only the captured video will in this orientation.
|
|
92
|
-
*/
|
|
93
|
-
lockVideoOrientation: ScrcpyVideoOrientation;
|
|
94
|
-
|
|
95
|
-
tunnelForward: boolean;
|
|
96
|
-
|
|
97
|
-
crop: string;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Send PTS so that the client may record properly
|
|
101
|
-
*
|
|
102
|
-
* Note: When `sendFrameMeta: false` is specified,
|
|
103
|
-
* `onChangeEncoding` event won't fire and `onVideoData` event doesn't
|
|
104
|
-
* merge sps/pps frame and first video frame. Which means you can't use
|
|
105
|
-
* the shipped decoders to render the video
|
|
106
|
-
* (You can still record the stream into a file).
|
|
107
|
-
*
|
|
108
|
-
* @default true
|
|
109
|
-
*/
|
|
110
|
-
sendFrameMeta: boolean;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* @default true
|
|
114
|
-
*/
|
|
115
|
-
control: boolean;
|
|
116
|
-
|
|
117
|
-
displayId: number;
|
|
118
|
-
|
|
119
|
-
showTouches: boolean;
|
|
120
|
-
|
|
121
|
-
stayAwake: boolean;
|
|
122
|
-
|
|
123
|
-
codecOptions: CodecOptions;
|
|
124
|
-
|
|
125
|
-
encoderName: string;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const VideoPacket =
|
|
129
|
-
new Struct()
|
|
130
|
-
.uint64('pts')
|
|
131
|
-
.uint32('size')
|
|
132
|
-
.uint8Array('data', { lengthField: 'size' });
|
|
133
|
-
|
|
134
|
-
export const NO_PTS = BigInt(1) << BigInt(63);
|
|
135
|
-
|
|
136
|
-
export const ScrcpyBackOrScreenOnEvent1_16 =
|
|
137
|
-
ScrcpySimpleControlMessage;
|
|
138
|
-
|
|
139
|
-
export const ScrcpyInjectScrollControlMessage1_16 =
|
|
140
|
-
new Struct()
|
|
141
|
-
.fields(ScrcpySimpleControlMessage)
|
|
142
|
-
.uint32('pointerX')
|
|
143
|
-
.uint32('pointerY')
|
|
144
|
-
.uint16('screenWidth')
|
|
145
|
-
.uint16('screenHeight')
|
|
146
|
-
.int32('scrollX')
|
|
147
|
-
.int32('scrollY');
|
|
148
|
-
|
|
149
|
-
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
|
|
150
|
-
public value: Partial<T>;
|
|
151
|
-
|
|
152
|
-
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
|
153
|
-
if (new.target === ScrcpyOptions1_16 &&
|
|
154
|
-
value.logLevel === ScrcpyLogLevel.Verbose) {
|
|
155
|
-
value.logLevel = ScrcpyLogLevel.Debug;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (new.target === ScrcpyOptions1_16 &&
|
|
159
|
-
value.lockVideoOrientation === ScrcpyVideoOrientation.Initial) {
|
|
160
|
-
value.lockVideoOrientation = ScrcpyVideoOrientation.Unlocked;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
this.value = value as Partial<T>;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
protected getArgumentOrder(): (keyof T)[] {
|
|
167
|
-
return [
|
|
168
|
-
'logLevel',
|
|
169
|
-
'maxSize',
|
|
170
|
-
'bitRate',
|
|
171
|
-
'maxFps',
|
|
172
|
-
'lockVideoOrientation',
|
|
173
|
-
'tunnelForward',
|
|
174
|
-
'crop',
|
|
175
|
-
'sendFrameMeta',
|
|
176
|
-
'control',
|
|
177
|
-
'displayId',
|
|
178
|
-
'showTouches',
|
|
179
|
-
'stayAwake',
|
|
180
|
-
'codecOptions',
|
|
181
|
-
'encoderName',
|
|
182
|
-
];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
protected getDefaultValue(): T {
|
|
186
|
-
return {
|
|
187
|
-
logLevel: ScrcpyLogLevel.Debug,
|
|
188
|
-
maxSize: 0,
|
|
189
|
-
bitRate: 8_000_000,
|
|
190
|
-
maxFps: 0,
|
|
191
|
-
lockVideoOrientation: ScrcpyVideoOrientation.Unlocked,
|
|
192
|
-
tunnelForward: false,
|
|
193
|
-
crop: '-',
|
|
194
|
-
sendFrameMeta: true,
|
|
195
|
-
control: true,
|
|
196
|
-
displayId: 0,
|
|
197
|
-
showTouches: false,
|
|
198
|
-
stayAwake: false,
|
|
199
|
-
codecOptions: new CodecOptions({}),
|
|
200
|
-
encoderName: '-',
|
|
201
|
-
} as T;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
public formatServerArguments(): string[] {
|
|
205
|
-
const defaults = this.getDefaultValue();
|
|
206
|
-
return this.getArgumentOrder()
|
|
207
|
-
.map(key => toScrcpyOptionValue(this.value[key] || defaults[key], '-'));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
public createConnection(adb: Adb): ScrcpyClientConnection {
|
|
211
|
-
const options: ScrcpyClientConnectionOptions = {
|
|
212
|
-
// Old scrcpy connection always have control stream no matter what the option is
|
|
213
|
-
control: true,
|
|
214
|
-
sendDummyByte: true,
|
|
215
|
-
sendDeviceMeta: true,
|
|
216
|
-
};
|
|
217
|
-
if (this.value.tunnelForward) {
|
|
218
|
-
return new ScrcpyClientForwardConnection(adb, options);
|
|
219
|
-
} else {
|
|
220
|
-
return new ScrcpyClientReverseConnection(adb, options);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
public getOutputEncoderNameRegex(): RegExp {
|
|
225
|
-
return /\s+scrcpy --encoder-name '(.*?)'/;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
public createVideoStreamTransformer(): TransformStream<Uint8Array, VideoStreamPacket> {
|
|
229
|
-
// Optimized path for video frames only
|
|
230
|
-
if (this.value.sendFrameMeta === false) {
|
|
231
|
-
return new TransformStream({
|
|
232
|
-
transform(chunk, controller) {
|
|
233
|
-
controller.enqueue({
|
|
234
|
-
type: 'frame',
|
|
235
|
-
data: chunk,
|
|
236
|
-
});
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
let header: Uint8Array | undefined;
|
|
242
|
-
|
|
243
|
-
let deserializeStream = new StructDeserializeStream(VideoPacket);
|
|
244
|
-
return {
|
|
245
|
-
writable: deserializeStream.writable,
|
|
246
|
-
readable: deserializeStream.readable.pipeThrough(new TransformStream({
|
|
247
|
-
transform(packet, controller) {
|
|
248
|
-
if (packet.pts === NO_PTS) {
|
|
249
|
-
const sequenceParameterSet = parse_sequence_parameter_set(packet.data.slice().buffer);
|
|
250
|
-
|
|
251
|
-
const {
|
|
252
|
-
profile_idc: profileIndex,
|
|
253
|
-
constraint_set: constraintSet,
|
|
254
|
-
level_idc: levelIndex,
|
|
255
|
-
pic_width_in_mbs_minus1,
|
|
256
|
-
pic_height_in_map_units_minus1,
|
|
257
|
-
frame_mbs_only_flag,
|
|
258
|
-
frame_crop_left_offset,
|
|
259
|
-
frame_crop_right_offset,
|
|
260
|
-
frame_crop_top_offset,
|
|
261
|
-
frame_crop_bottom_offset,
|
|
262
|
-
} = sequenceParameterSet;
|
|
263
|
-
|
|
264
|
-
const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
|
|
265
|
-
const encodedHeight = (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
|
|
266
|
-
const cropLeft = frame_crop_left_offset * 2;
|
|
267
|
-
const cropRight = frame_crop_right_offset * 2;
|
|
268
|
-
const cropTop = frame_crop_top_offset * 2;
|
|
269
|
-
const cropBottom = frame_crop_bottom_offset * 2;
|
|
270
|
-
|
|
271
|
-
const croppedWidth = encodedWidth - cropLeft - cropRight;
|
|
272
|
-
const croppedHeight = encodedHeight - cropTop - cropBottom;
|
|
273
|
-
|
|
274
|
-
header = packet.data;
|
|
275
|
-
controller.enqueue({
|
|
276
|
-
type: 'configuration',
|
|
277
|
-
data: {
|
|
278
|
-
profileIndex,
|
|
279
|
-
constraintSet,
|
|
280
|
-
levelIndex,
|
|
281
|
-
encodedWidth,
|
|
282
|
-
encodedHeight,
|
|
283
|
-
cropLeft,
|
|
284
|
-
cropRight,
|
|
285
|
-
cropTop,
|
|
286
|
-
cropBottom,
|
|
287
|
-
croppedWidth,
|
|
288
|
-
croppedHeight,
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
let frameData: Uint8Array;
|
|
295
|
-
if (header) {
|
|
296
|
-
frameData = new Uint8Array(header.byteLength + packet.data.byteLength);
|
|
297
|
-
frameData.set(header);
|
|
298
|
-
frameData.set(packet.data, header.byteLength);
|
|
299
|
-
header = undefined;
|
|
300
|
-
} else {
|
|
301
|
-
frameData = packet.data;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
controller.enqueue({
|
|
305
|
-
type: 'frame',
|
|
306
|
-
pts: packet.pts,
|
|
307
|
-
data: frameData,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}))
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
public getControlMessageTypes(): ScrcpyControlMessageType[] {
|
|
315
|
-
return [
|
|
316
|
-
/* 0 */ ScrcpyControlMessageType.InjectKeycode,
|
|
317
|
-
/* 1 */ ScrcpyControlMessageType.InjectText,
|
|
318
|
-
/* 2 */ ScrcpyControlMessageType.InjectTouch,
|
|
319
|
-
/* 3 */ ScrcpyControlMessageType.InjectScroll,
|
|
320
|
-
/* 4 */ ScrcpyControlMessageType.BackOrScreenOn,
|
|
321
|
-
/* 5 */ ScrcpyControlMessageType.ExpandNotificationPanel,
|
|
322
|
-
/* 6 */ ScrcpyControlMessageType.CollapseNotificationPanel,
|
|
323
|
-
/* 7 */ ScrcpyControlMessageType.GetClipboard,
|
|
324
|
-
/* 8 */ ScrcpyControlMessageType.SetClipboard,
|
|
325
|
-
/* 9 */ ScrcpyControlMessageType.SetScreenPowerMode,
|
|
326
|
-
/* 10 */ ScrcpyControlMessageType.RotateDevice,
|
|
327
|
-
];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
public serializeBackOrScreenOnControlMessage(
|
|
331
|
-
message: ScrcpyBackOrScreenOnEvent1_18,
|
|
332
|
-
) {
|
|
333
|
-
if (message.action === AndroidKeyEventAction.Down) {
|
|
334
|
-
return ScrcpyBackOrScreenOnEvent1_16.serialize(message);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
public serializeInjectScrollControlMessage(
|
|
341
|
-
message: ScrcpyInjectScrollControlMessage1_22,
|
|
342
|
-
): Uint8Array {
|
|
343
|
-
return ScrcpyInjectScrollControlMessage1_16.serialize(message);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
1
|
+
export * from './codec-options.js';
|
|
2
|
+
export * from './options.js';
|
|
3
|
+
export * from './sps.js';
|