appium-android-driver 7.8.3 → 8.0.1
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.md +32 -0
- package/build/lib/commands/app-management.d.ts +129 -5
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +433 -128
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/appearance.d.ts +17 -4
- package/build/lib/commands/appearance.d.ts.map +1 -1
- package/build/lib/commands/appearance.js +32 -33
- package/build/lib/commands/appearance.js.map +1 -1
- package/build/lib/commands/context/cache.d.ts +19 -0
- package/build/lib/commands/context/cache.d.ts.map +1 -0
- package/build/lib/commands/context/cache.js +32 -0
- package/build/lib/commands/context/cache.js.map +1 -0
- package/build/lib/commands/context/exports.d.ts +141 -0
- package/build/lib/commands/context/exports.d.ts.map +1 -0
- package/build/lib/commands/context/exports.js +351 -0
- package/build/lib/commands/context/exports.js.map +1 -0
- package/build/lib/commands/context/helpers.d.ts +98 -0
- package/build/lib/commands/context/helpers.d.ts.map +1 -0
- package/build/lib/commands/context/helpers.js +715 -0
- package/build/lib/commands/context/helpers.js.map +1 -0
- package/build/lib/commands/device/common.d.ts +23 -0
- package/build/lib/commands/device/common.d.ts.map +1 -0
- package/build/lib/commands/device/common.js +230 -0
- package/build/lib/commands/device/common.js.map +1 -0
- package/build/lib/commands/device/emulator-actions.d.ts +114 -0
- package/build/lib/commands/device/emulator-actions.d.ts.map +1 -0
- package/build/lib/commands/device/emulator-actions.js +197 -0
- package/build/lib/commands/device/emulator-actions.js.map +1 -0
- package/build/lib/commands/device/emulator-console.d.ts +7 -0
- package/build/lib/commands/device/emulator-console.d.ts.map +1 -0
- package/build/lib/commands/device/emulator-console.js +24 -0
- package/build/lib/commands/device/emulator-console.js.map +1 -0
- package/build/lib/commands/device/utils.d.ts +50 -0
- package/build/lib/commands/device/utils.d.ts.map +1 -0
- package/build/lib/commands/device/utils.js +238 -0
- package/build/lib/commands/device/utils.js.map +1 -0
- package/build/lib/commands/deviceidle.d.ts +8 -5
- package/build/lib/commands/deviceidle.d.ts.map +1 -1
- package/build/lib/commands/deviceidle.js +31 -37
- package/build/lib/commands/deviceidle.js.map +1 -1
- package/build/lib/commands/element.d.ts +99 -5
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +152 -116
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/execute.d.ts +12 -4
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +83 -78
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts +42 -5
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +230 -194
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts +5 -4
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +7 -10
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts +45 -0
- package/build/lib/commands/geolocation.d.ts.map +1 -0
- package/build/lib/commands/geolocation.js +182 -0
- package/build/lib/commands/geolocation.js.map +1 -0
- package/build/lib/commands/ime.d.ts +25 -5
- package/build/lib/commands/ime.d.ts.map +1 -1
- package/build/lib/commands/ime.js +59 -42
- package/build/lib/commands/ime.js.map +1 -1
- package/build/lib/commands/intent.d.ts +56 -5
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +135 -83
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +58 -4
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +119 -17
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/lock/exports.d.ts +301 -0
- package/build/lib/commands/lock/exports.d.ts.map +1 -0
- package/build/lib/commands/lock/exports.js +121 -0
- package/build/lib/commands/lock/exports.js.map +1 -0
- package/build/lib/commands/lock/helpers.d.ts +349 -0
- package/build/lib/commands/lock/helpers.d.ts.map +1 -0
- package/build/lib/commands/lock/helpers.js +375 -0
- package/build/lib/commands/lock/helpers.js.map +1 -0
- package/build/lib/commands/log.d.ts +59 -5
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +150 -140
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts +16 -5
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +69 -58
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/memory.d.ts +9 -5
- package/build/lib/commands/memory.d.ts.map +1 -1
- package/build/lib/commands/memory.js +19 -24
- package/build/lib/commands/memory.js.map +1 -1
- package/build/lib/commands/misc.d.ts +42 -0
- package/build/lib/commands/misc.d.ts.map +1 -0
- package/build/lib/commands/misc.js +100 -0
- package/build/lib/commands/misc.js.map +1 -0
- package/build/lib/commands/network.d.ts +61 -5
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +196 -189
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/performance.d.ts +67 -27
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +105 -80
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +12 -6
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +65 -62
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +44 -5
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +131 -126
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/resources.d.ts +16 -0
- package/build/lib/commands/resources.d.ts.map +1 -0
- package/build/lib/commands/resources.js +91 -0
- package/build/lib/commands/resources.js.map +1 -0
- package/build/lib/commands/shell.d.ts +8 -5
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +29 -33
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts +34 -6
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +166 -162
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts +18 -13
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +68 -64
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/time.d.ts +14 -0
- package/build/lib/commands/time.d.ts.map +1 -0
- package/build/lib/commands/time.js +39 -0
- package/build/lib/commands/time.js.map +1 -0
- package/build/lib/commands/touch.d.ts +99 -6
- package/build/lib/commands/touch.d.ts.map +1 -1
- package/build/lib/commands/touch.js +399 -280
- package/build/lib/commands/touch.js.map +1 -1
- package/build/lib/commands/types.d.ts +110 -2
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/doctor/checks.d.ts.map +1 -1
- package/build/lib/doctor/checks.js +4 -4
- package/build/lib/doctor/checks.js.map +1 -1
- package/build/lib/driver.d.ts +224 -27
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +232 -7
- package/build/lib/driver.js.map +1 -1
- package/build/lib/index.d.ts +1 -4
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +1 -13
- package/build/lib/index.js.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/method-map.d.ts +0 -23
- package/build/lib/method-map.d.ts.map +1 -1
- package/build/lib/method-map.js +0 -11
- package/build/lib/method-map.js.map +1 -1
- package/build/lib/utils.d.ts +12 -0
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +38 -2
- package/build/lib/utils.js.map +1 -1
- package/lib/commands/app-management.js +470 -145
- package/lib/commands/appearance.js +29 -36
- package/lib/commands/context/cache.js +29 -0
- package/lib/commands/context/exports.js +379 -0
- package/lib/commands/context/helpers.js +802 -0
- package/lib/commands/device/common.js +264 -0
- package/lib/commands/device/emulator-actions.js +194 -0
- package/lib/commands/device/emulator-console.js +24 -0
- package/lib/commands/device/utils.js +285 -0
- package/lib/commands/deviceidle.js +31 -44
- package/lib/commands/element.js +149 -142
- package/lib/commands/execute.js +86 -87
- package/lib/commands/file-actions.js +249 -222
- package/lib/commands/find.ts +13 -19
- package/lib/commands/geolocation.js +179 -0
- package/lib/commands/ime.js +53 -45
- package/lib/commands/intent.js +149 -91
- package/lib/commands/keyboard.js +114 -17
- package/lib/commands/lock/exports.js +139 -0
- package/lib/commands/lock/helpers.js +379 -0
- package/lib/commands/log.js +170 -166
- package/lib/commands/media-projection.js +75 -70
- package/lib/commands/memory.js +17 -29
- package/lib/commands/misc.js +94 -0
- package/lib/commands/network.js +209 -223
- package/lib/commands/performance.js +88 -73
- package/lib/commands/permissions.js +83 -84
- package/lib/commands/recordscreen.js +171 -170
- package/lib/commands/resources.js +96 -0
- package/lib/commands/shell.js +28 -42
- package/lib/commands/streamscreen.js +207 -206
- package/lib/commands/system-bars.js +76 -77
- package/lib/commands/time.js +36 -0
- package/lib/commands/touch.js +442 -346
- package/lib/commands/types.ts +123 -2
- package/lib/doctor/checks.js +24 -16
- package/lib/driver.ts +454 -12
- package/lib/index.ts +1 -13
- package/lib/logger.js +1 -1
- package/lib/method-map.js +0 -11
- package/lib/utils.js +40 -3
- package/package.json +2 -2
- package/build/lib/commands/actions.d.ts +0 -8
- package/build/lib/commands/actions.d.ts.map +0 -1
- package/build/lib/commands/actions.js +0 -207
- package/build/lib/commands/actions.js.map +0 -1
- package/build/lib/commands/alert.d.ts +0 -8
- package/build/lib/commands/alert.d.ts.map +0 -1
- package/build/lib/commands/alert.js +0 -29
- package/build/lib/commands/alert.js.map +0 -1
- package/build/lib/commands/context.d.ts +0 -10
- package/build/lib/commands/context.d.ts.map +0 -1
- package/build/lib/commands/context.js +0 -431
- package/build/lib/commands/context.js.map +0 -1
- package/build/lib/commands/emu-console.d.ts +0 -7
- package/build/lib/commands/emu-console.d.ts.map +0 -1
- package/build/lib/commands/emu-console.js +0 -27
- package/build/lib/commands/emu-console.js.map +0 -1
- package/build/lib/commands/general.d.ts +0 -9
- package/build/lib/commands/general.d.ts.map +0 -1
- package/build/lib/commands/general.js +0 -293
- package/build/lib/commands/general.js.map +0 -1
- package/build/lib/commands/index.d.ts +0 -28
- package/build/lib/commands/index.d.ts.map +0 -1
- package/build/lib/commands/index.js +0 -57
- package/build/lib/commands/index.js.map +0 -1
- package/build/lib/commands/mixins.d.ts +0 -747
- package/build/lib/commands/mixins.d.ts.map +0 -1
- package/build/lib/commands/mixins.js +0 -19
- package/build/lib/commands/mixins.js.map +0 -1
- package/build/lib/helpers/android.d.ts +0 -163
- package/build/lib/helpers/android.d.ts.map +0 -1
- package/build/lib/helpers/android.js +0 -818
- package/build/lib/helpers/android.js.map +0 -1
- package/build/lib/helpers/index.d.ts +0 -7
- package/build/lib/helpers/index.d.ts.map +0 -1
- package/build/lib/helpers/index.js +0 -29
- package/build/lib/helpers/index.js.map +0 -1
- package/build/lib/helpers/types.d.ts +0 -122
- package/build/lib/helpers/types.d.ts.map +0 -1
- package/build/lib/helpers/types.js +0 -3
- package/build/lib/helpers/types.js.map +0 -1
- package/build/lib/helpers/unlock.d.ts +0 -32
- package/build/lib/helpers/unlock.d.ts.map +0 -1
- package/build/lib/helpers/unlock.js +0 -273
- package/build/lib/helpers/unlock.js.map +0 -1
- package/build/lib/helpers/webview.d.ts +0 -74
- package/build/lib/helpers/webview.d.ts.map +0 -1
- package/build/lib/helpers/webview.js +0 -448
- package/build/lib/helpers/webview.js.map +0 -1
- package/lib/commands/actions.js +0 -244
- package/lib/commands/alert.js +0 -34
- package/lib/commands/context.js +0 -507
- package/lib/commands/emu-console.js +0 -31
- package/lib/commands/general.js +0 -343
- package/lib/commands/index.ts +0 -54
- package/lib/commands/mixins.ts +0 -976
- package/lib/helpers/android.ts +0 -1153
- package/lib/helpers/index.ts +0 -6
- package/lib/helpers/types.ts +0 -136
- package/lib/helpers/unlock.ts +0 -329
- package/lib/helpers/webview.ts +0 -610
|
@@ -10,7 +10,6 @@ import net from 'node:net';
|
|
|
10
10
|
import url from 'node:url';
|
|
11
11
|
import {checkPortStatus} from 'portscanner';
|
|
12
12
|
import {SubProcess, exec} from 'teen_process';
|
|
13
|
-
import {mixin} from './mixins';
|
|
14
13
|
|
|
15
14
|
const RECORDING_INTERVAL_SEC = 5;
|
|
16
15
|
const STREAMING_STARTUP_TIMEOUT_MS = 5000;
|
|
@@ -34,6 +33,201 @@ const BOUNDARY_STRING = '--2ae9746887f170b8cf7c271047ce314c';
|
|
|
34
33
|
|
|
35
34
|
const ADB_SCREEN_STREAMING_FEATURE = 'adb_screen_streaming';
|
|
36
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @this {import('../driver').AndroidDriver}
|
|
38
|
+
* @param {import('./types').StartScreenStreamingOpts} [options={}]
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
export async function mobileStartScreenStreaming(options = {}) {
|
|
42
|
+
this.ensureFeatureEnabled(ADB_SCREEN_STREAMING_FEATURE);
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
width,
|
|
46
|
+
height,
|
|
47
|
+
bitRate,
|
|
48
|
+
host = DEFAULT_HOST,
|
|
49
|
+
port = DEFAULT_PORT,
|
|
50
|
+
pathname,
|
|
51
|
+
tcpPort = DEFAULT_PORT + 1,
|
|
52
|
+
quality = DEFAULT_QUALITY,
|
|
53
|
+
considerRotation = false,
|
|
54
|
+
logPipelineDetails = false,
|
|
55
|
+
} = options;
|
|
56
|
+
if (_.isUndefined(this._screenStreamingProps)) {
|
|
57
|
+
await verifyStreamingRequirements(this.adb);
|
|
58
|
+
}
|
|
59
|
+
if (!_.isEmpty(this._screenStreamingProps)) {
|
|
60
|
+
this.log.info(
|
|
61
|
+
`The screen streaming session is already running. ` +
|
|
62
|
+
`Stop it first in order to start a new one.`,
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if ((await checkPortStatus(port, host)) === 'open') {
|
|
67
|
+
this.log.info(
|
|
68
|
+
`The port #${port} at ${host} is busy. ` + `Assuming the screen streaming is already running`,
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if ((await checkPortStatus(tcpPort, TCP_HOST)) === 'open') {
|
|
73
|
+
this.log.errorAndThrow(
|
|
74
|
+
`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
75
|
+
`Make sure there are no leftovers from previous sessions.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
this._screenStreamingProps = undefined;
|
|
79
|
+
|
|
80
|
+
const deviceInfo = await getDeviceInfo(this.adb, this.log);
|
|
81
|
+
const deviceStreamingProc = await initDeviceStreamingProc(this.adb, this.log, deviceInfo, {
|
|
82
|
+
width,
|
|
83
|
+
height,
|
|
84
|
+
bitRate,
|
|
85
|
+
});
|
|
86
|
+
let gstreamerPipeline;
|
|
87
|
+
try {
|
|
88
|
+
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, this.log, {
|
|
89
|
+
width,
|
|
90
|
+
height,
|
|
91
|
+
quality,
|
|
92
|
+
tcpPort,
|
|
93
|
+
considerRotation,
|
|
94
|
+
logPipelineDetails,
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (deviceStreamingProc.kill(0)) {
|
|
98
|
+
deviceStreamingProc.kill();
|
|
99
|
+
}
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @type {import('node:net').Socket|undefined} */
|
|
104
|
+
let mjpegSocket;
|
|
105
|
+
/** @type {import('node:http').Server|undefined} */
|
|
106
|
+
let mjpegServer;
|
|
107
|
+
try {
|
|
108
|
+
await new B((resolve, reject) => {
|
|
109
|
+
mjpegSocket = net.createConnection(tcpPort, TCP_HOST, () => {
|
|
110
|
+
this.log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
|
|
111
|
+
mjpegServer = http.createServer((req, res) => {
|
|
112
|
+
const remoteAddress = extractRemoteAddress(req);
|
|
113
|
+
const currentPathname = url.parse(String(req.url)).pathname;
|
|
114
|
+
this.log.info(
|
|
115
|
+
`Got an incoming screen broadcasting request from ${remoteAddress} ` +
|
|
116
|
+
`(${req.headers['user-agent'] || 'User Agent unknown'}) at ${currentPathname}`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (pathname && currentPathname !== pathname) {
|
|
120
|
+
this.log.info(
|
|
121
|
+
'Rejecting the broadcast request since it does not match the given pathname',
|
|
122
|
+
);
|
|
123
|
+
res.writeHead(404, {
|
|
124
|
+
Connection: 'close',
|
|
125
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
126
|
+
});
|
|
127
|
+
res.write(`'${currentPathname}' did not match any known endpoints`);
|
|
128
|
+
res.end();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.log.info('Starting MJPEG broadcast');
|
|
133
|
+
res.writeHead(200, {
|
|
134
|
+
'Cache-Control':
|
|
135
|
+
'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
|
|
136
|
+
Pragma: 'no-cache',
|
|
137
|
+
Connection: 'close',
|
|
138
|
+
'Content-Type': `multipart/x-mixed-replace; boundary=${BOUNDARY_STRING}`,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/** @type {import('node:net').Socket} */ (mjpegSocket).pipe(res);
|
|
142
|
+
});
|
|
143
|
+
mjpegServer.on('error', (e) => {
|
|
144
|
+
this.log.warn(e);
|
|
145
|
+
reject(e);
|
|
146
|
+
});
|
|
147
|
+
mjpegServer.on('close', () => {
|
|
148
|
+
this.log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
149
|
+
});
|
|
150
|
+
mjpegServer.on('listening', () => {
|
|
151
|
+
this.log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
mjpegServer.listen(port, host);
|
|
155
|
+
});
|
|
156
|
+
mjpegSocket.on('error', (e) => {
|
|
157
|
+
this.log.error(e);
|
|
158
|
+
reject(e);
|
|
159
|
+
});
|
|
160
|
+
}).timeout(
|
|
161
|
+
STREAMING_STARTUP_TIMEOUT_MS,
|
|
162
|
+
`Cannot connect to the streaming server within ${STREAMING_STARTUP_TIMEOUT_MS}ms`,
|
|
163
|
+
);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
if (deviceStreamingProc.kill(0)) {
|
|
166
|
+
deviceStreamingProc.kill();
|
|
167
|
+
}
|
|
168
|
+
if (gstreamerPipeline.isRunning) {
|
|
169
|
+
await gstreamerPipeline.stop();
|
|
170
|
+
}
|
|
171
|
+
if (mjpegSocket) {
|
|
172
|
+
mjpegSocket.destroy();
|
|
173
|
+
}
|
|
174
|
+
if (mjpegServer && mjpegServer.listening) {
|
|
175
|
+
mjpegServer.close();
|
|
176
|
+
}
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this._screenStreamingProps = {
|
|
181
|
+
deviceStreamingProc,
|
|
182
|
+
gstreamerPipeline,
|
|
183
|
+
mjpegSocket,
|
|
184
|
+
mjpegServer,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @this {import('../driver').AndroidDriver}
|
|
190
|
+
* @returns {Promise<void>}
|
|
191
|
+
*/
|
|
192
|
+
export async function mobileStopScreenStreaming() {
|
|
193
|
+
if (_.isEmpty(this._screenStreamingProps)) {
|
|
194
|
+
if (!_.isUndefined(this._screenStreamingProps)) {
|
|
195
|
+
this.log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const {deviceStreamingProc, gstreamerPipeline, mjpegSocket, mjpegServer} =
|
|
201
|
+
this._screenStreamingProps;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
mjpegSocket.end();
|
|
205
|
+
if (mjpegServer.listening) {
|
|
206
|
+
mjpegServer.close();
|
|
207
|
+
}
|
|
208
|
+
if (deviceStreamingProc.kill(0)) {
|
|
209
|
+
deviceStreamingProc.kill('SIGINT');
|
|
210
|
+
}
|
|
211
|
+
if (gstreamerPipeline.isRunning) {
|
|
212
|
+
try {
|
|
213
|
+
await gstreamerPipeline.stop('SIGINT');
|
|
214
|
+
} catch (e) {
|
|
215
|
+
this.log.warn(e);
|
|
216
|
+
try {
|
|
217
|
+
await gstreamerPipeline.stop('SIGKILL');
|
|
218
|
+
} catch (e1) {
|
|
219
|
+
this.log.error(e1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
this.log.info(`Successfully terminated the screen streaming MJPEG server`);
|
|
224
|
+
} finally {
|
|
225
|
+
this._screenStreamingProps = undefined;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// #region Internal helpers
|
|
230
|
+
|
|
37
231
|
/**
|
|
38
232
|
*
|
|
39
233
|
* @param {string} streamName
|
|
@@ -46,7 +240,7 @@ function createStreamingLogger(streamName, udid) {
|
|
|
46
240
|
_.truncate(udid, {
|
|
47
241
|
length: 8,
|
|
48
242
|
omission: '',
|
|
49
|
-
})
|
|
243
|
+
}),
|
|
50
244
|
);
|
|
51
245
|
}
|
|
52
246
|
|
|
@@ -57,7 +251,7 @@ function createStreamingLogger(streamName, udid) {
|
|
|
57
251
|
async function verifyStreamingRequirements(adb) {
|
|
58
252
|
if (!_.trim(await adb.shell(['which', SCREENRECORD_BINARY]))) {
|
|
59
253
|
throw new Error(
|
|
60
|
-
`The required '${SCREENRECORD_BINARY}' binary is not available on the device under test
|
|
254
|
+
`The required '${SCREENRECORD_BINARY}' binary is not available on the device under test`,
|
|
61
255
|
);
|
|
62
256
|
}
|
|
63
257
|
|
|
@@ -70,10 +264,10 @@ async function verifyStreamingRequirements(adb) {
|
|
|
70
264
|
} catch (e) {
|
|
71
265
|
throw new Error(
|
|
72
266
|
`The '${binaryName}' binary is not available in the PATH on the host system. ` +
|
|
73
|
-
`See ${GST_TUTORIAL_URL} for more details on how to install it
|
|
267
|
+
`See ${GST_TUTORIAL_URL} for more details on how to install it.`,
|
|
74
268
|
);
|
|
75
269
|
}
|
|
76
|
-
})()
|
|
270
|
+
})(),
|
|
77
271
|
);
|
|
78
272
|
}
|
|
79
273
|
await B.all(gstreamerCheckPromises);
|
|
@@ -86,10 +280,10 @@ async function verifyStreamingRequirements(adb) {
|
|
|
86
280
|
if (!_.includes(stdout, modName)) {
|
|
87
281
|
throw new Error(
|
|
88
282
|
`The required GStreamer plugin '${name}' from '${modName}' module is not installed. ` +
|
|
89
|
-
`See ${GST_TUTORIAL_URL} for more details on how to install it
|
|
283
|
+
`See ${GST_TUTORIAL_URL} for more details on how to install it.`,
|
|
90
284
|
);
|
|
91
285
|
}
|
|
92
|
-
})()
|
|
286
|
+
})(),
|
|
93
287
|
);
|
|
94
288
|
}
|
|
95
289
|
await B.all(moduleCheckPromises);
|
|
@@ -118,7 +312,7 @@ async function getDeviceInfo(adb, log) {
|
|
|
118
312
|
log?.debug(output);
|
|
119
313
|
throw new Error(
|
|
120
314
|
`Cannot parse the device ${key} from the adb command output. ` +
|
|
121
|
-
`Check the server log for more details
|
|
315
|
+
`Check the server log for more details.`,
|
|
122
316
|
);
|
|
123
317
|
}
|
|
124
318
|
result[key] = parseInt(match[1], 10);
|
|
@@ -198,7 +392,7 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
|
198
392
|
log.errorAndThrow(
|
|
199
393
|
`Cannot start the screen streaming process. Original error: ${
|
|
200
394
|
/** @type {Error} */ (e).message
|
|
201
|
-
}
|
|
395
|
+
}`,
|
|
202
396
|
);
|
|
203
397
|
} finally {
|
|
204
398
|
deviceStreaming.stderr.removeListener('data', errorsListener);
|
|
@@ -253,7 +447,7 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts)
|
|
|
253
447
|
],
|
|
254
448
|
{
|
|
255
449
|
stdio: [deviceStreamingProc.stdout, 'pipe', 'pipe'],
|
|
256
|
-
}
|
|
450
|
+
},
|
|
257
451
|
);
|
|
258
452
|
gstreamerPipeline.on('exit', (code, signal) => {
|
|
259
453
|
log.debug(`Pipeline streaming process exited with code ${code}, signal ${signal}`);
|
|
@@ -285,14 +479,14 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts)
|
|
|
285
479
|
{
|
|
286
480
|
waitMs: STREAMING_STARTUP_TIMEOUT_MS,
|
|
287
481
|
intervalMs: 300,
|
|
288
|
-
}
|
|
482
|
+
},
|
|
289
483
|
);
|
|
290
484
|
} catch (e) {
|
|
291
485
|
didFail = true;
|
|
292
486
|
log.errorAndThrow(
|
|
293
487
|
`Cannot start the screen streaming pipeline. Original error: ${
|
|
294
488
|
/** @type {Error} */ (e).message
|
|
295
|
-
}
|
|
489
|
+
}`,
|
|
296
490
|
);
|
|
297
491
|
} finally {
|
|
298
492
|
if (!logPipelineDetails || didFail) {
|
|
@@ -316,200 +510,7 @@ function extractRemoteAddress(req) {
|
|
|
316
510
|
);
|
|
317
511
|
}
|
|
318
512
|
|
|
319
|
-
|
|
320
|
-
* @type {import('./mixins').StreamScreenMixin & ThisType<import('../driver').AndroidDriver>}
|
|
321
|
-
* @satisfies {import('@appium/types').ExternalDriver}
|
|
322
|
-
*/
|
|
323
|
-
const StreamScreenMixin = {
|
|
324
|
-
async mobileStartScreenStreaming(options = {}) {
|
|
325
|
-
this.ensureFeatureEnabled(ADB_SCREEN_STREAMING_FEATURE);
|
|
326
|
-
|
|
327
|
-
const {
|
|
328
|
-
width,
|
|
329
|
-
height,
|
|
330
|
-
bitRate,
|
|
331
|
-
host = DEFAULT_HOST,
|
|
332
|
-
port = DEFAULT_PORT,
|
|
333
|
-
pathname,
|
|
334
|
-
tcpPort = DEFAULT_PORT + 1,
|
|
335
|
-
quality = DEFAULT_QUALITY,
|
|
336
|
-
considerRotation = false,
|
|
337
|
-
logPipelineDetails = false,
|
|
338
|
-
} = options;
|
|
339
|
-
if (_.isUndefined(this._screenStreamingProps)) {
|
|
340
|
-
await verifyStreamingRequirements(this.adb);
|
|
341
|
-
}
|
|
342
|
-
if (!_.isEmpty(this._screenStreamingProps)) {
|
|
343
|
-
this.log.info(
|
|
344
|
-
`The screen streaming session is already running. ` +
|
|
345
|
-
`Stop it first in order to start a new one.`
|
|
346
|
-
);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if ((await checkPortStatus(port, host)) === 'open') {
|
|
350
|
-
this.log.info(
|
|
351
|
-
`The port #${port} at ${host} is busy. ` +
|
|
352
|
-
`Assuming the screen streaming is already running`
|
|
353
|
-
);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if ((await checkPortStatus(tcpPort, TCP_HOST)) === 'open') {
|
|
357
|
-
this.log.errorAndThrow(
|
|
358
|
-
`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
359
|
-
`Make sure there are no leftovers from previous sessions.`
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
this._screenStreamingProps = undefined;
|
|
363
|
-
|
|
364
|
-
const deviceInfo = await getDeviceInfo(this.adb, this.log);
|
|
365
|
-
const deviceStreamingProc = await initDeviceStreamingProc(this.adb, this.log, deviceInfo, {
|
|
366
|
-
width,
|
|
367
|
-
height,
|
|
368
|
-
bitRate,
|
|
369
|
-
});
|
|
370
|
-
let gstreamerPipeline;
|
|
371
|
-
try {
|
|
372
|
-
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, this.log, {
|
|
373
|
-
width,
|
|
374
|
-
height,
|
|
375
|
-
quality,
|
|
376
|
-
tcpPort,
|
|
377
|
-
considerRotation,
|
|
378
|
-
logPipelineDetails,
|
|
379
|
-
});
|
|
380
|
-
} catch (e) {
|
|
381
|
-
if (deviceStreamingProc.kill(0)) {
|
|
382
|
-
deviceStreamingProc.kill();
|
|
383
|
-
}
|
|
384
|
-
throw e;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/** @type {import('node:net').Socket|undefined} */
|
|
388
|
-
let mjpegSocket;
|
|
389
|
-
/** @type {import('node:http').Server|undefined} */
|
|
390
|
-
let mjpegServer;
|
|
391
|
-
try {
|
|
392
|
-
await new B((resolve, reject) => {
|
|
393
|
-
mjpegSocket = net.createConnection(tcpPort, TCP_HOST, () => {
|
|
394
|
-
this.log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
|
|
395
|
-
mjpegServer = http.createServer((req, res) => {
|
|
396
|
-
const remoteAddress = extractRemoteAddress(req);
|
|
397
|
-
const currentPathname = url.parse(String(req.url)).pathname;
|
|
398
|
-
this.log.info(
|
|
399
|
-
`Got an incoming screen broadcasting request from ${remoteAddress} ` +
|
|
400
|
-
`(${req.headers['user-agent'] || 'User Agent unknown'}) at ${currentPathname}`
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
if (pathname && currentPathname !== pathname) {
|
|
404
|
-
this.log.info(
|
|
405
|
-
'Rejecting the broadcast request since it does not match the given pathname'
|
|
406
|
-
);
|
|
407
|
-
res.writeHead(404, {
|
|
408
|
-
Connection: 'close',
|
|
409
|
-
'Content-Type': 'text/plain; charset=utf-8',
|
|
410
|
-
});
|
|
411
|
-
res.write(`'${currentPathname}' did not match any known endpoints`);
|
|
412
|
-
res.end();
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
this.log.info('Starting MJPEG broadcast');
|
|
417
|
-
res.writeHead(200, {
|
|
418
|
-
'Cache-Control':
|
|
419
|
-
'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
|
|
420
|
-
Pragma: 'no-cache',
|
|
421
|
-
Connection: 'close',
|
|
422
|
-
'Content-Type': `multipart/x-mixed-replace; boundary=${BOUNDARY_STRING}`,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
/** @type {import('node:net').Socket} */ (mjpegSocket).pipe(res);
|
|
426
|
-
});
|
|
427
|
-
mjpegServer.on('error', (e) => {
|
|
428
|
-
this.log.warn(e);
|
|
429
|
-
reject(e);
|
|
430
|
-
});
|
|
431
|
-
mjpegServer.on('close', () => {
|
|
432
|
-
this.log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
433
|
-
});
|
|
434
|
-
mjpegServer.on('listening', () => {
|
|
435
|
-
this.log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
436
|
-
resolve();
|
|
437
|
-
});
|
|
438
|
-
mjpegServer.listen(port, host);
|
|
439
|
-
});
|
|
440
|
-
mjpegSocket.on('error', (e) => {
|
|
441
|
-
this.log.error(e);
|
|
442
|
-
reject(e);
|
|
443
|
-
});
|
|
444
|
-
}).timeout(
|
|
445
|
-
STREAMING_STARTUP_TIMEOUT_MS,
|
|
446
|
-
`Cannot connect to the streaming server within ${STREAMING_STARTUP_TIMEOUT_MS}ms`
|
|
447
|
-
);
|
|
448
|
-
} catch (e) {
|
|
449
|
-
if (deviceStreamingProc.kill(0)) {
|
|
450
|
-
deviceStreamingProc.kill();
|
|
451
|
-
}
|
|
452
|
-
if (gstreamerPipeline.isRunning) {
|
|
453
|
-
await gstreamerPipeline.stop();
|
|
454
|
-
}
|
|
455
|
-
if (mjpegSocket) {
|
|
456
|
-
mjpegSocket.destroy();
|
|
457
|
-
}
|
|
458
|
-
if (mjpegServer && mjpegServer.listening) {
|
|
459
|
-
mjpegServer.close();
|
|
460
|
-
}
|
|
461
|
-
throw e;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
this._screenStreamingProps = {
|
|
465
|
-
deviceStreamingProc,
|
|
466
|
-
gstreamerPipeline,
|
|
467
|
-
mjpegSocket,
|
|
468
|
-
mjpegServer,
|
|
469
|
-
};
|
|
470
|
-
},
|
|
471
|
-
|
|
472
|
-
async mobileStopScreenStreaming() {
|
|
473
|
-
if (_.isEmpty(this._screenStreamingProps)) {
|
|
474
|
-
if (!_.isUndefined(this._screenStreamingProps)) {
|
|
475
|
-
this.log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
476
|
-
}
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const {deviceStreamingProc, gstreamerPipeline, mjpegSocket, mjpegServer} =
|
|
481
|
-
this._screenStreamingProps;
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
mjpegSocket.end();
|
|
485
|
-
if (mjpegServer.listening) {
|
|
486
|
-
mjpegServer.close();
|
|
487
|
-
}
|
|
488
|
-
if (deviceStreamingProc.kill(0)) {
|
|
489
|
-
deviceStreamingProc.kill('SIGINT');
|
|
490
|
-
}
|
|
491
|
-
if (gstreamerPipeline.isRunning) {
|
|
492
|
-
try {
|
|
493
|
-
await gstreamerPipeline.stop('SIGINT');
|
|
494
|
-
} catch (e) {
|
|
495
|
-
this.log.warn(e);
|
|
496
|
-
try {
|
|
497
|
-
await gstreamerPipeline.stop('SIGKILL');
|
|
498
|
-
} catch (e1) {
|
|
499
|
-
this.log.error(e1);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
this.log.info(`Successfully terminated the screen streaming MJPEG server`);
|
|
504
|
-
} finally {
|
|
505
|
-
this._screenStreamingProps = undefined;
|
|
506
|
-
}
|
|
507
|
-
},
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
mixin(StreamScreenMixin);
|
|
511
|
-
|
|
512
|
-
export default StreamScreenMixin;
|
|
513
|
+
// #endregion
|
|
513
514
|
|
|
514
515
|
/**
|
|
515
516
|
* @typedef {import('appium-adb').ADB} ADB
|