Skip to content

Commit

Permalink
feat: Add video-stream command support (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Sep 7, 2023
1 parent dfcbce4 commit ebb0c48
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 11 deletions.
12 changes: 2 additions & 10 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,9 @@ jobs:
outputs:
versions: ${{ steps.generate-matrix.outputs.versions }}
steps:
- name: Generate Node.js versions matrix
- name: Select 3 most recent LTS versions of Node.js
id: generate-matrix
run: |
sudo apt-get install -y lynx
lynx -dump https://endoflife.date/nodejs | grep -E -o '[0-9]+[( a-zA-Z]+LTS\)' | grep -E -o '([0-9]+)' > eol.list
cat eol.list
lts1=$(cat eol.list | head -1)
lts2=$(cat eol.list | head -2 | tail -1)
lts3=$(cat eol.list | head -3 | tail -1)
VERSIONS="[$lts1, $lts2, $lts3]"
echo "versions=${VERSIONS}" >> $GITHUB_OUTPUT
run: echo "versions=$(curl -s https://endoflife.date/api/nodejs.json | jq -c '[[.[] | select(.lts != false)][:3] | .[].cycle | tonumber]')" >> "$GITHUB_OUTPUT"

test:
needs:
Expand Down
4 changes: 3 additions & 1 deletion lib/tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import accessibilityCommands from './accessibility-commands';
import crashlogCommands from './crashlog-commands';
import miscCommands from './misc-commands';
import xctestCommands from './xctest-commands';
import videoCommands from './video-commands';


// https://www.fbidb.io/docs/commands
Expand All @@ -15,7 +16,8 @@ Object.assign(
accessibilityCommands,
crashlogCommands,
miscCommands,
xctestCommands
xctestCommands,
videoCommands,
);

export default systemCommands;
110 changes: 110 additions & 0 deletions lib/tools/video-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import _ from 'lodash';
import { exec } from 'teen_process';
import { spawn } from 'node:child_process';
import log from '../logger.js';
import { util } from '@appium/support';
import B from 'bluebird';

const DEFAULT_STARTUP_TIMEOUT_MS = 30000;
const KILL_SIGNAL = 2; // SIGINT

const videoCommands = {};

/**
* @typedef {Object} VideoStreamingOptions
*
* @property {number} [timeoutMs=30000] The number of millisecods to wait until
* the video streaming starts.
* @property {number} fps The number of frames that are produced by idb per second.
* This can be arbitrarily large or small.
* A higher frame rate will increase system utilization.
* Increasing the fps may not result in smoother presentation, as an iOS Simulator
* may be refreshing it's screen less frequently than the target frame rate.
* Typically an iOS Simulator may not render transparencies at 60fps.
* @property {'h264'|'rbga'|'mjpeg'|'minicap'} format represents the format of the video stream itself.
* A variety of outputs are available:
* - h264 This is an Annexe-B H.264 Stream
* - rbga is a stream of raw RBGA bytes.
* - mjpeg is an stream of encoed JPEG images, typically called MJPEG.
* - minicap is format used by the minicap project. It's fundementally a MJPEG
* stream with a header at the start of the stream and length headers per frame.
* @property {number} [compressionQuality] 1.0 represents the quality level used for encoded frames,
* this is a value between 0.0 and 1.0. It applies to all formats except for the raw rbga format.
*/

/**
* Runs video streaming from device or simulator
*
* @see https://fbidb.io/docs/video/
* @this {import('../idb.js').IDB}
* @param {VideoStreamingOptions} opts
* @returns {Promise<import('node:child_process').ChildProcessWithoutNullStreams>}
*/
videoCommands.startVideoStream = async function startVideoStream (opts) {
/** @type {string[]} */
const args = [];
if (!_.isNil(opts.fps)) {
args.push('--fps', `${opts.fps}`);
}
if (!_.isNil(opts.format)) {
args.push('--format', opts.format);
}
if (!_.isNil(opts.compressionQuality)) {
args.push('--compression-quality', `${opts.compressionQuality}`);
}
const idbArgs = ['video-stream', ...this.executable.defaultArgs, ...args];
log.debug(`Spawning IDB with args: ${util.quote(idbArgs)}`);
const videoStreamProcess = spawn(this.executable.path, idbArgs);
const timeoutMs = opts.timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
try {
await new B((resolve, reject) => {
let errorMessageChunks = [];
videoStreamProcess.stderr.on('data', (data) => {errorMessageChunks.push(data);});
videoStreamProcess.stdout.once('data', () => {resolve();});
videoStreamProcess.once('error', (err) => {
reject(
new Error(`The IDB video streamer has failed to start. Original error: ${err.message}; stderr: ` +
Buffer.concat(errorMessageChunks).toString('utf8')
)
);
});
videoStreamProcess.once('close', (code) => {
reject(
new Error(`The IDB video streamer has exited unexpectedly with code ${code}, stderr: ` +
Buffer.concat(errorMessageChunks).toString('utf8')
)
);
});
}).timeout(
timeoutMs,
`The IDB video streamer has failed to start streaming after ${timeoutMs}ms timeout`
);
} finally {
videoStreamProcess.stderr.removeAllListeners('data');
videoStreamProcess.stdout.removeAllListeners('data');
videoStreamProcess.removeAllListeners('error');
videoStreamProcess.removeAllListeners('close');
}
return videoStreamProcess;
};

/**
* Stops video streaming from device or simulator
*
* @see https://fbidb.io/docs/video/
* @this {import('../idb.js').IDB}
* @param {import('node:child_process').ChildProcessWithoutNullStreams?} [process] If provided
* then only this process will be killed, otherwise all matching idb video streaming processes
* for the particluar device udid will be terminated via SIGINT.
*/
videoCommands.stopVideoStream = async function stopVideoStream (process = null) {
if (process) {
process.kill(KILL_SIGNAL);
} else {
try {
await exec('pkill', [`-${KILL_SIGNAL}`, '-f', ['idb', 'video-stream', this.udid].join('.*')]);
} catch (ign) {}
}
};

export default videoCommands;

0 comments on commit ebb0c48

Please sign in to comment.