refactor(scrcpy): revert changing control message client to stream

refs #427
This commit is contained in:
Simon Chan 2022-06-25 23:20:00 +08:00
parent 32f98424e5
commit c13bbe3a61
10 changed files with 129 additions and 128 deletions

View file

@ -9,7 +9,7 @@ import { CSSProperties, ReactNode, useEffect, useState } from "react";
import { ADB_SYNC_MAX_PACKET_SIZE } from '@yume-chan/adb'; import { ADB_SYNC_MAX_PACKET_SIZE } from '@yume-chan/adb';
import { EventEmitter } from "@yume-chan/event"; import { EventEmitter } from "@yume-chan/event";
import { AdbScrcpyClient, AdbScrcpyOptions1_22, AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, CodecOptions, DEFAULT_SERVER_PATH, ScrcpyControlMessage, ScrcpyControlMessageType, ScrcpyLogLevel, ScrcpyOptions1_24, ScrcpyVideoOrientation, TinyH264Decoder, WebCodecsDecoder, type H264Decoder, type H264DecoderConstructor, type VideoStreamPacket } from "@yume-chan/scrcpy"; import { AdbScrcpyClient, AdbScrcpyOptions1_22, AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, CodecOptions, DEFAULT_SERVER_PATH, ScrcpyDeviceMessageType, ScrcpyLogLevel, ScrcpyOptions1_24, ScrcpyVideoOrientation, TinyH264Decoder, WebCodecsDecoder, type H264Decoder, type H264DecoderConstructor, type VideoStreamPacket } from "@yume-chan/scrcpy";
import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version'; import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version';
import { ChunkStream, InspectStream, ReadableStream, WritableStream } from '@yume-chan/stream-extra'; import { ChunkStream, InspectStream, ReadableStream, WritableStream } from '@yume-chan/stream-extra';
@ -243,7 +243,6 @@ class ScrcpyPageState {
get rotatedHeight() { return state.rotate & 1 ? state.width : state.height; } get rotatedHeight() { return state.rotate & 1 ? state.width : state.height; }
client: AdbScrcpyClient | undefined = undefined; client: AdbScrcpyClient | undefined = undefined;
controlMessageWriter: WritableStreamDefaultWriter<ScrcpyControlMessage> | undefined = undefined;
async pushServer() { async pushServer() {
const serverBuffer = await fetchServer(); const serverBuffer = await fetchServer();
@ -364,7 +363,7 @@ class ScrcpyPageState {
iconProps: { iconName: Icons.Orientation }, iconProps: { iconName: Icons.Orientation },
iconOnly: true, iconOnly: true,
text: 'Rotate Device', text: 'Rotate Device',
onClick: () => { this.controlMessageWriter!.write({ type: ScrcpyControlMessageType.RotateDevice }); }, onClick: () => { this.client!.controlMessageSerializer!.rotateDevice(); },
}); });
result.push({ result.push({
@ -795,13 +794,16 @@ class ScrcpyPageState {
client.deviceMessageStream!.pipeTo(new WritableStream({ client.deviceMessageStream!.pipeTo(new WritableStream({
write(message) { write(message) {
window.navigator.clipboard.writeText(message.content); switch (message.type) {
case ScrcpyDeviceMessageType.Clipboard:
window.navigator.clipboard.writeText(message.content);
break;
}
} }
})).catch(() => { }); })).catch(() => { });
runInAction(() => { runInAction(() => {
this.client = client; this.client = client;
this.controlMessageWriter = client.controlMessageStream!.getWriter();
this.running = true; this.running = true;
}); });
} catch (e: any) { } catch (e: any) {
@ -850,10 +852,7 @@ class ScrcpyPageState {
} }
e.currentTarget.setPointerCapture(e.pointerId); e.currentTarget.setPointerCapture(e.pointerId);
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.backOrScreenOn(AndroidKeyEventAction.Down);
type: ScrcpyControlMessageType.BackOrScreenOn,
action: AndroidKeyEventAction.Down,
});
}; };
handleBackPointerUp = (e: React.PointerEvent<HTMLDivElement>) => { handleBackPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
@ -865,10 +864,7 @@ class ScrcpyPageState {
return; return;
} }
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.backOrScreenOn(AndroidKeyEventAction.Up);
type: ScrcpyControlMessageType.BackOrScreenOn,
action: AndroidKeyEventAction.Up,
});
}; };
handleHomePointerDown = (e: React.PointerEvent<HTMLDivElement>) => { handleHomePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
@ -881,8 +877,7 @@ class ScrcpyPageState {
} }
e.currentTarget.setPointerCapture(e.pointerId); e.currentTarget.setPointerCapture(e.pointerId);
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Down, action: AndroidKeyEventAction.Down,
keyCode: AndroidKeyCode.Home, keyCode: AndroidKeyCode.Home,
repeat: 0, repeat: 0,
@ -899,8 +894,7 @@ class ScrcpyPageState {
return; return;
} }
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Up, action: AndroidKeyEventAction.Up,
keyCode: AndroidKeyCode.Home, keyCode: AndroidKeyCode.Home,
repeat: 0, repeat: 0,
@ -918,8 +912,7 @@ class ScrcpyPageState {
} }
e.currentTarget.setPointerCapture(e.pointerId); e.currentTarget.setPointerCapture(e.pointerId);
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Down, action: AndroidKeyEventAction.Down,
keyCode: AndroidKeyCode.AppSwitch, keyCode: AndroidKeyCode.AppSwitch,
repeat: 0, repeat: 0,
@ -936,8 +929,7 @@ class ScrcpyPageState {
return; return;
} }
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Up, action: AndroidKeyEventAction.Up,
keyCode: AndroidKeyCode.AppSwitch, keyCode: AndroidKeyCode.AppSwitch,
repeat: 0, repeat: 0,
@ -981,8 +973,7 @@ class ScrcpyPageState {
} }
const { x, y } = this.calculatePointerPosition(e.clientX, e.clientY); const { x, y } = this.calculatePointerPosition(e.clientX, e.clientY);
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectTouch({
type: ScrcpyControlMessageType.InjectTouch,
action, action,
pointerId: e.pointerType === "mouse" ? BigInt(-1) : BigInt(e.pointerId), pointerId: e.pointerType === "mouse" ? BigInt(-1) : BigInt(e.pointerId),
screenWidth: this.client!.screenWidth!, screenWidth: this.client!.screenWidth!,
@ -1021,8 +1012,7 @@ class ScrcpyPageState {
e.stopPropagation(); e.stopPropagation();
const { x, y } = this.calculatePointerPosition(e.clientX, e.clientY); const { x, y } = this.calculatePointerPosition(e.clientX, e.clientY);
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectScroll({
type: ScrcpyControlMessageType.InjectScroll,
screenWidth: this.client!.screenWidth!, screenWidth: this.client!.screenWidth!,
screenHeight: this.client!.screenHeight!, screenHeight: this.client!.screenHeight!,
pointerX: x, pointerX: x,
@ -1044,10 +1034,7 @@ class ScrcpyPageState {
const { key, code } = e; const { key, code } = e;
if (key.match(/^[!-`{-~]$/i)) { if (key.match(/^[!-`{-~]$/i)) {
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectText(key);
type: ScrcpyControlMessageType.InjectText,
text: key,
});
return; return;
} }
@ -1058,15 +1045,13 @@ class ScrcpyPageState {
} as Record<string, AndroidKeyCode | undefined>)[code]; } as Record<string, AndroidKeyCode | undefined>)[code];
if (keyCode) { if (keyCode) {
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Down, action: AndroidKeyEventAction.Down,
keyCode, keyCode,
metaState: 0, metaState: 0,
repeat: 0, repeat: 0,
}); });
this.controlMessageWriter!.write({ this.client!.controlMessageSerializer!.injectKeyCode({
type: ScrcpyControlMessageType.InjectKeyCode,
action: AndroidKeyEventAction.Up, action: AndroidKeyEventAction.Up,
keyCode, keyCode,
metaState: 0, metaState: 0,

View file

@ -1,7 +1,7 @@
import { Adb, AdbSubprocessNoneProtocol, AdbSubprocessProtocol, AdbSync } from '@yume-chan/adb'; import { Adb, AdbSubprocessNoneProtocol, AdbSubprocessProtocol, AdbSync } from '@yume-chan/adb';
import { DecodeUtf8Stream, InspectStream, pipeFrom, ReadableStream, SplitStringStream, WrapWritableStream, WritableStream, type ReadableWritablePair } from '@yume-chan/stream-extra'; import { DecodeUtf8Stream, InspectStream, ReadableStream, SplitStringStream, WrapWritableStream, WritableStream, type ReadableWritablePair } from '@yume-chan/stream-extra';
import { ScrcpyControlMessageSerializeStream, type ScrcpyControlMessage } from '../control/index.js'; import { ScrcpyControlMessageSerializer } from '../control/index.js';
import { ScrcpyDeviceMessageDeserializeStream, type ScrcpyDeviceMessage } from '../device-message/index.js'; import { ScrcpyDeviceMessageDeserializeStream, type ScrcpyDeviceMessage } from '../device-message/index.js';
import { DEFAULT_SERVER_PATH, type VideoStreamPacket } from '../options/index.js'; import { DEFAULT_SERVER_PATH, type VideoStreamPacket } from '../options/index.js';
import type { AdbScrcpyOptions } from './options/index.js'; import type { AdbScrcpyOptions } from './options/index.js';
@ -255,8 +255,8 @@ export class AdbScrcpyClient {
private _videoStream: ReadableStream<VideoStreamPacket>; private _videoStream: ReadableStream<VideoStreamPacket>;
public get videoStream() { return this._videoStream; } public get videoStream() { return this._videoStream; }
private _controlMessageStream: WritableStream<ScrcpyControlMessage> | undefined; private _controlMessageSerializer: ScrcpyControlMessageSerializer | undefined;
public get controlMessageStream() { return this._controlMessageStream; } public get controlMessageSerializer() { return this._controlMessageSerializer; }
private _deviceMessageStream: ReadableStream<ScrcpyDeviceMessage> | undefined; private _deviceMessageStream: ReadableStream<ScrcpyDeviceMessage> | undefined;
public get deviceMessageStream() { return this._deviceMessageStream; } public get deviceMessageStream() { return this._deviceMessageStream; }
@ -281,7 +281,7 @@ export class AdbScrcpyClient {
})); }));
if (controlStream) { if (controlStream) {
this._controlMessageStream = pipeFrom(controlStream.writable, new ScrcpyControlMessageSerializeStream(options)); this._controlMessageSerializer = new ScrcpyControlMessageSerializer(controlStream.writable, options);
this._deviceMessageStream = controlStream.readable.pipeThrough(new ScrcpyDeviceMessageDeserializeStream()); this._deviceMessageStream = controlStream.readable.pipeThrough(new ScrcpyDeviceMessageDeserializeStream());
} }
} }

