UNPKG

io.appium.settings

Version:
168 lines 6.01 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MediaProjectionRecorder = void 0; exports.makeMediaProjectionRecorder = makeMediaProjectionRecorder; exports.adjustMediaProjectionServicePermissions = adjustMediaProjectionServicePermissions; const asyncbox_1 = require("asyncbox"); const promises_1 = __importDefault(require("node:fs/promises")); const node_path_1 = __importDefault(require("node:path")); const constants_1 = require("../constants"); /** * Creates a new instance of the MediaProjection-based recorder. * The recorder only works since Android API 29+ * * @returns The recorder instance */ function makeMediaProjectionRecorder() { return new MediaProjectionRecorder(this.adb); } /** * Adjusts the necessary permissions for the Media Projection-based recording service. * This method only applies to devices running Android API 29 or higher. * * @returns True if permissions were adjusted, false if device API level is below 29 */ 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; } 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`; /** * Media projection recorder for capturing device screen recordings. * This class provides methods to start, stop, and manage screen recordings * using Android's MediaProjection API (API 29+). */ class MediaProjectionRecorder { adb; /** * Creates a new MediaProjectionRecorder instance. * * @param adb - ADB instance for device communication */ constructor(adb) { this.adb = adb; } /** * Checks if the recording is currently running. */ async isRunning() { const stdout = await this.adb.shell([ 'dumpsys', 'activity', 'services', constants_1.RECORDING_SERVICE_NAME, ]); return stdout.includes(constants_1.RECORDING_SERVICE_NAME); } /** * Starts the media projection recording. * If a recording is already running, this method will return false without starting a new one. * * @param opts Recording options including filename, resolution, duration, and priority * @returns True if recording was started successfully, false if already running * @throws {Error} If recording fails to start within the timeout period */ 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); try { await (0, asyncbox_1.waitForCondition)(async () => await this.isRunning(), { waitMs: RECORDING_STARTUP_TIMEOUT_MS, intervalMs: 500, }); } catch { throw new Error(`The media projection recording is not running after ${RECORDING_STARTUP_TIMEOUT_MS}ms. ` + `Please check the logcat output for more details.`); } return true; } /** * Cleans up old recording files. */ async cleanup() { await this.adb.shell([`rm -f ${RECORDINGS_ROOT}/*`]); } /** * Pulls the most recent recording file from the device. * * @returns Path to the pulled file, or null if no recordings exist */ async pullRecent() { const recordings = await this.adb.ls(RECORDINGS_ROOT, ['-tr']); if (recordings.length === 0) { 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; } /** * Stops the current recording. * * @returns True if recording was stopped successfully, false if no recording was running * @throws {Error} If the recording fails to stop within the timeout period */ 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; } } exports.MediaProjectionRecorder = MediaProjectionRecorder; //# sourceMappingURL=media-projection.js.map