Start server
The server is a jar
file, so it's not directly executable. On Android, app_process
is the program to start the system server, all the apps, and standalone Java applications:
adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1
With @yume-chan/scrcpy
@yume-chan/scrcpy
only provides APIs to serialize and deserialize Scrcpy protocol messages. It can't start the server directly.
The ScrcpyOptions#serialize
method generates the command line arguments for the specified options:
- JavaScript
- TypeScript
import { ScrcpyOptions2_1 } from "@yume-chan/scrcpy";
const options = ScrcpyOptions2_1({
// options
});
const commandLineArguments = options.serialize();
import { ScrcpyOptions2_1 } from "@yume-chan/scrcpy";
const options = ScrcpyOptions2_1({
// options
});
const commandLineArguments: string[] = options.serialize();
Then you need to use varies method on the options class to parse the outputs.
With @yume-chan/adb-scrcpy
Use AdbScrcpyClient.start()
method with an option class to start the server.
import type { AdbScrcpyOptions } from "@yume-chan/adb-scrcpy";
import type { ScrcpyOptions1_15 } from "@yume-chan/scrcpy";
declare class AdbScrcpyClient {
static async start<
TOptions extends AdbScrcpyOptions<
Pick<ScrcpyOptions1_15.Init, "tunnelForward">
>,
>(
adb: Adb,
path: string,
options: TOptions,
): Promise<AdbScrcpyClient<TOptions>>;
}
Parameter | Required/Default value | Description |
---|---|---|
adb | Required | An Adb instance to run ADB commands. |
path | Required | The path on device where the server file is. |
options | Required | The options to start the server. |
It automatically sets up port forwarding, launches the server, and connects to it.
Examples
Using the default server path
- JavaScript
- TypeScript
import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from "@yume-chan/adb-scrcpy";
import { DefaultServerPath } from "@yume-chan/scrcpy";
const client = await AdbScrcpyClient.start(
adb,
DefaultServerPath,
new AdbScrcpyOptions2_1({
// options
}),
);
import type { Adb } from "@yume-chan/adb";
import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from "@yume-chan/adb-scrcpy";
import { DefaultServerPath } from "@yume-chan/scrcpy";
declare const adb: Adb;
const client: AdbScrcpyClient = await AdbScrcpyClient.start(
adb,
DefaultServerPath,
new AdbScrcpyOptions2_1({
// options
}),
);
Using custom server path
- JavaScript
- TypeScript
import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from "@yume-chan/adb-scrcpy";
const client = await AdbScrcpyClient.start(
adb,
"/data/local/tmp/scrcpy-server.jar",
new AdbScrcpyOptions2_1({
// options
}),
);
import type { Adb } from "@yume-chan/adb";
import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from "@yume-chan/adb-scrcpy";
declare const adb: Adb;
const client: AdbScrcpyClient = await AdbScrcpyClient.start(
adb,
"/data/local/tmp/scrcpy-server.jar",
new AdbScrcpyOptions2_1({
// options
}),
);
Error handling
If the server process exits before the connection is established, the client will throw an AdbScrcpyExitedError
.
export class AdbScrcpyExitedError extends Error {
output: readonly string[];
}
The output
field contains the output of the server process.
- JavaScript
- TypeScript
import { AdbScrcpyClient, AdbScrcpyOptions2_1, AdbScrcpyExitedError } from "@yume-chan/adb-scrcpy";
import { DefaultServerPath } from "@yume-chan/scrcpy";
try {
const client = await AdbScrcpyClient.start(adb, DefaultServerPath, new AdbScrcpyOptions2_1({}));
} catch (e) {
if (e instanceof AdbScrcpyExitedError) {
console.error(e.output);
} else {
console.error(e);
}
}
import type { Adb } from "@yume-chan/adb";
import { AdbScrcpyClient, AdbScrcpyOptions2_1, AdbScrcpyExitedError } from "@yume-chan/adb-scrcpy";
import { DefaultServerPath } from "@yume-chan/scrcpy";
declare const adb: Adb;
try {
const client: AdbScrcpyClient = await AdbScrcpyClient.start(
adb,
DefaultServerPath,
new AdbScrcpyOptions2_1({}),
);
} catch (e) {
if (e instanceof AdbScrcpyExitedError) {
console.error(e.output);
} else {
console.error(e);
}
}
Server output
After the start
process succeeds, the output
field of the AdbScrcpyClient
object contains the output of the server process.
- JavaScript
- TypeScript
import { WritableStream } from "@yume-chan/stream-extra";
// Print output of Scrcpy server
void client.output.pipeTo(
new WritableStream({
write(chunk) {
console.log(chunk);
},
}),
);
import type { AdbScrcpyClient } from "@yume-chan/adb-scrcpy";
import { WritableStream } from "@yume-chan/stream-extra";
declare const client: AdbScrcpyClient;
// Print output of Scrcpy server
void client.output.pipeTo(
new WritableStream<string>({
write(chunk) {
console.log(chunk);
},
}),
);
pipeTo
returns a Promise
that resolves when the stream ends.
Don't await
this Promise
here, as it will block the code below from executing.
However, you can save the Promise
to a variable and later use Promise.all
to wait for all concurrent tasks to complete.
ADB is a multiplexing protocol (multiple logic streams are transmitted over one connection), so blocking one stream will block all other streams.
You must continuously read from all incoming streams (either by piping them to WritableStream
s, using for await
loop, or calling reader.read()
in a loop) to prevent this from happening.
If the remaining data is not needed, stream.cancel()
(or reader.cancel()
if using a reader) can be called to discard them.
Video stream
videoStream
field contains the AdbScrcpyVideoStream
object, or undefined
if video: false
option is specified.(since v2.1)
- JavaScript
- TypeScript
import { WritableStream } from "@yume-chan/stream-extra";
if (client.videoStream) {
const { metadata: videoMetadata, stream: videoPacketStream } = await client.videoStream;
console.log(videoMetadata.codec);
void videoPacketStream.pipeTo(
new WritableStream({
write(chunk) {
console.log(chunk.type);
},
}),
);
}
import type { AdbScrcpyClient } from "@yume-chan/adb-scrcpy";
import { WritableStream } from "@yume-chan/stream-extra";
declare const client: AdbScrcpyClient;
if (client.videoStream) {
const { metadata: videoMetadata, stream: videoPacketStream } = await client.videoStream;
console.log(videoMetadata.codec);
void videoPacketStream.pipeTo(
new WritableStream({
write(chunk) {
console.log(chunk.type);
},
}),
);
}
Audio stream
Since v2.0, if audio: false
option is not specified, the audioStream
field represents the audio forwarding stream.
The audio stream can have three states:
disabled
: Audio forwarding not supported by deviceerrored
: Other error when initializing audio forwardingsuccess
: Audio forwarding initialized successfully. Thestream
field contains audio packets in the specified codec.
When audio forwarding failed to start (either disabled
or errored
), other sockets (video and control) still work.
- JavaScript
- TypeScript
import { WritableStream } from "@yume-chan/stream-extra";
if (client.audioStream) {
const metadata = await client.audioStream;
switch (metadata.type) {
case "disabled":
break;
case "errored":
break;
case "success":
const audioPacketStream = metadata.stream;
void audioPacketStream
.pipeTo(
new WritableStream({
write(packet) {
console.log(packet);
},
}),
)
.catch((e) => console.error(e));
break;
}
}
import type { AdbScrcpyClient } from "@yume-chan/adb-scrcpy";
import type { ScrcpyMediaStreamPacket } from "@yume-chan/scrcpy";
import type { ReadableStream } from "@yume-chan/stream-extra";
import { WritableStream } from "@yume-chan/stream-extra";
declare const client: AdbScrcpyClient;
if (client.audioStream) {
const metadata = await client.audioStream;
switch (metadata.type) {
case "disabled":
break;
case "errored":
break;
case "success":
const audioPacketStream: ReadableStream<ScrcpyMediaStreamPacket> = metadata.stream;
void audioPacketStream
.pipeTo(
new WritableStream<ScrcpyMediaStreamPacket>({
write(packet) {
console.log(packet);
},
}),
)
.catch((e) => console.error(e));
break;
}
}
Controller
Controller is used to send control messages to server, or undefined
if control: false
option is specified.(since v2.1)
- JavaScript
- TypeScript
const controller = client.controller;
if (controller) {
await controller.setScreenPowerMode(AndroidScreenPowerMode.Off);
}
import type { AdbScrcpyClient } from "@yume-chan/adb-scrcpy";
import type { ScrcpyControlMessageWriter } from "@yume-chan/scrcpy";
declare const client: AdbScrcpyClient;
const controller: ScrcpyControlMessageWriter | undefined = client.controller;
if (controller) {
await controller.setScreenPowerMode(AndroidScreenPowerMode.Off);
}
Clipboard stream
If control: false
option is not specified and clipboardAutosync: false
option is also not specified(since v2.1), Scrcpy server will monitor device clipboard and send the change to client.
When clipboard syncing is enabled, the clipboard
field will be a ReadableStream<string>
that contains the clipboard content.
- JavaScript
- TypeScript
const clipboard = client.clipboard;
if (clipboard) {
void clipboard
?.pipeTo(
new WritableStream({
write(chunk) {
// Handle device clipboard change
console.log(chunk);
},
}),
)
.catch((e) => console.error(e));
}
import type { ReadableStream } from "@yume-chan/stream-extra";
const clipboard: ReadableStream<string> | undefined = client.clipboard;
if (clipboard) {
void clipboard
?.pipeTo(
new WritableStream<string>({
write(chunk) {
// Handle device clipboard change
console.log(chunk);
},
}),
)
.catch((e) => console.error(e));
}
Scrcpy only supports text type in clipboard.
Stop the server
The stop
method can be used to stop the server:
await client.close();
With Tango
AdbNoneProtocolSubprocessService#spawn can be used to start a process on device:
- JavaScript
- TypeScript
import { TextDecoderStream } from "@yume-chan/stream-extra";
const process = await adb.subprocess.noneProtocol.spawn(
"CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1",
);
// Print output of Scrcpy server
await process.output.pipeThrough(new TextDecoderStream()).pipeTo(
new WritableStream({
write(chunk) {
console.log(chunk);
},
}),
);
import type { Adb } from "@yume-chan/adb";
import { TextDecoderStream } from "@yume-chan/stream-extra";
declare const adb: Adb;
const process = await adb.subprocess.noneProtocol.spawn(
"CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1",
);
// Print output of Scrcpy server
await process.output.pipeThrough(new TextDecoderStream()).pipeTo(
new WritableStream<string>({
write(chunk) {
console.log(chunk);
},
}),
);
Then you need to connect to Scrcpy server yourself.