View file

@ -2,5 +2,5 @@ export * from './inject-keycode.js';
export * from './inject-text.js'; export * from './inject-text.js';
export * from './inject-touch.js'; export * from './inject-touch.js';
export * from './rotate-device.js'; export * from './rotate-device.js';
export * from './stream.js'; export * from './serializer.js';
export * from './type.js'; export * from './type.js';

View file

@ -0,0 +1,78 @@
import type { WritableStreamDefaultWriter } from '@yume-chan/stream-extra';
import type { ScrcpyInjectScrollControlMessage1_22, ScrcpyOptions } from '../options/index.js';
import { AndroidKeyEventAction, ScrcpyInjectKeyCodeControlMessage } from './inject-keycode.js';
import { ScrcpyInjectTextControlMessage } from './inject-text.js';
import { ScrcpyInjectTouchControlMessage } from './inject-touch.js';
import { ScrcpyRotateDeviceControlMessage } from './rotate-device.js';
import { ScrcpyControlMessageType } from './type.js';
export class ScrcpyControlMessageSerializer {
private options: ScrcpyOptions<any>;
/** Control message type values for current version of server */
private types: ScrcpyControlMessageType[];
private writer: WritableStreamDefaultWriter<Uint8Array>;
public constructor(stream: WritableStream<Uint8Array>, options: ScrcpyOptions<any>) {
this.options = options;
this.types = options.getControlMessageTypes();
this.writer = stream.getWriter();
}
public getTypeValue(type: ScrcpyControlMessageType): number {
const value = this.types.indexOf(type);
if (value === -1) {
throw new Error('Not supported');
}
return value;
}
public injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, 'type'>) {
return this.writer.write(ScrcpyInjectKeyCodeControlMessage.serialize({
...message,
type: this.getTypeValue(ScrcpyControlMessageType.InjectKeyCode),
}));
}
public injectText(text: string) {
return this.writer.write(ScrcpyInjectTextControlMessage.serialize({
text,
type: this.getTypeValue(ScrcpyControlMessageType.InjectText),
}));
}
public injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, 'type'>) {
return this.writer.write(ScrcpyInjectTouchControlMessage.serialize({
...message,
type: this.getTypeValue(ScrcpyControlMessageType.InjectTouch),
}));
}
public injectScroll(message: Omit<ScrcpyInjectScrollControlMessage1_22, 'type'>) {
return this.writer.write(this.options.serializeInjectScrollControlMessage({
...message,
type: this.getTypeValue(ScrcpyControlMessageType.InjectScroll),
}));
}
public async backOrScreenOn(action: AndroidKeyEventAction) {
const buffer = this.options.serializeBackOrScreenOnControlMessage({
action,
type: this.getTypeValue(ScrcpyControlMessageType.BackOrScreenOn),
});
if (buffer) {
return await this.writer.write(buffer);
}
}
public rotateDevice() {
return this.writer.write(ScrcpyRotateDeviceControlMessage.serialize({
type: this.getTypeValue(ScrcpyControlMessageType.RotateDevice),
}));
}
public close() {
return this.writer.close();
}
};

