UNPKG

@appium/support

Version:

Support libs used across Appium packages

185 lines 6.74 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MJpegStream = void 0; const lodash_1 = __importDefault(require("lodash")); const logger_1 = __importDefault(require("./logger")); const bluebird_1 = __importDefault(require("bluebird")); const image_util_1 = require("./image-util"); const stream_1 = require("stream"); const node_1 = require("./node"); const axios_1 = __importDefault(require("axios")); // lazy load this, as it might not be available let MJpegConsumer = null; /** * @throws {Error} If `mjpeg-consumer` module is not installed or cannot be loaded */ async function initMJpegConsumer() { if (!MJpegConsumer) { try { MJpegConsumer = await (0, node_1.requirePackage)('mjpeg-consumer'); } catch { } } if (!MJpegConsumer) { throw new Error('mjpeg-consumer module is required to use MJPEG-over-HTTP features. ' + 'Please install it first (npm i -g mjpeg-consumer) and restart Appium.'); } } // amount of time to wait for the first image in the stream const MJPEG_SERVER_TIMEOUT_MS = 10000; /** Class which stores the last bit of data streamed into it */ class MJpegStream extends stream_1.Writable { /** * @type {number} */ updateCount = 0; /** * Create an MJpegStream * @param {string} mJpegUrl - URL of MJPEG-over-HTTP stream * @param {function} [errorHandler=noop] - additional function that will be * called in the case of any errors. * @param {object} [options={}] - Options to pass to the Writable constructor */ constructor(mJpegUrl, errorHandler = lodash_1.default.noop, options = {}) { super(options); this.errorHandler = errorHandler; this.url = mJpegUrl; this.clear(); } /** * Get the base64-encoded version of the JPEG * * @returns {?string} base64-encoded JPEG image data * or `null` if no image can be parsed */ get lastChunkBase64() { const lastChunk = /** @type {Buffer} */ (this.lastChunk); return !lodash_1.default.isEmpty(this.lastChunk) && lodash_1.default.isBuffer(this.lastChunk) ? lastChunk.toString('base64') : null; } /** * Get the PNG version of the JPEG buffer * * @returns {Promise<Buffer?>} PNG image data or `null` if no PNG * image can be parsed */ async lastChunkPNG() { const lastChunk = /** @type {Buffer} */ (this.lastChunk); if (lodash_1.default.isEmpty(lastChunk) || !lodash_1.default.isBuffer(lastChunk)) { return null; } try { return await (0, image_util_1.requireSharp)()(lastChunk).png().toBuffer(); } catch { return null; } } /** * Get the base64-encoded version of the PNG * * @returns {Promise<string?>} base64-encoded PNG image data * or `null` if no image can be parsed */ async lastChunkPNGBase64() { const png = await this.lastChunkPNG(); return png ? png.toString('base64') : null; } /** * Reset internal state */ clear() { this.registerStartSuccess = null; this.registerStartFailure = null; this.responseStream = null; this.consumer = null; this.lastChunk = null; this.updateCount = 0; } /** * Start reading the MJpeg stream and storing the last image */ async start(serverTimeout = MJPEG_SERVER_TIMEOUT_MS) { // ensure we're not started already this.stop(); await initMJpegConsumer(); this.consumer = new MJpegConsumer(); const url = this.url; try { this.responseStream = (await (0, axios_1.default)({ url, responseType: 'stream', timeout: serverTimeout, })).data; } catch (e) { throw new Error(`Cannot connect to the MJPEG stream at ${url}. ` + `Original error: ${lodash_1.default.has(e, 'response') ? JSON.stringify(e.response) : /** @type {Error} */ (e).message}`); } const onErr = (/** @type {Error} */ err) => { // Make sure we don't get an outdated screenshot if there was an error this.lastChunk = null; logger_1.default.error(`Error getting MJpeg screenshot chunk: ${err.message}`); this.errorHandler(err); if (this.registerStartFailure) { this.registerStartFailure(err); } }; const onClose = () => { logger_1.default.debug(`The connection to MJPEG server at ${url} has been closed`); this.lastChunk = null; }; // use the deferred pattern so we can wait for the start of the stream // based on what comes in from an external pipe const startPromise = new bluebird_1.default((res, rej) => { this.registerStartSuccess = res; this.registerStartFailure = rej; }) // start a timeout so that if the server does not return data, we don't // block forever. .timeout(serverTimeout, `Waited ${serverTimeout}ms but the MJPEG server never sent any images`); this.responseStream .once('close', onClose) .on('error', onErr) // ensure we do something with errors .pipe(this.consumer) // allow chunking and transforming of jpeg data .pipe(this); // send the actual jpegs to ourself await startPromise; } /** * Stop reading the MJpeg stream. Ensure we disconnect all the pipes and stop * the HTTP request itself. Then reset the state. */ stop() { if (this.consumer) { this.consumer.unpipe(this); } if (this.responseStream) { if (this.consumer) { this.responseStream.unpipe(this.consumer); } this.responseStream.destroy(); } this.clear(); } /** * Override the Writable write() method in order to save the last image and * log the number of images we have received * @override * @param {Buffer} data - binary data streamed from the MJpeg consumer */ write(data) { this.lastChunk = data; this.updateCount++; if (this.registerStartSuccess) { this.registerStartSuccess(); this.registerStartSuccess = null; } return true; } } exports.MJpegStream = MJpegStream; //# sourceMappingURL=mjpeg.js.map