Skip to content

Commit

Permalink
fix(commands): fix startAudioRecording and stopAudioRecording execute…
Browse files Browse the repository at this point in the history
… methods

These were missing from the execute method map, and also needed to be refactored
  • Loading branch information
boneskull committed Apr 14, 2023
1 parent 5117430 commit 990547a
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 91 deletions.
72 changes: 26 additions & 46 deletions lib/commands/record-audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import {SubProcess} from 'teen_process';
import {encodeBase64OrUpload} from '../utils';
import {waitForCondition} from 'asyncbox';

const MAX_RECORDING_TIME_SEC = 43200;
const AUDIO_RECORD_FEAT_NAME = 'audio_record';
const MAX_RECORDING_TIME_SEC = 60 * 60 * 12;
const DEFAULT_RECORDING_TIME_SEC = 60 * 3;
const PROCESS_STARTUP_TIMEOUT_MS = 5000;
const DEFAULT_SOURCE = 'avfoundation';
const DEFAULT_BITRATE = '128k';
const DEFAULT_CODEC = 'aac';
const DEFAULT_CHANNELS = 2;
const DEFAULT_RATE = 44100;
const PROCESS_STARTUP_TIMEOUT_MS = 5000;

const DEFAULT_EXT = '.mp4';
const FFMPEG_BINARY = 'ffmpeg';
const ffmpegLogger = logger.getLogger(FFMPEG_BINARY);
Expand Down Expand Up @@ -145,56 +141,40 @@ export class AudioRecorder {
}
}

/**
* @typedef {Object} StartRecordingOptions
*
* @property {string} audioInput - The name of the corresponding audio input device to use for the
* capture. The full list of capture devices could be shown using `ffmpeg -f avfoundation -list_devices true -i ""`
* Terminal command.
* @property {string} [audioCodec='aac'] - The name of the audio codec. The Advanced Audio Codec is used by default.
* @property {string} [audioBitrate='128k'] - The bitrate of the resulting audio stream. 128k by default.
* @property {string|number} [audioChannels=2] - The count of audio channels in the resulting stream. Setting it to `1`
* will create a single channel (mono) audio stream.
* @property {string|number} [audioRate=44100] - The sampling rate of the resulting audio stream.
* @property {string|number} [timeLimit=180] - The maximum recording time, in seconds.
* The default value is 180, the maximum value is 43200 (12 hours).
* @property {boolean} [forceRestart] - Whether to restart audio capture process forcefully when
* startRecordingAudio is called (`true`) or ignore the call until the current audio recording is completed.
*/

