fix(goLive): retry VIDEO(op12)/SPEAKING(op5) opcodes until ws OPEN — broken shared-screen video
Root cause: BaseMediaConnection.sendOpcode is a silent no-op when ws.readyState !== OPEN. In GoLive, playStream() calls setVideoAttributes(true) + setSpeaking(true) the instant createStream() resolves (right after SELECT_PROTOCOL_ACK), but the StreamConnection WebSocket can still be in CONNECTING for a few ms — so op 12 (VIDEO, activating the video SSRC) was silently DROPPED every session. Empirically verified: 0 ops 12/5 ever logged across the entire journal, yet 10k+ video frames were sent and audio played (audio SSRC is activated via the VoiceConnection handshake, independent of GoLive op 12). Discord's media server thus received video RTP on video_ssrc but was never told to forward it → black/broken shared-screen video with working voice. sendOpcodeWhenOpen retries up to ~2s for ws OPEN instead of dropping. Also emits a=fmtp:101 packetization-mode=1;profile-level-id=42e01f in the answer SDP (H264 FU-A fragments require packetization-mode=1 to reassemble). Also removes pre-existing noNonNullAssertion lint (biome 2.5.8 now errors) that was blocking the deploy CI.
This commit is contained in:
@@ -77,12 +77,11 @@ export class MessagesRepository {
|
||||
|
||||
// Exclude spam threads (NULL-safe: non-thread messages are kept)
|
||||
if (EXCLUDED_THREAD_IDS.length > 0) {
|
||||
conditions.push(
|
||||
or(
|
||||
isNull(pgMessagesTable.thread_id),
|
||||
notInArray(pgMessagesTable.thread_id, EXCLUDED_THREAD_IDS),
|
||||
)!,
|
||||
const excludeThreads = or(
|
||||
isNull(pgMessagesTable.thread_id),
|
||||
notInArray(pgMessagesTable.thread_id, EXCLUDED_THREAD_IDS),
|
||||
);
|
||||
if (excludeThreads) conditions.push(excludeThreads);
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
||||
@@ -150,12 +149,11 @@ export class MessagesRepository {
|
||||
|
||||
// Exclude spam threads (NULL-safe)
|
||||
if (EXCLUDED_THREAD_IDS.length > 0) {
|
||||
conditions.push(
|
||||
or(
|
||||
isNull(pgMessagesTable.thread_id),
|
||||
notInArray(pgMessagesTable.thread_id, EXCLUDED_THREAD_IDS),
|
||||
)!,
|
||||
const excludeThreads = or(
|
||||
isNull(pgMessagesTable.thread_id),
|
||||
notInArray(pgMessagesTable.thread_id, EXCLUDED_THREAD_IDS),
|
||||
);
|
||||
if (excludeThreads) conditions.push(excludeThreads);
|
||||
}
|
||||
|
||||
const rows = await db
|
||||
@@ -316,12 +314,13 @@ export class MessagesRepository {
|
||||
like(pgAttachmentsTable.type, "image/%"),
|
||||
// Exclude spam threads (NULL-safe for non-thread messages)
|
||||
...(EXCLUDED_THREAD_IDS.length > 0
|
||||
? [
|
||||
or(
|
||||
? (() => {
|
||||
const excludeThreads = or(
|
||||
isNull(pgAttachmentsTable.thread_id),
|
||||
notInArray(pgAttachmentsTable.thread_id, EXCLUDED_THREAD_IDS),
|
||||
)!,
|
||||
]
|
||||
);
|
||||
return excludeThreads ? [excludeThreads] : [];
|
||||
})()
|
||||
: []),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -652,11 +652,7 @@ a=ice-lite
|
||||
* CONNECTING) breaks GoLive video while leaving audio intact. We wait for
|
||||
* the open state instead of dropping.
|
||||
*/
|
||||
private sendOpcodeWhenOpen(
|
||||
code: number,
|
||||
data: unknown,
|
||||
label: string,
|
||||
): void {
|
||||
private sendOpcodeWhenOpen(code: number, data: unknown, label: string): void {
|
||||
const attempt = (triesLeft: number) => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.sendOpcode(code, data);
|
||||
|
||||
@@ -108,5 +108,8 @@ export async function retryWithBackoff<T>(
|
||||
});
|
||||
}
|
||||
}
|
||||
throw lastError!;
|
||||
// lastError is always set: the for-loop only exits via break when attempt
|
||||
// === retries, which only happens in the catch branch that sets lastError.
|
||||
if (!lastError) throw new Error("Unknown retry error");
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user