@m7mdxzx1/discord-video-strem 0.0.1
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/README.md +294 -0
- package/dist/client/GatewayEvents.d.ts +27 -0
- package/dist/client/GatewayEvents.js +1 -0
- package/dist/client/GatewayOpCodes.d.ts +40 -0
- package/dist/client/GatewayOpCodes.js +41 -0
- package/dist/client/Streamer.d.ts +41 -0
- package/dist/client/Streamer.js +189 -0
- package/dist/client/encryptor/TransportEncryptor.d.ts +16 -0
- package/dist/client/encryptor/TransportEncryptor.js +38 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +4 -0
- package/dist/client/packet/AudioPacketizer.d.ts +8 -0
- package/dist/client/packet/AudioPacketizer.js +22 -0
- package/dist/client/packet/BaseMediaPacketizer.d.ts +109 -0
- package/dist/client/packet/BaseMediaPacketizer.js +243 -0
- package/dist/client/packet/VideoPacketizerAnnexB.d.ts +132 -0
- package/dist/client/packet/VideoPacketizerAnnexB.js +231 -0
- package/dist/client/packet/VideoPacketizerVP8.d.ts +15 -0
- package/dist/client/packet/VideoPacketizerVP8.js +56 -0
- package/dist/client/packet/index.d.ts +4 -0
- package/dist/client/packet/index.js +4 -0
- package/dist/client/processing/AnnexBHelper.d.ts +93 -0
- package/dist/client/processing/AnnexBHelper.js +132 -0
- package/dist/client/voice/BaseMediaConnection.d.ts +118 -0
- package/dist/client/voice/BaseMediaConnection.js +319 -0
- package/dist/client/voice/MediaUdp.d.ts +26 -0
- package/dist/client/voice/MediaUdp.js +140 -0
- package/dist/client/voice/StreamConnection.d.ts +10 -0
- package/dist/client/voice/StreamConnection.js +30 -0
- package/dist/client/voice/VoiceConnection.d.ts +7 -0
- package/dist/client/voice/VoiceConnection.js +10 -0
- package/dist/client/voice/VoiceMessageTypes.d.ts +136 -0
- package/dist/client/voice/VoiceMessageTypes.js +1 -0
- package/dist/client/voice/VoiceOpCodes.d.ts +21 -0
- package/dist/client/voice/VoiceOpCodes.js +22 -0
- package/dist/client/voice/index.d.ts +5 -0
- package/dist/client/voice/index.js +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/media/AudioStream.d.ts +25 -0
- package/dist/media/AudioStream.js +63 -0
- package/dist/media/BaseMediaStream.d.ts +31 -0
- package/dist/media/BaseMediaStream.js +145 -0
- package/dist/media/LibavCodecId.d.ts +541 -0
- package/dist/media/LibavCodecId.js +552 -0
- package/dist/media/LibavDecoder.d.ts +5 -0
- package/dist/media/LibavDecoder.js +63 -0
- package/dist/media/LibavDemuxer.d.ts +23 -0
- package/dist/media/LibavDemuxer.js +295 -0
- package/dist/media/VideoStream.d.ts +7 -0
- package/dist/media/VideoStream.js +10 -0
- package/dist/media/index.d.ts +1 -0
- package/dist/media/index.js +1 -0
- package/dist/media/newApi.d.ts +126 -0
- package/dist/media/newApi.js +387 -0
- package/dist/media/utils.d.ts +1 -0
- package/dist/media/utils.js +4 -0
- package/dist/utils.d.ts +28 -0
- package/dist/utils.js +54 -0
- package/package.json +69 -0
- package/src/client/GatewayEvents.ts +41 -0
- package/src/client/GatewayOpCodes.ts +40 -0
- package/src/client/Streamer.ts +279 -0
- package/src/client/encryptor/TransportEncryptor.ts +62 -0
- package/src/client/index.ts +4 -0
- package/src/client/packet/AudioPacketizer.ts +28 -0
- package/src/client/packet/BaseMediaPacketizer.ts +307 -0
- package/src/client/packet/VideoPacketizerAnnexB.ts +263 -0
- package/src/client/packet/VideoPacketizerVP8.ts +73 -0
- package/src/client/packet/index.ts +4 -0
- package/src/client/processing/AnnexBHelper.ts +142 -0
- package/src/client/voice/BaseMediaConnection.ts +407 -0
- package/src/client/voice/MediaUdp.ts +171 -0
- package/src/client/voice/StreamConnection.ts +33 -0
- package/src/client/voice/VoiceConnection.ts +15 -0
- package/src/client/voice/VoiceMessageTypes.ts +164 -0
- package/src/client/voice/VoiceOpCodes.ts +21 -0
- package/src/client/voice/index.ts +5 -0
- package/src/index.ts +5 -0
- package/src/media/AudioStream.ts +81 -0
- package/src/media/BaseMediaStream.ts +173 -0
- package/src/media/LibavCodecId.ts +566 -0
- package/src/media/LibavDecoder.ts +82 -0
- package/src/media/LibavDemuxer.ts +348 -0
- package/src/media/VideoStream.ts +15 -0
- package/src/media/index.ts +1 -0
- package/src/media/newApi.ts +618 -0
- package/src/media/utils.ts +6 -0
- package/src/utils.ts +77 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { VoiceOpCodes } from "./VoiceOpCodes.js"
|
|
2
|
+
import type { SupportedEncryptionModes } from "../../utils.js"
|
|
3
|
+
|
|
4
|
+
type StreamInfo = {
|
|
5
|
+
active: boolean,
|
|
6
|
+
quality: number,
|
|
7
|
+
rid: string,
|
|
8
|
+
ssrc: number,
|
|
9
|
+
rtx_ssrc: number,
|
|
10
|
+
/**
|
|
11
|
+
* always "video" from what I observed
|
|
12
|
+
*/
|
|
13
|
+
type: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type SimulcastInfo = {
|
|
17
|
+
type: string,
|
|
18
|
+
rid: string,
|
|
19
|
+
quality: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type CodecPayloadType = {
|
|
23
|
+
name: string,
|
|
24
|
+
type: "audio",
|
|
25
|
+
priority: number,
|
|
26
|
+
payload_type: number
|
|
27
|
+
} | {
|
|
28
|
+
name: string,
|
|
29
|
+
type: "video",
|
|
30
|
+
priority: number,
|
|
31
|
+
payload_type: number,
|
|
32
|
+
rtx_payload_type: number,
|
|
33
|
+
encode: boolean,
|
|
34
|
+
decode: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export namespace Message {
|
|
38
|
+
// Request messages
|
|
39
|
+
export type Identify = {
|
|
40
|
+
server_id: string,
|
|
41
|
+
user_id: string,
|
|
42
|
+
session_id: string,
|
|
43
|
+
token: string,
|
|
44
|
+
video: boolean,
|
|
45
|
+
streams: SimulcastInfo[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type Resume = {
|
|
49
|
+
server_id: string,
|
|
50
|
+
session_id: string,
|
|
51
|
+
token: string,
|
|
52
|
+
seq_ack: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type Heartbeat = {
|
|
56
|
+
t: number,
|
|
57
|
+
seq_ack?: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type SelectProtocol = {
|
|
61
|
+
protocol: string,
|
|
62
|
+
codecs: CodecPayloadType[],
|
|
63
|
+
data: {
|
|
64
|
+
address: string,
|
|
65
|
+
port: number,
|
|
66
|
+
mode: SupportedEncryptionModes
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type Video = {
|
|
71
|
+
audio_ssrc: number,
|
|
72
|
+
video_ssrc: number,
|
|
73
|
+
rtx_ssrc: number,
|
|
74
|
+
streams: {
|
|
75
|
+
type: "video",
|
|
76
|
+
rid: string,
|
|
77
|
+
ssrc: number,
|
|
78
|
+
active: boolean,
|
|
79
|
+
quality: number,
|
|
80
|
+
rtx_ssrc: number,
|
|
81
|
+
max_bitrate: number,
|
|
82
|
+
max_framerate: number,
|
|
83
|
+
max_resolution: {
|
|
84
|
+
type: "fixed",
|
|
85
|
+
width: number,
|
|
86
|
+
height: number
|
|
87
|
+
}
|
|
88
|
+
}[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Response messages
|
|
92
|
+
export type Hello = {
|
|
93
|
+
heartbeat_interval: number
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type Ready = {
|
|
97
|
+
ssrc: number,
|
|
98
|
+
ip: string,
|
|
99
|
+
port: number,
|
|
100
|
+
modes: SupportedEncryptionModes[],
|
|
101
|
+
experiments: string[],
|
|
102
|
+
streams: StreamInfo[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type Speaking = {
|
|
106
|
+
speaking: 0 | 1 | 2,
|
|
107
|
+
delay: number,
|
|
108
|
+
ssrc: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type SelectProtocolAck = {
|
|
112
|
+
secret_key: number[],
|
|
113
|
+
audio_codec: string,
|
|
114
|
+
video_codec: string,
|
|
115
|
+
mode: string,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type HeartbeatAck = {
|
|
119
|
+
t: number
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export namespace GatewayResponse {
|
|
124
|
+
type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
|
|
125
|
+
op: Op,
|
|
126
|
+
d: T,
|
|
127
|
+
seq?: number
|
|
128
|
+
}
|
|
129
|
+
export type Hello = Generic<VoiceOpCodes.HELLO, Message.Hello>
|
|
130
|
+
export type Ready = Generic<VoiceOpCodes.READY, Message.Ready>
|
|
131
|
+
export type Resumed = Generic<VoiceOpCodes.RESUMED, null>
|
|
132
|
+
export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>
|
|
133
|
+
export type SelectProtocolAck = Generic<VoiceOpCodes.SELECT_PROTOCOL_ACK, Message.SelectProtocolAck>
|
|
134
|
+
export type HeartbeatAck = Generic<VoiceOpCodes.HEARTBEAT_ACK, Message.HeartbeatAck>
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type GatewayResponse =
|
|
138
|
+
GatewayResponse.Hello |
|
|
139
|
+
GatewayResponse.Ready |
|
|
140
|
+
GatewayResponse.Resumed |
|
|
141
|
+
GatewayResponse.Speaking |
|
|
142
|
+
GatewayResponse.SelectProtocolAck |
|
|
143
|
+
GatewayResponse.HeartbeatAck
|
|
144
|
+
|
|
145
|
+
export namespace GatewayRequest {
|
|
146
|
+
type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
|
|
147
|
+
op: Op,
|
|
148
|
+
d: T
|
|
149
|
+
}
|
|
150
|
+
export type Identify = Generic<VoiceOpCodes.IDENTIFY, Message.Identify>
|
|
151
|
+
export type Resume = Generic<VoiceOpCodes.RESUME, Message.Resume>
|
|
152
|
+
export type Heartbeat = Generic<VoiceOpCodes.HEARTBEAT, Message.Heartbeat>
|
|
153
|
+
export type SelectProtocol = Generic<VoiceOpCodes.SELECT_PROTOCOL, Message.SelectProtocol>
|
|
154
|
+
export type Video = Generic<VoiceOpCodes.VIDEO, Message.Video>
|
|
155
|
+
export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type GatewayRequest =
|
|
159
|
+
GatewayRequest.Identify |
|
|
160
|
+
GatewayRequest.Resume |
|
|
161
|
+
GatewayRequest.Heartbeat |
|
|
162
|
+
GatewayRequest.SelectProtocol |
|
|
163
|
+
GatewayRequest.Video |
|
|
164
|
+
GatewayRequest.Speaking
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export enum VoiceOpCodes {
|
|
2
|
+
IDENTIFY = 0,
|
|
3
|
+
SELECT_PROTOCOL = 1,
|
|
4
|
+
READY = 2,
|
|
5
|
+
HEARTBEAT = 3,
|
|
6
|
+
SELECT_PROTOCOL_ACK = 4,
|
|
7
|
+
SPEAKING = 5,
|
|
8
|
+
HEARTBEAT_ACK = 6,
|
|
9
|
+
RESUME = 7,
|
|
10
|
+
HELLO = 8,
|
|
11
|
+
RESUMED = 9,
|
|
12
|
+
VIDEO = 12,
|
|
13
|
+
CLIENT_DISCONNECT = 13,
|
|
14
|
+
SESSION_UPDATE = 14,
|
|
15
|
+
MEDIA_SINK_WANTS = 15,
|
|
16
|
+
VOICE_BACKEND_VERSION = 16,
|
|
17
|
+
CHANNEL_OPTIONS_UPDATE = 17,
|
|
18
|
+
FLAGS = 18,
|
|
19
|
+
SPEED_TEST = 19,
|
|
20
|
+
PLATFORM = 20
|
|
21
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import OpusScript from "opusscript";
|
|
2
|
+
import type { MediaUdp } from "../client/voice/MediaUdp.js";
|
|
3
|
+
import { BaseMediaStream } from "./BaseMediaStream.js";
|
|
4
|
+
|
|
5
|
+
export class AudioStream extends BaseMediaStream {
|
|
6
|
+
public udp: MediaUdp;
|
|
7
|
+
private _isMuted: boolean; // Internal state for muting
|
|
8
|
+
private _volume: number = 1.0;
|
|
9
|
+
private _opusEncoder: OpusScript;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
constructor(udp: MediaUdp, noSleep = false, initialMuted: boolean = false) {
|
|
14
|
+
super("audio", noSleep);
|
|
15
|
+
this.udp = udp;
|
|
16
|
+
this._isMuted = initialMuted; // Initialize mute state
|
|
17
|
+
this._opusEncoder = new OpusScript(48000, 2, 2048);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mutes the audio stream. No audio frames will be sent.
|
|
22
|
+
*/
|
|
23
|
+
public mute(): void {
|
|
24
|
+
this._isMuted = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Unmutes the audio stream. Audio frames will resume sending.
|
|
29
|
+
*/
|
|
30
|
+
public unmute(): void {
|
|
31
|
+
this._isMuted = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Checks if the audio stream is currently muted.
|
|
36
|
+
* @returns True if muted, false otherwise.
|
|
37
|
+
*/
|
|
38
|
+
public isMuted(): boolean {
|
|
39
|
+
return this._isMuted;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public setVolume(volume: number): void {
|
|
43
|
+
if (volume < 0) {
|
|
44
|
+
this._volume = 0;
|
|
45
|
+
} else {
|
|
46
|
+
this._volume = Math.min(volume, 2.0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public getVolume(): number {
|
|
51
|
+
return this._volume;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected override async _sendFrame(frame: Buffer, frametime: number): Promise<void> {
|
|
55
|
+
if (this._isMuted) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let processedFrame = frame;
|
|
60
|
+
|
|
61
|
+
if (this._volume !== 1.0) {
|
|
62
|
+
try {
|
|
63
|
+
const pcmData = this._opusEncoder.decode(frame);
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < pcmData.length; i += 2) {
|
|
66
|
+
const sample = pcmData.readInt16LE(i);
|
|
67
|
+
const newSample = Math.max(-32768, Math.min(32767, Math.floor(sample * this._volume)));
|
|
68
|
+
pcmData.writeInt16LE(newSample, i);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
processedFrame = this._opusEncoder.encode(pcmData, 960);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("[AudioStream] Error processing audio frame:", error);
|
|
74
|
+
processedFrame = frame;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await this.udp.sendAudioFrame(processedFrame, frametime);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Log } from "debug-level";
|
|
2
|
+
import { setTimeout } from "node:timers/promises";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import { combineLoHi } from "./utils.js";
|
|
5
|
+
import type { Packet } from "@lng2004/libav.js-variant-webcodecs-avf-with-decoders";
|
|
6
|
+
|
|
7
|
+
export class BaseMediaStream extends Writable {
|
|
8
|
+
private _pts?: number;
|
|
9
|
+
private _syncTolerance = 20;
|
|
10
|
+
private _loggerSend: Log;
|
|
11
|
+
private _loggerSync: Log;
|
|
12
|
+
private _loggerSleep: Log;
|
|
13
|
+
|
|
14
|
+
private _noSleep: boolean;
|
|
15
|
+
private _startTime?: number;
|
|
16
|
+
private _startPts?: number;
|
|
17
|
+
private _sync = true;
|
|
18
|
+
private _syncStream?: BaseMediaStream;
|
|
19
|
+
|
|
20
|
+
constructor(type: string, noSleep = false) {
|
|
21
|
+
super({ objectMode: true, highWaterMark: 0 });
|
|
22
|
+
this._loggerSend = new Log(`stream:${type}:send`);
|
|
23
|
+
this._loggerSync = new Log(`stream:${type}:sync`);
|
|
24
|
+
this._loggerSleep = new Log(`stream:${type}:sleep`);
|
|
25
|
+
this._noSleep = noSleep;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get sync(): boolean {
|
|
29
|
+
return this._sync;
|
|
30
|
+
}
|
|
31
|
+
set sync(val: boolean) {
|
|
32
|
+
this._sync = val;
|
|
33
|
+
if (val)
|
|
34
|
+
this._loggerSync.debug("Sync enabled");
|
|
35
|
+
else
|
|
36
|
+
this._loggerSync.debug("Sync disabled");
|
|
37
|
+
}
|
|
38
|
+
get syncStream() {
|
|
39
|
+
return this._syncStream;
|
|
40
|
+
}
|
|
41
|
+
set syncStream(stream: BaseMediaStream | undefined)
|
|
42
|
+
{
|
|
43
|
+
if (stream === this)
|
|
44
|
+
throw new Error("A stream cannot sync with itself");
|
|
45
|
+
this._syncStream = stream;
|
|
46
|
+
}
|
|
47
|
+
get noSleep(): boolean {
|
|
48
|
+
return this._noSleep;
|
|
49
|
+
}
|
|
50
|
+
set noSleep(val: boolean) {
|
|
51
|
+
this._noSleep = val;
|
|
52
|
+
if (!val)
|
|
53
|
+
this.resetTimingCompensation();
|
|
54
|
+
}
|
|
55
|
+
get pts(): number | undefined {
|
|
56
|
+
return this._pts;
|
|
57
|
+
}
|
|
58
|
+
get syncTolerance() {
|
|
59
|
+
return this._syncTolerance;
|
|
60
|
+
}
|
|
61
|
+
set syncTolerance(n: number) {
|
|
62
|
+
if (n < 0)
|
|
63
|
+
return;
|
|
64
|
+
this._syncTolerance = n;
|
|
65
|
+
}
|
|
66
|
+
protected async _sendFrame(frame: Buffer, frametime: number): Promise<void>
|
|
67
|
+
{
|
|
68
|
+
throw new Error("Not implemented");
|
|
69
|
+
}
|
|
70
|
+
private ptsDelta() {
|
|
71
|
+
if (this.pts !== undefined && this.syncStream?.pts !== undefined)
|
|
72
|
+
return this.pts - this.syncStream.pts;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
private isAhead() {
|
|
76
|
+
const delta = this.ptsDelta();
|
|
77
|
+
return this.syncStream?.writableEnded === false && delta !== undefined && delta > this.syncTolerance;
|
|
78
|
+
}
|
|
79
|
+
private isBehind() {
|
|
80
|
+
const delta = this.ptsDelta();
|
|
81
|
+
return this.syncStream?.writableEnded === false && delta !== undefined && delta < -this.syncTolerance;
|
|
82
|
+
}
|
|
83
|
+
private resetTimingCompensation() {
|
|
84
|
+
this._startTime = this._startPts = undefined;
|
|
85
|
+
}
|
|
86
|
+
async _write(frame: Packet, _: BufferEncoding, callback: (error?: Error | null) => void) {
|
|
87
|
+
const { data, ptshi, pts, durationhi, duration, time_base_num, time_base_den } = frame;
|
|
88
|
+
// biome-ignore lint/style/noNonNullAssertion: this will never happen with our media stream
|
|
89
|
+
const frametime = combineLoHi(durationhi!, duration!) / time_base_den! * time_base_num! * 1000;
|
|
90
|
+
|
|
91
|
+
const start_sendFrame = performance.now();
|
|
92
|
+
await this._sendFrame(Buffer.from(data), frametime);
|
|
93
|
+
const end_sendFrame = performance.now();
|
|
94
|
+
|
|
95
|
+
// biome-ignore lint/style/noNonNullAssertion: this will never happen with our media stream
|
|
96
|
+
this._pts = combineLoHi(ptshi!, pts!) / time_base_den! * time_base_num! * 1000;
|
|
97
|
+
this.emit("pts", this._pts);
|
|
98
|
+
|
|
99
|
+
const sendTime = end_sendFrame - start_sendFrame;
|
|
100
|
+
const ratio = sendTime / frametime;
|
|
101
|
+
this._loggerSend.debug({
|
|
102
|
+
stats: {
|
|
103
|
+
pts: this._pts,
|
|
104
|
+
frame_size: data.length,
|
|
105
|
+
duration: sendTime,
|
|
106
|
+
frametime
|
|
107
|
+
}
|
|
108
|
+
}, `Frame sent in ${sendTime.toFixed(2)}ms (${(ratio * 100).toFixed(2)}% frametime)`);
|
|
109
|
+
if (ratio > 1)
|
|
110
|
+
{
|
|
111
|
+
this._loggerSend.warn({
|
|
112
|
+
frame_size: data.length,
|
|
113
|
+
duration: sendTime,
|
|
114
|
+
frametime
|
|
115
|
+
}, `Frame takes too long to send (${(ratio * 100).toFixed(2)}% frametime)`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this._startTime ??= start_sendFrame;
|
|
119
|
+
this._startPts ??= this._pts;
|
|
120
|
+
const sleep = Math.max(
|
|
121
|
+
0, this._pts - this._startPts + frametime - (end_sendFrame - this._startTime)
|
|
122
|
+
);
|
|
123
|
+
if (this._noSleep || sleep === 0)
|
|
124
|
+
{
|
|
125
|
+
callback(null);
|
|
126
|
+
}
|
|
127
|
+
else if (this.sync && this.isBehind())
|
|
128
|
+
{
|
|
129
|
+
this._loggerSync.debug({
|
|
130
|
+
stats: {
|
|
131
|
+
pts: this.pts,
|
|
132
|
+
pts_other: this.syncStream?.pts
|
|
133
|
+
}
|
|
134
|
+
}, "Stream is behind. Not sleeping for this frame");
|
|
135
|
+
this.resetTimingCompensation();
|
|
136
|
+
callback(null);
|
|
137
|
+
}
|
|
138
|
+
else if (this.sync && this.isAhead())
|
|
139
|
+
{
|
|
140
|
+
do
|
|
141
|
+
{
|
|
142
|
+
this._loggerSync.debug({
|
|
143
|
+
stats: {
|
|
144
|
+
pts: this.pts,
|
|
145
|
+
pts_other: this.syncStream?.pts,
|
|
146
|
+
frametime
|
|
147
|
+
}
|
|
148
|
+
}, `Stream is ahead. Waiting for ${frametime}ms`);
|
|
149
|
+
await setTimeout(frametime);
|
|
150
|
+
}
|
|
151
|
+
while (this.sync && this.isAhead());
|
|
152
|
+
this.resetTimingCompensation();
|
|
153
|
+
callback(null);
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
{
|
|
157
|
+
this._loggerSleep.debug({
|
|
158
|
+
stats: {
|
|
159
|
+
pts: this._pts,
|
|
160
|
+
startPts: this._startPts,
|
|
161
|
+
time: end_sendFrame,
|
|
162
|
+
startTime: this._startTime,
|
|
163
|
+
frametime
|
|
164
|
+
}
|
|
165
|
+
}, `Sleeping for ${sleep}ms`);
|
|
166
|
+
setTimeout(sleep).then(() => callback(null));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
_destroy(error: Error | null, callback: (error?: Error | null) => void): void {
|
|
170
|
+
super._destroy(error, callback);
|
|
171
|
+
this.syncStream = undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|