UNPKG

io.appium.settings

Version:
164 lines 6.08 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeMediaProjectionRecorder = makeMediaProjectionRecorder; exports.adjustMediaProjectionServicePermissions = adjustMediaProjectionServicePermissions; const asyncbox_1 = require("asyncbox"); const bluebird_1 = __importDefault(require("bluebird")); const lodash_1 = __importDefault(require("lodash")); const promises_1 = __importDefault(require("fs/promises")); const node_path_1 = __importDefault(require("node:path")); const constants_1 = require("../constants"); const RECORDING_STARTUP_TIMEOUT_MS = 3 * 1000; const RECORDING_STOP_TIMEOUT_MS = 3 * 1000; const RECORDINGS_ROOT = `/storage/emulated/0/Android/data/${constants_1.SETTINGS_HELPER_ID}/files`; /** * @typedef {Object} StartMediaProjectionRecordingOpts * @property {string} [resolution] Maximum supported resolution on-device (Detected automatically by the app * itself), which usually equals to Full HD 1920x1080 on most phones however * you can change it to following supported resolutions as well: "1920x1080", * "1280x720", "720x480", "320x240", "176x144". * @property {number} [maxDurationSec=900] Maximum allowed duration is 15 minutes; you can increase it if your test * takes longer than that. * @property {'high' | 'normal' | 'low'} [priority='high'] Recording thread priority. * If you face performance drops during testing with recording enabled, you * can reduce recording priority * @property {string} [filename] You can type recording video file name as you want, but recording currently * supports only "mp4" format so your filename must end with ".mp4". An * invalid file name will fail to start the recording. */ class MediaProjectionRecorder { /** * @param {ADB} adb */ constructor(adb) { this.adb = adb; } /** * * @returns {Promise<boolean>} */ async isRunning() { const stdout = await this.adb.shell([ 'dumpsys', 'activity', 'services', constants_1.RECORDING_SERVICE_NAME, ]); return stdout.includes(constants_1.RECORDING_SERVICE_NAME); } /** * * @param {StartMediaProjectionRecordingOpts} opts * @returns {Promise<boolean>} */ async start(opts = {}) { if (await this.isRunning()) { return false; } await this.cleanup(); const { filename, maxDurationSec, priority, resolution } = opts; const args = ['am', 'start', '-n', constants_1.RECORDING_ACTIVITY_NAME, '-a', constants_1.RECORDING_ACTION_START]; if (filename) { args.push('--es', 'filename', filename); } if (maxDurationSec) { args.push('--es', 'max_duration_sec', `${maxDurationSec}`); } if (priority) { args.push('--es', 'priority', priority); } if (resolution) { args.push('--es', 'resolution', resolution); } await this.adb.shell(args); await new bluebird_1.default((resolve, reject) => { setTimeout(async () => { if (!(await this.isRunning())) { return reject(new Error(`The media projection recording is not running after ${RECORDING_STARTUP_TIMEOUT_MS}ms. ` + `Please check the logcat output for more details.`)); } resolve(); }, RECORDING_STARTUP_TIMEOUT_MS); }); return true; } /** * @returns {Promise<void>} */ async cleanup() { await this.adb.shell([`rm -f ${RECORDINGS_ROOT}/*`]); } /** * * @returns {Promise<string?>} */ async pullRecent() { const recordings = await this.adb.ls(RECORDINGS_ROOT, ['-tr']); if (lodash_1.default.isEmpty(recordings)) { return null; } const tmpRoot = await promises_1.default.mkdtemp('recording'); const dstPath = node_path_1.default.join(tmpRoot, recordings[0]); // increase timeout to 5 minutes because it might take a while to pull a large video file await this.adb.pull(`${RECORDINGS_ROOT}/${recordings[0]}`, dstPath, { timeout: 300000 }); return dstPath; } /** * @returns {Promise<boolean>} */ async stop() { if (!(await this.isRunning())) { return false; } await this.adb.shell([ 'am', 'start', '-n', constants_1.RECORDING_ACTIVITY_NAME, '-a', constants_1.RECORDING_ACTION_STOP, ]); try { await (0, asyncbox_1.waitForCondition)(async () => !(await this.isRunning()), { waitMs: RECORDING_STOP_TIMEOUT_MS, intervalMs: 500, }); } catch { throw new Error(`The attempt to stop the current media projection recording timed out after ` + `${RECORDING_STOP_TIMEOUT_MS}ms`); } return true; } } /** * Creates a new instance of the MediaProjection-based recorder * The recorder only works since Android API 29+ * * @this {import('../client').SettingsApp} * @returns {MediaProjectionRecorder} The recorder instance */ function makeMediaProjectionRecorder() { return new MediaProjectionRecorder(this.adb); } /** * Adjusts the necessary permissions for the * Media Projection-based recording service * * @this {import('../client').SettingsApp} * @returns {Promise<boolean>} If the permssions adjustment has actually been made */ async function adjustMediaProjectionServicePermissions() { if (await this.adb.getApiLevel() >= 29) { await this.adb.shell(['appops', 'set', constants_1.SETTINGS_HELPER_ID, 'PROJECT_MEDIA', 'allow']); return true; } return false; } /** * @typedef {import('appium-adb').ADB} ADB */ //# sourceMappingURL=media-projection.js.map