appium-android-driver 5.14.7 → 6.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 +7 -0
- package/build/lib/commands/actions.d.ts +6 -224
- package/build/lib/commands/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js +306 -405
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/alert.d.ts +7 -9
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +24 -18
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +7 -313
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +135 -293
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/context.d.ts +8 -92
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +381 -439
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/element.d.ts +8 -35
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +153 -136
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/emu-console.d.ts +6 -48
- package/build/lib/commands/emu-console.d.ts.map +1 -1
- package/build/lib/commands/emu-console.js +19 -34
- package/build/lib/commands/emu-console.js.map +1 -1
- package/build/lib/commands/execute.d.ts +6 -5
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +77 -66
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts +7 -128
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +183 -219
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts +8 -12
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +19 -23
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +9 -132
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +281 -312
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/ime.d.ts +7 -10
- package/build/lib/commands/ime.d.ts.map +1 -1
- package/build/lib/commands/ime.js +47 -35
- package/build/lib/commands/ime.js.map +1 -1
- package/build/lib/commands/index.d.ts +27 -2
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/commands/index.js +41 -19
- package/build/lib/commands/index.js.map +1 -1
- package/build/lib/commands/intent.d.ts +7 -417
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +104 -216
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +6 -5
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +16 -8
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/log.d.ts +7 -44
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +146 -108
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts +7 -143
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +113 -140
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/mixins.d.ts +740 -0
- package/build/lib/commands/mixins.d.ts.map +1 -0
- package/build/lib/commands/mixins.js +19 -0
- package/build/lib/commands/mixins.js.map +1 -0
- package/build/lib/commands/network.d.ts +7 -138
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +212 -254
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/performance.d.ts +24 -70
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +144 -100
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +8 -92
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +75 -87
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +7 -193
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +151 -182
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/shell.d.ts +7 -7
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +40 -33
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts +9 -103
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +261 -218
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts +22 -90
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +76 -74
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/touch.d.ts +10 -29
- package/build/lib/commands/touch.d.ts.map +1 -1
- package/build/lib/commands/touch.js +301 -285
- package/build/lib/commands/touch.js.map +1 -1
- package/build/lib/commands/types.d.ts +978 -0
- package/build/lib/commands/types.d.ts.map +1 -0
- package/build/lib/commands/types.js +3 -0
- package/build/lib/commands/types.js.map +1 -0
- package/build/lib/constraints.d.ts +291 -0
- package/build/lib/constraints.d.ts.map +1 -0
- package/build/lib/{desired-caps.js → constraints.js} +103 -102
- package/build/lib/constraints.js.map +1 -0
- package/build/lib/driver.d.ts +68 -37
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +123 -80
- package/build/lib/driver.js.map +1 -1
- package/build/lib/helpers/android.d.ts +164 -0
- package/build/lib/helpers/android.d.ts.map +1 -0
- package/build/lib/helpers/android.js +819 -0
- package/build/lib/helpers/android.js.map +1 -0
- package/build/lib/helpers/index.d.ts +7 -0
- package/build/lib/helpers/index.d.ts.map +1 -0
- package/build/lib/helpers/index.js +29 -0
- package/build/lib/helpers/index.js.map +1 -0
- package/build/lib/helpers/types.d.ts +121 -0
- package/build/lib/helpers/types.d.ts.map +1 -0
- package/build/lib/helpers/types.js +3 -0
- package/build/lib/helpers/types.js.map +1 -0
- package/build/lib/helpers/unlock.d.ts +32 -0
- package/build/lib/helpers/unlock.d.ts.map +1 -0
- package/build/lib/helpers/unlock.js +273 -0
- package/build/lib/helpers/unlock.js.map +1 -0
- package/build/lib/helpers/webview.d.ts +74 -0
- package/build/lib/helpers/webview.d.ts.map +1 -0
- package/build/lib/helpers/webview.js +421 -0
- package/build/lib/helpers/webview.js.map +1 -0
- package/build/lib/index.d.ts +9 -0
- package/build/lib/index.d.ts.map +1 -0
- package/build/lib/index.js +37 -0
- package/build/lib/index.js.map +1 -0
- package/build/lib/method-map.d.ts +0 -8
- package/build/lib/method-map.d.ts.map +1 -1
- package/build/lib/method-map.js +63 -74
- package/build/lib/method-map.js.map +1 -1
- package/build/lib/stubs.d.ts +0 -1
- package/build/lib/stubs.d.ts.map +1 -1
- package/build/lib/stubs.js +1 -0
- package/build/lib/stubs.js.map +1 -1
- package/build/lib/utils.d.ts +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/lib/commands/actions.js +351 -464
- package/lib/commands/alert.js +27 -17
- package/lib/commands/app-management.js +156 -314
- package/lib/commands/context.js +457 -441
- package/lib/commands/element.js +201 -157
- package/lib/commands/emu-console.js +25 -45
- package/lib/commands/execute.js +106 -90
- package/lib/commands/file-actions.js +222 -240
- package/lib/commands/find.ts +103 -0
- package/lib/commands/general.js +327 -339
- package/lib/commands/ime.js +50 -34
- package/lib/commands/{index.js → index.ts} +20 -24
- package/lib/commands/intent.js +108 -249
- package/lib/commands/keyboard.js +20 -8
- package/lib/commands/log.js +172 -116
- package/lib/commands/media-projection.js +134 -161
- package/lib/commands/mixins.ts +966 -0
- package/lib/commands/network.js +252 -281
- package/lib/commands/performance.js +203 -132
- package/lib/commands/permissions.js +108 -109
- package/lib/commands/recordscreen.js +212 -209
- package/lib/commands/shell.js +51 -40
- package/lib/commands/streamscreen.js +355 -289
- package/lib/commands/system-bars.js +92 -83
- package/lib/commands/touch.js +357 -294
- package/lib/commands/types.ts +1097 -0
- package/lib/{desired-caps.js → constraints.ts} +106 -103
- package/lib/{driver.js → driver.ts} +278 -132
- package/lib/helpers/android.ts +1143 -0
- package/lib/helpers/index.ts +6 -0
- package/lib/helpers/types.ts +134 -0
- package/lib/helpers/unlock.ts +329 -0
- package/lib/helpers/webview.ts +582 -0
- package/lib/index.ts +18 -0
- package/lib/method-map.js +87 -98
- package/lib/stubs.ts +0 -1
- package/package.json +26 -19
- package/build/index.js +0 -51
- package/build/lib/android-helpers.d.ts +0 -136
- package/build/lib/android-helpers.d.ts.map +0 -1
- package/build/lib/android-helpers.js +0 -855
- package/build/lib/android-helpers.js.map +0 -1
- package/build/lib/commands/coverage.d.ts +0 -5
- package/build/lib/commands/coverage.d.ts.map +0 -1
- package/build/lib/commands/coverage.js +0 -19
- package/build/lib/commands/coverage.js.map +0 -1
- package/build/lib/desired-caps.d.ts +0 -353
- package/build/lib/desired-caps.d.ts.map +0 -1
- package/build/lib/desired-caps.js.map +0 -1
- package/build/lib/unlock-helpers.d.ts +0 -38
- package/build/lib/unlock-helpers.d.ts.map +0 -1
- package/build/lib/unlock-helpers.js +0 -266
- package/build/lib/unlock-helpers.js.map +0 -1
- package/build/lib/webview-helpers.d.ts +0 -224
- package/build/lib/webview-helpers.d.ts.map +0 -1
- package/build/lib/webview-helpers.js +0 -528
- package/build/lib/webview-helpers.js.map +0 -1
- package/index.js +0 -24
- package/lib/android-helpers.js +0 -983
- package/lib/commands/coverage.js +0 -18
- package/lib/commands/find.js +0 -82
- package/lib/unlock-helpers.js +0 -278
- package/lib/webview-helpers.js +0 -602
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// @ts-check
|
|
2
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
5
|
};
|
|
5
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
7
7
|
const support_1 = require("@appium/support");
|
|
8
|
-
const teen_process_1 = require("teen_process");
|
|
9
|
-
const portscanner_1 = require("portscanner");
|
|
10
|
-
const http_1 = __importDefault(require("http"));
|
|
11
|
-
const net_1 = __importDefault(require("net"));
|
|
12
|
-
const bluebird_1 = __importDefault(require("bluebird"));
|
|
13
8
|
const asyncbox_1 = require("asyncbox");
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
9
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
|
10
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
13
|
+
const node_net_1 = __importDefault(require("node:net"));
|
|
14
|
+
const node_url_1 = __importDefault(require("node:url"));
|
|
15
|
+
const portscanner_1 = require("portscanner");
|
|
16
|
+
const teen_process_1 = require("teen_process");
|
|
17
|
+
const mixins_1 = require("./mixins");
|
|
17
18
|
const RECORDING_INTERVAL_SEC = 5;
|
|
18
19
|
const STREAMING_STARTUP_TIMEOUT_MS = 5000;
|
|
19
20
|
const GSTREAMER_BINARY = `gst-launch-1.0${support_1.system.isWindows() ? '.exe' : ''}`;
|
|
@@ -34,12 +35,23 @@ const DEFAULT_QUALITY = 70;
|
|
|
34
35
|
const DEFAULT_BITRATE = 4000000; // 4 Mbps
|
|
35
36
|
const BOUNDARY_STRING = '--2ae9746887f170b8cf7c271047ce314c';
|
|
36
37
|
const ADB_SCREEN_STREAMING_FEATURE = 'adb_screen_streaming';
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {string} streamName
|
|
41
|
+
* @param {string} udid
|
|
42
|
+
* @returns {AppiumLogger}
|
|
43
|
+
*/
|
|
37
44
|
function createStreamingLogger(streamName, udid) {
|
|
38
|
-
return support_1.logger.getLogger(`${streamName}@` +
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
return support_1.logger.getLogger(`${streamName}@` +
|
|
46
|
+
lodash_1.default.truncate(udid, {
|
|
47
|
+
length: 8,
|
|
48
|
+
omission: '',
|
|
49
|
+
}));
|
|
42
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* @param {ADB} adb
|
|
54
|
+
*/
|
|
43
55
|
async function verifyStreamingRequirements(adb) {
|
|
44
56
|
if (!lodash_1.default.trim(await adb.shell(['which', SCREENRECORD_BINARY]))) {
|
|
45
57
|
throw new Error(`The required '${SCREENRECORD_BINARY}' binary is not available on the device under test`);
|
|
@@ -69,14 +81,23 @@ async function verifyStreamingRequirements(adb) {
|
|
|
69
81
|
}
|
|
70
82
|
await bluebird_1.default.all(moduleCheckPromises);
|
|
71
83
|
}
|
|
72
|
-
|
|
84
|
+
const deviceInfoRegexes = /** @type {const} */ ([
|
|
85
|
+
['width', /\bdeviceWidth=(\d+)/],
|
|
86
|
+
['height', /\bdeviceHeight=(\d+)/],
|
|
87
|
+
['fps', /\bfps=(\d+)/],
|
|
88
|
+
]);
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param {ADB} adb
|
|
92
|
+
* @param {AppiumLogger} [log]
|
|
93
|
+
*/
|
|
94
|
+
async function getDeviceInfo(adb, log) {
|
|
73
95
|
const output = await adb.shell(['dumpsys', 'display']);
|
|
96
|
+
/**
|
|
97
|
+
* @type {DeviceInfo}
|
|
98
|
+
*/
|
|
74
99
|
const result = {};
|
|
75
|
-
for (const [key, pattern] of
|
|
76
|
-
['width', /\bdeviceWidth=(\d+)/],
|
|
77
|
-
['height', /\bdeviceHeight=(\d+)/],
|
|
78
|
-
['fps', /\bfps=(\d+)/],
|
|
79
|
-
]) {
|
|
100
|
+
for (const [key, pattern] of deviceInfoRegexes) {
|
|
80
101
|
const match = pattern.exec(output);
|
|
81
102
|
if (!match) {
|
|
82
103
|
log?.debug(output);
|
|
@@ -85,14 +106,22 @@ async function getDeviceInfo(adb, log = null) {
|
|
|
85
106
|
}
|
|
86
107
|
result[key] = parseInt(match[1], 10);
|
|
87
108
|
}
|
|
88
|
-
result.udid = adb.curDeviceId;
|
|
109
|
+
result.udid = String(adb.curDeviceId);
|
|
89
110
|
return result;
|
|
90
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param {ADB} adb
|
|
115
|
+
* @param {AppiumLogger} log
|
|
116
|
+
* @param {DeviceInfo} deviceInfo
|
|
117
|
+
* @param {{width?: string|number, height?: string|number, bitRate?: string|number}} opts
|
|
118
|
+
* @returns
|
|
119
|
+
*/
|
|
91
120
|
async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
92
|
-
const { width, height, bitRate
|
|
93
|
-
const adjustedWidth =
|
|
94
|
-
const adjustedHeight =
|
|
95
|
-
const adjustedBitrate = parseInt(bitRate, 10)
|
|
121
|
+
const { width, height, bitRate } = opts;
|
|
122
|
+
const adjustedWidth = lodash_1.default.isUndefined(width) ? deviceInfo.width : parseInt(String(width), 10);
|
|
123
|
+
const adjustedHeight = lodash_1.default.isUndefined(height) ? deviceInfo.height : parseInt(String(height), 10);
|
|
124
|
+
const adjustedBitrate = lodash_1.default.isUndefined(bitRate) ? DEFAULT_BITRATE : parseInt(String(bitRate), 10);
|
|
96
125
|
let screenRecordCmd = SCREENRECORD_BINARY +
|
|
97
126
|
` --output-format=h264` +
|
|
98
127
|
// 5 seconds is fine to detect rotation changes
|
|
@@ -110,12 +139,16 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
|
110
139
|
// for screenrecord is always limited
|
|
111
140
|
`while true; do ${screenRecordCmd} -; done`,
|
|
112
141
|
];
|
|
113
|
-
const deviceStreaming = (0,
|
|
142
|
+
const deviceStreaming = (0, node_child_process_1.spawn)(adb.executable.path, adbArgs);
|
|
114
143
|
deviceStreaming.on('exit', (code, signal) => {
|
|
115
144
|
log.debug(`Device streaming process exited with code ${code}, signal ${signal}`);
|
|
116
145
|
});
|
|
117
146
|
let isStarted = false;
|
|
118
147
|
const deviceStreamingLogger = createStreamingLogger(SCREENRECORD_BINARY, deviceInfo.udid);
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {Buffer|string} chunk
|
|
151
|
+
*/
|
|
119
152
|
const errorsListener = (chunk) => {
|
|
120
153
|
const stderr = chunk.toString();
|
|
121
154
|
if (lodash_1.default.trim(stderr)) {
|
|
@@ -123,6 +156,10 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
|
123
156
|
}
|
|
124
157
|
};
|
|
125
158
|
deviceStreaming.stderr.on('data', errorsListener);
|
|
159
|
+
/**
|
|
160
|
+
*
|
|
161
|
+
* @param {Buffer|string} chunk
|
|
162
|
+
*/
|
|
126
163
|
const startupListener = (chunk) => {
|
|
127
164
|
if (!isStarted) {
|
|
128
165
|
isStarted = !lodash_1.default.isEmpty(chunk);
|
|
@@ -137,7 +174,8 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
|
137
174
|
});
|
|
138
175
|
}
|
|
139
176
|
catch (e) {
|
|
140
|
-
log.errorAndThrow(`Cannot start the screen streaming process. Original error: ${
|
|
177
|
+
log.errorAndThrow(`Cannot start the screen streaming process. Original error: ${
|
|
178
|
+
/** @type {Error} */ (e).message}`);
|
|
141
179
|
}
|
|
142
180
|
finally {
|
|
143
181
|
deviceStreaming.stderr.removeListener('data', errorsListener);
|
|
@@ -145,32 +183,59 @@ async function initDeviceStreamingProc(adb, log, deviceInfo, opts = {}) {
|
|
|
145
183
|
}
|
|
146
184
|
return deviceStreaming;
|
|
147
185
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
* @param {import('node:child_process').ChildProcess} deviceStreamingProc
|
|
189
|
+
* @param {DeviceInfo} deviceInfo
|
|
190
|
+
* @param {AppiumLogger} log
|
|
191
|
+
* @param {import('./types').InitGStreamerPipelineOpts} opts
|
|
192
|
+
*/
|
|
193
|
+
async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts) {
|
|
194
|
+
const { width, height, quality, tcpPort, considerRotation, logPipelineDetails } = opts;
|
|
195
|
+
const adjustedWidth = parseInt(String(width), 10) || deviceInfo.width;
|
|
196
|
+
const adjustedHeight = parseInt(String(height), 10) || deviceInfo.height;
|
|
152
197
|
const gstreamerPipeline = new teen_process_1.SubProcess(GSTREAMER_BINARY, [
|
|
153
198
|
'-v',
|
|
154
|
-
'fdsrc',
|
|
155
|
-
'
|
|
199
|
+
'fdsrc',
|
|
200
|
+
'fd=0',
|
|
201
|
+
'!',
|
|
202
|
+
'video/x-h264,' +
|
|
156
203
|
`width=${considerRotation ? Math.max(adjustedWidth, adjustedHeight) : adjustedWidth},` +
|
|
157
204
|
`height=${considerRotation ? Math.max(adjustedWidth, adjustedHeight) : adjustedHeight},` +
|
|
158
205
|
`framerate=${deviceInfo.fps}/1,` +
|
|
159
206
|
'byte-stream=true',
|
|
160
|
-
'!',
|
|
161
|
-
'
|
|
162
|
-
'!',
|
|
163
|
-
'
|
|
164
|
-
'
|
|
165
|
-
'!',
|
|
166
|
-
'
|
|
207
|
+
'!',
|
|
208
|
+
'h264parse',
|
|
209
|
+
'!',
|
|
210
|
+
'queue',
|
|
211
|
+
'leaky=downstream',
|
|
212
|
+
'!',
|
|
213
|
+
'avdec_h264',
|
|
214
|
+
'!',
|
|
215
|
+
'queue',
|
|
216
|
+
'leaky=downstream',
|
|
217
|
+
'!',
|
|
218
|
+
'jpegenc',
|
|
219
|
+
`quality=${quality}`,
|
|
220
|
+
'!',
|
|
221
|
+
'multipartmux',
|
|
222
|
+
`boundary=${BOUNDARY_STRING}`,
|
|
223
|
+
'!',
|
|
224
|
+
'tcpserversink',
|
|
225
|
+
`host=${TCP_HOST}`,
|
|
226
|
+
`port=${tcpPort}`,
|
|
167
227
|
], {
|
|
168
|
-
stdio: [deviceStreamingProc.stdout, 'pipe', 'pipe']
|
|
228
|
+
stdio: [deviceStreamingProc.stdout, 'pipe', 'pipe'],
|
|
169
229
|
});
|
|
170
230
|
gstreamerPipeline.on('exit', (code, signal) => {
|
|
171
231
|
log.debug(`Pipeline streaming process exited with code ${code}, signal ${signal}`);
|
|
172
232
|
});
|
|
173
233
|
const gstreamerLogger = createStreamingLogger('gst', deviceInfo.udid);
|
|
234
|
+
/**
|
|
235
|
+
*
|
|
236
|
+
* @param {string} stdout
|
|
237
|
+
* @param {string} stderr
|
|
238
|
+
*/
|
|
174
239
|
const gstOutputListener = (stdout, stderr) => {
|
|
175
240
|
if (lodash_1.default.trim(stderr || stdout)) {
|
|
176
241
|
gstreamerLogger.debug(stderr || stdout);
|
|
@@ -195,7 +260,8 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts
|
|
|
195
260
|
}
|
|
196
261
|
catch (e) {
|
|
197
262
|
didFail = true;
|
|
198
|
-
log.errorAndThrow(`Cannot start the screen streaming pipeline. Original error: ${
|
|
263
|
+
log.errorAndThrow(`Cannot start the screen streaming pipeline. Original error: ${
|
|
264
|
+
/** @type {Error} */ (e).message}`);
|
|
199
265
|
}
|
|
200
266
|
finally {
|
|
201
267
|
if (!logPipelineDetails || didFail) {
|
|
@@ -204,205 +270,182 @@ async function initGstreamerPipeline(deviceStreamingProc, deviceInfo, log, opts
|
|
|
204
270
|
}
|
|
205
271
|
return gstreamerPipeline;
|
|
206
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* @param {import('node:http').IncomingMessage} req
|
|
275
|
+
* @privateRemarks This may need to be future-proofed, as `IncomingMessage.connection` is deprecated and its `socket` prop is likely private
|
|
276
|
+
*/
|
|
207
277
|
function extractRemoteAddress(req) {
|
|
208
|
-
return req.headers['x-forwarded-for']
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
return (req.headers['x-forwarded-for'] ||
|
|
279
|
+
req.socket.remoteAddress ||
|
|
280
|
+
req.connection.remoteAddress ||
|
|
281
|
+
// @ts-expect-error socket may be a private API??
|
|
282
|
+
req.connection.socket.remoteAddress);
|
|
212
283
|
}
|
|
213
284
|
/**
|
|
214
|
-
* @
|
|
215
|
-
*
|
|
216
|
-
* @property {?number} width - The scaled width of the device's screen. If unset then the script will assign it
|
|
217
|
-
* to the actual screen width measured in pixels.
|
|
218
|
-
* @property {?number} height - The scaled height of the device's screen. If unset then the script will assign it
|
|
219
|
-
* to the actual screen height measured in pixels.
|
|
220
|
-
* @property {?number} bitRate - The video bit rate for the video, in bits per second.
|
|
221
|
-
* The default value is 4000000 (4 Mb/s). You can increase the bit rate to improve video quality,
|
|
222
|
-
* but doing so results in larger movie files.
|
|
223
|
-
* @property {?string} host [127.0.0.1] - The IP address/host name to start the MJPEG server on.
|
|
224
|
-
* You can set it to `0.0.0.0` to trigger the broadcast on all available network interfaces.
|
|
225
|
-
* @property {?string} pathname - The HTTP request path the MJPEG server should be available on.
|
|
226
|
-
* If unset then any pathname on the given `host`/`port` combination will work. Note that the value
|
|
227
|
-
* should always start with a single slash: `/`
|
|
228
|
-
* @property {?number} tcpPort [8094] - The port number to start the internal TCP MJPEG broadcast on.
|
|
229
|
-
* This type of broadcast always starts on the loopback interface (`127.0.0.1`).
|
|
230
|
-
* @property {?number} port [8093] - The port number to start the MJPEG server on.
|
|
231
|
-
* @property {?number} quality [70] - The quality value for the streamed JPEG images.
|
|
232
|
-
* This number should be in range [1, 100], where 100 is the best quality.
|
|
233
|
-
* @property {?boolean} considerRotation [false] - If set to `true` then GStreamer pipeline will
|
|
234
|
-
* increase the dimensions of the resulting images to properly fit images in both landscape and
|
|
235
|
-
* portrait orientations. Set it to `true` if the device rotation is not going to be the same during the
|
|
236
|
-
* broadcasting session.
|
|
237
|
-
* @property {?boolean} logPipelineDetails [false] - Whether to log GStreamer pipeline events into
|
|
238
|
-
* the standard log output. Might be useful for debugging purposes.
|
|
285
|
+
* @type {import('./mixins').StreamScreenMixin & ThisType<import('../driver').AndroidDriver>}
|
|
286
|
+
* @satisfies {import('@appium/types').ExternalDriver}
|
|
239
287
|
*/
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
`Assuming the screen streaming is already running`);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if ((await (0, portscanner_1.checkPortStatus)(tcpPort, TCP_HOST)) === 'open') {
|
|
269
|
-
this.log.errorAndThrow(`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
270
|
-
`Make sure there are no leftovers from previous sessions.`);
|
|
271
|
-
}
|
|
272
|
-
this._screenStreamingProps = null;
|
|
273
|
-
const deviceInfo = await getDeviceInfo(this.adb, this.log);
|
|
274
|
-
const deviceStreamingProc = await initDeviceStreamingProc(this.adb, this.log, deviceInfo, {
|
|
275
|
-
width,
|
|
276
|
-
height,
|
|
277
|
-
bitRate,
|
|
278
|
-
});
|
|
279
|
-
let gstreamerPipeline;
|
|
280
|
-
try {
|
|
281
|
-
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, this.log, {
|
|
288
|
+
const StreamScreenMixin = {
|
|
289
|
+
async mobileStartScreenStreaming(options = {}) {
|
|
290
|
+
this.ensureFeatureEnabled(ADB_SCREEN_STREAMING_FEATURE);
|
|
291
|
+
const { width, height, bitRate, host = DEFAULT_HOST, port = DEFAULT_PORT, pathname, tcpPort = DEFAULT_PORT + 1, quality = DEFAULT_QUALITY, considerRotation = false, logPipelineDetails = false, } = options;
|
|
292
|
+
const adb = /** @type {ADB} */ (this.adb);
|
|
293
|
+
if (lodash_1.default.isUndefined(this._screenStreamingProps)) {
|
|
294
|
+
await verifyStreamingRequirements(adb);
|
|
295
|
+
}
|
|
296
|
+
if (!lodash_1.default.isEmpty(this._screenStreamingProps)) {
|
|
297
|
+
this.log.info(`The screen streaming session is already running. ` +
|
|
298
|
+
`Stop it first in order to start a new one.`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if ((await (0, portscanner_1.checkPortStatus)(port, host)) === 'open') {
|
|
302
|
+
this.log.info(`The port #${port} at ${host} is busy. ` +
|
|
303
|
+
`Assuming the screen streaming is already running`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if ((await (0, portscanner_1.checkPortStatus)(tcpPort, TCP_HOST)) === 'open') {
|
|
307
|
+
this.log.errorAndThrow(`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
308
|
+
`Make sure there are no leftovers from previous sessions.`);
|
|
309
|
+
}
|
|
310
|
+
this._screenStreamingProps = undefined;
|
|
311
|
+
const deviceInfo = await getDeviceInfo(adb, this.log);
|
|
312
|
+
const deviceStreamingProc = await initDeviceStreamingProc(adb, this.log, deviceInfo, {
|
|
282
313
|
width,
|
|
283
314
|
height,
|
|
284
|
-
|
|
285
|
-
tcpPort,
|
|
286
|
-
considerRotation,
|
|
287
|
-
logPipelineDetails,
|
|
315
|
+
bitRate,
|
|
288
316
|
});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
317
|
+
let gstreamerPipeline;
|
|
318
|
+
try {
|
|
319
|
+
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, this.log, {
|
|
320
|
+
width,
|
|
321
|
+
height,
|
|
322
|
+
quality,
|
|
323
|
+
tcpPort,
|
|
324
|
+
considerRotation,
|
|
325
|
+
logPipelineDetails,
|
|
326
|
+
});
|
|
293
327
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
328
|
+
catch (e) {
|
|
329
|
+
if (deviceStreamingProc.kill(0)) {
|
|
330
|
+
deviceStreamingProc.kill();
|
|
331
|
+
}
|
|
332
|
+
throw e;
|
|
333
|
+
}
|
|
334
|
+
/** @type {import('node:net').Socket|undefined} */
|
|
335
|
+
let mjpegSocket;
|
|
336
|
+
/** @type {import('node:http').Server|undefined} */
|
|
337
|
+
let mjpegServer;
|
|
338
|
+
try {
|
|
339
|
+
await new bluebird_1.default((resolve, reject) => {
|
|
340
|
+
mjpegSocket = node_net_1.default.createConnection(tcpPort, TCP_HOST, () => {
|
|
341
|
+
this.log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
|
|
342
|
+
mjpegServer = node_http_1.default.createServer((req, res) => {
|
|
343
|
+
const remoteAddress = extractRemoteAddress(req);
|
|
344
|
+
const currentPathname = node_url_1.default.parse(String(req.url)).pathname;
|
|
345
|
+
this.log.info(`Got an incoming screen broadcasting request from ${remoteAddress} ` +
|
|
346
|
+
`(${req.headers['user-agent'] || 'User Agent unknown'}) at ${currentPathname}`);
|
|
347
|
+
if (pathname && currentPathname !== pathname) {
|
|
348
|
+
this.log.info('Rejecting the broadcast request since it does not match the given pathname');
|
|
349
|
+
res.writeHead(404, {
|
|
350
|
+
Connection: 'close',
|
|
351
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
352
|
+
});
|
|
353
|
+
res.write(`'${currentPathname}' did not match any known endpoints`);
|
|
354
|
+
res.end();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.log.info('Starting MJPEG broadcast');
|
|
358
|
+
res.writeHead(200, {
|
|
359
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
|
|
360
|
+
Pragma: 'no-cache',
|
|
310
361
|
Connection: 'close',
|
|
311
|
-
'Content-Type':
|
|
362
|
+
'Content-Type': `multipart/x-mixed-replace; boundary=${BOUNDARY_STRING}`,
|
|
312
363
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Connection: 'close',
|
|
322
|
-
'Content-Type': `multipart/x-mixed-replace; boundary=${BOUNDARY_STRING}`
|
|
364
|
+
/** @type {import('node:net').Socket} */ (mjpegSocket).pipe(res);
|
|
365
|
+
});
|
|
366
|
+
mjpegServer.on('error', (e) => {
|
|
367
|
+
this.log.warn(e);
|
|
368
|
+
reject(e);
|
|
369
|
+
});
|
|
370
|
+
mjpegServer.on('close', () => {
|
|
371
|
+
this.log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
323
372
|
});
|
|
324
|
-
|
|
373
|
+
mjpegServer.on('listening', () => {
|
|
374
|
+
this.log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
375
|
+
resolve();
|
|
376
|
+
});
|
|
377
|
+
mjpegServer.listen(port, host);
|
|
325
378
|
});
|
|
326
|
-
|
|
327
|
-
this.log.
|
|
379
|
+
mjpegSocket.on('error', (e) => {
|
|
380
|
+
this.log.error(e);
|
|
328
381
|
reject(e);
|
|
329
382
|
});
|
|
330
|
-
|
|
331
|
-
this.log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
332
|
-
});
|
|
333
|
-
mjpegServer.on('listening', () => {
|
|
334
|
-
this.log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
335
|
-
resolve();
|
|
336
|
-
});
|
|
337
|
-
mjpegServer.listen(port, host);
|
|
338
|
-
});
|
|
339
|
-
mjpegSocket.on('error', (e) => {
|
|
340
|
-
this.log.error(e);
|
|
341
|
-
reject(e);
|
|
342
|
-
});
|
|
343
|
-
}).timeout(STREAMING_STARTUP_TIMEOUT_MS, `Cannot connect to the streaming server within ${STREAMING_STARTUP_TIMEOUT_MS}ms`);
|
|
344
|
-
}
|
|
345
|
-
catch (e) {
|
|
346
|
-
if (deviceStreamingProc.kill(0)) {
|
|
347
|
-
deviceStreamingProc.kill();
|
|
348
|
-
}
|
|
349
|
-
if (gstreamerPipeline.isRunning) {
|
|
350
|
-
await gstreamerPipeline.stop();
|
|
351
|
-
}
|
|
352
|
-
if (mjpegSocket) {
|
|
353
|
-
mjpegSocket.destroy();
|
|
354
|
-
}
|
|
355
|
-
if (mjpegServer && mjpegServer.listening) {
|
|
356
|
-
mjpegServer.close();
|
|
357
|
-
}
|
|
358
|
-
throw e;
|
|
359
|
-
}
|
|
360
|
-
this._screenStreamingProps = {
|
|
361
|
-
deviceStreamingProc,
|
|
362
|
-
gstreamerPipeline,
|
|
363
|
-
mjpegSocket,
|
|
364
|
-
mjpegServer,
|
|
365
|
-
};
|
|
366
|
-
};
|
|
367
|
-
/**
|
|
368
|
-
* Stop screen streaming.
|
|
369
|
-
* If no screen streaming server has been started then nothing is done.
|
|
370
|
-
*/
|
|
371
|
-
commands.mobileStopScreenStreaming = async function mobileStopScreenStreaming( /* options = {} */) {
|
|
372
|
-
if (lodash_1.default.isEmpty(this._screenStreamingProps)) {
|
|
373
|
-
if (!lodash_1.default.isUndefined(this._screenStreamingProps)) {
|
|
374
|
-
this.log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
383
|
+
}).timeout(STREAMING_STARTUP_TIMEOUT_MS, `Cannot connect to the streaming server within ${STREAMING_STARTUP_TIMEOUT_MS}ms`);
|
|
375
384
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
catch (e) {
|
|
386
|
+
if (deviceStreamingProc.kill(0)) {
|
|
387
|
+
deviceStreamingProc.kill();
|
|
388
|
+
}
|
|
389
|
+
if (gstreamerPipeline.isRunning) {
|
|
390
|
+
await gstreamerPipeline.stop();
|
|
391
|
+
}
|
|
392
|
+
if (mjpegSocket) {
|
|
393
|
+
mjpegSocket.destroy();
|
|
394
|
+
}
|
|
395
|
+
if (mjpegServer && mjpegServer.listening) {
|
|
396
|
+
mjpegServer.close();
|
|
397
|
+
}
|
|
398
|
+
throw e;
|
|
383
399
|
}
|
|
384
|
-
|
|
385
|
-
deviceStreamingProc
|
|
400
|
+
this._screenStreamingProps = {
|
|
401
|
+
deviceStreamingProc,
|
|
402
|
+
gstreamerPipeline,
|
|
403
|
+
mjpegSocket,
|
|
404
|
+
mjpegServer,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
async mobileStopScreenStreaming() {
|
|
408
|
+
if (lodash_1.default.isEmpty(this._screenStreamingProps)) {
|
|
409
|
+
if (!lodash_1.default.isUndefined(this._screenStreamingProps)) {
|
|
410
|
+
this.log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
386
413
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
414
|
+
const { deviceStreamingProc, gstreamerPipeline, mjpegSocket, mjpegServer } = this._screenStreamingProps;
|
|
415
|
+
try {
|
|
416
|
+
mjpegSocket.end();
|
|
417
|
+
if (mjpegServer.listening) {
|
|
418
|
+
mjpegServer.close();
|
|
390
419
|
}
|
|
391
|
-
|
|
392
|
-
|
|
420
|
+
if (deviceStreamingProc.kill(0)) {
|
|
421
|
+
deviceStreamingProc.kill('SIGINT');
|
|
422
|
+
}
|
|
423
|
+
if (gstreamerPipeline.isRunning) {
|
|
393
424
|
try {
|
|
394
|
-
await gstreamerPipeline.stop('
|
|
425
|
+
await gstreamerPipeline.stop('SIGINT');
|
|
395
426
|
}
|
|
396
|
-
catch (
|
|
397
|
-
this.log.
|
|
427
|
+
catch (e) {
|
|
428
|
+
this.log.warn(e);
|
|
429
|
+
try {
|
|
430
|
+
await gstreamerPipeline.stop('SIGKILL');
|
|
431
|
+
}
|
|
432
|
+
catch (e1) {
|
|
433
|
+
this.log.error(e1);
|
|
434
|
+
}
|
|
398
435
|
}
|
|
399
436
|
}
|
|
437
|
+
this.log.info(`Successfully terminated the screen streaming MJPEG server`);
|
|
400
438
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
439
|
+
finally {
|
|
440
|
+
this._screenStreamingProps = undefined;
|
|
441
|
+
}
|
|
442
|
+
},
|
|
406
443
|
};
|
|
407
|
-
|
|
444
|
+
(0, mixins_1.mixin)(StreamScreenMixin);
|
|
445
|
+
exports.default = StreamScreenMixin;
|
|
446
|
+
/**
|
|
447
|
+
* @typedef {import('appium-adb').ADB} ADB
|
|
448
|
+
* @typedef {import('@appium/types').AppiumLogger} AppiumLogger
|
|
449
|
+
* @typedef {import('./types').DeviceInfo} DeviceInfo
|
|
450
|
+
*/
|
|
408
451
|
//# sourceMappingURL=streamscreen.js.map
|