@appium/coresim 1.4.0 → 1.6.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 +12 -0
- package/README.md +14 -0
- package/binding.gyp +14 -1
- package/lib/src/commands/video-recording.d.ts +57 -0
- package/lib/src/commands/video-recording.d.ts.map +1 -0
- package/lib/src/commands/video-recording.js +123 -0
- package/lib/src/commands/video-recording.js.map +1 -0
- package/lib/src/commands/video-stream.d.ts +92 -0
- package/lib/src/commands/video-stream.d.ts.map +1 -0
- package/lib/src/commands/video-stream.js +242 -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 +21 -1
- package/lib/src/native-simctl.js.map +1 -1
- package/lib/src/types.d.ts +134 -0
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/utils/index.d.ts +1 -1
- package/lib/src/utils/index.d.ts.map +1 -1
- package/lib/src/utils/index.js +1 -1
- package/lib/src/utils/index.js.map +1 -1
- package/lib/src/utils/run-catching.d.ts +4 -0
- package/lib/src/utils/run-catching.d.ts.map +1 -1
- package/lib/src/utils/run-catching.js +11 -0
- package/lib/src/utils/run-catching.js.map +1 -1
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@appium+coresim.node +0 -0
- package/src/commands/video-recording.ts +158 -0
- package/src/commands/video-stream.ts +283 -0
- package/src/coresim.mm +708 -1
- package/src/index.ts +4 -0
- package/src/native/audio_encoder.h +65 -0
- package/src/native/audio_encoder.mm +270 -0
- package/src/native/av_recording.h +52 -0
- package/src/native/av_recording.mm +424 -0
- package/src/native/av_stream.h +70 -0
- package/src/native/av_stream.mm +217 -0
- package/src/native/monotonic_clock.h +20 -0
- package/src/native/sim_audio_tap.h +67 -0
- package/src/native/sim_audio_tap.mm +350 -0
- package/src/native/sim_process.h +10 -0
- package/src/native/sim_process.mm +81 -18
- 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 +64 -0
- package/src/native/sim_video_stream.mm +71 -0
- package/src/native/video_encoder.h +76 -0
- package/src/native/video_encoder.mm +417 -0
- package/src/native-simctl.ts +22 -1
- package/src/types.ts +149 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/run-catching.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.6.0](https://github.com/appium/coresim/compare/v1.5.0...v1.6.0) (2026-09-23)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add optional audio capture to video recording and streaming ([#18](https://github.com/appium/coresim/issues/18)) ([e634078](https://github.com/appium/coresim/commit/e634078669db724a676bf95fa074a89d3ac5a9c8))
|
|
6
|
+
|
|
7
|
+
## [1.5.0](https://github.com/appium/coresim/compare/v1.4.0...v1.5.0) (2026-09-22)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add video recording API ([#17](https://github.com/appium/coresim/issues/17)) ([d399403](https://github.com/appium/coresim/commit/d399403c1d329f303ab51726fa382313cf09ad0f))
|
|
12
|
+
|
|
1
13
|
## [1.4.0](https://github.com/appium/coresim/compare/v1.3.0...v1.4.0) (2026-09-19)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/README.md
CHANGED
|
@@ -54,6 +54,20 @@ await sim.deleteDevice(device.udid);
|
|
|
54
54
|
readiness with `getBootStatus()`/`waitForBoot()`.
|
|
55
55
|
- **Apps** — install, remove, launch, terminate, and inspect apps.
|
|
56
56
|
- **Processes** — spawn a process on the simulator and stream its stdout/stderr live.
|
|
57
|
+
- **Screen capture** — screenshots, video recording to a file, and a real-time encoded video
|
|
58
|
+
stream — optionally with the device's own audio, muxed into the recording or interleaved into
|
|
59
|
+
the stream (`audio: true` on `startVideoRecording`/`startVideoStream`). Audio capture requires:
|
|
60
|
+
- **macOS 14.2+** on the host (Core Audio process taps).
|
|
61
|
+
- The host's **"System Audio Recording Only"** privacy permission (System Settings > Privacy &
|
|
62
|
+
Security > Screen & System Audio Recording). This can't be granted programmatically, and a
|
|
63
|
+
denial isn't a thrown error — it silently produces an audio-less/near-silent track.
|
|
64
|
+
- A default audio output device on the host, and a booted simulator that has produced audio at
|
|
65
|
+
least once (e.g. a foreground app that plays sound) — an empty guest process set throws.
|
|
66
|
+
- Uses a host-side Core Audio API, not anything iOS-version-specific, so it's expected to work
|
|
67
|
+
on any simulator runtime; only Xcode 26.x has actually been exercised so far.
|
|
68
|
+
|
|
69
|
+
On some CI/headless hosts, starting an audio capture can block for a while before failing —
|
|
70
|
+
see [`CLAUDE.md`](./CLAUDE.md) for the known root cause and current workaround.
|
|
57
71
|
- **Simulator settings** — appearance (light/dark), accessibility (increase contrast, content
|
|
58
72
|
size), location, and permissions.
|
|
59
73
|
- **Extras** — keychain certificates, push notifications, and Darwin notifications.
|
package/binding.gyp
CHANGED
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
"src/native/tcc_privacy.mm",
|
|
14
14
|
"src/native/sim_pasteboard.mm",
|
|
15
15
|
"src/native/sim_process.mm",
|
|
16
|
-
"src/native/
|
|
16
|
+
"src/native/sim_audio_tap.mm",
|
|
17
|
+
"src/native/audio_encoder.mm",
|
|
18
|
+
"src/native/av_recording.mm",
|
|
19
|
+
"src/native/av_stream.mm",
|
|
20
|
+
"src/native/sim_screenshot.mm",
|
|
21
|
+
"src/native/sim_video_recording.mm",
|
|
22
|
+
"src/native/video_encoder.mm",
|
|
23
|
+
"src/native/sim_video_stream.mm"
|
|
17
24
|
],
|
|
18
25
|
"include_dirs": [
|
|
19
26
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
@@ -42,9 +49,15 @@
|
|
|
42
49
|
"OTHER_LDFLAGS": [
|
|
43
50
|
"-framework", "Foundation",
|
|
44
51
|
"-framework", "AppKit",
|
|
52
|
+
"-framework", "AudioToolbox",
|
|
53
|
+
"-framework", "AVFoundation",
|
|
54
|
+
"-framework", "CoreAudio",
|
|
45
55
|
"-framework", "CoreImage",
|
|
56
|
+
"-framework", "CoreMedia",
|
|
57
|
+
"-framework", "CoreVideo",
|
|
46
58
|
"-framework", "ImageIO",
|
|
47
59
|
"-framework", "IOSurface",
|
|
60
|
+
"-framework", "VideoToolbox",
|
|
48
61
|
"-lsqlite3"
|
|
49
62
|
]
|
|
50
63
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { NativeSimctl } from '../native-simctl.js';
|
|
2
|
+
import type { VideoRecordingOptions } from '../types.js';
|
|
3
|
+
declare module '../native-simctl.js' {
|
|
4
|
+
interface NativeSimctl {
|
|
5
|
+
startVideoRecording(udid: string, outputFile: string, options?: VideoRecordingOptions): Promise<void>;
|
|
6
|
+
stopVideoRecording(udid: string): Promise<void>;
|
|
7
|
+
isVideoRecording(udid: string): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Starts recording the device's display (and, with `options.audio`, its audio too, muxed as a
|
|
12
|
+
* second track) to `outputFile` — the native equivalent of `simctl io <udid> recordVideo` when
|
|
13
|
+
* both `audio` and `fps` are unset. Resolves once the first frame has actually been recorded, so
|
|
14
|
+
* it's always safe to call {@link stopVideoRecording} immediately after. Only one recording may be
|
|
15
|
+
* active per device at a time; starting a second one while the first is still running rejects.
|
|
16
|
+
*
|
|
17
|
+
* Without `audio`/`fps`, rejects with `NativeSimUnavailableError` if this CoreSimulator predates
|
|
18
|
+
* the private capture API — confirmed missing on Xcode 16.4's, present on Xcode 26.5+ (Apple
|
|
19
|
+
* documents no exact version floor). With either set, see {@link VideoRecordingOptions.audio}'s
|
|
20
|
+
* own doc comment for the host-permission requirement that applies specifically to `audio`; a
|
|
21
|
+
* failure *after* this call has already resolved (e.g. the audio tap's guest process set vanishing
|
|
22
|
+
* mid-recording) doesn't reject it — it's logged here instead — call {@link stopVideoRecording} to
|
|
23
|
+
* observe it as a rejection and release the recording's resources.
|
|
24
|
+
*
|
|
25
|
+
* @param udid — UDID of the device to record; must be booted
|
|
26
|
+
* @param outputFile — filesystem path to write the video to; resolved against `process.cwd()` if
|
|
27
|
+
* relative, since the native layer requires an absolute path
|
|
28
|
+
* @param options — `displayId`, `codec`, `mask`, `audio`, `fps`, `bitrate` — see {@link VideoRecordingOptions}
|
|
29
|
+
* @throws {Error} if a recording is already in progress for this device
|
|
30
|
+
*/
|
|
31
|
+
export declare function startVideoRecording(this: NativeSimctl, udid: string, outputFile: string, options?: VideoRecordingOptions): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Stops a recording previously started by {@link startVideoRecording} on the same device.
|
|
34
|
+
* Resolves once the video file has been finalized on disk and is safe to read. If `start` is still
|
|
35
|
+
* in flight (e.g. a concurrent caller stopping as soon as {@link isVideoRecording} turns `true`),
|
|
36
|
+
* waits for it to settle first — issuing the native stop call any earlier is a silent CoreSimulator
|
|
37
|
+
* race (see CLAUDE.md).
|
|
38
|
+
*
|
|
39
|
+
* Safe to call concurrently or to retry after a failure: every concurrent call for the same
|
|
40
|
+
* recording shares a single in-flight native stop call rather than each issuing its own (which
|
|
41
|
+
* could otherwise stop whatever *new* recording has since started on this device instead), and a
|
|
42
|
+
* failed stop — even one that failed before ever reaching the native recorder, e.g. a transient
|
|
43
|
+
* device-lookup error — leaves the recording tracked as still active so a retry can reach the
|
|
44
|
+
* native call rather than the caller losing the ability to stop it at all.
|
|
45
|
+
*
|
|
46
|
+
* @param udid — UDID of the device to stop recording
|
|
47
|
+
* @throws {Error} if no recording is currently in progress for this device
|
|
48
|
+
*/
|
|
49
|
+
export declare function stopVideoRecording(this: NativeSimctl, udid: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Whether a recording started by {@link startVideoRecording} is currently active for this device.
|
|
52
|
+
* Pure local state — no CoreSimulator dispatch — so this never rejects with a native error.
|
|
53
|
+
*
|
|
54
|
+
* @param udid — UDID of the device to check
|
|
55
|
+
*/
|
|
56
|
+
export declare function isVideoRecording(this: NativeSimctl, udid: string): Promise<boolean>;
|
|
57
|
+
//# sourceMappingURL=video-recording.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-recording.d.ts","sourceRoot":"","sources":["../../../src/commands/video-recording.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAA6B,qBAAqB,EAAC,MAAM,aAAa,CAAC;AAGnF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,YAAY;QACpB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACtG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAClD;CACF;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BxF;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { logger } from '@appium/support';
|
|
3
|
+
import { runCatchingAsync, toTypedError } from '../utils/index.js';
|
|
4
|
+
const log = logger.getLogger('CoreSim');
|
|
5
|
+
// Tracks which devices currently have an active recording — module-level since the constraint (at
|
|
6
|
+
// most one per device) is CoreSimulator's own, not per-NativeSimctl-instance.
|
|
7
|
+
const activeRecordings = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Starts recording the device's display (and, with `options.audio`, its audio too, muxed as a
|
|
10
|
+
* second track) to `outputFile` — the native equivalent of `simctl io <udid> recordVideo` when
|
|
11
|
+
* both `audio` and `fps` are unset. Resolves once the first frame has actually been recorded, so
|
|
12
|
+
* it's always safe to call {@link stopVideoRecording} immediately after. Only one recording may be
|
|
13
|
+
* active per device at a time; starting a second one while the first is still running rejects.
|
|
14
|
+
*
|
|
15
|
+
* Without `audio`/`fps`, rejects with `NativeSimUnavailableError` if this CoreSimulator predates
|
|
16
|
+
* the private capture API — confirmed missing on Xcode 16.4's, present on Xcode 26.5+ (Apple
|
|
17
|
+
* documents no exact version floor). With either set, see {@link VideoRecordingOptions.audio}'s
|
|
18
|
+
* own doc comment for the host-permission requirement that applies specifically to `audio`; a
|
|
19
|
+
* failure *after* this call has already resolved (e.g. the audio tap's guest process set vanishing
|
|
20
|
+
* mid-recording) doesn't reject it — it's logged here instead — call {@link stopVideoRecording} to
|
|
21
|
+
* observe it as a rejection and release the recording's resources.
|
|
22
|
+
*
|
|
23
|
+
* @param udid — UDID of the device to record; must be booted
|
|
24
|
+
* @param outputFile — filesystem path to write the video to; resolved against `process.cwd()` if
|
|
25
|
+
* relative, since the native layer requires an absolute path
|
|
26
|
+
* @param options — `displayId`, `codec`, `mask`, `audio`, `fps`, `bitrate` — see {@link VideoRecordingOptions}
|
|
27
|
+
* @throws {Error} if a recording is already in progress for this device
|
|
28
|
+
*/
|
|
29
|
+
export async function startVideoRecording(udid, outputFile, options = {}) {
|
|
30
|
+
// Mirrors startVideoStream's identical validation — both reach the same native encoder path,
|
|
31
|
+
// which otherwise silently truncates an out-of-range value instead of rejecting it.
|
|
32
|
+
if (options.fps !== undefined && (!Number.isFinite(options.fps) || options.fps < 1)) {
|
|
33
|
+
throw new RangeError(`fps must be a finite number >= 1, got ${options.fps}`);
|
|
34
|
+
}
|
|
35
|
+
if (options.bitrate !== undefined &&
|
|
36
|
+
(!Number.isFinite(options.bitrate) || options.bitrate <= 0 || options.bitrate > 2 ** 31 - 1)) {
|
|
37
|
+
throw new RangeError(`bitrate must be a positive number no greater than ${2 ** 31 - 1}, got ${options.bitrate}`);
|
|
38
|
+
}
|
|
39
|
+
const key = udid.toLowerCase();
|
|
40
|
+
if (activeRecordings.has(key)) {
|
|
41
|
+
throw new Error(`A video recording is already in progress for device '${udid}'`);
|
|
42
|
+
}
|
|
43
|
+
const absoluteOutputFile = path.resolve(outputFile);
|
|
44
|
+
const startPromise = runCatchingAsync(async () => (await this._findDevice(udid)).startVideoRecording(absoluteOutputFile, options, (err) => {
|
|
45
|
+
// Only ever invoked on the `audio`/`fps` (own-encoder) path — see its own doc comment above.
|
|
46
|
+
// No live consumer to report to (this call already resolved), so logged instead of lost.
|
|
47
|
+
log.error(`Unhandled video recording error for device '${udid}': ${toTypedError(err).stack ?? err}`);
|
|
48
|
+
}));
|
|
49
|
+
// Marked before the native call resolves, not after, so a concurrent startVideoRecording for the
|
|
50
|
+
// same device is rejected immediately instead of racing this one — rolled back below on failure.
|
|
51
|
+
const state = { start: startPromise };
|
|
52
|
+
activeRecordings.set(key, state);
|
|
53
|
+
try {
|
|
54
|
+
await startPromise;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Only clear our own entry — a concurrent stopVideoRecording (see below) may already have.
|
|
58
|
+
if (activeRecordings.get(key) === state) {
|
|
59
|
+
activeRecordings.delete(key);
|
|
60
|
+
}
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Stops a recording previously started by {@link startVideoRecording} on the same device.
|
|
66
|
+
* Resolves once the video file has been finalized on disk and is safe to read. If `start` is still
|
|
67
|
+
* in flight (e.g. a concurrent caller stopping as soon as {@link isVideoRecording} turns `true`),
|
|
68
|
+
* waits for it to settle first — issuing the native stop call any earlier is a silent CoreSimulator
|
|
69
|
+
* race (see CLAUDE.md).
|
|
70
|
+
*
|
|
71
|
+
* Safe to call concurrently or to retry after a failure: every concurrent call for the same
|
|
72
|
+
* recording shares a single in-flight native stop call rather than each issuing its own (which
|
|
73
|
+
* could otherwise stop whatever *new* recording has since started on this device instead), and a
|
|
74
|
+
* failed stop — even one that failed before ever reaching the native recorder, e.g. a transient
|
|
75
|
+
* device-lookup error — leaves the recording tracked as still active so a retry can reach the
|
|
76
|
+
* native call rather than the caller losing the ability to stop it at all.
|
|
77
|
+
*
|
|
78
|
+
* @param udid — UDID of the device to stop recording
|
|
79
|
+
* @throws {Error} if no recording is currently in progress for this device
|
|
80
|
+
*/
|
|
81
|
+
export async function stopVideoRecording(udid) {
|
|
82
|
+
const key = udid.toLowerCase();
|
|
83
|
+
const state = activeRecordings.get(key);
|
|
84
|
+
if (!state) {
|
|
85
|
+
throw new Error(`No video recording is in progress for device '${udid}'`);
|
|
86
|
+
}
|
|
87
|
+
let handle;
|
|
88
|
+
try {
|
|
89
|
+
handle = await state.start;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// start itself failed — its own catch already cleaned up activeRecordings; nothing to stop.
|
|
93
|
+
throw new Error(`No video recording is in progress for device '${udid}'`);
|
|
94
|
+
}
|
|
95
|
+
const stopPromise = (state.stop ??= runCatchingAsync(async () => {
|
|
96
|
+
await handle.stop();
|
|
97
|
+
}));
|
|
98
|
+
try {
|
|
99
|
+
await stopPromise;
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
// Only clear our own attempt, and only if it's still the current one — a future call must be
|
|
103
|
+
// able to retry the native stop rather than replaying this same rejection forever, but must
|
|
104
|
+
// not clobber a newer attempt another concurrent caller may have already started instead.
|
|
105
|
+
if (state.stop === stopPromise) {
|
|
106
|
+
state.stop = undefined;
|
|
107
|
+
}
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
if (activeRecordings.get(key) === state) {
|
|
111
|
+
activeRecordings.delete(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether a recording started by {@link startVideoRecording} is currently active for this device.
|
|
116
|
+
* Pure local state — no CoreSimulator dispatch — so this never rejects with a native error.
|
|
117
|
+
*
|
|
118
|
+
* @param udid — UDID of the device to check
|
|
119
|
+
*/
|
|
120
|
+
export async function isVideoRecording(udid) {
|
|
121
|
+
return activeRecordings.has(udid.toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=video-recording.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-recording.js","sourceRoot":"","sources":["../../../src/commands/video-recording.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { NativeSimctl } from '../native-simctl.js';
|
|
3
|
+
import type { VideoAccessUnit, VideoStreamOptions } from '../types.js';
|
|
4
|
+
declare module '../native-simctl.js' {
|
|
5
|
+
interface NativeSimctl {
|
|
6
|
+
startVideoStream(udid: string, options?: VideoStreamOptions): Promise<VideoStream>;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare const MAX_BUFFERED_UNITS = 60;
|
|
10
|
+
/**
|
|
11
|
+
* Single-consumer FIFO between native's per-frame callback and `accessUnits()`. Unlike routing
|
|
12
|
+
* through `EventEmitter`, a unit pushed before any consumer has started iterating is retained
|
|
13
|
+
* (fixing the encoder's own first-frame/keyframe otherwise being lost to a startup race) rather
|
|
14
|
+
* than silently dropped.
|
|
15
|
+
*
|
|
16
|
+
* Bounded at `MAX_BUFFERED_UNITS`, but resync-aware about *how* it sheds load once a slow consumer
|
|
17
|
+
* falls behind: since interframes only reference earlier frames they were encoded against (no
|
|
18
|
+
* `AllowFrameReordering`), dropping an arbitrary one would orphan every later one from its
|
|
19
|
+
* reference chain, corrupting decode from that point on even though delivery looks unbroken.
|
|
20
|
+
* Overflow instead clears the backlog entirely and enters a resync state — `dropsDuringResync`
|
|
21
|
+
* decides which further units to discard (not buffer) until `endsResync` sees a self-decodable
|
|
22
|
+
* point to resume clean delivery from. Generic (not hardcoded to video's own reference-chain
|
|
23
|
+
* concern) so `VideoStream` can reuse this same machinery when it's carrying interleaved audio
|
|
24
|
+
* too (`VideoStreamOptions.audio`) — an audio unit is always independently decodable, so it's
|
|
25
|
+
* configured to never be dropped and never itself end a resync; only video interframes are.
|
|
26
|
+
*/
|
|
27
|
+
export declare class AccessUnitQueue<T> {
|
|
28
|
+
private readonly opts;
|
|
29
|
+
private readonly buffer;
|
|
30
|
+
private waiter;
|
|
31
|
+
private ended;
|
|
32
|
+
private error;
|
|
33
|
+
private resyncing;
|
|
34
|
+
constructor(opts: {
|
|
35
|
+
/** Whether `unit` should be discarded (not buffered) while resyncing. */
|
|
36
|
+
dropsDuringResync: (unit: T) => boolean;
|
|
37
|
+
/** Whether `unit` is a self-decodable point resync can resume clean delivery from. */
|
|
38
|
+
endsResync: (unit: T) => boolean;
|
|
39
|
+
/** Called once when overflow first forces a resync, e.g. to request a fresh keyframe. */
|
|
40
|
+
onOverflow?: () => void;
|
|
41
|
+
});
|
|
42
|
+
push(unit: T): void;
|
|
43
|
+
/** Ends the queue with an error — any pending or future `next()` rejects with it. */
|
|
44
|
+
fail(err: unknown): void;
|
|
45
|
+
/** Ends the queue cleanly — any pending or future `next()` resolves `done`. */
|
|
46
|
+
end(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Resolves `done` (not rejects) if `signal` aborts while waiting, mirroring `events.on()`.
|
|
49
|
+
* Checks cancellation/end *before* dequeuing, so an already-aborted signal or an already-ended
|
|
50
|
+
* queue never hands out a stale buffered unit — an aborted consumer, or a fresh iterator started
|
|
51
|
+
* after `stop()`, must see the boundary immediately rather than draining leftovers first.
|
|
52
|
+
*/
|
|
53
|
+
next(signal: AbortSignal): Promise<IteratorResult<T>>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A live video stream from `NativeSimctl.startVideoStream` — encodes the device's display (and,
|
|
57
|
+
* with `options.audio`, its audio too — see {@link VideoStreamOptions}) in real time via
|
|
58
|
+
* VideoToolbox/Core Audio, unlike `startVideoRecording`, which drives CoreSimulator's own private,
|
|
59
|
+
* file-only recorder. Mirrors `appium-ios-remotexpc`'s `ScreenStreamCapture` shape
|
|
60
|
+
* (`accessUnits()`/`stop()`) for API consistency; the transport is otherwise unrelated.
|
|
61
|
+
*/
|
|
62
|
+
export declare class VideoStream extends EventEmitter {
|
|
63
|
+
readonly codec: 'h264' | 'hevc';
|
|
64
|
+
private handle;
|
|
65
|
+
private readonly stopController;
|
|
66
|
+
private stopPromise;
|
|
67
|
+
private readonly queue;
|
|
68
|
+
private activeConsumers;
|
|
69
|
+
/**
|
|
70
|
+
* Yields each encoded access unit as it's produced, until {@link stop} is called or the stream
|
|
71
|
+
* errors (in which case the error is thrown out of the loop). Pass `signal` to stop iterating
|
|
72
|
+
* without treating that as an error. Mirrors `ScreenStreamCapture.accessUnits()`'s shape.
|
|
73
|
+
*
|
|
74
|
+
* Only one active consumer is supported at a time — a second concurrent call rejects rather
|
|
75
|
+
* than silently sharing (and corrupting) the first one's single internal waiter slot.
|
|
76
|
+
*/
|
|
77
|
+
accessUnits(signal?: AbortSignal): AsyncGenerator<VideoAccessUnit>;
|
|
78
|
+
/** Stops the stream and releases the underlying encoder. Idempotent, including concurrently. */
|
|
79
|
+
stop(): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Starts encoding the device's display (and, with `options.audio`, its audio) in real time.
|
|
83
|
+
* Resolves once the encoder(s) have actually started; the returned {@link VideoStream}'s
|
|
84
|
+
* `accessUnits()` then yields each unit as it arrives. Independent of
|
|
85
|
+
* `startVideoRecording`/`stopVideoRecording` — both, and any number of concurrent streams, can
|
|
86
|
+
* run on the same device at once.
|
|
87
|
+
*
|
|
88
|
+
* @param udid — UDID of the device to stream; must be booted
|
|
89
|
+
* @param options — `displayId`, `codec`, `fps`, `bitrate`, `audio` — see {@link VideoStreamOptions}
|
|
90
|
+
*/
|
|
91
|
+
export declare function startVideoStream(this: NativeSimctl, udid: string, options?: VideoStreamOptions): Promise<VideoStream>;
|
|
92
|
+
//# sourceMappingURL=video-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stream.d.ts","sourceRoot":"","sources":["../../../src/commands/video-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAIzC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAA0B,eAAe,EAAE,kBAAkB,EAAC,MAAM,aAAa,CAAC;AAG9F,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,YAAY;QACpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;KACpF;CACF;AASD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAKrC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAe,CAAC,CAAC;IAQ1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,MAAM,CAA6F;IAC3G,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,SAAS,CAAS;gBAGP,IAAI,EAAE;QACrB,yEAAyE;QACzE,iBAAiB,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;QACxC,sFAAsF;QACtF,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;QACjC,yFAAyF;QACzF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;KACzB;IAGH,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IA0BnB,qFAAqF;IACrF,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAcxB,+EAA+E;IAC/E,GAAG,IAAI,IAAI;IAaX;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CA6BtD;AAED;;;;;;GAMG;AACH,qBAAa,WAAY,SAAQ,YAAY;aAiBf,KAAK,EAAE,MAAM,GAAG,MAAM;IAhBlD,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,WAAW,CAA4B;IAM/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;IACH,OAAO,CAAC,eAAe,CAAK;IAiC5B;;;;;;;OAOG;IACI,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,eAAe,CAAC;IAmBzE,gGAAgG;IAC1F,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,CAuBtB"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { logger } from '@appium/support';
|
|
3
|
+
import { runCatchingAsync, toTypedError } from '../utils/index.js';
|
|
4
|
+
const log = logger.getLogger('CoreSim');
|
|
5
|
+
// Matches the native side's own ThreadSafeFunction queue bound (see coresim.mm's
|
|
6
|
+
// kAccessUnitQueueSize) — kept here too since the native bound alone provides no real
|
|
7
|
+
// backpressure: _handleAccessUnit's emit-equivalent push always returns immediately, regardless of
|
|
8
|
+
// how slow the actual accessUnits() consumer is, so without a bound of its own this queue could
|
|
9
|
+
// otherwise grow without limit while a slow consumer falls behind.
|
|
10
|
+
export const MAX_BUFFERED_UNITS = 60;
|
|
11
|
+
const SINGLE_CONSUMER_ERROR = 'VideoStream.accessUnits() supports only one active consumer at a time — a second concurrent call rejects.';
|
|
12
|
+
/**
|
|
13
|
+
* Single-consumer FIFO between native's per-frame callback and `accessUnits()`. Unlike routing
|
|
14
|
+
* through `EventEmitter`, a unit pushed before any consumer has started iterating is retained
|
|
15
|
+
* (fixing the encoder's own first-frame/keyframe otherwise being lost to a startup race) rather
|
|
16
|
+
* than silently dropped.
|
|
17
|
+
*
|
|
18
|
+
* Bounded at `MAX_BUFFERED_UNITS`, but resync-aware about *how* it sheds load once a slow consumer
|
|
19
|
+
* falls behind: since interframes only reference earlier frames they were encoded against (no
|
|
20
|
+
* `AllowFrameReordering`), dropping an arbitrary one would orphan every later one from its
|
|
21
|
+
* reference chain, corrupting decode from that point on even though delivery looks unbroken.
|
|
22
|
+
* Overflow instead clears the backlog entirely and enters a resync state — `dropsDuringResync`
|
|
23
|
+
* decides which further units to discard (not buffer) until `endsResync` sees a self-decodable
|
|
24
|
+
* point to resume clean delivery from. Generic (not hardcoded to video's own reference-chain
|
|
25
|
+
* concern) so `VideoStream` can reuse this same machinery when it's carrying interleaved audio
|
|
26
|
+
* too (`VideoStreamOptions.audio`) — an audio unit is always independently decodable, so it's
|
|
27
|
+
* configured to never be dropped and never itself end a resync; only video interframes are.
|
|
28
|
+
*/
|
|
29
|
+
export class AccessUnitQueue {
|
|
30
|
+
opts;
|
|
31
|
+
buffer = [];
|
|
32
|
+
waiter;
|
|
33
|
+
ended = false;
|
|
34
|
+
error;
|
|
35
|
+
resyncing = false;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
}
|
|
39
|
+
push(unit) {
|
|
40
|
+
if (this.ended) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (this.resyncing) {
|
|
44
|
+
if (this.opts.dropsDuringResync(unit)) {
|
|
45
|
+
return; // still waiting for a self-decodable point to resume delivery from
|
|
46
|
+
}
|
|
47
|
+
if (this.opts.endsResync(unit)) {
|
|
48
|
+
this.resyncing = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (this.waiter) {
|
|
52
|
+
const { resolve } = this.waiter;
|
|
53
|
+
this.waiter = undefined;
|
|
54
|
+
resolve({ value: unit, done: false });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.buffer.push(unit);
|
|
58
|
+
if (this.buffer.length > MAX_BUFFERED_UNITS) {
|
|
59
|
+
this.buffer.length = 0;
|
|
60
|
+
this.resyncing = true;
|
|
61
|
+
this.opts.onOverflow?.();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Ends the queue with an error — any pending or future `next()` rejects with it. */
|
|
65
|
+
fail(err) {
|
|
66
|
+
if (this.ended) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.ended = true;
|
|
70
|
+
this.error = err;
|
|
71
|
+
this.buffer.length = 0;
|
|
72
|
+
if (this.waiter) {
|
|
73
|
+
const { reject } = this.waiter;
|
|
74
|
+
this.waiter = undefined;
|
|
75
|
+
reject(err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Ends the queue cleanly — any pending or future `next()` resolves `done`. */
|
|
79
|
+
end() {
|
|
80
|
+
if (this.ended) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.ended = true;
|
|
84
|
+
this.buffer.length = 0;
|
|
85
|
+
if (this.waiter) {
|
|
86
|
+
const { resolve } = this.waiter;
|
|
87
|
+
this.waiter = undefined;
|
|
88
|
+
resolve({ value: undefined, done: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolves `done` (not rejects) if `signal` aborts while waiting, mirroring `events.on()`.
|
|
93
|
+
* Checks cancellation/end *before* dequeuing, so an already-aborted signal or an already-ended
|
|
94
|
+
* queue never hands out a stale buffered unit — an aborted consumer, or a fresh iterator started
|
|
95
|
+
* after `stop()`, must see the boundary immediately rather than draining leftovers first.
|
|
96
|
+
*/
|
|
97
|
+
next(signal) {
|
|
98
|
+
if (signal.aborted) {
|
|
99
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
100
|
+
}
|
|
101
|
+
if (this.ended) {
|
|
102
|
+
return this.error ? Promise.reject(this.error) : Promise.resolve({ value: undefined, done: true });
|
|
103
|
+
}
|
|
104
|
+
const buffered = this.buffer.shift();
|
|
105
|
+
if (buffered !== undefined) {
|
|
106
|
+
return Promise.resolve({ value: buffered, done: false });
|
|
107
|
+
}
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const onAbort = () => {
|
|
110
|
+
this.waiter = undefined;
|
|
111
|
+
resolve({ value: undefined, done: true });
|
|
112
|
+
};
|
|
113
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
114
|
+
this.waiter = {
|
|
115
|
+
resolve: (result) => {
|
|
116
|
+
signal.removeEventListener('abort', onAbort);
|
|
117
|
+
resolve(result);
|
|
118
|
+
},
|
|
119
|
+
reject: (err) => {
|
|
120
|
+
signal.removeEventListener('abort', onAbort);
|
|
121
|
+
reject(err);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A live video stream from `NativeSimctl.startVideoStream` — encodes the device's display (and,
|
|
129
|
+
* with `options.audio`, its audio too — see {@link VideoStreamOptions}) in real time via
|
|
130
|
+
* VideoToolbox/Core Audio, unlike `startVideoRecording`, which drives CoreSimulator's own private,
|
|
131
|
+
* file-only recorder. Mirrors `appium-ios-remotexpc`'s `ScreenStreamCapture` shape
|
|
132
|
+
* (`accessUnits()`/`stop()`) for API consistency; the transport is otherwise unrelated.
|
|
133
|
+
*/
|
|
134
|
+
export class VideoStream extends EventEmitter {
|
|
135
|
+
codec;
|
|
136
|
+
handle;
|
|
137
|
+
stopController = new AbortController();
|
|
138
|
+
stopPromise;
|
|
139
|
+
// Requests a fresh keyframe on resync so delivery can resume immediately rather than waiting
|
|
140
|
+
// for the next periodic one — `handle` may not be attached yet on a startup-time overflow
|
|
141
|
+
// (vanishingly unlikely given MAX_BUFFERED_UNITS), in which case this is just a no-op. Only a
|
|
142
|
+
// video keyframe ends/is exempt from resync — an audio unit (when present) is always
|
|
143
|
+
// independently decodable, so it's never dropped and never itself ends a resync.
|
|
144
|
+
queue = new AccessUnitQueue({
|
|
145
|
+
dropsDuringResync: (unit) => unit.track === 'video' && !unit.isKeyFrame,
|
|
146
|
+
endsResync: (unit) => unit.track === 'video' && unit.isKeyFrame,
|
|
147
|
+
onOverflow: () => this.handle?.requestKeyFrame(),
|
|
148
|
+
});
|
|
149
|
+
activeConsumers = 0;
|
|
150
|
+
/** @internal */
|
|
151
|
+
constructor(codec) {
|
|
152
|
+
super();
|
|
153
|
+
this.codec = codec;
|
|
154
|
+
}
|
|
155
|
+
/** @internal */
|
|
156
|
+
_handleAccessUnit(unit) {
|
|
157
|
+
this.queue.push(unit);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @internal
|
|
161
|
+
* An active `accessUnits()` consumer receives the error via the queue itself (thrown out of its
|
|
162
|
+
* `for await` loop, per that method's contract) rather than the `'error'` event, so `emit` is
|
|
163
|
+
* only used for an explicit external listener; with neither, it's logged instead of lost.
|
|
164
|
+
*/
|
|
165
|
+
_handleError(err) {
|
|
166
|
+
const error = toTypedError(err);
|
|
167
|
+
this.queue.fail(error);
|
|
168
|
+
if (this.listenerCount('error') > 0) {
|
|
169
|
+
this.emit('error', error);
|
|
170
|
+
}
|
|
171
|
+
else if (this.activeConsumers === 0) {
|
|
172
|
+
log.error(`Unhandled VideoStream error: ${error.stack ?? error}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** @internal */
|
|
176
|
+
_attachHandle(handle) {
|
|
177
|
+
this.handle = handle;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Yields each encoded access unit as it's produced, until {@link stop} is called or the stream
|
|
181
|
+
* errors (in which case the error is thrown out of the loop). Pass `signal` to stop iterating
|
|
182
|
+
* without treating that as an error. Mirrors `ScreenStreamCapture.accessUnits()`'s shape.
|
|
183
|
+
*
|
|
184
|
+
* Only one active consumer is supported at a time — a second concurrent call rejects rather
|
|
185
|
+
* than silently sharing (and corrupting) the first one's single internal waiter slot.
|
|
186
|
+
*/
|
|
187
|
+
async *accessUnits(signal) {
|
|
188
|
+
if (this.activeConsumers > 0) {
|
|
189
|
+
throw new Error(SINGLE_CONSUMER_ERROR);
|
|
190
|
+
}
|
|
191
|
+
const combined = signal ? AbortSignal.any([signal, this.stopController.signal]) : this.stopController.signal;
|
|
192
|
+
this.activeConsumers++;
|
|
193
|
+
try {
|
|
194
|
+
for (;;) {
|
|
195
|
+
const result = await this.queue.next(combined);
|
|
196
|
+
if (result.done) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
yield result.value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
this.activeConsumers--;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Stops the stream and releases the underlying encoder. Idempotent, including concurrently. */
|
|
207
|
+
async stop() {
|
|
208
|
+
this.stopPromise ??= (async () => {
|
|
209
|
+
this.stopController.abort();
|
|
210
|
+
this.queue.end();
|
|
211
|
+
await this.handle?.stop();
|
|
212
|
+
})();
|
|
213
|
+
return this.stopPromise;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Starts encoding the device's display (and, with `options.audio`, its audio) in real time.
|
|
218
|
+
* Resolves once the encoder(s) have actually started; the returned {@link VideoStream}'s
|
|
219
|
+
* `accessUnits()` then yields each unit as it arrives. Independent of
|
|
220
|
+
* `startVideoRecording`/`stopVideoRecording` — both, and any number of concurrent streams, can
|
|
221
|
+
* run on the same device at once.
|
|
222
|
+
*
|
|
223
|
+
* @param udid — UDID of the device to stream; must be booted
|
|
224
|
+
* @param options — `displayId`, `codec`, `fps`, `bitrate`, `audio` — see {@link VideoStreamOptions}
|
|
225
|
+
*/
|
|
226
|
+
export async function startVideoStream(udid, options = {}) {
|
|
227
|
+
// The native poller clamps below 1 fps to 1 fps rather than actually polling that slowly, so a
|
|
228
|
+
// sub-1 value here would silently poll far more often than requested — rejected instead.
|
|
229
|
+
if (options.fps !== undefined && (!Number.isFinite(options.fps) || options.fps < 1)) {
|
|
230
|
+
throw new RangeError(`fps must be a finite number >= 1, got ${options.fps}`);
|
|
231
|
+
}
|
|
232
|
+
if (options.bitrate !== undefined &&
|
|
233
|
+
(!Number.isFinite(options.bitrate) || options.bitrate <= 0 || options.bitrate > 2 ** 31 - 1)) {
|
|
234
|
+
throw new RangeError(`bitrate must be a positive number no greater than ${2 ** 31 - 1}, got ${options.bitrate}`);
|
|
235
|
+
}
|
|
236
|
+
const device = await runCatchingAsync(() => this._findDevice(udid));
|
|
237
|
+
const stream = new VideoStream(options.codec === 'hevc' ? 'hevc' : 'h264');
|
|
238
|
+
const handle = await runCatchingAsync(() => device.startVideoStream(options, (unit) => stream._handleAccessUnit(unit), (err) => stream._handleError(err)));
|
|
239
|
+
stream._attachHandle(handle);
|
|
240
|
+
return stream;
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=video-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stream.js","sourceRoot":"","sources":["../../../src/commands/video-stream.ts"],"names":[],"mappings":"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"}
|
package/lib/src/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { NativeSimError, NativeSimUnavailableError, NativeSimDispatchError, NativeSimOperationError } from './errors.js';
|
|
2
2
|
export { NativeSimctl } from './native-simctl.js';
|
|
3
3
|
export { SpawnedProcess } from './commands/spawn.js';
|
|
4
|
+
export { VideoStream } from './commands/video-stream.js';
|
|
4
5
|
export type { AppContainerType } from './commands/app.js';
|
|
5
6
|
export type { BiometricName } from './commands/biometric.js';
|
|
6
|
-
export { SimBootStatus, SimDeviceState, type ApnsAlert, type ApnsPayload, type ApnsSound, type PushNotificationPayload, type ScreenshotOptions, type SimBootInfo, type SimDeviceInfo, type SimDeviceTypeInfo, type SimDisplayInfo, type SimPermissionService, type SimPermissionStatus, type SimProcessInfo, type SimRuntimeInfo, type SpawnOptions, } from './types.js';
|
|
7
|
+
export { SimBootStatus, SimDeviceState, type ApnsAlert, type ApnsPayload, type ApnsSound, type PushNotificationPayload, type ScreenshotOptions, type SimBootInfo, type SimDeviceInfo, type SimDeviceTypeInfo, type SimDisplayInfo, type SimPermissionService, type SimPermissionStatus, type SimProcessInfo, type SimRuntimeInfo, type SpawnOptions, type VideoAccessUnit, type VideoRecordingOptions, type VideoStreamOptions, } from './types.js';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,uBAAuB,EAAC,MAAM,aAAa,CAAC;AACvH,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,uBAAuB,EAAC,MAAM,aAAa,CAAC;AACvH,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAC;AACvD,YAAY,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC"}
|
package/lib/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { NativeSimError, NativeSimUnavailableError, NativeSimDispatchError, NativeSimOperationError } from './errors.js';
|
|
2
2
|
export { NativeSimctl } from './native-simctl.js';
|
|
3
3
|
export { SpawnedProcess } from './commands/spawn.js';
|
|
4
|
+
export { VideoStream } from './commands/video-stream.js';
|
|
4
5
|
export { SimBootStatus, SimDeviceState, } from './types.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|