export default {
/**
* @type {AudioRecorder?}
*/
_audioRecorder: null,
/**
* Records the given hardware audio input into an .mp4 file.
* Records the given hardware audio input and saves it into an `.mp4` file.
*
* **To use this command, the `audio_record` security feature must be enabled _and_ [FFMpeg](https://ffmpeg.org/) must be installed on the Appium server.**
*
* @param {StartRecordingOptions} [options] - The available options.
* @throws {Error} If audio recording has failed to start.
* @param {string|number} audioInput - The name of the corresponding audio input device to use for the capture. The full list of capture devices could be shown by executing `ffmpeg -f avfoundation -list_devices true -i ""`
* @param {string|number} timeLimit - The maximum recording time, in seconds.
* @param {string} audioCodec - The name of the audio codec.
* @param {string} audioBitrate - The bitrate of the resulting audio stream.
* @param {string|number} audioChannels - The count of audio channels in the resulting stream. Setting it to `1` will create a single channel (mono) audio stream.
* @param {string|number} audioRate - The sampling rate of the resulting audio stream (in Hz).
* @param {boolean} forceRestart - Whether to restart audio capture process forcefully when `mobile: startRecordingAudio` is called (`true`) or ignore the call until the current audio recording is completed (`false`).
* @group Real Device Only
* @this {XCUITestDriver}
* @returns {Promise<void>}
* @privateRemarks Using string literals for the default parameters makes better documentation.
*/
async startAudioRecording(options = /** @type {StartRecordingOptions} */({})) {
async startAudioRecording(
audioInput,
timeLimit = 180,
audioCodec = 'aac',
audioBitrate = '128k',
audioChannels = 2,
audioRate = 44100,
forceRestart = false
) {
if (!this.isFeatureEnabled(AUDIO_RECORD_FEAT_NAME)) {
this.log.errorAndThrow(
`Audio capture feature must be enabled on the server side. ` +
`Please set '--relaxed-security' or '--allow-insecure' with '${AUDIO_RECORD_FEAT_NAME}' option. ` +
`Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/security.md for more details.`
);
}

const {
timeLimit = DEFAULT_RECORDING_TIME_SEC,
audioInput,
// @ts-expect-error Undocumented feature
audioSource,
audioCodec = DEFAULT_CODEC,
audioBitrate = DEFAULT_BITRATE,
audioChannels = DEFAULT_CHANNELS,
audioRate = DEFAULT_RATE,
forceRestart,
} = options;

if (!audioInput) {
this.log.errorAndThrow(
`The mandatory audioInput option is not provided. Please set it ` +
Expand Down Expand Up @@ -227,7 +207,7 @@ export default {
});

const audioRecorder = new AudioRecorder(audioInput, this.log, audioPath, {
audioSource,
audioSource: DEFAULT_SOURCE,
audioCodec,
audioBitrate,
audioChannels,
Expand Down
93 changes: 48 additions & 45 deletions lib/driver.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
import IDB from 'appium-idb';
import {getSimulator} from 'appium-ios-simulator';
import {WebDriverAgent} from 'appium-webdriveragent';
import {BaseDriver, DeviceSettings} from 'appium/driver';
import {util, mjpeg, fs} from 'appium/support';
import {fs, mjpeg, util} from 'appium/support';
import AsyncLock from 'async-lock';
import {retry, retryInterval} from 'asyncbox';
import B from 'bluebird';
import _ from 'lodash';
import url from 'url';
import {WebDriverAgent} from 'appium-webdriveragent';
import LRU from 'lru-cache';
import EventEmitter from 'node:events';
import path from 'node:path';
import url from 'node:url';
import {
APP_EXT,
IPA_EXT,
SAFARI_BUNDLE_ID,
extractBundleId,
extractBundleVersion,
fetchSupportedAppPlatforms,
findApps,
isAppBundle,
isolateAppBundle,
verifyApplicationPlatform,
} from './app-utils';
import commands from './commands';
import {PLATFORM_NAME_IOS, PLATFORM_NAME_TVOS, desiredCapConstraints} from './desired-caps';
import DEVICE_CONNECTIONS_FACTORY from './device-connections-factory';
import {executeMethodMap} from './execute-method-map';
import {newMethodMap} from './method-map';
import Pyidevice from './py-ios-device-client';
import {
getConnectedDevices,
getRealDeviceObj,
installToRealDevice,
runRealDeviceReset,
} from './real-device-management';
import {
createSim,
getExistingSim,
runSimulatorReset,
installToSimulator,
runSimulatorReset,
setLocalizationPrefs,
setSafariPrefs,
shutdownOtherSimulators,
shutdownSimulator,
setSafariPrefs,
setLocalizationPrefs,
} from './simulator-management';
import {getSimulator} from 'appium-ios-simulator';
import {retryInterval, retry} from 'asyncbox';
import {
verifyApplicationPlatform,
extractBundleId,
SAFARI_BUNDLE_ID,
fetchSupportedAppPlatforms,
APP_EXT,
IPA_EXT,
findApps,
isAppBundle,
isolateAppBundle,
extractBundleVersion,
} from './app-utils';
import {desiredCapConstraints, PLATFORM_NAME_IOS, PLATFORM_NAME_TVOS} from './desired-caps';
import {
DEFAULT_TIMEOUT_KEY,
checkAppPresent,
clearSystemFiles,
detectUdid,
getAndCheckXcodeVersion,
getAndCheckIosSdkVersion,
checkAppPresent,
getAndCheckXcodeVersion,
getDriverInfo,
clearSystemFiles,
translateDeviceName,
normalizeCommandTimeouts,
DEFAULT_TIMEOUT_KEY,
isLocalHost,
markSystemFilesForCleanup,
normalizeCommandTimeouts,
normalizePlatformVersion,
printUser,
removeAllSessionWebSocketHandlers,
normalizePlatformVersion,
isLocalHost,
translateDeviceName,
} from './utils';
import {
getConnectedDevices,
runRealDeviceReset,
installToRealDevice,
getRealDeviceObj,
} from './real-device-management';
import B from 'bluebird';
import AsyncLock from 'async-lock';
import path from 'path';
import IDB from 'appium-idb';
import DEVICE_CONNECTIONS_FACTORY from './device-connections-factory';
import Pyidevice from './py-ios-device-client';
import commands from './commands';
import EventEmitter from 'node:events';
import {executeMethodMap} from './execute-method-map';

const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims';
const CUSTOMIZE_RESULT_BUNDPE_PATH = 'customize_result_bundle_path';
Expand Down Expand Up @@ -250,6 +250,9 @@ class XCUITestDriver extends BaseDriver {
/** @type {XCUITestDriverOpts} */
opts;

/** @type {import('./commands/record-audio').AudioRecorder|null} */
_audioRecorder;

/**
*
* @param {XCUITestDriverOpts} opts
Expand Down Expand Up @@ -295,6 +298,7 @@ class XCUITestDriver extends BaseDriver {
this[fn] = _.memoize(this[fn]);
}
this.lifecycleData = {};
this._audioRecorder = null;
}

async onSettingsUpdate(key, value) {
Expand Down Expand Up @@ -2080,7 +2084,6 @@ class XCUITestDriver extends BaseDriver {
| RECORDAUDIO |
+-------------+*/

_audioRecorder = commands.recordAudioExtensions._audioRecorder;
startAudioRecording = commands.recordAudioExtensions.startAudioRecording;
stopAudioRecording = commands.recordAudioExtensions.stopAudioRecording;

Expand Down
17 changes: 17 additions & 0 deletions lib/execute-method-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,21 @@ export const executeMethodMap = {
'mobile: shake': {
command: 'mobileShake',
},
'mobile: startAudioRecording': {
command: 'startAudioRecording',
params: {
required: ['audioInput'],
optional: [
'timeLimit',
'audioCodec',
'audioBitrate',
'audioChannels',
'audioRate',
'forceRestart',
],
},
},
'mobile: stopAudioRecording': {
command: 'stopAudioRecording',
},
} as const satisfies ExecuteMethodMap<XCUITestDriver>;

0 comments on commit 990547a

Please sign in to comment.