From 8be3943d00e2dda6ba44c883d64b54f85fcbc0b2 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Tue, 17 Nov 2020 14:27:46 +0100 Subject: [PATCH] Added legacy support for the non 1.5.0 TeaSpeak server voice bridge and fixed the client status updates --- shared/js/ConnectionHandler.ts | 8 +- shared/js/connection/ServerFeatures.ts | 3 +- shared/js/connection/VoiceConnection.ts | 2 +- web/.gitignore | 2 +- .../connection/LegacySupportVoiceBridge.ts | 246 ++++++++++++++++++ web/app/connection/ServerConnection.ts | 63 ++++- web/app/index.ts | 2 +- web/app/{ => legacy}/audio-lib/AudioClient.ts | 2 +- .../{ => legacy}/audio-lib/WorkerMessages.ts | 0 web/app/{ => legacy}/audio-lib/index.ts | 4 +- .../audio-lib/worker/async_require.d.ts | 0 .../audio-lib/worker/async_require.js | 0 .../{ => legacy}/audio-lib/worker/index.ts | 2 +- web/app/{ => legacy}/voice/AudioResampler.ts | 0 web/app/{ => legacy}/voice/VoiceClient.ts | 2 +- web/app/{ => legacy}/voice/VoiceHandler.ts | 16 +- web/app/{ => legacy}/voice/VoicePlayer.ts | 4 +- web/app/{ => legacy}/voice/VoiceWhisper.ts | 4 +- .../voice/bridge/NativeWebRTCVoiceBridge.ts | 2 +- .../{ => legacy}/voice/bridge/VoiceBridge.ts | 0 .../voice/bridge/WebRTCVoiceBridge.ts | 0 web/app/rtc/Connection.ts | 22 +- web/app/rtc/video/Connection.ts | 5 + web/app/rtc/voice/Connection.ts | 7 +- 24 files changed, 359 insertions(+), 37 deletions(-) create mode 100644 web/app/connection/LegacySupportVoiceBridge.ts rename web/app/{ => legacy}/audio-lib/AudioClient.ts (94%) rename web/app/{ => legacy}/audio-lib/WorkerMessages.ts (100%) rename web/app/{ => legacy}/audio-lib/index.ts (96%) rename web/app/{ => legacy}/audio-lib/worker/async_require.d.ts (100%) rename web/app/{ => legacy}/audio-lib/worker/async_require.js (100%) rename web/app/{ => legacy}/audio-lib/worker/index.ts (97%) rename web/app/{ => legacy}/voice/AudioResampler.ts (100%) rename web/app/{ => legacy}/voice/VoiceClient.ts (83%) rename web/app/{ => legacy}/voice/VoiceHandler.ts (97%) rename web/app/{ => legacy}/voice/VoicePlayer.ts (98%) rename web/app/{ => legacy}/voice/VoiceWhisper.ts (97%) rename web/app/{ => legacy}/voice/bridge/NativeWebRTCVoiceBridge.ts (98%) rename web/app/{ => legacy}/voice/bridge/VoiceBridge.ts (100%) rename web/app/{ => legacy}/voice/bridge/WebRTCVoiceBridge.ts (100%) diff --git a/shared/js/ConnectionHandler.ts b/shared/js/ConnectionHandler.ts index b97ec895..54f02d0e 100644 --- a/shared/js/ConnectionHandler.ts +++ b/shared/js/ConnectionHandler.ts @@ -175,6 +175,7 @@ export class ConnectionHandler { lastChannelCodecWarned: -1 }; + private clientStatusUnsync = false; private inputHardwareState: InputHardwareState = InputHardwareState.MISSING; @@ -746,14 +747,17 @@ export class ConnectionHandler { { const currentClientProperties = this.getClient().properties; for(const key of Object.keys(localClientUpdates)) { - if(currentClientProperties[key] === localClientUpdates[key]) + if(currentClientProperties[key] === localClientUpdates[key] && !this.clientStatusUnsync) delete localClientUpdates[key]; } if(Object.keys(localClientUpdates).length > 0) { - this.serverConnection.send_command("clientupdate", localClientUpdates).catch(error => { + this.serverConnection.send_command("clientupdate", localClientUpdates).then(() => { + this.clientStatusUnsync = false; + }).catch(error => { log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error); this.log.log(EventType.ERROR_CUSTOM, { message: tr("Failed to update audio hardware properties.") }); + this.clientStatusUnsync = true; /* Update these properties anyways (for case the server fails to handle the command) */ const updates = []; diff --git a/shared/js/connection/ServerFeatures.ts b/shared/js/connection/ServerFeatures.ts index 9b37921e..e9059360 100644 --- a/shared/js/connection/ServerFeatures.ts +++ b/shared/js/connection/ServerFeatures.ts @@ -11,7 +11,8 @@ export enum ServerFeature { ERROR_BULKS= "error-bulks", /* Current version is 1 */ ADVANCED_CHANNEL_CHAT= "advanced-channel-chat", /* Current version is 1 */ LOG_QUERY= "log-query", /* Current version is 1 */ - WHISPER_ECHO = "whisper-echo" /* Current version is 1 */ + WHISPER_ECHO = "whisper-echo", /* Current version is 1 */ + VIDEO = "video" } export interface ServerFeatureEvents { diff --git a/shared/js/connection/VoiceConnection.ts b/shared/js/connection/VoiceConnection.ts index ac8e07bb..56cb627a 100644 --- a/shared/js/connection/VoiceConnection.ts +++ b/shared/js/connection/VoiceConnection.ts @@ -69,7 +69,7 @@ export abstract class AbstractVoiceConnection { abstract encodingSupported(codec: number) : boolean; abstract decodingSupported(codec: number) : boolean; - abstract registerVoiceClient(clientId: number); + abstract registerVoiceClient(clientId: number) : VoiceClient; abstract availableVoiceClients() : VoiceClient[]; abstract unregisterVoiceClient(client: VoiceClient); diff --git a/web/.gitignore b/web/.gitignore index b126382e..01ede610 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -8,4 +8,4 @@ app/**/*.css.map app/**/*.js app/**/*.js.map -!app/audio-lib/worker/async_require.js \ No newline at end of file +!app/legacy/audio-lib/worker/async_require.js \ No newline at end of file diff --git a/web/app/connection/LegacySupportVoiceBridge.ts b/web/app/connection/LegacySupportVoiceBridge.ts new file mode 100644 index 00000000..f83a2d0d --- /dev/null +++ b/web/app/connection/LegacySupportVoiceBridge.ts @@ -0,0 +1,246 @@ +import { + AbstractVoiceConnection, + VoiceConnectionStatus, + WhisperSessionInitializer +} from "tc-shared/connection/VoiceConnection"; +import {RtpVoiceConnection} from "tc-backend/web/rtc/voice/Connection"; +import {VoiceConnection} from "tc-backend/web/legacy/voice/VoiceHandler"; +import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; +import {VoiceClient} from "tc-shared/voice/VoiceClient"; +import {WhisperSession, WhisperTarget} from "tc-shared/voice/VoiceWhisper"; +import {AbstractServerConnection, ConnectionStatistics} from "tc-shared/connection/ConnectionBase"; +import {Registry} from "tc-shared/events"; +import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer"; + +class ProxiedVoiceClient implements VoiceClient { + readonly clientId: number; + readonly events: Registry; + + handle: VoiceClient; + + private volume: number; + private latencySettings: VoicePlayerLatencySettings | undefined; + + constructor(clientId: number) { + this.clientId = clientId; + this.events = new Registry(); + + this.volume = 1; + } + + setHandle(handle: VoiceClient | undefined) { + this.handle?.events.disconnectAll(this.events); + this.handle = handle; + + if(this.latencySettings) { + this.handle?.setLatencySettings(this.latencySettings); + } + this.handle?.setVolume(this.volume); + this.handle?.events.connectAll(this.events); + } + + abortReplay() { + this.handle?.abortReplay(); + } + + flushBuffer() { + this.handle?.flushBuffer(); + } + + getClientId(): number { + return this.clientId; + } + + getLatencySettings(): Readonly { + return this.handle?.getLatencySettings() || this.latencySettings || { maxBufferTime: 200, minBufferTime: 10 }; + } + + getState(): VoicePlayerState { + return this.handle ? this.handle.getState() : VoicePlayerState.STOPPED; + } + + getVolume(): number { + return this.handle?.getVolume() || this.volume; + } + + resetLatencySettings() { + this.handle.resetLatencySettings(); + this.latencySettings = undefined; + } + + setLatencySettings(settings: VoicePlayerLatencySettings) { + this.latencySettings = settings; + this.handle?.setLatencySettings(this.latencySettings); + } + + setVolume(volume: number) { + this.volume = volume; + this.handle?.setVolume(volume); + } + +} + +export class LegacySupportVoiceBridge extends AbstractVoiceConnection { + private readonly newVoiceBride: RtpVoiceConnection; + private readonly oldVoiceBridge: VoiceConnection; + + private activeBridge: AbstractVoiceConnection; + + private encoderCodec: number; + private currentRecorder: RecorderProfile; + + private registeredClients: ProxiedVoiceClient[] = []; + + constructor(connection: AbstractServerConnection, oldVoiceBridge: VoiceConnection, newVoiceBride: RtpVoiceConnection) { + super(connection); + + this.oldVoiceBridge = oldVoiceBridge; + this.newVoiceBride = newVoiceBride; + } + + async setVoiceBridge(type: "old" | "new" | "unset") { + const oldState = this.getConnectionState(); + + this.registeredClients.forEach(e => { + if(e.handle) { + this.activeBridge.unregisterVoiceClient(e.handle); + e.setHandle(undefined); + } + }); + this.activeBridge?.events.disconnectAll(this.events); + this.activeBridge = type === "old" ? this.oldVoiceBridge : type === "new" ? this.newVoiceBride : undefined; + + if(this.activeBridge) { + this.activeBridge.events.connectAll(this.events); + + this.registeredClients.forEach(e => { + if(!e.handle) { + e.setHandle(this.activeBridge.registerVoiceClient(e.clientId)); + } + }); + + await this.activeBridge.acquireVoiceRecorder(this.currentRecorder); + + /* FIXME: Fire only if the state changed */ + this.events.fire("notify_connection_status_changed", { oldStatus: oldState, newStatus: this.activeBridge.getConnectionState() }); + this.events.fire("notify_voice_replay_state_change", { replaying: this.activeBridge.isReplayingVoice() }); + } else { + /* FIXME: Fire only if the state changed */ + this.events.fire("notify_connection_status_changed", { oldStatus: oldState, newStatus: VoiceConnectionStatus.Disconnected }); + this.events.fire("notify_voice_replay_state_change", { replaying: false }); + } + } + + acquireVoiceRecorder(recorder: RecorderProfile | undefined): Promise { + this.currentRecorder = recorder; + return this.activeBridge?.acquireVoiceRecorder(recorder); + } + + decodingSupported(codec: number): boolean { + return !!this.activeBridge?.decodingSupported(codec); + } + + encodingSupported(codec: number): boolean { + return !!this.activeBridge?.encodingSupported(codec); + } + + dropWhisperSession(session: WhisperSession) { + this.activeBridge?.dropWhisperSession(session); + } + + getConnectionState(): VoiceConnectionStatus { + return this.activeBridge ? this.activeBridge.getConnectionState() : VoiceConnectionStatus.Disconnected; + } + + getConnectionStats(): Promise { + return this.activeBridge ? this.activeBridge.getConnectionStats() : Promise.resolve({ + bytesSend: 0, + bytesReceived: 0 + }); + } + + getEncoderCodec(): number { + return this.activeBridge ? this.activeBridge.getEncoderCodec() : this.encoderCodec; + } + + getFailedMessage(): string { + return this.activeBridge?.getFailedMessage(); + } + + getRetryTimestamp(): number | 0 { + return this.activeBridge ? this.activeBridge.getRetryTimestamp() : 0; + } + + getWhisperSessionInitializer(): WhisperSessionInitializer | undefined { + return this.activeBridge?.getWhisperSessionInitializer(); + } + + getWhisperSessions(): WhisperSession[] { + return this.activeBridge?.getWhisperSessions() || []; + } + + getWhisperTarget(): WhisperTarget | undefined { + return this.activeBridge?.getWhisperTarget(); + } + + isReplayingVoice(): boolean { + return !!this.activeBridge?.isReplayingVoice(); + } + + availableVoiceClients(): VoiceClient[] { + return this.registeredClients; + } + + registerVoiceClient(clientId: number) { + if(this.registeredClients.findIndex(e => e.clientId === clientId) !== -1) { + throw tr("voice client already exists"); + } + + const client = new ProxiedVoiceClient(clientId); + client.setHandle(this.activeBridge?.registerVoiceClient(clientId)); + this.registeredClients.push(client); + return client; + } + + setEncoderCodec(codec: number) { + this.encoderCodec = codec; + this.newVoiceBride.setEncoderCodec(codec); + this.oldVoiceBridge.setEncoderCodec(codec); + } + + setWhisperSessionInitializer(initializer: WhisperSessionInitializer | undefined) { + this.newVoiceBride.setWhisperSessionInitializer(initializer); + this.oldVoiceBridge.setWhisperSessionInitializer(initializer); + } + + startWhisper(target: WhisperTarget): Promise { + return this.activeBridge ? this.activeBridge.startWhisper(target) : Promise.reject(tr("voice bridge not connected")); + } + + stopAllVoiceReplays() { + this.activeBridge?.stopAllVoiceReplays(); + } + + stopWhisper() { + this.oldVoiceBridge?.stopWhisper(); + this.newVoiceBride?.stopWhisper(); + } + + unregisterVoiceClient(client: VoiceClient) { + if(!(client instanceof ProxiedVoiceClient)) { + throw tr("invalid voice client"); + } + + const index = this.registeredClients.indexOf(client); + if(index === -1) { return; } + + this.registeredClients.splice(index, 1); + if(client.handle) { + this.activeBridge?.unregisterVoiceClient(client.handle); + } + } + + voiceRecorder(): RecorderProfile { + return this.currentRecorder; + } +} \ No newline at end of file diff --git a/web/app/connection/ServerConnection.ts b/web/app/connection/ServerConnection.ts index 382aa90e..e10a840a 100644 --- a/web/app/connection/ServerConnection.ts +++ b/web/app/connection/ServerConnection.ts @@ -1,8 +1,9 @@ import { AbstractServerConnection, CommandOptionDefaults, - CommandOptions, ConnectionStatistics, + CommandOptions, ConnectionStateListener, + ConnectionStatistics, } from "tc-shared/connection/ConnectionBase"; import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler"; import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler"; @@ -10,7 +11,7 @@ import {ConnectionCommandHandler, ServerConnectionCommandBoss} from "tc-shared/c import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {settings, Settings} from "tc-shared/settings"; import * as log from "tc-shared/log"; -import {LogCategory, logTrace} from "tc-shared/log"; +import {LogCategory, logDebug, logError, logTrace} from "tc-shared/log"; import {Regex} from "tc-shared/ui/modal/ModalConnect"; import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler"; import {EventType} from "tc-shared/ui/frames/log/Definitions"; @@ -22,6 +23,9 @@ import {RTCConnection} from "tc-backend/web/rtc/Connection"; import {RtpVoiceConnection} from "tc-backend/web/rtc/voice/Connection"; import {RtpVideoConnection} from "tc-backend/web/rtc/video/Connection"; import {VideoConnection} from "tc-shared/connection/VideoConnection"; +import {VoiceConnection} from "tc-backend/web/legacy/voice/VoiceHandler"; +import {LegacySupportVoiceBridge} from "tc-backend/web/connection/LegacySupportVoiceBridge"; +import {ServerFeature} from "tc-shared/connection/ServerFeatures"; class ReturnListener { resolve: (value?: T | PromiseLike) => void; @@ -50,6 +54,10 @@ export class ServerConnection extends AbstractServerConnection { private voiceConnection: RtpVoiceConnection; private videoConnection: RtpVideoConnection; + /* legacy */ + private oldVoiceConnection: VoiceConnection; + private legacyVoiceConnection: LegacySupportVoiceBridge; + private pingStatistics = { thread_id: 0, @@ -77,6 +85,9 @@ export class ServerConnection extends AbstractServerConnection { this.rtcConnection = new RTCConnection(this); this.voiceConnection = new RtpVoiceConnection(this, this.rtcConnection); this.videoConnection = new RtpVideoConnection(this.rtcConnection); + + this.oldVoiceConnection = new VoiceConnection(this); + this.legacyVoiceConnection = new LegacySupportVoiceBridge(this, this.oldVoiceConnection, this.voiceConnection); } destroy() { @@ -107,6 +118,11 @@ export class ServerConnection extends AbstractServerConnection { this.voiceConnection && this.voiceConnection.destroy(); this.voiceConnection = undefined; + this.oldVoiceConnection?.destroy(); + this.oldVoiceConnection = undefined; + + this.legacyVoiceConnection = undefined; + this.commandHandlerBoss && this.commandHandlerBoss.destroy(); this.commandHandlerBoss = undefined; @@ -334,9 +350,7 @@ export class ServerConnection extends AbstractServerConnection { }); if(json["command"] === "initserver") { - this.pingStatistics.thread_id = setInterval(() => this.doNextPing(), this.pingStatistics.interval) as any; - this.doNextPing(); - this.updateConnectionState(ConnectionState.CONNECTED); + this.handleServerInit(); } /* devel-block(log-networking-commands) */ group.end(); @@ -350,9 +364,7 @@ export class ServerConnection extends AbstractServerConnection { }); if(command.command === "initserver") { - this.pingStatistics.thread_id = setInterval(() => this.doNextPing(), this.pingStatistics.interval) as any; - this.doNextPing(); - this.updateConnectionState(ConnectionState.CONNECTED); + this.handleServerInit(); } } else if(json["type"] === "ping") { this.sendData(JSON.stringify({ @@ -369,6 +381,8 @@ export class ServerConnection extends AbstractServerConnection { this.pingStatistics.currentNativeValue = parseInt(json["ping_native"]) / 1000; /* we're getting it in microseconds and not milliseconds */ //log.debug(LogCategory.NETWORKING, tr("Received new pong. Updating ping to: JS: %o Native: %o"), this._ping.value.toFixed(3), this._ping.value_native.toFixed(3)); } + } else if(json["type"] === "WebRTC") { + this.oldVoiceConnection?.handleControlPacket(json); } else { log.warn(LogCategory.NETWORKING, tr("Unknown command type %o"), json["type"]); } @@ -386,6 +400,37 @@ export class ServerConnection extends AbstractServerConnection { this.socket.sendMessage(data); } + private handleServerInit() { + this.pingStatistics.thread_id = setInterval(() => this.doNextPing(), this.pingStatistics.interval) as any; + this.doNextPing(); + this.updateConnectionState(ConnectionState.CONNECTED); + this.client.serverFeatures.awaitFeatures().then(succeeded => { + if(!succeeded) { + /* something like a disconnect happened or so */ + return; + } + + if(this.client.serverFeatures.supportsFeature(ServerFeature.VIDEO, 1)) { + this.legacyVoiceConnection.setVoiceBridge("new").then(() => { + this.rtcConnection.doInitialSetup(); + }).catch(error => { + logError(LogCategory.VOICE, tr("Failed to setup the voice bridge: %o"), error); + /* FIXME: Some kind of error modal? */ + }); + } else{ + /* old voice connection */ + logDebug(LogCategory.NETWORKING, tr("Using legacy voice connection for TeaSpeak server bellow 1.4.5")); + this.legacyVoiceConnection.setVoiceBridge("old").then(() => { + this.oldVoiceConnection.startVoiceBridge(); + this.rtcConnection.setNotSupported(); + }).catch(error => { + logError(LogCategory.VOICE, tr("Failed to setup the old voice bridge: %o"), error); + /* FIXME: Some kind of error modal? */ + }); + } + }); + } + private static commandDataToJson(input: any) : string { return JSON.stringify(input, (key, value) => { switch (typeof value) { @@ -444,7 +489,7 @@ export class ServerConnection extends AbstractServerConnection { } getVoiceConnection(): AbstractVoiceConnection { - return this.voiceConnection /* || this.dummyVoiceConnection; */ + return this.legacyVoiceConnection; } getVideoConnection(): VideoConnection { diff --git a/web/app/index.ts b/web/app/index.ts index 6b9beba2..1e389d0e 100644 --- a/web/app/index.ts +++ b/web/app/index.ts @@ -4,7 +4,7 @@ import "webcrypto-liner"; import "./index.scss"; import "./FileTransfer"; -import "./audio-lib"; +import "./legacy/audio-lib"; import "./hooks/ServerConnection"; import "./hooks/ExternalModal"; diff --git a/web/app/audio-lib/AudioClient.ts b/web/app/legacy/audio-lib/AudioClient.ts similarity index 94% rename from web/app/audio-lib/AudioClient.ts rename to web/app/legacy/audio-lib/AudioClient.ts index 7074087d..a5884286 100644 --- a/web/app/audio-lib/AudioClient.ts +++ b/web/app/legacy/audio-lib/AudioClient.ts @@ -1,4 +1,4 @@ -import {AudioLibrary} from "tc-backend/web/audio-lib/index"; +import {AudioLibrary} from "./index"; import {LogCategory, logWarn} from "tc-shared/log"; export class AudioClient { diff --git a/web/app/audio-lib/WorkerMessages.ts b/web/app/legacy/audio-lib/WorkerMessages.ts similarity index 100% rename from web/app/audio-lib/WorkerMessages.ts rename to web/app/legacy/audio-lib/WorkerMessages.ts diff --git a/web/app/audio-lib/index.ts b/web/app/legacy/audio-lib/index.ts similarity index 96% rename from web/app/audio-lib/index.ts rename to web/app/legacy/audio-lib/index.ts index 30a4b8a0..ec72c421 100644 --- a/web/app/audio-lib/index.ts +++ b/web/app/legacy/audio-lib/index.ts @@ -5,8 +5,8 @@ import { AWMessageRelations, AWNotifies, AWNotifiesWorker -} from "tc-backend/web/audio-lib/WorkerMessages"; -import {AudioClient} from "tc-backend/web/audio-lib/AudioClient"; +} from "./WorkerMessages"; +import {AudioClient} from "./AudioClient"; import {LogCategory, logWarn} from "tc-shared/log"; import * as loader from "tc-loader"; import {Stage} from "tc-loader"; diff --git a/web/app/audio-lib/worker/async_require.d.ts b/web/app/legacy/audio-lib/worker/async_require.d.ts similarity index 100% rename from web/app/audio-lib/worker/async_require.d.ts rename to web/app/legacy/audio-lib/worker/async_require.d.ts diff --git a/web/app/audio-lib/worker/async_require.js b/web/app/legacy/audio-lib/worker/async_require.js similarity index 100% rename from web/app/audio-lib/worker/async_require.js rename to web/app/legacy/audio-lib/worker/async_require.js diff --git a/web/app/audio-lib/worker/index.ts b/web/app/legacy/audio-lib/worker/index.ts similarity index 97% rename from web/app/audio-lib/worker/index.ts rename to web/app/legacy/audio-lib/worker/index.ts index f65c76e2..71221958 100644 --- a/web/app/audio-lib/worker/index.ts +++ b/web/app/legacy/audio-lib/worker/index.ts @@ -8,7 +8,7 @@ import { AWMessageRelations, AWNotifies, AWNotifiesWorker -} from "tc-backend/web/audio-lib/WorkerMessages"; +} from "../WorkerMessages"; import {AudioLibrary, getAudioLibraryInstance} from "./async_require"; diff --git a/web/app/voice/AudioResampler.ts b/web/app/legacy/voice/AudioResampler.ts similarity index 100% rename from web/app/voice/AudioResampler.ts rename to web/app/legacy/voice/AudioResampler.ts diff --git a/web/app/voice/VoiceClient.ts b/web/app/legacy/voice/VoiceClient.ts similarity index 83% rename from web/app/voice/VoiceClient.ts rename to web/app/legacy/voice/VoiceClient.ts index c750458b..cf3c2abf 100644 --- a/web/app/voice/VoiceClient.ts +++ b/web/app/legacy/voice/VoiceClient.ts @@ -1,5 +1,5 @@ import {VoiceClient} from "tc-shared/voice/VoiceClient"; -import {WebVoicePlayer} from "tc-backend/web/voice/VoicePlayer"; +import {WebVoicePlayer} from "./VoicePlayer"; export class VoiceClientController extends WebVoicePlayer implements VoiceClient { private readonly clientId: number; diff --git a/web/app/voice/VoiceHandler.ts b/web/app/legacy/voice/VoiceHandler.ts similarity index 97% rename from web/app/voice/VoiceHandler.ts rename to web/app/legacy/voice/VoiceHandler.ts index 43d805ee..16cd6cfc 100644 --- a/web/app/voice/VoiceHandler.ts +++ b/web/app/legacy/voice/VoiceHandler.ts @@ -1,7 +1,7 @@ import * as log from "tc-shared/log"; import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log"; -import * as aplayer from "../audio/player"; -import {ServerConnection} from "../connection/ServerConnection"; +import * as aplayer from "../../audio/player"; +import {ServerConnection} from "../../connection/ServerConnection"; import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; import {VoiceClientController} from "./VoiceClient"; import {tr} from "tc-shared/i18n/localize"; @@ -23,7 +23,7 @@ import { WhisperTarget } from "tc-shared/voice/VoiceWhisper"; import {VoiceClient} from "tc-shared/voice/VoiceClient"; -import {WebWhisperSession} from "tc-backend/web/voice/VoiceWhisper"; +import {WebWhisperSession} from "./VoiceWhisper"; import {VoicePlayerState} from "tc-shared/voice/VoicePlayer"; type CancelableWhisperTarget = WhisperTarget & { canceled: boolean }; @@ -83,6 +83,10 @@ export class VoiceConnection extends AbstractVoiceConnection { return this.failedConnectionMessage; } + getRetryTimestamp(): number | 0 { + return 0; + } + destroy() { this.connection.events.off(this.serverConnectionStateListener); this.dropVoiceBridge(); @@ -156,8 +160,7 @@ export class VoiceConnection extends AbstractVoiceConnection { this.events.fire("notify_recorder_changed"); } - private startVoiceBridge() { - return; /* We're not doing this currently */ + public startVoiceBridge() { if(!aplayer.initialized()) { logDebug(LogCategory.VOICE, tr("Audio player isn't initialized yet. Waiting for it to initialize.")); if(!this.awaitingAudioInitialize) { @@ -319,7 +322,8 @@ export class VoiceConnection extends AbstractVoiceConnection { private handleServerConnectionStateChanged(event: ServerConnectionEvents["notify_connection_state_changed"]) { if(event.newState === ConnectionState.CONNECTED) { - this.startVoiceBridge(); + /* startVoiceBridge() will be called by the server connection if we're using this old voice bridge */ + /* this.startVoiceBridge(); */ } else { this.connectAttemptCounter = 0; this.lastConnectAttempt = 0; diff --git a/web/app/voice/VoicePlayer.ts b/web/app/legacy/voice/VoicePlayer.ts similarity index 98% rename from web/app/voice/VoicePlayer.ts rename to web/app/legacy/voice/VoicePlayer.ts index ab184af6..c1b09752 100644 --- a/web/app/voice/VoicePlayer.ts +++ b/web/app/legacy/voice/VoicePlayer.ts @@ -4,11 +4,11 @@ import { VoicePlayerLatencySettings, VoicePlayerState } from "tc-shared/voice/VoicePlayer"; -import {AudioClient} from "tc-backend/web/audio-lib/AudioClient"; +import {AudioClient} from "../audio-lib/AudioClient"; import {AudioResampler} from "./AudioResampler"; import {Registry} from "tc-shared/events"; import * as aplayer from "tc-backend/web/audio/player"; -import {getAudioLibrary} from "tc-backend/web/audio-lib"; +import {getAudioLibrary} from "../audio-lib"; import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log"; const kDefaultLatencySettings = { diff --git a/web/app/voice/VoiceWhisper.ts b/web/app/legacy/voice/VoiceWhisper.ts similarity index 97% rename from web/app/voice/VoiceWhisper.ts rename to web/app/legacy/voice/VoiceWhisper.ts index ce050416..a4528631 100644 --- a/web/app/voice/VoiceWhisper.ts +++ b/web/app/legacy/voice/VoiceWhisper.ts @@ -2,8 +2,8 @@ import {WhisperSession, WhisperSessionEvents, WhisperSessionState} from "tc-shar import {Registry} from "tc-shared/events"; import {VoicePlayer, VoicePlayerState} from "tc-shared/voice/VoicePlayer"; import {WhisperSessionInitializeData} from "tc-shared/connection/VoiceConnection"; -import {VoiceWhisperPacket} from "tc-backend/web/voice/bridge/VoiceBridge"; -import {WebVoicePlayer} from "tc-backend/web/voice/VoicePlayer"; +import {VoiceWhisperPacket} from "./bridge/VoiceBridge"; +import {WebVoicePlayer} from "./VoicePlayer"; const kMaxUninitializedBuffers = 10; export class WebWhisperSession implements WhisperSession { diff --git a/web/app/voice/bridge/NativeWebRTCVoiceBridge.ts b/web/app/legacy/voice/bridge/NativeWebRTCVoiceBridge.ts similarity index 98% rename from web/app/voice/bridge/NativeWebRTCVoiceBridge.ts rename to web/app/legacy/voice/bridge/NativeWebRTCVoiceBridge.ts index ca4bbffb..f586d4fd 100644 --- a/web/app/voice/bridge/NativeWebRTCVoiceBridge.ts +++ b/web/app/legacy/voice/bridge/NativeWebRTCVoiceBridge.ts @@ -4,7 +4,7 @@ import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; import {tr} from "tc-shared/i18n/localize"; import {WebRTCVoiceBridge} from "./WebRTCVoiceBridge"; -import {VoiceWhisperPacket} from "tc-backend/web/voice/bridge/VoiceBridge"; +import {VoiceWhisperPacket} from "./VoiceBridge"; import {CryptoHelper} from "tc-shared/profiles/identities/TeamSpeakIdentity"; import arraybuffer_to_string = CryptoHelper.arraybuffer_to_string; diff --git a/web/app/voice/bridge/VoiceBridge.ts b/web/app/legacy/voice/bridge/VoiceBridge.ts similarity index 100% rename from web/app/voice/bridge/VoiceBridge.ts rename to web/app/legacy/voice/bridge/VoiceBridge.ts diff --git a/web/app/voice/bridge/WebRTCVoiceBridge.ts b/web/app/legacy/voice/bridge/WebRTCVoiceBridge.ts similarity index 100% rename from web/app/voice/bridge/WebRTCVoiceBridge.ts rename to web/app/legacy/voice/bridge/WebRTCVoiceBridge.ts diff --git a/web/app/rtc/Connection.ts b/web/app/rtc/Connection.ts index 9c8637dc..f2e5e0d1 100644 --- a/web/app/rtc/Connection.ts +++ b/web/app/rtc/Connection.ts @@ -15,6 +15,7 @@ import { } from "tc-backend/web/rtc/RemoteTrack"; import {SdpCompressor, SdpProcessor} from "tc-backend/web/rtc/SdpUtils"; import {context} from "tc-backend/web/audio/player"; +import {ErrorCode} from "tc-shared/connection/ErrorCode"; const kSdpCompressionMode = 1; @@ -218,7 +219,8 @@ export enum RTPConnectionState { DISCONNECTED, CONNECTING, CONNECTED, - FAILED + FAILED, + NOT_SUPPORTED } class InternalRemoteRTPAudioTrack extends RemoteRTPAudioTrack { @@ -560,6 +562,11 @@ export class RTCConnection { }); } + public setNotSupported() { + this.reset(false); + this.updateConnectionState(RTPConnectionState.NOT_SUPPORTED); + } + private updateConnectionState(newState: RTPConnectionState) { if(this.connectionState === newState) { return; } @@ -615,7 +622,10 @@ export class RTCConnection { private enableDtx(_sender: RTCRtpSender) { } - private doInitialSetup() { + public doInitialSetup() { + /* initialize rtc connection */ + this.retryCalculator.reset(); + if(!('RTCPeerConnection' in window)) { this.handleFatalError(tr("WebRTC has been disabled (RTCPeerConnection is not defined)"), false); return; @@ -716,6 +726,10 @@ export class RTCConnection { } catch (error) { if(this.peer !== peer) { return; } if(error instanceof CommandResult) { + if(error.id === ErrorCode.COMMAND_NOT_FOUND) { + this.setNotSupported(); + return; + } error = error.formattedMessage(); } logWarn(LogCategory.VOICE, tr("Failed to initialize RTP connection: %o"), error); @@ -729,9 +743,7 @@ export class RTCConnection { private handleConnectionStateChanged(event: ServerConnectionEvents["notify_connection_state_changed"]) { if(event.newState === ConnectionState.CONNECTED) { - /* initialize rtc connection */ - this.retryCalculator.reset(); - this.doInitialSetup(); + /* will be called by the server connection handler */ } else { this.reset(true); } diff --git a/web/app/rtc/video/Connection.ts b/web/app/rtc/video/Connection.ts index d9a3455c..eeb13194 100644 --- a/web/app/rtc/video/Connection.ts +++ b/web/app/rtc/video/Connection.ts @@ -15,6 +15,7 @@ import {RtpVideoClient} from "tc-backend/web/rtc/video/VideoClient"; import {tr} from "tc-shared/i18n/localize"; import {ConnectionState} from "tc-shared/ConnectionHandler"; import {ConnectionStatistics} from "tc-shared/connection/ConnectionBase"; +import {VoiceConnectionStatus} from "tc-shared/connection/VoiceConnection"; type VideoBroadcast = { readonly source: VideoSource; @@ -240,6 +241,10 @@ export class RtpVideoConnection implements VideoConnection { case RTPConnectionState.FAILED: this.setConnectionState(VideoConnectionStatus.Failed); break; + + case RTPConnectionState.NOT_SUPPORTED: + this.setConnectionState(VideoConnectionStatus.Unsupported); + break; } } diff --git a/web/app/rtc/voice/Connection.ts b/web/app/rtc/voice/Connection.ts index f5c5a8c6..7d840c1e 100644 --- a/web/app/rtc/voice/Connection.ts +++ b/web/app/rtc/voice/Connection.ts @@ -256,8 +256,9 @@ export class RtpVoiceConnection extends AbstractVoiceConnection { } unregisterVoiceClient(client: VoiceClient) { - if(!(client instanceof RtpVoiceClient)) + if(!(client instanceof RtpVoiceClient)) { throw "Invalid client type"; + } console.error("Destroy voice client %d", client.getClientId()); client.events.off("notify_state_changed", this.voiceClientStateChangedEventListener); @@ -357,6 +358,10 @@ export class RtpVoiceConnection extends AbstractVoiceConnection { this.localFailedReason = undefined; this.setConnectionState(VoiceConnectionStatus.Failed); break; + + case RTPConnectionState.NOT_SUPPORTED: + this.setConnectionState(VoiceConnectionStatus.ServerUnsupported); + break; } }