View file

@ -1,79 +0,0 @@
import { TransformStream } from '@yume-chan/stream-extra';
import type { ScrcpyBackOrScreenOnControlMessage1_18, ScrcpyInjectScrollControlMessage1_22, ScrcpyOptions } from '../options/index.js';
import { ScrcpyInjectKeyCodeControlMessage } from './inject-keycode.js';
import { ScrcpyInjectTextControlMessage } from './inject-text.js';
import { ScrcpyInjectTouchControlMessage } from './inject-touch.js';
import { ScrcpyRotateDeviceControlMessage } from './rotate-device.js';
import { ScrcpyControlMessageType } from './type.js';
export type ScrcpyControlMessage =
| ScrcpyInjectKeyCodeControlMessage
| ScrcpyInjectTextControlMessage
| ScrcpyInjectTouchControlMessage
| ScrcpyInjectScrollControlMessage1_22
| ScrcpyBackOrScreenOnControlMessage1_18
| ScrcpyRotateDeviceControlMessage;
export class ScrcpyControlMessageSerializeStream extends TransformStream<ScrcpyControlMessage, Uint8Array> {
public constructor(options: ScrcpyOptions<any>) {
// Get control message types for current version of server
const types = options.getControlMessageTypes();
super({
transform(message, controller) {
const type = types.indexOf(message.type);
if (type === -1) {
throw new Error('Not supported');
}
switch (message.type) {
case ScrcpyControlMessageType.InjectKeyCode:
controller.enqueue(ScrcpyInjectKeyCodeControlMessage.serialize({
...message,
type
}));
break;
case ScrcpyControlMessageType.InjectText:
controller.enqueue(ScrcpyInjectTextControlMessage.serialize({
...message,
type,
}));
break;
case ScrcpyControlMessageType.InjectTouch:
// ADB streams are actually pretty low-bandwidth and laggy
// Re-sample move events to avoid flooding the connection
controller.enqueue(ScrcpyInjectTouchControlMessage.serialize({
...message,
type,
}));
break;
case ScrcpyControlMessageType.InjectScroll:
controller.enqueue(options.serializeInjectScrollControlMessage({
...message,
type,
}))
break;
case ScrcpyControlMessageType.BackOrScreenOn:
{
const buffer = options.serializeBackOrScreenOnControlMessage({
...message,
type,
});
if (buffer) {
controller.enqueue(buffer);
}
}
break;
case ScrcpyControlMessageType.RotateDevice:
controller.enqueue(ScrcpyRotateDeviceControlMessage.serialize({
type,
}));
break;
}
}
})
}
}

