Added legacy support for the non 1.5.0 TeaSpeak server voice bridge and fixed the client status updates
parent
769563757e
commit
8be3943d00
|
@ -175,6 +175,7 @@ export class ConnectionHandler {
|
||||||
|
|
||||||
lastChannelCodecWarned: -1
|
lastChannelCodecWarned: -1
|
||||||
};
|
};
|
||||||
|
private clientStatusUnsync = false;
|
||||||
|
|
||||||
private inputHardwareState: InputHardwareState = InputHardwareState.MISSING;
|
private inputHardwareState: InputHardwareState = InputHardwareState.MISSING;
|
||||||
|
|
||||||
|
@ -746,14 +747,17 @@ export class ConnectionHandler {
|
||||||
{
|
{
|
||||||
const currentClientProperties = this.getClient().properties;
|
const currentClientProperties = this.getClient().properties;
|
||||||
for(const key of Object.keys(localClientUpdates)) {
|
for(const key of Object.keys(localClientUpdates)) {
|
||||||
if(currentClientProperties[key] === localClientUpdates[key])
|
if(currentClientProperties[key] === localClientUpdates[key] && !this.clientStatusUnsync)
|
||||||
delete localClientUpdates[key];
|
delete localClientUpdates[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Object.keys(localClientUpdates).length > 0) {
|
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);
|
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.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) */
|
/* Update these properties anyways (for case the server fails to handle the command) */
|
||||||
const updates = [];
|
const updates = [];
|
||||||
|
|
|
@ -11,7 +11,8 @@ export enum ServerFeature {
|
||||||
ERROR_BULKS= "error-bulks", /* Current version is 1 */
|
ERROR_BULKS= "error-bulks", /* Current version is 1 */
|
||||||
ADVANCED_CHANNEL_CHAT= "advanced-channel-chat", /* Current version is 1 */
|
ADVANCED_CHANNEL_CHAT= "advanced-channel-chat", /* Current version is 1 */
|
||||||
LOG_QUERY= "log-query", /* 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 {
|
export interface ServerFeatureEvents {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export abstract class AbstractVoiceConnection {
|
||||||
abstract encodingSupported(codec: number) : boolean;
|
abstract encodingSupported(codec: number) : boolean;
|
||||||
abstract decodingSupported(codec: number) : boolean;
|
abstract decodingSupported(codec: number) : boolean;
|
||||||
|
|
||||||
abstract registerVoiceClient(clientId: number);
|
abstract registerVoiceClient(clientId: number) : VoiceClient;
|
||||||
abstract availableVoiceClients() : VoiceClient[];
|
abstract availableVoiceClients() : VoiceClient[];
|
||||||
abstract unregisterVoiceClient(client: VoiceClient);
|
abstract unregisterVoiceClient(client: VoiceClient);
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,4 @@ app/**/*.css.map
|
||||||
app/**/*.js
|
app/**/*.js
|
||||||
app/**/*.js.map
|
app/**/*.js.map
|
||||||
|
|
||||||
!app/audio-lib/worker/async_require.js
|
!app/legacy/audio-lib/worker/async_require.js
|
|
@ -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<VoicePlayerEvents>;
|
||||||
|
|
||||||
|
handle: VoiceClient;
|
||||||
|
|
||||||
|
private volume: number;
|
||||||
|
private latencySettings: VoicePlayerLatencySettings | undefined;
|
||||||
|
|
||||||
|
constructor(clientId: number) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.events = new Registry<VoicePlayerEvents>();
|
||||||
|
|
||||||
|
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<VoicePlayerLatencySettings> {
|
||||||
|
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<void> {
|
||||||
|
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<ConnectionStatistics> {
|
||||||
|
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<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import {
|
import {
|
||||||
AbstractServerConnection,
|
AbstractServerConnection,
|
||||||
CommandOptionDefaults,
|
CommandOptionDefaults,
|
||||||
CommandOptions, ConnectionStatistics,
|
CommandOptions,
|
||||||
ConnectionStateListener,
|
ConnectionStateListener,
|
||||||
|
ConnectionStatistics,
|
||||||
} from "tc-shared/connection/ConnectionBase";
|
} from "tc-shared/connection/ConnectionBase";
|
||||||
import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";
|
import {ConnectionHandler, ConnectionState, DisconnectReason} from "tc-shared/ConnectionHandler";
|
||||||
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
|
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 {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
import {settings, Settings} from "tc-shared/settings";
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
import * as log from "tc-shared/log";
|
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 {Regex} from "tc-shared/ui/modal/ModalConnect";
|
||||||
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
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 {RtpVoiceConnection} from "tc-backend/web/rtc/voice/Connection";
|
||||||
import {RtpVideoConnection} from "tc-backend/web/rtc/video/Connection";
|
import {RtpVideoConnection} from "tc-backend/web/rtc/video/Connection";
|
||||||
import {VideoConnection} from "tc-shared/connection/VideoConnection";
|
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<T> {
|
class ReturnListener<T> {
|
||||||
resolve: (value?: T | PromiseLike<T>) => void;
|
resolve: (value?: T | PromiseLike<T>) => void;
|
||||||
|
@ -50,6 +54,10 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
private voiceConnection: RtpVoiceConnection;
|
private voiceConnection: RtpVoiceConnection;
|
||||||
private videoConnection: RtpVideoConnection;
|
private videoConnection: RtpVideoConnection;
|
||||||
|
|
||||||
|
/* legacy */
|
||||||
|
private oldVoiceConnection: VoiceConnection;
|
||||||
|
private legacyVoiceConnection: LegacySupportVoiceBridge;
|
||||||
|
|
||||||
private pingStatistics = {
|
private pingStatistics = {
|
||||||
thread_id: 0,
|
thread_id: 0,
|
||||||
|
|
||||||
|
@ -77,6 +85,9 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
this.rtcConnection = new RTCConnection(this);
|
this.rtcConnection = new RTCConnection(this);
|
||||||
this.voiceConnection = new RtpVoiceConnection(this, this.rtcConnection);
|
this.voiceConnection = new RtpVoiceConnection(this, this.rtcConnection);
|
||||||
this.videoConnection = new RtpVideoConnection(this.rtcConnection);
|
this.videoConnection = new RtpVideoConnection(this.rtcConnection);
|
||||||
|
|
||||||
|
this.oldVoiceConnection = new VoiceConnection(this);
|
||||||
|
this.legacyVoiceConnection = new LegacySupportVoiceBridge(this, this.oldVoiceConnection, this.voiceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -107,6 +118,11 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
this.voiceConnection && this.voiceConnection.destroy();
|
this.voiceConnection && this.voiceConnection.destroy();
|
||||||
this.voiceConnection = undefined;
|
this.voiceConnection = undefined;
|
||||||
|
|
||||||
|
this.oldVoiceConnection?.destroy();
|
||||||
|
this.oldVoiceConnection = undefined;
|
||||||
|
|
||||||
|
this.legacyVoiceConnection = undefined;
|
||||||
|
|
||||||
this.commandHandlerBoss && this.commandHandlerBoss.destroy();
|
this.commandHandlerBoss && this.commandHandlerBoss.destroy();
|
||||||
this.commandHandlerBoss = undefined;
|
this.commandHandlerBoss = undefined;
|
||||||
|
|
||||||
|
@ -334,9 +350,7 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
});
|
});
|
||||||
|
|
||||||
if(json["command"] === "initserver") {
|
if(json["command"] === "initserver") {
|
||||||
this.pingStatistics.thread_id = setInterval(() => this.doNextPing(), this.pingStatistics.interval) as any;
|
this.handleServerInit();
|
||||||
this.doNextPing();
|
|
||||||
this.updateConnectionState(ConnectionState.CONNECTED);
|
|
||||||
}
|
}
|
||||||
/* devel-block(log-networking-commands) */
|
/* devel-block(log-networking-commands) */
|
||||||
group.end();
|
group.end();
|
||||||
|
@ -350,9 +364,7 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
});
|
});
|
||||||
|
|
||||||
if(command.command === "initserver") {
|
if(command.command === "initserver") {
|
||||||
this.pingStatistics.thread_id = setInterval(() => this.doNextPing(), this.pingStatistics.interval) as any;
|
this.handleServerInit();
|
||||||
this.doNextPing();
|
|
||||||
this.updateConnectionState(ConnectionState.CONNECTED);
|
|
||||||
}
|
}
|
||||||
} else if(json["type"] === "ping") {
|
} else if(json["type"] === "ping") {
|
||||||
this.sendData(JSON.stringify({
|
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 */
|
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));
|
//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 {
|
} else {
|
||||||
log.warn(LogCategory.NETWORKING, tr("Unknown command type %o"), json["type"]);
|
log.warn(LogCategory.NETWORKING, tr("Unknown command type %o"), json["type"]);
|
||||||
}
|
}
|
||||||
|
@ -386,6 +400,37 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
this.socket.sendMessage(data);
|
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 {
|
private static commandDataToJson(input: any) : string {
|
||||||
return JSON.stringify(input, (key, value) => {
|
return JSON.stringify(input, (key, value) => {
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
|
@ -444,7 +489,7 @@ export class ServerConnection extends AbstractServerConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
getVoiceConnection(): AbstractVoiceConnection {
|
getVoiceConnection(): AbstractVoiceConnection {
|
||||||
return this.voiceConnection /* || this.dummyVoiceConnection; */
|
return this.legacyVoiceConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoConnection(): VideoConnection {
|
getVideoConnection(): VideoConnection {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "webcrypto-liner";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import "./FileTransfer";
|
import "./FileTransfer";
|
||||||
|
|
||||||
import "./audio-lib";
|
import "./legacy/audio-lib";
|
||||||
|
|
||||||
import "./hooks/ServerConnection";
|
import "./hooks/ServerConnection";
|
||||||
import "./hooks/ExternalModal";
|
import "./hooks/ExternalModal";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {AudioLibrary} from "tc-backend/web/audio-lib/index";
|
import {AudioLibrary} from "./index";
|
||||||
import {LogCategory, logWarn} from "tc-shared/log";
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
|
|
||||||
export class AudioClient {
|
export class AudioClient {
|
|
@ -5,8 +5,8 @@ import {
|
||||||
AWMessageRelations,
|
AWMessageRelations,
|
||||||
AWNotifies,
|
AWNotifies,
|
||||||
AWNotifiesWorker
|
AWNotifiesWorker
|
||||||
} from "tc-backend/web/audio-lib/WorkerMessages";
|
} from "./WorkerMessages";
|
||||||
import {AudioClient} from "tc-backend/web/audio-lib/AudioClient";
|
import {AudioClient} from "./AudioClient";
|
||||||
import {LogCategory, logWarn} from "tc-shared/log";
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import {Stage} from "tc-loader";
|
|
@ -8,7 +8,7 @@ import {
|
||||||
AWMessageRelations,
|
AWMessageRelations,
|
||||||
AWNotifies,
|
AWNotifies,
|
||||||
AWNotifiesWorker
|
AWNotifiesWorker
|
||||||
} from "tc-backend/web/audio-lib/WorkerMessages";
|
} from "../WorkerMessages";
|
||||||
|
|
||||||
import {AudioLibrary, getAudioLibraryInstance} from "./async_require";
|
import {AudioLibrary, getAudioLibraryInstance} from "./async_require";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {VoiceClient} from "tc-shared/voice/VoiceClient";
|
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 {
|
export class VoiceClientController extends WebVoicePlayer implements VoiceClient {
|
||||||
private readonly clientId: number;
|
private readonly clientId: number;
|
|
@ -1,7 +1,7 @@
|
||||||
import * as log from "tc-shared/log";
|
import * as log from "tc-shared/log";
|
||||||
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 * as aplayer from "../../audio/player";
|
||||||
import {ServerConnection} from "../connection/ServerConnection";
|
import {ServerConnection} from "../../connection/ServerConnection";
|
||||||
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
|
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
|
||||||
import {VoiceClientController} from "./VoiceClient";
|
import {VoiceClientController} from "./VoiceClient";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
@ -23,7 +23,7 @@ import {
|
||||||
WhisperTarget
|
WhisperTarget
|
||||||
} from "tc-shared/voice/VoiceWhisper";
|
} from "tc-shared/voice/VoiceWhisper";
|
||||||
import {VoiceClient} from "tc-shared/voice/VoiceClient";
|
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";
|
import {VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
||||||
|
|
||||||
type CancelableWhisperTarget = WhisperTarget & { canceled: boolean };
|
type CancelableWhisperTarget = WhisperTarget & { canceled: boolean };
|
||||||
|
@ -83,6 +83,10 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
return this.failedConnectionMessage;
|
return this.failedConnectionMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRetryTimestamp(): number | 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.connection.events.off(this.serverConnectionStateListener);
|
this.connection.events.off(this.serverConnectionStateListener);
|
||||||
this.dropVoiceBridge();
|
this.dropVoiceBridge();
|
||||||
|
@ -156,8 +160,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
this.events.fire("notify_recorder_changed");
|
this.events.fire("notify_recorder_changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
private startVoiceBridge() {
|
public startVoiceBridge() {
|
||||||
return; /* We're not doing this currently */
|
|
||||||
if(!aplayer.initialized()) {
|
if(!aplayer.initialized()) {
|
||||||
logDebug(LogCategory.VOICE, tr("Audio player isn't initialized yet. Waiting for it to initialize."));
|
logDebug(LogCategory.VOICE, tr("Audio player isn't initialized yet. Waiting for it to initialize."));
|
||||||
if(!this.awaitingAudioInitialize) {
|
if(!this.awaitingAudioInitialize) {
|
||||||
|
@ -319,7 +322,8 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
||||||
|
|
||||||
private handleServerConnectionStateChanged(event: ServerConnectionEvents["notify_connection_state_changed"]) {
|
private handleServerConnectionStateChanged(event: ServerConnectionEvents["notify_connection_state_changed"]) {
|
||||||
if(event.newState === ConnectionState.CONNECTED) {
|
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 {
|
} else {
|
||||||
this.connectAttemptCounter = 0;
|
this.connectAttemptCounter = 0;
|
||||||
this.lastConnectAttempt = 0;
|
this.lastConnectAttempt = 0;
|
|
@ -4,11 +4,11 @@ import {
|
||||||
VoicePlayerLatencySettings,
|
VoicePlayerLatencySettings,
|
||||||
VoicePlayerState
|
VoicePlayerState
|
||||||
} from "tc-shared/voice/VoicePlayer";
|
} 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 {AudioResampler} from "./AudioResampler";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import * as aplayer from "tc-backend/web/audio/player";
|
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";
|
import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log";
|
||||||
|
|
||||||
const kDefaultLatencySettings = {
|
const kDefaultLatencySettings = {
|
|
@ -2,8 +2,8 @@ import {WhisperSession, WhisperSessionEvents, WhisperSessionState} from "tc-shar
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {VoicePlayer, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
import {VoicePlayer, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
||||||
import {WhisperSessionInitializeData} from "tc-shared/connection/VoiceConnection";
|
import {WhisperSessionInitializeData} from "tc-shared/connection/VoiceConnection";
|
||||||
import {VoiceWhisperPacket} from "tc-backend/web/voice/bridge/VoiceBridge";
|
import {VoiceWhisperPacket} from "./bridge/VoiceBridge";
|
||||||
import {WebVoicePlayer} from "tc-backend/web/voice/VoicePlayer";
|
import {WebVoicePlayer} from "./VoicePlayer";
|
||||||
|
|
||||||
const kMaxUninitializedBuffers = 10;
|
const kMaxUninitializedBuffers = 10;
|
||||||
export class WebWhisperSession implements WhisperSession {
|
export class WebWhisperSession implements WhisperSession {
|
|
@ -4,7 +4,7 @@ import * as log from "tc-shared/log";
|
||||||
import {LogCategory} from "tc-shared/log";
|
import {LogCategory} from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {WebRTCVoiceBridge} from "./WebRTCVoiceBridge";
|
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 {CryptoHelper} from "tc-shared/profiles/identities/TeamSpeakIdentity";
|
||||||
import arraybuffer_to_string = CryptoHelper.arraybuffer_to_string;
|
import arraybuffer_to_string = CryptoHelper.arraybuffer_to_string;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "tc-backend/web/rtc/RemoteTrack";
|
} from "tc-backend/web/rtc/RemoteTrack";
|
||||||
import {SdpCompressor, SdpProcessor} from "tc-backend/web/rtc/SdpUtils";
|
import {SdpCompressor, SdpProcessor} from "tc-backend/web/rtc/SdpUtils";
|
||||||
import {context} from "tc-backend/web/audio/player";
|
import {context} from "tc-backend/web/audio/player";
|
||||||
|
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||||
|
|
||||||
const kSdpCompressionMode = 1;
|
const kSdpCompressionMode = 1;
|
||||||
|
|
||||||
|
@ -218,7 +219,8 @@ export enum RTPConnectionState {
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
FAILED
|
FAILED,
|
||||||
|
NOT_SUPPORTED
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternalRemoteRTPAudioTrack extends RemoteRTPAudioTrack {
|
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) {
|
private updateConnectionState(newState: RTPConnectionState) {
|
||||||
if(this.connectionState === newState) { return; }
|
if(this.connectionState === newState) { return; }
|
||||||
|
|
||||||
|
@ -615,7 +622,10 @@ export class RTCConnection {
|
||||||
|
|
||||||
private enableDtx(_sender: RTCRtpSender) { }
|
private enableDtx(_sender: RTCRtpSender) { }
|
||||||
|
|
||||||
private doInitialSetup() {
|
public doInitialSetup() {
|
||||||
|
/* initialize rtc connection */
|
||||||
|
this.retryCalculator.reset();
|
||||||
|
|
||||||
if(!('RTCPeerConnection' in window)) {
|
if(!('RTCPeerConnection' in window)) {
|
||||||
this.handleFatalError(tr("WebRTC has been disabled (RTCPeerConnection is not defined)"), false);
|
this.handleFatalError(tr("WebRTC has been disabled (RTCPeerConnection is not defined)"), false);
|
||||||
return;
|
return;
|
||||||
|
@ -716,6 +726,10 @@ export class RTCConnection {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if(this.peer !== peer) { return; }
|
if(this.peer !== peer) { return; }
|
||||||
if(error instanceof CommandResult) {
|
if(error instanceof CommandResult) {
|
||||||
|
if(error.id === ErrorCode.COMMAND_NOT_FOUND) {
|
||||||
|
this.setNotSupported();
|
||||||
|
return;
|
||||||
|
}
|
||||||
error = error.formattedMessage();
|
error = error.formattedMessage();
|
||||||
}
|
}
|
||||||
logWarn(LogCategory.VOICE, tr("Failed to initialize RTP connection: %o"), error);
|
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"]) {
|
private handleConnectionStateChanged(event: ServerConnectionEvents["notify_connection_state_changed"]) {
|
||||||
if(event.newState === ConnectionState.CONNECTED) {
|
if(event.newState === ConnectionState.CONNECTED) {
|
||||||
/* initialize rtc connection */
|
/* will be called by the server connection handler */
|
||||||
this.retryCalculator.reset();
|
|
||||||
this.doInitialSetup();
|
|
||||||
} else {
|
} else {
|
||||||
this.reset(true);
|
this.reset(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {RtpVideoClient} from "tc-backend/web/rtc/video/VideoClient";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {ConnectionState} from "tc-shared/ConnectionHandler";
|
import {ConnectionState} from "tc-shared/ConnectionHandler";
|
||||||
import {ConnectionStatistics} from "tc-shared/connection/ConnectionBase";
|
import {ConnectionStatistics} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {VoiceConnectionStatus} from "tc-shared/connection/VoiceConnection";
|
||||||
|
|
||||||
type VideoBroadcast = {
|
type VideoBroadcast = {
|
||||||
readonly source: VideoSource;
|
readonly source: VideoSource;
|
||||||
|
@ -240,6 +241,10 @@ export class RtpVideoConnection implements VideoConnection {
|
||||||
case RTPConnectionState.FAILED:
|
case RTPConnectionState.FAILED:
|
||||||
this.setConnectionState(VideoConnectionStatus.Failed);
|
this.setConnectionState(VideoConnectionStatus.Failed);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RTPConnectionState.NOT_SUPPORTED:
|
||||||
|
this.setConnectionState(VideoConnectionStatus.Unsupported);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -256,8 +256,9 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterVoiceClient(client: VoiceClient) {
|
unregisterVoiceClient(client: VoiceClient) {
|
||||||
if(!(client instanceof RtpVoiceClient))
|
if(!(client instanceof RtpVoiceClient)) {
|
||||||
throw "Invalid client type";
|
throw "Invalid client type";
|
||||||
|
}
|
||||||
|
|
||||||
console.error("Destroy voice client %d", client.getClientId());
|
console.error("Destroy voice client %d", client.getClientId());
|
||||||
client.events.off("notify_state_changed", this.voiceClientStateChangedEventListener);
|
client.events.off("notify_state_changed", this.voiceClientStateChangedEventListener);
|
||||||
|
@ -357,6 +358,10 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
|
||||||
this.localFailedReason = undefined;
|
this.localFailedReason = undefined;
|
||||||
this.setConnectionState(VoiceConnectionStatus.Failed);
|
this.setConnectionState(VoiceConnectionStatus.Failed);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RTPConnectionState.NOT_SUPPORTED:
|
||||||
|
this.setConnectionState(VoiceConnectionStatus.ServerUnsupported);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue