Fixed some bugs and fixed a critical bug within the event registry

master
WolverinDEV 2021-02-15 15:53:01 +01:00
parent 4c5dfbbb3b
commit e5ed31fb47
12 changed files with 116 additions and 67 deletions

View File

@ -138,6 +138,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "server manager init", name: "server manager init",
function: async () => { function: async () => {
server_connections = new ConnectionManager(); server_connections = new ConnectionManager();
(window as any).server_connections = server_connections;
}, },
priority: 80 priority: 80
}); });

View File

@ -11,6 +11,7 @@ import {ErrorCode} from "tc-shared/connection/ErrorCode";
import {WhisperTarget} from "tc-shared/voice/VoiceWhisper"; import {WhisperTarget} from "tc-shared/voice/VoiceWhisper";
import {globalAudioContext} from "tc-backend/audio/player"; import {globalAudioContext} from "tc-backend/audio/player";
import {VideoBroadcastConfig, VideoBroadcastType} from "tc-shared/connection/VideoConnection"; import {VideoBroadcastConfig, VideoBroadcastType} from "tc-shared/connection/VideoConnection";
import {Settings, settings} from "tc-shared/settings";
const kSdpCompressionMode = 1; const kSdpCompressionMode = 1;
@ -372,9 +373,7 @@ class InternalRemoteRTPAudioTrack extends RemoteRTPAudioTrack {
if(state === 1) { if(state === 1) {
validateInfo(); validateInfo();
this.shouldReplay = true; this.shouldReplay = true;
if(this.gainNode) { this.updateGainNode();
this.gainNode.gain.value = this.gain;
}
this.setState(RemoteRTPTrackState.Started); this.setState(RemoteRTPTrackState.Started);
} else { } else {
/* There wil be no info present */ /* There wil be no info present */
@ -383,9 +382,7 @@ class InternalRemoteRTPAudioTrack extends RemoteRTPAudioTrack {
/* since we're might still having some jitter stuff */ /* since we're might still having some jitter stuff */
this.muteTimeout = setTimeout(() => { this.muteTimeout = setTimeout(() => {
this.shouldReplay = false; this.shouldReplay = false;
if(this.gainNode) { this.updateGainNode();
this.gainNode.gain.value = 0;
}
}, 1000); }, 1000);
} }
} }
@ -882,18 +879,23 @@ export class RTCConnection {
iceServers: [{ urls: ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"] }] iceServers: [{ urls: ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"] }]
}); });
/* If set to false FF failed: FIXME! */
const kAddGenericTransceiver = true;
if(this.audioSupport) { if(this.audioSupport) {
this.currentTransceiver["audio"] = this.peer.addTransceiver("audio"); this.currentTransceiver["audio"] = this.peer.addTransceiver("audio");
this.currentTransceiver["audio-whisper"] = this.peer.addTransceiver("audio"); this.currentTransceiver["audio-whisper"] = this.peer.addTransceiver("audio");
/* add some other transceivers for later use */ if(window.detectedBrowser.name === "firefox") {
for(let i = 0; i < 8 && kAddGenericTransceiver; i++) { /*
const transceiver = this.peer.addTransceiver("audio"); * For some reason FF (<= 85.0) does not replay any audio from extra added transceivers.
/* we only want to received on that and don't share any bandwidth limits */ * On the other hand, if the server is creating that track or we're using it for sending audio as well
transceiver.direction = "recvonly"; * it works. So we just wait for the server to come up with new streams (even though we need to renegotiate...).
* For Chrome we only need to negotiate once in most cases.
* Side note: This does not apply to video channels!
*/
} else {
/* add some other transceivers for later use */
for(let i = 0; i < settings.getValue(Settings.KEY_RTC_EXTRA_AUDIO_CHANNELS); i++) {
this.peer.addTransceiver("audio", { direction: "recvonly" });
}
} }
} }
@ -901,10 +903,8 @@ export class RTCConnection {
this.currentTransceiver["video-screen"] = this.peer.addTransceiver("video"); this.currentTransceiver["video-screen"] = this.peer.addTransceiver("video");
/* add some other transceivers for later use */ /* add some other transceivers for later use */
for(let i = 0; i < 4 && kAddGenericTransceiver; i++) { for(let i = 0; i < settings.getValue(Settings.KEY_RTC_EXTRA_VIDEO_CHANNELS); i++) {
const transceiver = this.peer.addTransceiver("video"); this.peer.addTransceiver("video", { direction: "recvonly" });
/* we only want to received on that and don't share any bandwidth limits */
transceiver.direction = "recvonly";
} }
this.peer.onicecandidate = event => this.handleLocalIceCandidate(event.candidate); this.peer.onicecandidate = event => this.handleLocalIceCandidate(event.candidate);

View File

@ -65,7 +65,7 @@ export class RemoteRTPTrack {
} }
getSsrc() : number { getSsrc() : number {
return this.ssrc; return this.ssrc >>> 0;
} }
getTrack() : MediaStreamTrack { getTrack() : MediaStreamTrack {
@ -144,7 +144,20 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
this.htmlAudioNode.msRealTime = true; this.htmlAudioNode.msRealTime = true;
/* /*
TODO: ontimeupdate may gives us a hint whatever we're still replaying audio or not {
const track = transceiver.receiver.track;
for(let key in track) {
if(!key.startsWith("on")) {
continue;
}
track[key] = () => console.log("Track %d: %s", this.getSsrc(), key);
}
}
*/
/*
//TODO: ontimeupdate may gives us a hint whatever we're still replaying audio or not
for(let key in this.htmlAudioNode) { for(let key in this.htmlAudioNode) {
if(!key.startsWith("on")) { if(!key.startsWith("on")) {
continue; continue;
@ -153,7 +166,7 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
this.htmlAudioNode[key] = () => console.log("AudioElement %d: %s", this.getSsrc(), key); this.htmlAudioNode[key] = () => console.log("AudioElement %d: %s", this.getSsrc(), key);
this.htmlAudioNode.ontimeupdate = () => { this.htmlAudioNode.ontimeupdate = () => {
console.log("AudioElement %d: Time update. Current time: %d", this.getSsrc(), this.htmlAudioNode.currentTime, this.htmlAudioNode.buffered) console.log("AudioElement %d: Time update. Current time: %d", this.getSsrc(), this.htmlAudioNode.currentTime, this.htmlAudioNode.buffered)
} };
} }
*/ */
@ -166,8 +179,7 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
const audioContext = globalAudioContext(); const audioContext = globalAudioContext();
this.audioNode = audioContext.createMediaStreamSource(this.mediaStream); this.audioNode = audioContext.createMediaStreamSource(this.mediaStream);
this.gainNode = audioContext.createGain(); this.gainNode = audioContext.createGain();
this.updateGainNode();
this.gainNode.gain.value = this.shouldReplay ? this.gain : 0;
this.audioNode.connect(this.gainNode); this.audioNode.connect(this.gainNode);
this.gainNode.connect(audioContext.destination); this.gainNode.connect(audioContext.destination);
@ -195,10 +207,7 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
setGain(value: number) { setGain(value: number) {
this.gain = value; this.gain = value;
this.updateGainNode();
if(this.gainNode) {
this.gainNode.gain.value = this.shouldReplay ? this.gain : 0;
}
} }
/** /**
@ -209,4 +218,13 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
this.gainNode.gain.value = 0; this.gainNode.gain.value = 0;
} }
} }
protected updateGainNode() {
if(!this.gainNode) {
return;
}
this.gainNode.gain.value = this.shouldReplay ? this.gain : 0;
//console.error("Change gain for %d to %f (%o)", this.getSsrc(), this.gainNode.gain.value, this.shouldReplay);
}
} }

View File

@ -28,7 +28,7 @@ export class SdpProcessor {
rate: 48000, rate: 48000,
encoding: 2, encoding: 2,
fmtp: { minptime: 1, maxptime: 20, useinbandfec: 1, usedtx: 1, stereo: 0, "sprop-stereo": 0 }, fmtp: { minptime: 1, maxptime: 20, useinbandfec: 1, usedtx: 1, stereo: 0, "sprop-stereo": 0 },
rtcpFb: [ "transport-cc" ] rtcpFb: [ "transport-cc", "nack", "goog-remb" ]
}, },
{ {
// Opus Stereo/Opus Music // Opus Stereo/Opus Music
@ -37,7 +37,7 @@ export class SdpProcessor {
rate: 48000, rate: 48000,
encoding: 2, encoding: 2,
fmtp: { minptime: 1, maxptime: 20, useinbandfec: 1, usedtx: 1, stereo: 1, "sprop-stereo": 1 }, fmtp: { minptime: 1, maxptime: 20, useinbandfec: 1, usedtx: 1, stereo: 1, "sprop-stereo": 1 },
rtcpFb: [ "transport-cc" ] rtcpFb: [ "transport-cc", "nack", "goog-remb" ]
}, },
]; ];

View File

@ -298,7 +298,8 @@ export class Registry<Events extends EventMap<Events> = EventMap<any>> implement
} }
} }
for(const handler of this.persistentEventHandler[event.type] || []) { const handlers = [...(this.persistentEventHandler[event.type] || [])];
for(const handler of handlers) {
handler(event); handler(event);
} }

View File

@ -558,8 +558,9 @@ export class FileManager {
"proto": 1 "proto": 1
}, {process_result: false}); }, {process_result: false});
if(transfer.transferState() === FileTransferState.INITIALIZING) if(transfer.transferState() === FileTransferState.INITIALIZING) {
throw tr("missing transfer start notify"); throw tr("missing transfer start notify");
}
} catch (error) { } catch (error) {
transfer.setFailed({ transfer.setFailed({
@ -620,8 +621,9 @@ export class FileManager {
transfer: transfer, transfer: transfer,
executeCallback: async () => { executeCallback: async () => {
await callbackInitialize(transfer); /* noexcept */ await callbackInitialize(transfer); /* noexcept */
if(transfer.transferState() !== FileTransferState.CONNECTING) if(transfer.transferState() !== FileTransferState.CONNECTING) {
return; return;
}
try { try {
const provider = TransferProvider.provider(); const provider = TransferProvider.provider();
@ -633,12 +635,13 @@ export class FileManager {
return; return;
} }
if(transfer instanceof FileDownloadTransfer) if(transfer instanceof FileDownloadTransfer) {
provider.executeFileDownload(transfer); provider.executeFileDownload(transfer);
else if(transfer instanceof FileUploadTransfer) } else if(transfer instanceof FileUploadTransfer) {
provider.executeFileUpload(transfer); provider.executeFileUpload(transfer);
else } else {
throw tr("unknown transfer type"); throw tr("unknown transfer type");
}
} catch (error) { } catch (error) {
const message = typeof error === "string" ? error : error instanceof Error ? error.message : tr("Unknown error"); const message = typeof error === "string" ? error : error instanceof Error ? error.message : tr("Unknown error");
transfer.setFailed({ transfer.setFailed({
@ -651,7 +654,7 @@ export class FileManager {
finishPromise: new Promise(resolve => { finishPromise: new Promise(resolve => {
const unregisterTransfer = () => { const unregisterTransfer = () => {
transfer.events.off("notify_state_updated", stateListener); transfer.events.off("notify_state_updated", stateListener);
transfer.events.off("action_request_cancel", cancelListener); transfer.events.off("notify_transfer_canceled", unregisterTransfer);
const index = this.registeredTransfers_.findIndex(e => e.transfer === transfer); const index = this.registeredTransfers_.findIndex(e => e.transfer === transfer);
if(index === -1) { if(index === -1) {
@ -681,6 +684,9 @@ export class FileManager {
} else { } else {
logWarn(LogCategory.FILE_TRANSFER, tra("File transfer finished callback called with invalid transfer state ({0})", FileTransferState[state])); logWarn(LogCategory.FILE_TRANSFER, tra("File transfer finished callback called with invalid transfer state ({0})", FileTransferState[state]));
} }
/* destroy the transfer after all events have been fired */
setTimeout(() => transfer.destroy(), 250);
}; };
const stateListener = () => { const stateListener = () => {
@ -690,13 +696,9 @@ export class FileManager {
} }
}; };
const cancelListener = () => {
unregisterTransfer();
transfer.events.fire_later("notify_transfer_canceled", {}, resolve);
};
transfer.events.on("notify_state_updated", stateListener); transfer.events.on("notify_state_updated", stateListener);
transfer.events.on("action_request_cancel", cancelListener); transfer.events.on("notify_transfer_canceled", unregisterTransfer);
stateListener();
}) })
}); });
@ -705,8 +707,9 @@ export class FileManager {
} }
private scheduleTransferUpdate() { private scheduleTransferUpdate() {
if(this.scheduledTransferUpdate) if(this.scheduledTransferUpdate) {
return; return;
}
this.scheduledTransferUpdate = setTimeout(() => { this.scheduledTransferUpdate = setTimeout(() => {
this.scheduledTransferUpdate = undefined; this.scheduledTransferUpdate = undefined;

View File

@ -113,8 +113,6 @@ export enum FileTransferDirection {
export interface FileTransferEvents { export interface FileTransferEvents {
"notify_state_updated": { oldState: FileTransferState, newState: FileTransferState }, "notify_state_updated": { oldState: FileTransferState, newState: FileTransferState },
"notify_progress": { progress: TransferProgress }, "notify_progress": { progress: TransferProgress },
"action_request_cancel": { reason: CancelReason },
"notify_transfer_canceled": {} "notify_transfer_canceled": {}
} }
@ -239,9 +237,14 @@ export class FileTransfer {
this.setTransferState(FileTransferState.PENDING); this.setTransferState(FileTransferState.PENDING);
this.events = new Registry<FileTransferEvents>(); this.events = new Registry<FileTransferEvents>();
this.events.on("notify_transfer_canceled", () => { }
destroy() {
if(!this.isFinished()) {
this.setTransferState(FileTransferState.CANCELED); this.setTransferState(FileTransferState.CANCELED);
}); }
this.events.destroy();
} }
isRunning() { isRunning() {
@ -253,7 +256,7 @@ export class FileTransfer {
} }
isFinished() { isFinished() {
return this.transferState() === FileTransferState.FINISHED || this.transferState() === FileTransferState.ERRORED || this.transferState() === FileTransferState.CANCELED; return this.transferState_ === FileTransferState.FINISHED || this.transferState_ === FileTransferState.ERRORED || this.transferState_ === FileTransferState.CANCELED;
} }
transferState() { transferState() {
@ -297,16 +300,19 @@ export class FileTransfer {
} }
requestCancel(reason: CancelReason) { requestCancel(reason: CancelReason) {
if(this.isFinished()) if(this.isFinished()) {
throw tr("invalid transfer state"); throw tr("invalid transfer state");
}
this.cancelReason = reason; this.cancelReason = reason;
this.events.fire("action_request_cancel"); this.events.fire("notify_transfer_canceled");
this.setTransferState(FileTransferState.CANCELED);
} }
setTransferState(newState: FileTransferState) { setTransferState(newState: FileTransferState) {
if(this.transferState_ === newState) if(this.transferState_ === newState) {
return; return;
}
const newIsFinishedState = newState === FileTransferState.CANCELED || newState === FileTransferState.ERRORED || newState === FileTransferState.FINISHED; const newIsFinishedState = newState === FileTransferState.CANCELED || newState === FileTransferState.ERRORED || newState === FileTransferState.FINISHED;
try { try {
@ -335,8 +341,9 @@ export class FileTransfer {
case FileTransferState.FINISHED: case FileTransferState.FINISHED:
case FileTransferState.CANCELED: case FileTransferState.CANCELED:
case FileTransferState.ERRORED: case FileTransferState.ERRORED:
if(this.isFinished()) if(this.isFinished()) {
throw void 0; throw void 0;
}
this.timings.timestampEnd = Date.now(); this.timings.timestampEnd = Date.now();
break; break;
} }
@ -358,7 +365,6 @@ export class FileTransfer {
} }
} catch (e) { } catch (e) {
throw "invalid transfer state transform from " + this.transferState_ + " to " + newState; throw "invalid transfer state transform from " + this.transferState_ + " to " + newState;
return;
} }
const oldState = this.transferState_; const oldState = this.transferState_;
@ -368,7 +374,7 @@ export class FileTransfer {
updateProgress(progress: TransferProgress) { updateProgress(progress: TransferProgress) {
this.progress_ = progress; this.progress_ = progress;
this.events.fire_later("notify_progress", { progress: progress }); this.events.fire("notify_progress", { progress: progress });
} }
awaitFinished() : Promise<void> { awaitFinished() : Promise<void> {

View File

@ -156,7 +156,7 @@ if(!JSON.map_field_to) {
if (!Array.prototype.remove) { if (!Array.prototype.remove) {
Array.prototype.remove = function<T>(elem?: T): boolean { Array.prototype.remove = function<T>(elem?: T): boolean {
const index = this.indexOf(elem, 0); const index = this.indexOf(elem);
if (index > -1) { if (index > -1) {
this.splice(index, 1); this.splice(index, 1);
return true; return true;

View File

@ -591,10 +591,30 @@ export class Settings {
valueType: "boolean", valueType: "boolean",
}; };
static readonly KEY_RTC_EXTRA_VIDEO_CHANNELS: ValuedRegistryKey<number> = {
key: "rtc_extra_video_channels",
defaultValue: 0,
requireRestart: true,
valueType: "number",
description: "Extra video channels within the initial WebRTC sdp offer.\n" +
"Note: By default the screen/camera share channels are already present"
};
static readonly KEY_RTC_EXTRA_AUDIO_CHANNELS: ValuedRegistryKey<number> = {
key: "rtc_extra_audio_channels",
defaultValue: 6,
requireRestart: true,
valueType: "number",
description: "Extra audio channels within the initial WebRTC sdp offer.\n" +
"Note:\n" +
"1. By default the voice/whisper channels are already present.\n" +
"2. This setting does not work for Firefox."
};
static readonly KEY_RNNOISE_FILTER: ValuedRegistryKey<boolean> = { static readonly KEY_RNNOISE_FILTER: ValuedRegistryKey<boolean> = {
key: "rnnoise_filter", key: "rnnoise_filter",
defaultValue: true, defaultValue: true,
description: "Enable the rnnoise filter for supressing background noise", description: "Enable the rnnoise filter for suppressing background noise",
valueType: "boolean", valueType: "boolean",
}; };

View File

@ -1,3 +1,4 @@
import * as aplayer from "../audio/player";
import { import {
AbstractVoiceConnection, AbstractVoiceConnection,
VoiceConnectionStatus, VoiceConnectionStatus,
@ -15,7 +16,6 @@ import {RTCConnection, RTCConnectionEvents, RTPConnectionState} from "tc-shared/
import {AbstractServerConnection, ConnectionStatistics} from "tc-shared/connection/ConnectionBase"; import {AbstractServerConnection, ConnectionStatistics} from "tc-shared/connection/ConnectionBase";
import {VoicePlayerState} from "tc-shared/voice/VoicePlayer"; import {VoicePlayerState} from "tc-shared/voice/VoicePlayer";
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log"; import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
import * as aplayer from "../audio/player";
import {tr} from "tc-shared/i18n/localize"; import {tr} from "tc-shared/i18n/localize";
import {RtpVoiceClient} from "tc-backend/web/voice/VoiceClient"; import {RtpVoiceClient} from "tc-backend/web/voice/VoiceClient";
import {InputConsumerType} from "tc-shared/voice/RecorderBase"; import {InputConsumerType} from "tc-shared/voice/RecorderBase";
@ -279,9 +279,9 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
} }
const client = new RtpVoiceClient(clientId); const client = new RtpVoiceClient(clientId);
this.voiceClients[clientId] = client; client.setGloballyMuted(this.speakerMuted);
this.voiceClients[clientId].setGloballyMuted(this.speakerMuted);
client.events.on("notify_state_changed", this.voiceClientStateChangedEventListener); client.events.on("notify_state_changed", this.voiceClientStateChangedEventListener);
this.voiceClients[clientId] = client;
return client; return client;
} }
@ -414,8 +414,9 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
} }
private setConnectionState(state: VoiceConnectionStatus) { private setConnectionState(state: VoiceConnectionStatus) {
if(this.connectionState === state) if(this.connectionState === state) {
return; return;
}
const oldState = this.connectionState; const oldState = this.connectionState;
this.connectionState = state; this.connectionState = state;

View File

@ -1,5 +1,8 @@
import {VoiceClient} from "tc-shared/voice/VoiceClient"; import {VoiceClient} from "tc-shared/voice/VoiceClient";
import {VoicePlayer} from "./VoicePlayer"; import {VoicePlayer} from "./VoicePlayer";
import {LogCategory, logTrace} from "tc-shared/log";
import {tr} from "tc-shared/i18n/localize";
import {RemoteRTPAudioTrack} from "tc-shared/connection/rtc/RemoteTrack";
export class RtpVoiceClient extends VoicePlayer implements VoiceClient { export class RtpVoiceClient extends VoicePlayer implements VoiceClient {
private readonly clientId: number; private readonly clientId: number;

View File

@ -1,12 +1,8 @@
import { import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
VoicePlayerEvents,
VoicePlayerLatencySettings,
VoicePlayerState
} from "tc-shared/voice/VoicePlayer";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {LogCategory, logWarn} from "tc-shared/log"; import {LogCategory, logTrace, logWarn} from "tc-shared/log";
import {RemoteRTPAudioTrack, RemoteRTPTrackState} from "tc-shared/connection/rtc/RemoteTrack"; import {RemoteRTPAudioTrack, RemoteRTPTrackState} from "tc-shared/connection/rtc/RemoteTrack";
import { tr } from "tc-shared/i18n/localize"; import {tr} from "tc-shared/i18n/localize";
export interface RtpVoicePlayerEvents { export interface RtpVoicePlayerEvents {
notify_state_changed: { oldState: VoicePlayerState, newState: VoicePlayerState } notify_state_changed: { oldState: VoicePlayerState, newState: VoicePlayerState }