View file

@ -1,5 +1,5 @@
import { PromiseResolver } from '@yume-chan/async'; import { PromiseResolver } from '@yume-chan/async';
import { AutoDisposable, EventEmitter } from '@yume-chan/event'; import { AutoDisposable, Disposable, EventEmitter } from '@yume-chan/event';
let worker: Worker | undefined; let worker: Worker | undefined;
let workerReady = false; let workerReady = false;
@ -16,7 +16,17 @@ export interface PictureReadyEventArgs {
data: ArrayBuffer; data: ArrayBuffer;
} }
const pictureReadyEvent = new EventEmitter<PictureReadyEventArgs>(); const PICTURE_READY_SUBSCRIPTIONS = new Map<number, (e: PictureReadyEventArgs) => void>();
function subscribePictureReady(streamId: number, handler: (e: PictureReadyEventArgs) => void): Disposable {
PICTURE_READY_SUBSCRIPTIONS.set(streamId, handler);
return {
dispose() {
PICTURE_READY_SUBSCRIPTIONS.delete(streamId);
}
};
}
export class TinyH264Wrapper extends AutoDisposable { export class TinyH264Wrapper extends AutoDisposable {
public readonly streamId: number; public readonly streamId: number;
@ -28,14 +38,12 @@ export class TinyH264Wrapper extends AutoDisposable {
super(); super();
this.streamId = streamId; this.streamId = streamId;
this.addDisposable(pictureReadyEvent.event(this.handlePictureReady, this)); this.addDisposable(subscribePictureReady(streamId, this.handlePictureReady));
} }
private handlePictureReady(e: PictureReadyEventArgs) { private handlePictureReady = (e: PictureReadyEventArgs) => {
if (e.renderStateId === this.streamId) { this.pictureReadyEvent.fire(e);
this.pictureReadyEvent.fire(e); };
}
}
public feed(data: ArrayBuffer) { public feed(data: ArrayBuffer) {
worker!.postMessage({ worker!.postMessage({
@ -71,7 +79,7 @@ export function createTinyH264Wrapper(): Promise<TinyH264Wrapper> {
pendingResolvers.length = 0; pendingResolvers.length = 0;
break; break;
case 'pictureReady': case 'pictureReady':
pictureReadyEvent.fire(data); PICTURE_READY_SUBSCRIPTIONS.get(data.renderStateId)?.(data);
break; break;
} }
}); });

View file

@ -1,9 +1,11 @@
import Struct from '@yume-chan/struct'; import Struct from '@yume-chan/struct';
import { ScrcpyDeviceMessageType } from './type.js';
export const ScrcpyClipboardDeviceMessage = export const ScrcpyClipboardDeviceMessage =
new Struct() new Struct()
.uint32('length') .uint32('length')
.string('content', { lengthField: 'length' }); .string('content', { lengthField: 'length' })
.extra({ type: ScrcpyDeviceMessageType.Clipboard as const });
export type ScrcpyClipboardDeviceMessage = export type ScrcpyClipboardDeviceMessage =
typeof ScrcpyClipboardDeviceMessage['TDeserializeResult']; typeof ScrcpyClipboardDeviceMessage['TDeserializeResult'];

View file

@ -1,2 +1,3 @@
export * from './clipboard.js'; export * from './clipboard.js';
export * from './stream.js'; export * from './stream.js';
export * from './type.js';

View file

@ -0,0 +1,5 @@
// https://github.com/Genymobile/scrcpy/blob/41abe021e2a73efd4899b0efcd0b9eef9ec68c9b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java#L5
export enum ScrcpyDeviceMessageType {
Clipboard,
AckClipboard,
}

View file

@ -2,4 +2,5 @@ export * from './adb/index.js';
export * from './codec.js'; export * from './codec.js';
export * from './control/index.js'; export * from './control/index.js';
export * from './decoder/index.js'; export * from './decoder/index.js';
export * from './device-message/index.js';
export * from './options/index.js'; export * from './options/index.js';