io.appium.settings
Version:
App for dealing with Android settings
168 lines • 6.01 kB
JavaScript
;
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