@appium/coresim 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/binding.gyp +7 -1
- package/lib/src/commands/video-recording.d.ts +50 -0
- package/lib/src/commands/video-recording.d.ts.map +1 -0
- package/lib/src/commands/video-recording.js +98 -0
- package/lib/src/commands/video-recording.js.map +1 -0
- package/lib/src/commands/video-stream.d.ts +44 -0
- package/lib/src/commands/video-stream.d.ts.map +1 -0
- package/lib/src/commands/video-stream.js +240 -0
- package/lib/src/commands/video-stream.js.map +1 -0
- package/lib/src/index.d.ts +2 -1
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +1 -0
- package/lib/src/index.js.map +1 -1
- package/lib/src/native-simctl.d.ts +4 -0
- package/lib/src/native-simctl.d.ts.map +1 -1
- package/lib/src/native-simctl.js +9 -0
- package/lib/src/native-simctl.js.map +1 -1
- package/lib/src/types.d.ts +72 -0
- package/lib/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@appium+coresim.node +0 -0
- package/src/commands/video-recording.ts +128 -0
- package/src/commands/video-stream.ts +274 -0
- package/src/coresim.mm +279 -1
- package/src/index.ts +4 -0
- package/src/native/sim_screenshot.h +10 -0
- package/src/native/sim_screenshot.mm +11 -6
- package/src/native/sim_video_recording.h +27 -0
- package/src/native/sim_video_recording.mm +87 -0
- package/src/native/sim_video_stream.h +61 -0
- package/src/native/sim_video_stream.mm +427 -0
- package/src/native-simctl.ts +10 -0
- package/src/types.ts +76 -0
package/lib/src/types.d.ts
CHANGED
|
@@ -104,6 +104,51 @@ export interface ScreenshotOptions {
|
|
|
104
104
|
*/
|
|
105
105
|
quality?: number;
|
|
106
106
|
}
|
|
107
|
+
/** Options for `NativeSimctl.startVideoRecording`. */
|
|
108
|
+
export interface VideoRecordingOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Which display to record, by `id` from `getDisplays()`. Defaults to the primary display
|
|
111
|
+
* (falling back to the first renderable display if none is primary, e.g. tvOS).
|
|
112
|
+
*/
|
|
113
|
+
displayId?: string;
|
|
114
|
+
/** Video codec — `'h264'` (default) or `'hevc'`. */
|
|
115
|
+
codec?: 'h264' | 'hevc';
|
|
116
|
+
/**
|
|
117
|
+
* For a non-rectangular display (e.g. a Dynamic Island cutout): `'ignored'` (default) saves the
|
|
118
|
+
* unmasked framebuffer, `'black'` renders the mask black, `'alpha'` is not supported and
|
|
119
|
+
* behaves like `'black'`.
|
|
120
|
+
*/
|
|
121
|
+
mask?: 'ignored' | 'alpha' | 'black';
|
|
122
|
+
}
|
|
123
|
+
/** Options for `NativeSimctl.startVideoStream`. */
|
|
124
|
+
export interface VideoStreamOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Which display to stream, by `id` from `getDisplays()`. Defaults to the primary display
|
|
127
|
+
* (falling back to the first renderable display if none is primary, e.g. tvOS).
|
|
128
|
+
*/
|
|
129
|
+
displayId?: string;
|
|
130
|
+
/** Video codec — `'h264'` (default) or `'hevc'`. */
|
|
131
|
+
codec?: 'h264' | 'hevc';
|
|
132
|
+
/**
|
|
133
|
+
* Max frames/sec to poll the framebuffer at — an unchanged frame is never re-encoded, so this
|
|
134
|
+
* is an upper bound, not a guarantee. Must be >= 1. Defaults to 15.
|
|
135
|
+
*/
|
|
136
|
+
fps?: number;
|
|
137
|
+
/** Target average bitrate, in bits/sec. Defaults to 2,000,000 (2 Mbps). */
|
|
138
|
+
bitrate?: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* One encoded frame from `VideoStream.accessUnits()` — Annex-B NAL units. A keyframe's `data` has
|
|
142
|
+
* parameter sets (SPS/PPS, or VPS/SPS/PPS for HEVC) prepended, so it's self-decodable alone.
|
|
143
|
+
*/
|
|
144
|
+
export interface VideoAccessUnit {
|
|
145
|
+
data: Buffer;
|
|
146
|
+
isKeyFrame: boolean;
|
|
147
|
+
/** Monotonically increasing per stream, starting at 0. */
|
|
148
|
+
sequence: number;
|
|
149
|
+
/** Microseconds since the stream started. */
|
|
150
|
+
timestampMicros: number;
|
|
151
|
+
}
|
|
107
152
|
/**
|
|
108
153
|
* Options for `NativeSimctl.spawnProcess`, passed through to CoreSimulator's
|
|
109
154
|
* `spawnWithPath:options:terminationQueue:terminationHandler:error:`. Only keys confirmed
|
|
@@ -195,6 +240,21 @@ export interface NativeSpawnResult {
|
|
|
195
240
|
* mirroring Node's own `ChildProcess` `'exit'` event — exactly one of `code`/`signal` is set.
|
|
196
241
|
*/
|
|
197
242
|
export type NativeSpawnExitCallback = (code: number | null, signal: number | null) => void;
|
|
243
|
+
/** Raw shape of an access unit as the native addon delivers it — see {@link VideoAccessUnit}. */
|
|
244
|
+
export interface NativeVideoAccessUnit {
|
|
245
|
+
data: Buffer;
|
|
246
|
+
isKeyFrame: boolean;
|
|
247
|
+
sequence: number;
|
|
248
|
+
timestampMicros: number;
|
|
249
|
+
}
|
|
250
|
+
export type NativeVideoAccessUnitCallback = (unit: NativeVideoAccessUnit) => void;
|
|
251
|
+
export type NativeVideoErrorCallback = (err: Error) => void;
|
|
252
|
+
/** A live encoder session, wrapped by `coresim.mm`'s `NativeVideoStream` — what `NativeDeviceHandle.startVideoStream()` resolves to. */
|
|
253
|
+
export interface NativeVideoStreamHandle {
|
|
254
|
+
stop(): Promise<void>;
|
|
255
|
+
/** Forces the next encoded frame to be a keyframe — trivial in-memory flag, so synchronous. */
|
|
256
|
+
requestKeyFrame(): void;
|
|
257
|
+
}
|
|
198
258
|
/** A `SimDevice`, wrapped by `coresim.mm`'s `NativeDevice` — what `NativeSimctl`'s `_findDevice()` resolves to. */
|
|
199
259
|
export interface NativeDeviceHandle {
|
|
200
260
|
udid(): string;
|
|
@@ -245,6 +305,18 @@ export interface NativeDeviceHandle {
|
|
|
245
305
|
quality?: number;
|
|
246
306
|
}): Promise<Buffer>;
|
|
247
307
|
getDisplays(): Promise<SimDisplayInfo[]>;
|
|
308
|
+
startVideoRecording(outputFile: string, options?: {
|
|
309
|
+
displayId?: string;
|
|
310
|
+
codec?: 'h264' | 'hevc';
|
|
311
|
+
mask?: 'ignored' | 'alpha' | 'black';
|
|
312
|
+
}): Promise<void>;
|
|
313
|
+
stopVideoRecording(): Promise<void>;
|
|
314
|
+
startVideoStream(options: {
|
|
315
|
+
displayId?: string;
|
|
316
|
+
codec?: 'h264' | 'hevc';
|
|
317
|
+
fps?: number;
|
|
318
|
+
bitrate?: number;
|
|
319
|
+
} | undefined, onAccessUnit: NativeVideoAccessUnitCallback, onError: NativeVideoErrorCallback): Promise<NativeVideoStreamHandle>;
|
|
248
320
|
spawn(path: string, options: SpawnOptions | undefined, onExit: NativeSpawnExitCallback): Promise<NativeSpawnResult>;
|
|
249
321
|
}
|
|
250
322
|
/** A `SimDeviceSet`, wrapped by `coresim.mm`'s `NativeDeviceSet`. */
|
package/lib/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import type {NativeSimctl} from '../native-simctl.js';
|
|
4
|
+
import type {VideoRecordingOptions} from '../types.js';
|
|
5
|
+
import {runCatchingAsync} from '../utils/index.js';
|
|
6
|
+
|
|
7
|
+
declare module '../native-simctl.js' {
|
|
8
|
+
interface NativeSimctl {
|
|
9
|
+
startVideoRecording(udid: string, outputFile: string, options?: VideoRecordingOptions): Promise<void>;
|
|
10
|
+
stopVideoRecording(udid: string): Promise<void>;
|
|
11
|
+
isVideoRecording(udid: string): Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// One device's recording lifecycle: `start` is the in-flight (or already-settled)
|
|
16
|
+
// startVideoRecording call, so a concurrent stopVideoRecording can always wait for it to fully
|
|
17
|
+
// resolve before racing it natively. `stop`, once set, is the in-flight native stop call shared by
|
|
18
|
+
// every concurrent stopVideoRecording for this same recording — see stopVideoRecording for why a
|
|
19
|
+
// second one must reuse it rather than issuing its own.
|
|
20
|
+
interface RecordingState {
|
|
21
|
+
readonly start: Promise<void>;
|
|
22
|
+
stop?: Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Tracks which devices currently have an active recording — module-level since the constraint (at
|
|
26
|
+
// most one per device) is CoreSimulator's own, not per-NativeSimctl-instance.
|
|
27
|
+
const activeRecordings = new Map<string, RecordingState>();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Starts recording the device's display to `outputFile` — the native equivalent of `simctl io
|
|
31
|
+
* <udid> recordVideo`. Resolves once the first frame has actually been recorded, so it's always
|
|
32
|
+
* safe to call {@link stopVideoRecording} immediately after. Only one recording may be active per
|
|
33
|
+
* device at a time; starting a second one while the first is still running rejects. Rejects with
|
|
34
|
+
* `NativeSimUnavailableError` if this CoreSimulator predates the private capture API — confirmed
|
|
35
|
+
* missing on Xcode 16.4's, present on Xcode 26.5+ (Apple documents no exact version floor).
|
|
36
|
+
*
|
|
37
|
+
* @param udid — UDID of the device to record; must be booted
|
|
38
|
+
* @param outputFile — filesystem path to write the video to; resolved against `process.cwd()` if
|
|
39
|
+
* relative, since the native layer requires an absolute path
|
|
40
|
+
* @param options — `displayId`, `codec`, `mask` — see {@link VideoRecordingOptions}
|
|
41
|
+
* @throws {Error} if a recording is already in progress for this device
|
|
42
|
+
*/
|
|
43
|
+
export async function startVideoRecording(
|
|
44
|
+
this: NativeSimctl,
|
|
45
|
+
udid: string,
|
|
46
|
+
outputFile: string,
|
|
47
|
+
options: VideoRecordingOptions = {},
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
const key = udid.toLowerCase();
|
|
50
|
+
if (activeRecordings.has(key)) {
|
|
51
|
+
throw new Error(`A video recording is already in progress for device '${udid}'`);
|
|
52
|
+
}
|
|
53
|
+
const absoluteOutputFile = path.resolve(outputFile);
|
|
54
|
+
const startPromise = runCatchingAsync(async () =>
|
|
55
|
+
(await this._findDevice(udid)).startVideoRecording(absoluteOutputFile, options),
|
|
56
|
+
);
|
|
57
|
+
// Marked before the native call resolves, not after, so a concurrent startVideoRecording for the
|
|
58
|
+
// same device is rejected immediately instead of racing this one — rolled back below on failure.
|
|
59
|
+
const state: RecordingState = {start: startPromise};
|
|
60
|
+
activeRecordings.set(key, state);
|
|
61
|
+
try {
|
|
62
|
+
await startPromise;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Only clear our own entry — a concurrent stopVideoRecording (see below) may already have.
|
|
65
|
+
if (activeRecordings.get(key) === state) {
|
|
66
|
+
activeRecordings.delete(key);
|
|
67
|
+
}
|
|
68
|
+
throw e;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Stops a recording previously started by {@link startVideoRecording} on the same device.
|
|
74
|
+
* Resolves once the video file has been finalized on disk and is safe to read. If `start` is still
|
|
75
|
+
* in flight (e.g. a concurrent caller stopping as soon as {@link isVideoRecording} turns `true`),
|
|
76
|
+
* waits for it to settle first — issuing the native stop call any earlier is a silent CoreSimulator
|
|
77
|
+
* race (see CLAUDE.md).
|
|
78
|
+
*
|
|
79
|
+
* Safe to call concurrently or to retry after a failure: every concurrent call for the same
|
|
80
|
+
* recording shares a single in-flight native stop call rather than each issuing its own (which
|
|
81
|
+
* could otherwise stop whatever *new* recording has since started on this device instead), and a
|
|
82
|
+
* failed stop — even one that failed before ever reaching the native recorder, e.g. a transient
|
|
83
|
+
* device-lookup error — leaves the recording tracked as still active so a retry can reach the
|
|
84
|
+
* native call rather than the caller losing the ability to stop it at all.
|
|
85
|
+
*
|
|
86
|
+
* @param udid — UDID of the device to stop recording
|
|
87
|
+
* @throws {Error} if no recording is currently in progress for this device
|
|
88
|
+
*/
|
|
89
|
+
export async function stopVideoRecording(this: NativeSimctl, udid: string): Promise<void> {
|
|
90
|
+
const key = udid.toLowerCase();
|
|
91
|
+
const state = activeRecordings.get(key);
|
|
92
|
+
if (!state) {
|
|
93
|
+
throw new Error(`No video recording is in progress for device '${udid}'`);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await state.start;
|
|
97
|
+
} catch {
|
|
98
|
+
// start itself failed — its own catch already cleaned up activeRecordings; nothing to stop.
|
|
99
|
+
throw new Error(`No video recording is in progress for device '${udid}'`);
|
|
100
|
+
}
|
|
101
|
+
const stopPromise = (state.stop ??= runCatchingAsync(async () =>
|
|
102
|
+
(await this._findDevice(udid)).stopVideoRecording(),
|
|
103
|
+
));
|
|
104
|
+
try {
|
|
105
|
+
await stopPromise;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// Only clear our own attempt, and only if it's still the current one — a future call must be
|
|
108
|
+
// able to retry the native stop rather than replaying this same rejection forever, but must
|
|
109
|
+
// not clobber a newer attempt another concurrent caller may have already started instead.
|
|
110
|
+
if (state.stop === stopPromise) {
|
|
111
|
+
state.stop = undefined;
|
|
112
|
+
}
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
if (activeRecordings.get(key) === state) {
|
|
116
|
+
activeRecordings.delete(key);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Whether a recording started by {@link startVideoRecording} is currently active for this device.
|
|
122
|
+
* Pure local state — no CoreSimulator dispatch — so this never rejects with a native error.
|
|
123
|
+
*
|
|
124
|
+
* @param udid — UDID of the device to check
|
|
125
|
+
*/
|
|
126
|
+
export async function isVideoRecording(this: NativeSimctl, udid: string): Promise<boolean> {
|
|
127
|
+
return activeRecordings.has(udid.toLowerCase());
|
|
128
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {EventEmitter} from 'node:events';
|
|
2
|
+
|
|
3
|
+
import {logger} from '@appium/support';
|
|
4
|
+
|
|
5
|
+
import {wrapNativeError} from '../errors.js';
|
|
6
|
+
import type {NativeSimctl} from '../native-simctl.js';
|
|
7
|
+
import type {NativeVideoStreamHandle, VideoAccessUnit, VideoStreamOptions} from '../types.js';
|
|
8
|
+
import {runCatchingAsync} from '../utils/index.js';
|
|
9
|
+
|
|
10
|
+
declare module '../native-simctl.js' {
|
|
11
|
+
interface NativeSimctl {
|
|
12
|
+
startVideoStream(udid: string, options?: VideoStreamOptions): Promise<VideoStream>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const log = logger.getLogger('CoreSim');
|
|
17
|
+
|
|
18
|
+
// Matches the native side's own ThreadSafeFunction queue bound (see coresim.mm's
|
|
19
|
+
// kAccessUnitQueueSize) — kept here too since the native bound alone provides no real
|
|
20
|
+
// backpressure: _handleAccessUnit's emit-equivalent push always returns immediately, regardless of
|
|
21
|
+
// how slow the actual accessUnits() consumer is, so without a bound of its own this queue could
|
|
22
|
+
// otherwise grow without limit while a slow consumer falls behind.
|
|
23
|
+
const MAX_BUFFERED_UNITS = 60;
|
|
24
|
+
|
|
25
|
+
const SINGLE_CONSUMER_ERROR =
|
|
26
|
+
'VideoStream.accessUnits() supports only one active consumer at a time — a second concurrent call rejects.';
|
|
27
|
+
|
|
28
|
+
/** `wrapNativeError` always throws — this just gets its thrown value back as a plain return, to emit rather than raise it. */
|
|
29
|
+
function toTypedError(err: unknown): Error {
|
|
30
|
+
try {
|
|
31
|
+
wrapNativeError(err);
|
|
32
|
+
} catch (wrapped) {
|
|
33
|
+
return wrapped as Error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Single-consumer FIFO between native's per-frame callback and `accessUnits()`. Unlike routing
|
|
39
|
+
* through `EventEmitter`, a unit pushed before any consumer has started iterating is retained
|
|
40
|
+
* (fixing the encoder's own first-frame/keyframe otherwise being lost to a startup race) rather
|
|
41
|
+
* than silently dropped.
|
|
42
|
+
*
|
|
43
|
+
* Bounded at `MAX_BUFFERED_UNITS`, but codec-aware about *how* it sheds load once a slow consumer
|
|
44
|
+
* falls behind: since frames only reference earlier frames they were encoded against (no
|
|
45
|
+
* `AllowFrameReordering`), dropping an arbitrary interframe would orphan every later one from its
|
|
46
|
+
* reference chain, corrupting decode from that point on even though delivery looks unbroken.
|
|
47
|
+
* Overflow instead clears the backlog entirely and enters a resync state, discarding every
|
|
48
|
+
* further interframe (not buffering them) until the next keyframe — self-decodable on its own —
|
|
49
|
+
* lets delivery resume cleanly.
|
|
50
|
+
*/
|
|
51
|
+
class AccessUnitQueue {
|
|
52
|
+
private readonly buffer: VideoAccessUnit[] = [];
|
|
53
|
+
private waiter:
|
|
54
|
+
| {resolve: (result: IteratorResult<VideoAccessUnit>) => void; reject: (err: unknown) => void}
|
|
55
|
+
| undefined;
|
|
56
|
+
private ended = false;
|
|
57
|
+
private error: unknown;
|
|
58
|
+
private resyncing = false;
|
|
59
|
+
|
|
60
|
+
/** @param onOverflow — called once when overflow first forces a resync, e.g. to request a fresh keyframe. */
|
|
61
|
+
constructor(private readonly onOverflow?: () => void) {}
|
|
62
|
+
|
|
63
|
+
push(unit: VideoAccessUnit): void {
|
|
64
|
+
if (this.ended) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (this.resyncing) {
|
|
68
|
+
if (!unit.isKeyFrame) {
|
|
69
|
+
return; // still waiting for a self-decodable point to resume delivery from
|
|
70
|
+
}
|
|
71
|
+
this.resyncing = false;
|
|
72
|
+
}
|
|
73
|
+
if (this.waiter) {
|
|
74
|
+
const {resolve} = this.waiter;
|
|
75
|
+
this.waiter = undefined;
|
|
76
|
+
resolve({value: unit, done: false});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.buffer.push(unit);
|
|
80
|
+
if (this.buffer.length > MAX_BUFFERED_UNITS) {
|
|
81
|
+
this.buffer.length = 0;
|
|
82
|
+
this.resyncing = true;
|
|
83
|
+
this.onOverflow?.();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Ends the queue with an error — any pending or future `next()` rejects with it. */
|
|
88
|
+
fail(err: unknown): void {
|
|
89
|
+
if (this.ended) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.ended = true;
|
|
93
|
+
this.error = err;
|
|
94
|
+
this.buffer.length = 0;
|
|
95
|
+
if (this.waiter) {
|
|
96
|
+
const {reject} = this.waiter;
|
|
97
|
+
this.waiter = undefined;
|
|
98
|
+
reject(err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Ends the queue cleanly — any pending or future `next()` resolves `done`. */
|
|
103
|
+
end(): void {
|
|
104
|
+
if (this.ended) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.ended = true;
|
|
108
|
+
this.buffer.length = 0;
|
|
109
|
+
if (this.waiter) {
|
|
110
|
+
const {resolve} = this.waiter;
|
|
111
|
+
this.waiter = undefined;
|
|
112
|
+
resolve({value: undefined, done: true});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolves `done` (not rejects) if `signal` aborts while waiting, mirroring `events.on()`.
|
|
118
|
+
* Checks cancellation/end *before* dequeuing, so an already-aborted signal or an already-ended
|
|
119
|
+
* queue never hands out a stale buffered unit — an aborted consumer, or a fresh iterator started
|
|
120
|
+
* after `stop()`, must see the boundary immediately rather than draining leftovers first.
|
|
121
|
+
*/
|
|
122
|
+
next(signal: AbortSignal): Promise<IteratorResult<VideoAccessUnit>> {
|
|
123
|
+
if (signal.aborted) {
|
|
124
|
+
return Promise.resolve({value: undefined, done: true});
|
|
125
|
+
}
|
|
126
|
+
if (this.ended) {
|
|
127
|
+
return this.error ? Promise.reject(this.error) : Promise.resolve({value: undefined, done: true});
|
|
128
|
+
}
|
|
129
|
+
const buffered = this.buffer.shift();
|
|
130
|
+
if (buffered !== undefined) {
|
|
131
|
+
return Promise.resolve({value: buffered, done: false});
|
|
132
|
+
}
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const onAbort = () => {
|
|
135
|
+
this.waiter = undefined;
|
|
136
|
+
resolve({value: undefined, done: true});
|
|
137
|
+
};
|
|
138
|
+
signal.addEventListener('abort', onAbort, {once: true});
|
|
139
|
+
this.waiter = {
|
|
140
|
+
resolve: (result) => {
|
|
141
|
+
signal.removeEventListener('abort', onAbort);
|
|
142
|
+
resolve(result);
|
|
143
|
+
},
|
|
144
|
+
reject: (err) => {
|
|
145
|
+
signal.removeEventListener('abort', onAbort);
|
|
146
|
+
reject(err);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A live video stream from `NativeSimctl.startVideoStream` — encodes the device's display in real
|
|
155
|
+
* time via VideoToolbox, unlike `startVideoRecording`, which drives CoreSimulator's own private,
|
|
156
|
+
* file-only recorder. Mirrors `appium-ios-remotexpc`'s `ScreenStreamCapture` shape
|
|
157
|
+
* (`accessUnits()`/`stop()`) for API consistency; the transport is otherwise unrelated.
|
|
158
|
+
*/
|
|
159
|
+
export class VideoStream extends EventEmitter {
|
|
160
|
+
private handle: NativeVideoStreamHandle | undefined;
|
|
161
|
+
private readonly stopController = new AbortController();
|
|
162
|
+
private stopPromise: Promise<void> | undefined;
|
|
163
|
+
// Requests a fresh keyframe on resync so delivery can resume immediately rather than waiting
|
|
164
|
+
// for the next periodic one — `handle` may not be attached yet on a startup-time overflow
|
|
165
|
+
// (vanishingly unlikely given MAX_BUFFERED_UNITS), in which case this is just a no-op.
|
|
166
|
+
private readonly queue = new AccessUnitQueue(() => this.handle?.requestKeyFrame());
|
|
167
|
+
private activeConsumers = 0;
|
|
168
|
+
|
|
169
|
+
/** @internal */
|
|
170
|
+
constructor(public readonly codec: 'h264' | 'hevc') {
|
|
171
|
+
super();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @internal */
|
|
175
|
+
_handleAccessUnit(unit: VideoAccessUnit): void {
|
|
176
|
+
this.queue.push(unit);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @internal
|
|
181
|
+
* An active `accessUnits()` consumer receives the error via the queue itself (thrown out of its
|
|
182
|
+
* `for await` loop, per that method's contract) rather than the `'error'` event, so `emit` is
|
|
183
|
+
* only used for an explicit external listener; with neither, it's logged instead of lost.
|
|
184
|
+
*/
|
|
185
|
+
_handleError(err: unknown): void {
|
|
186
|
+
const error = toTypedError(err);
|
|
187
|
+
this.queue.fail(error);
|
|
188
|
+
if (this.listenerCount('error') > 0) {
|
|
189
|
+
this.emit('error', error);
|
|
190
|
+
} else if (this.activeConsumers === 0) {
|
|
191
|
+
log.error(`Unhandled VideoStream error: ${error.stack ?? error}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @internal */
|
|
196
|
+
_attachHandle(handle: NativeVideoStreamHandle): void {
|
|
197
|
+
this.handle = handle;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Yields each encoded access unit as it's produced, until {@link stop} is called or the stream
|
|
202
|
+
* errors (in which case the error is thrown out of the loop). Pass `signal` to stop iterating
|
|
203
|
+
* without treating that as an error. Mirrors `ScreenStreamCapture.accessUnits()`'s shape.
|
|
204
|
+
*
|
|
205
|
+
* Only one active consumer is supported at a time — a second concurrent call rejects rather
|
|
206
|
+
* than silently sharing (and corrupting) the first one's single internal waiter slot.
|
|
207
|
+
*/
|
|
208
|
+
async *accessUnits(signal?: AbortSignal): AsyncGenerator<VideoAccessUnit> {
|
|
209
|
+
if (this.activeConsumers > 0) {
|
|
210
|
+
throw new Error(SINGLE_CONSUMER_ERROR);
|
|
211
|
+
}
|
|
212
|
+
const combined = signal ? AbortSignal.any([signal, this.stopController.signal]) : this.stopController.signal;
|
|
213
|
+
this.activeConsumers++;
|
|
214
|
+
try {
|
|
215
|
+
for (;;) {
|
|
216
|
+
const result = await this.queue.next(combined);
|
|
217
|
+
if (result.done) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
yield result.value;
|
|
221
|
+
}
|
|
222
|
+
} finally {
|
|
223
|
+
this.activeConsumers--;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Stops the stream and releases the underlying encoder. Idempotent, including concurrently. */
|
|
228
|
+
async stop(): Promise<void> {
|
|
229
|
+
this.stopPromise ??= (async () => {
|
|
230
|
+
this.stopController.abort();
|
|
231
|
+
this.queue.end();
|
|
232
|
+
await this.handle?.stop();
|
|
233
|
+
})();
|
|
234
|
+
return this.stopPromise;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Starts encoding the device's display in real time. Resolves once the encoder has actually
|
|
240
|
+
* started; the returned {@link VideoStream}'s `accessUnits()` then yields each frame as it
|
|
241
|
+
* arrives. Independent of `startVideoRecording`/`stopVideoRecording` — both, and any number of
|
|
242
|
+
* concurrent streams, can run on the same device at once.
|
|
243
|
+
*
|
|
244
|
+
* @param udid — UDID of the device to stream; must be booted
|
|
245
|
+
* @param options — `displayId`, `codec`, `fps`, `bitrate` — see {@link VideoStreamOptions}
|
|
246
|
+
*/
|
|
247
|
+
export async function startVideoStream(
|
|
248
|
+
this: NativeSimctl,
|
|
249
|
+
udid: string,
|
|
250
|
+
options: VideoStreamOptions = {},
|
|
251
|
+
): Promise<VideoStream> {
|
|
252
|
+
// The native poller clamps below 1 fps to 1 fps rather than actually polling that slowly, so a
|
|
253
|
+
// sub-1 value here would silently poll far more often than requested — rejected instead.
|
|
254
|
+
if (options.fps !== undefined && (!Number.isFinite(options.fps) || options.fps < 1)) {
|
|
255
|
+
throw new RangeError(`fps must be a finite number >= 1, got ${options.fps}`);
|
|
256
|
+
}
|
|
257
|
+
if (
|
|
258
|
+
options.bitrate !== undefined &&
|
|
259
|
+
(!Number.isFinite(options.bitrate) || options.bitrate <= 0 || options.bitrate > 2 ** 31 - 1)
|
|
260
|
+
) {
|
|
261
|
+
throw new RangeError(`bitrate must be a positive number no greater than ${2 ** 31 - 1}, got ${options.bitrate}`);
|
|
262
|
+
}
|
|
263
|
+
const device = await runCatchingAsync(() => this._findDevice(udid));
|
|
264
|
+
const stream = new VideoStream(options.codec === 'hevc' ? 'hevc' : 'h264');
|
|
265
|
+
const handle = await runCatchingAsync(() =>
|
|
266
|
+
device.startVideoStream(
|
|
267
|
+
options,
|
|
268
|
+
(unit) => stream._handleAccessUnit(unit),
|
|
269
|
+
(err) => stream._handleError(err),
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
stream._attachHandle(handle);
|
|
273
|
+
return stream;
|
|
274
|
+
}
|