UNPKG

@appium/support

Version:

Support libs used across Appium packages

162 lines 6.19 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 node_stream_1 = require("node:stream"); const node_1 = require("./node"); const axios_1 = __importDefault(require("axios")); 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 { 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.'); } } return MJpegConsumer; } const MJPEG_SERVER_TIMEOUT_MS = 10000; /** Class which stores the last bit of data streamed into it */ class MJpegStream extends node_stream_1.Writable { errorHandler; url; /** Number of JPEG frames received so far (reset on {@linkcode clear}). Use stream['updateCount'] in tests if needed. */ updateCount = 0; /** Last received JPEG chunk (base64 via {@linkcode lastChunkBase64}); `null` when no data yet or after {@linkcode clear}/stop. Use stream['lastChunk'] in tests if needed. */ lastChunk = null; registerStartSuccess = null; registerStartFailure = null; responseStream = null; consumer = null; /** * @param mJpegUrl - URL of MJPEG-over-HTTP stream * @param errorHandler - additional function that will be called in the case of any errors * @param 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 lastChunkBase64() { const lastChunk = this.lastChunk; if (lastChunk && !lodash_1.default.isEmpty(lastChunk) && lodash_1.default.isBuffer(lastChunk)) { return lastChunk.toString('base64'); } return null; } async lastChunkPNG() { const chunk = this.lastChunk; if (!chunk || lodash_1.default.isEmpty(chunk) || !lodash_1.default.isBuffer(chunk)) { return null; } try { return await (0, image_util_1.requireSharp)()(chunk).png().toBuffer(); } catch (err) { logger_1.default.warn(`Cannot convert MJPEG chunk to PNG: ${err.message}`); return null; } } async lastChunkPNGBase64() { const png = await this.lastChunkPNG(); return png ? png.toString('base64') : null; } clear() { this.registerStartSuccess = null; this.registerStartFailure = null; this.responseStream = null; this.consumer = null; this.lastChunk = null; this.updateCount = 0; } async start(serverTimeout = MJPEG_SERVER_TIMEOUT_MS) { this.stop(); const Consumer = await initMJpegConsumer(); this.consumer = new Consumer(); const url = this.url; try { this.responseStream = (await (0, axios_1.default)({ url, responseType: 'stream', timeout: serverTimeout, })).data; } catch (e) { let message; if (e && typeof e === 'object' && 'response' in e) { message = JSON.stringify(e.response); } else if (e instanceof Error) { message = e.message; } else { message = String(e); } throw new Error(`Cannot connect to the MJPEG stream at ${url}. Original error: ${message}`); } const onErr = (err) => { 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; }; // TODO: replace with native Promises in Appium 4 const startPromise = new bluebird_1.default((res, rej) => { this.registerStartSuccess = res; this.registerStartFailure = rej; }).timeout(serverTimeout, `Waited ${serverTimeout}ms but the MJPEG server never sent any images`); this.responseStream .once('close', onClose) .on('error', onErr) .pipe(this.consumer) .pipe(this); await startPromise; } stop() { if (this.consumer) { this.consumer.unpipe(this); } if (this.responseStream) { if (this.consumer) { this.responseStream.unpipe(this.consumer); } this.responseStream.destroy(); } this.clear(); } /* eslint-disable @typescript-eslint/no-unused-vars -- Writable.write signature requires encoding and callback */ /* eslint-disable promise/prefer-await-to-callbacks -- Node Writable.write is callback-based */ write(data, encoding, callback) { /* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable promise/prefer-await-to-callbacks */ this.lastChunk = Buffer.isBuffer(data) ? data : Buffer.from(data); this.updateCount++; if (this.registerStartSuccess) { this.registerStartSuccess(); this.registerStartSuccess = null; } return true; } } exports.MJpegStream = MJpegStream; //# sourceMappingURL=mjpeg.js.map