io.appium.settings 8.0.9 → 8.1.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 +126 -0
- package/apks/settings_apk-debug.apk +0 -0
- package/build/lib/client.d.ts +4 -0
- package/build/lib/client.d.ts.map +1 -1
- package/build/lib/client.js +4 -0
- package/build/lib/client.js.map +1 -1
- package/build/lib/commands/clipboard.d.ts.map +1 -1
- package/build/lib/commands/clipboard.js +1 -1
- package/build/lib/commands/clipboard.js.map +1 -1
- package/build/lib/commands/jpeg-stream.d.ts +51 -0
- package/build/lib/commands/jpeg-stream.d.ts.map +1 -0
- package/build/lib/commands/jpeg-stream.js +152 -0
- package/build/lib/commands/jpeg-stream.js.map +1 -0
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/media.js +1 -1
- package/build/lib/commands/media.js.map +1 -1
- package/build/lib/commands/network.js +1 -1
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/types.d.ts +65 -0
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/commands/video-stream.d.ts +50 -0
- package/build/lib/commands/video-stream.d.ts.map +1 -0
- package/build/lib/commands/video-stream.js +160 -0
- package/build/lib/commands/video-stream.js.map +1 -0
- package/build/lib/constants.d.ts +7 -0
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +7 -0
- package/build/lib/constants.js.map +1 -1
- package/build/lib/index.js +1 -1
- package/build/lib/index.js.map +1 -1
- package/build/lib/{utils.d.ts → utils/apk-path.d.ts} +1 -1
- package/build/lib/utils/apk-path.d.ts.map +1 -0
- package/build/lib/{utils.js → utils/apk-path.js} +1 -1
- package/build/lib/utils/apk-path.js.map +1 -0
- package/build/lib/utils/index.d.ts +5 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +4 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/streaming-protocol.d.ts +47 -0
- package/build/lib/utils/streaming-protocol.d.ts.map +1 -0
- package/build/lib/utils/streaming-protocol.js +67 -0
- package/build/lib/utils/streaming-protocol.js.map +1 -0
- package/build/lib/utils/streaming-transport.d.ts +142 -0
- package/build/lib/utils/streaming-transport.d.ts.map +1 -0
- package/build/lib/utils/streaming-transport.js +416 -0
- package/build/lib/utils/streaming-transport.js.map +1 -0
- package/lib/client.ts +5 -0
- package/lib/commands/clipboard.ts +1 -0
- package/lib/commands/jpeg-stream.ts +171 -0
- package/lib/commands/media-projection.ts +1 -5
- package/lib/commands/media.ts +1 -1
- package/lib/commands/network.ts +1 -1
- package/lib/commands/types.ts +70 -0
- package/lib/commands/video-stream.ts +179 -0
- package/lib/constants.ts +10 -0
- package/lib/index.ts +1 -1
- package/lib/utils/index.ts +4 -0
- package/lib/utils/streaming-protocol.ts +93 -0
- package/lib/utils/streaming-transport.ts +450 -0
- package/package.json +1 -1
- package/build/lib/utils.d.ts.map +0 -1
- package/build/lib/utils.js.map +0 -1
- /package/lib/{utils.ts → utils/apk-path.ts} +0 -0
package/lib/commands/types.ts
CHANGED
|
@@ -37,6 +37,76 @@ export interface StartMediaProjectionRecordingOpts {
|
|
|
37
37
|
filename?: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Options for choosing the local TCP port used to bridge a live stream's
|
|
42
|
+
* on-device socket to the host via `adb forward`. At most one of these may be
|
|
43
|
+
* provided; if neither is given, an OS-assigned ephemeral port is used.
|
|
44
|
+
*/
|
|
45
|
+
export interface LocalPortOpts {
|
|
46
|
+
/** Use this exact local TCP port. Throws if it's already in use. */
|
|
47
|
+
localPort?: number;
|
|
48
|
+
/** Pick the first free local TCP port from this inclusive [min, max] range. */
|
|
49
|
+
localPortRange?: [number, number];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Options for starting a live JPEG frame streaming session.
|
|
54
|
+
*/
|
|
55
|
+
export interface StartJpegStreamOpts extends LocalPortOpts {
|
|
56
|
+
/** Frames per second requested; actual rate may be lower on constrained devices. Default 60. */
|
|
57
|
+
fps?: number;
|
|
58
|
+
/** JPEG quality, 1-100. Default 80. */
|
|
59
|
+
quality?: number;
|
|
60
|
+
/** Percentage (1-100) to scale the captured frame to before JPEG encoding. Default 100. */
|
|
61
|
+
scale?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for starting a live H.264/HEVC video streaming session.
|
|
66
|
+
*/
|
|
67
|
+
export interface StartVideoStreamOpts extends LocalPortOpts {
|
|
68
|
+
/** Default 'h264'. */
|
|
69
|
+
codec?: 'h264' | 'hevc';
|
|
70
|
+
/** Default 30. */
|
|
71
|
+
fps?: number;
|
|
72
|
+
/** Default 4000000 (4 Mbps). */
|
|
73
|
+
bitrate?: number;
|
|
74
|
+
/** Same "WxH" syntax/allowed values as StartMediaProjectionRecordingOpts.resolution. */
|
|
75
|
+
resolution?: string;
|
|
76
|
+
/** Interleave an AAC audio track captured via AudioPlaybackCaptureConfiguration. Default false. */
|
|
77
|
+
audio?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A single JPEG-encoded frame of the device screen.
|
|
82
|
+
*/
|
|
83
|
+
export interface JpegFrame {
|
|
84
|
+
/** The raw JPEG-encoded image bytes. */
|
|
85
|
+
data: Buffer;
|
|
86
|
+
/** Monotonically increasing frame sequence number. */
|
|
87
|
+
sequence: number;
|
|
88
|
+
/** Frame timestamp, in microseconds since the stream session started. */
|
|
89
|
+
timestampMicros: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A single encoded access unit from a live video stream: either a video NAL
|
|
94
|
+
* unit (Annex-B, H.264/HEVC) or an AAC audio unit (ADTS-framed).
|
|
95
|
+
*/
|
|
96
|
+
export interface AccessUnit {
|
|
97
|
+
track: 'video' | 'audio';
|
|
98
|
+
/** The raw access unit bytes. */
|
|
99
|
+
data: Buffer;
|
|
100
|
+
/** Monotonically increasing sequence number, per track. */
|
|
101
|
+
sequence: number;
|
|
102
|
+
/** Presentation timestamp, in microseconds since the stream session started. */
|
|
103
|
+
timestampMicros: number;
|
|
104
|
+
/** Whether this is a video keyframe (always false for audio units). */
|
|
105
|
+
isKeyFrame: boolean;
|
|
106
|
+
/** Whether this unit carries codec configuration data (e.g. SPS/PPS) rather than actual samples. */
|
|
107
|
+
isConfig: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
40
110
|
/**
|
|
41
111
|
* Supported locale information.
|
|
42
112
|
*/
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {randomUUID} from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import type {ADB} from 'appium-adb';
|
|
4
|
+
import {waitForCondition} from 'asyncbox';
|
|
5
|
+
|
|
6
|
+
import type {SettingsApp} from '../client.js';
|
|
7
|
+
import {
|
|
8
|
+
SETTINGS_HELPER_ID,
|
|
9
|
+
STREAMING_ACTIVITY_NAME,
|
|
10
|
+
VIDEO_STREAM_ACTION_START,
|
|
11
|
+
VIDEO_STREAM_ACTION_STOP,
|
|
12
|
+
VIDEO_STREAM_SERVICE_NAME,
|
|
13
|
+
} from '../constants.js';
|
|
14
|
+
import {StreamTrack, StreamTransport, validateLocalPortOpts} from '../utils/index.js';
|
|
15
|
+
import type {AccessUnit, StartVideoStreamOpts} from './types.js';
|
|
16
|
+
|
|
17
|
+
const STREAM_STARTUP_TIMEOUT_MS = 3 * 1000;
|
|
18
|
+
const STREAM_STOP_TIMEOUT_MS = 3 * 1000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new instance of the live H.264/HEVC video streaming session.
|
|
22
|
+
* The stream only works since Android API 29+
|
|
23
|
+
*
|
|
24
|
+
* @returns The streaming session instance
|
|
25
|
+
*/
|
|
26
|
+
export function makeVideoStreamSession(this: SettingsApp): VideoStreamSession {
|
|
27
|
+
return new VideoStreamSession(this.adb);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validateVideoStreamOpts(opts: StartVideoStreamOpts): void {
|
|
31
|
+
const {codec, fps, bitrate} = opts;
|
|
32
|
+
if (codec !== undefined && codec !== 'h264' && codec !== 'hevc') {
|
|
33
|
+
throw new TypeError(`codec must be either 'h264' or 'hevc', got ${codec}`);
|
|
34
|
+
}
|
|
35
|
+
if (fps !== undefined && (!Number.isInteger(fps) || fps <= 0)) {
|
|
36
|
+
throw new TypeError(`fps must be a positive integer, got ${fps}`);
|
|
37
|
+
}
|
|
38
|
+
if (bitrate !== undefined && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
39
|
+
throw new TypeError(`bitrate must be a positive integer, got ${bitrate}`);
|
|
40
|
+
}
|
|
41
|
+
validateLocalPortOpts(opts);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Live H.264/HEVC video streaming session for the device screen, with an
|
|
46
|
+
* optional interleaved AAC audio track.
|
|
47
|
+
*/
|
|
48
|
+
export class VideoStreamSession {
|
|
49
|
+
private transport: StreamTransport | null = null;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new VideoStreamSession instance.
|
|
53
|
+
*
|
|
54
|
+
* @param adb - ADB instance for device communication
|
|
55
|
+
*/
|
|
56
|
+
constructor(private readonly adb: ADB) {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Checks if the video stream is currently running.
|
|
60
|
+
*/
|
|
61
|
+
async isRunning(): Promise<boolean> {
|
|
62
|
+
const stdout = await this.adb.shell(['dumpsys', 'activity', 'services', VIDEO_STREAM_SERVICE_NAME]);
|
|
63
|
+
return stdout.includes(VIDEO_STREAM_SERVICE_NAME);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Starts the live video stream.
|
|
68
|
+
* If a stream is already running, this method returns false without starting a new one.
|
|
69
|
+
*
|
|
70
|
+
* @param opts Streaming options including codec, fps, bitrate, resolution, audio, and the local port to use
|
|
71
|
+
* @returns True if the stream was started successfully, false if already running
|
|
72
|
+
* @throws {Error} If the stream fails to start within the timeout period
|
|
73
|
+
*/
|
|
74
|
+
async start(opts: StartVideoStreamOpts = {}): Promise<boolean> {
|
|
75
|
+
validateVideoStreamOpts(opts);
|
|
76
|
+
if (await this.isRunning()) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const {codec, fps, bitrate, resolution, audio, localPort, localPortRange} = opts;
|
|
81
|
+
// Socket names are validated on-device against ^[a-zA-Z0-9._-]{1,200}$, so this
|
|
82
|
+
// must not contain a "/" - unlike VIDEO_STREAM_SERVICE_NAME.
|
|
83
|
+
const socketName = `${SETTINGS_HELPER_ID}.videostream.${randomUUID()}`;
|
|
84
|
+
const args = [
|
|
85
|
+
'am',
|
|
86
|
+
'start',
|
|
87
|
+
'-n',
|
|
88
|
+
STREAMING_ACTIVITY_NAME,
|
|
89
|
+
'-a',
|
|
90
|
+
VIDEO_STREAM_ACTION_START,
|
|
91
|
+
'--es',
|
|
92
|
+
'socket_name',
|
|
93
|
+
socketName,
|
|
94
|
+
];
|
|
95
|
+
if (codec) {
|
|
96
|
+
args.push('--es', 'codec', codec);
|
|
97
|
+
}
|
|
98
|
+
if (fps) {
|
|
99
|
+
args.push('--es', 'fps', `${fps}`);
|
|
100
|
+
}
|
|
101
|
+
if (bitrate) {
|
|
102
|
+
args.push('--es', 'bitrate', `${bitrate}`);
|
|
103
|
+
}
|
|
104
|
+
if (resolution) {
|
|
105
|
+
args.push('--es', 'resolution', resolution);
|
|
106
|
+
}
|
|
107
|
+
if (audio !== undefined) {
|
|
108
|
+
args.push('--es', 'audio', audio ? 'true' : 'false');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await this.adb.shell(args);
|
|
112
|
+
try {
|
|
113
|
+
await waitForCondition(async () => await this.isRunning(), {
|
|
114
|
+
waitMs: STREAM_STARTUP_TIMEOUT_MS,
|
|
115
|
+
intervalMs: 300,
|
|
116
|
+
});
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`The video stream is not running after ${STREAM_STARTUP_TIMEOUT_MS}ms. ` +
|
|
120
|
+
`Please check the logcat output for more details.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.transport = await StreamTransport.connect(this.adb, socketName, {localPort, localPortRange});
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Yields access units (video NAL units and, if enabled, AAC audio units) as
|
|
130
|
+
* they arrive from the device. `start()` must be called before iterating.
|
|
131
|
+
*/
|
|
132
|
+
async *accessUnits(): AsyncGenerator<AccessUnit> {
|
|
133
|
+
if (!this.transport) {
|
|
134
|
+
throw new Error('The video stream has not been started. Call start() first.');
|
|
135
|
+
}
|
|
136
|
+
for await (const frame of this.transport.frames()) {
|
|
137
|
+
yield {
|
|
138
|
+
track: frame.track === StreamTrack.Audio ? 'audio' : 'video',
|
|
139
|
+
data: frame.data,
|
|
140
|
+
sequence: Number(frame.sequence),
|
|
141
|
+
timestampMicros: Number(frame.timestampMicros),
|
|
142
|
+
isKeyFrame: frame.isKeyFrame,
|
|
143
|
+
isConfig: frame.isConfig,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Stops the currently running video stream.
|
|
150
|
+
*
|
|
151
|
+
* @returns True if the stream was stopped successfully, false if it was not running
|
|
152
|
+
* @throws {Error} If the stream fails to stop within the timeout period
|
|
153
|
+
*/
|
|
154
|
+
async stop(): Promise<boolean> {
|
|
155
|
+
try {
|
|
156
|
+
if (!(await this.isRunning())) {
|
|
157
|
+
// The on-device service may have already stopped itself (e.g. the client
|
|
158
|
+
// disconnected), leaving a local transport/adb-forward that still needs closing.
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await this.adb.shell(['am', 'start', '-n', STREAMING_ACTIVITY_NAME, '-a', VIDEO_STREAM_ACTION_STOP]);
|
|
163
|
+
try {
|
|
164
|
+
await waitForCondition(async () => !(await this.isRunning()), {
|
|
165
|
+
waitMs: STREAM_STOP_TIMEOUT_MS,
|
|
166
|
+
intervalMs: 300,
|
|
167
|
+
});
|
|
168
|
+
} catch {
|
|
169
|
+
throw new Error(`The attempt to stop the current video stream timed out after ${STREAM_STOP_TIMEOUT_MS}ms`);
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
} finally {
|
|
173
|
+
if (this.transport) {
|
|
174
|
+
await this.transport.close();
|
|
175
|
+
this.transport = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
package/lib/constants.ts
CHANGED
|
@@ -43,3 +43,13 @@ export const RECORDING_SERVICE_NAME = `${SETTINGS_HELPER_ID}/.recorder.RecorderS
|
|
|
43
43
|
export const RECORDING_ACTIVITY_NAME = `${SETTINGS_HELPER_ID}/io.appium.settings.Settings`;
|
|
44
44
|
export const RECORDING_ACTION_START = `${SETTINGS_HELPER_ID}.recording.ACTION_START`;
|
|
45
45
|
export const RECORDING_ACTION_STOP = `${SETTINGS_HELPER_ID}.recording.ACTION_STOP`;
|
|
46
|
+
|
|
47
|
+
export const STREAMING_ACTIVITY_NAME = RECORDING_ACTIVITY_NAME;
|
|
48
|
+
|
|
49
|
+
export const JPEG_STREAM_SERVICE_NAME = `${SETTINGS_HELPER_ID}/.streaming.JpegStreamService`;
|
|
50
|
+
export const JPEG_STREAM_ACTION_START = `${SETTINGS_HELPER_ID}.streaming.jpeg.ACTION_START`;
|
|
51
|
+
export const JPEG_STREAM_ACTION_STOP = `${SETTINGS_HELPER_ID}.streaming.jpeg.ACTION_STOP`;
|
|
52
|
+
|
|
53
|
+
export const VIDEO_STREAM_SERVICE_NAME = `${SETTINGS_HELPER_ID}/.streaming.VideoStreamService`;
|
|
54
|
+
export const VIDEO_STREAM_ACTION_START = `${SETTINGS_HELPER_ID}.streaming.video.ACTION_START`;
|
|
55
|
+
export const VIDEO_STREAM_ACTION_STOP = `${SETTINGS_HELPER_ID}.streaming.video.ACTION_STOP`;
|
package/lib/index.ts
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export {getSettingsApkPath} from './apk-path.js';
|
|
2
|
+
export {createParserState, parseFrames, StreamTrack} from './streaming-protocol.js';
|
|
3
|
+
export type {FrameParserState, StreamFrame} from './streaming-protocol.js';
|
|
4
|
+
export {BoundedFrameQueue, resolveLocalPort, StreamTransport, validateLocalPortOpts} from './streaming-transport.js';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track discriminator for a streamed frame, matching the on-device
|
|
3
|
+
* `io.appium.settings.streaming.Frame.Track` enum ordinals.
|
|
4
|
+
*/
|
|
5
|
+
export enum StreamTrack {
|
|
6
|
+
Jpeg = 0,
|
|
7
|
+
Video = 1,
|
|
8
|
+
Audio = 2,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const FLAG_CONFIG = 0x01;
|
|
12
|
+
const FLAG_KEYFRAME = 0x02;
|
|
13
|
+
const FLAG_EOS = 0x04;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A single parsed frame/access-unit read off the streaming socket.
|
|
17
|
+
*/
|
|
18
|
+
export interface StreamFrame {
|
|
19
|
+
track: StreamTrack;
|
|
20
|
+
sequence: bigint;
|
|
21
|
+
timestampMicros: bigint;
|
|
22
|
+
data: Buffer;
|
|
23
|
+
isConfig: boolean;
|
|
24
|
+
isKeyFrame: boolean;
|
|
25
|
+
isEndOfStream: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const HEADER_SIZE = 28;
|
|
29
|
+
const MAGIC = Buffer.from('APST', 'ascii');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Incremental parser state, carried across chunk boundaries.
|
|
33
|
+
*/
|
|
34
|
+
export interface FrameParserState {
|
|
35
|
+
buf: Buffer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a fresh, empty parser state for use with {@link parseFrames}.
|
|
40
|
+
*
|
|
41
|
+
* @returns A new, empty FrameParserState
|
|
42
|
+
*/
|
|
43
|
+
export function createParserState(): FrameParserState {
|
|
44
|
+
return {buf: Buffer.alloc(0)};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parses as many complete frames as are available in `state.buf` + `chunk`,
|
|
49
|
+
* carrying over any trailing partial frame in `state` for the next call.
|
|
50
|
+
*
|
|
51
|
+
* Wire format (big-endian, matches `io.appium.settings.streaming.StreamProtocol`):
|
|
52
|
+
* offset 0 (4 bytes): magic "APST"; offset 4 (1 byte): version;
|
|
53
|
+
* offset 5 (1 byte): track; offset 6 (1 byte): flags; offset 7: reserved;
|
|
54
|
+
* offset 8 (8 bytes): sequence; offset 16 (8 bytes): timestampMicros;
|
|
55
|
+
* offset 24 (4 bytes): payloadLength; followed by `payloadLength` payload bytes.
|
|
56
|
+
*
|
|
57
|
+
* @throws {Error} If the stream desynchronizes (unexpected magic bytes)
|
|
58
|
+
*/
|
|
59
|
+
export function* parseFrames(state: FrameParserState, chunk: Buffer): Generator<StreamFrame> {
|
|
60
|
+
state.buf = state.buf.length ? Buffer.concat([state.buf, chunk]) : chunk;
|
|
61
|
+
for (;;) {
|
|
62
|
+
if (state.buf.length < HEADER_SIZE) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!state.buf.subarray(0, 4).equals(MAGIC)) {
|
|
66
|
+
throw new Error('Stream desynchronized: unexpected magic bytes in the streaming protocol header');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const payloadLength = state.buf.readUInt32BE(24);
|
|
70
|
+
const totalLength = HEADER_SIZE + payloadLength;
|
|
71
|
+
if (state.buf.length < totalLength) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const track = state.buf.readUInt8(5) as StreamTrack;
|
|
76
|
+
const flags = state.buf.readUInt8(6);
|
|
77
|
+
const sequence = state.buf.readBigUInt64BE(8);
|
|
78
|
+
const timestampMicros = state.buf.readBigUInt64BE(16);
|
|
79
|
+
const data = Buffer.from(state.buf.subarray(HEADER_SIZE, totalLength));
|
|
80
|
+
|
|
81
|
+
state.buf = state.buf.subarray(totalLength);
|
|
82
|
+
|
|
83
|
+
yield {
|
|
84
|
+
track,
|
|
85
|
+
sequence,
|
|
86
|
+
timestampMicros,
|
|
87
|
+
data,
|
|
88
|
+
isConfig: (flags & FLAG_CONFIG) !== 0,
|
|
89
|
+
isKeyFrame: (flags & FLAG_KEYFRAME) !== 0,
|
|
90
|
+
isEndOfStream: (flags & FLAG_EOS) !== 0,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|