import {ServerConnection} from "./ServerConnection"; import {NativeVoiceConnection} from "tc-native/connection"; import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; import {tr} from "tc-shared/i18n/localize"; import {LogCategory} from "tc-shared/log"; import * as log from "tc-shared/log"; import { AbstractVoiceConnection, LatencySettings, VoiceClient, VoiceConnectionStatus } from "tc-shared/connection/VoiceConnection"; import {NativeInput} from "../audio/AudioRecorder"; export class VoiceConnection extends AbstractVoiceConnection { readonly connection: ServerConnection; readonly handle: NativeVoiceConnection; private _audio_source: RecorderProfile; constructor(connection: ServerConnection, voice: NativeVoiceConnection) { super(connection); this.connection = connection; this.handle = voice; } setup() { } async acquire_voice_recorder(recorder: RecorderProfile | undefined, enforce?: boolean) { if(this._audio_source === recorder && !enforce) return; if(this._audio_source) await this._audio_source.unmount(); if(recorder) { if(!(recorder.input instanceof NativeInput)) throw "Recorder input must be an instance of NativeInput!"; await recorder.unmount(); } this.handleVoiceEnded(); this._audio_source = recorder; if(recorder) { recorder.current_handler = this.connection.client; recorder.callback_unmount = () => { this._audio_source = undefined; this.handle.set_audio_source(undefined); this.connection.client.update_voice_status(undefined); }; recorder.callback_start = this.on_voice_started.bind(this); recorder.callback_stop = this.handleVoiceEnded.bind(this); (recorder as any).callback_support_change = () => { this.connection.client.update_voice_status(undefined); }; this.handle.set_audio_source((recorder.input as NativeInput).getNativeConsumer()); } this.connection.client.update_voice_status(undefined); } voice_playback_support() : boolean { return this.connection.connected(); } voice_send_support() : boolean { return this.connection.connected(); } private current_channel_codec() : number { const chandler = this.connection.client; return (chandler.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec; } private handleVoiceEnded() { const chandler = this.connection.client; chandler.getClient().speaking = false; if(!chandler.connected) return false; if(chandler.isMicrophoneMuted()) return false; console.log(tr("Local voice ended")); //TODO Send end? (Or is this already an automated thing?) } private on_voice_started() { const chandler = this.connection.client; if(chandler.isMicrophoneMuted()) { /* evil hack due to the settings :D */ log.warn(LogCategory.VOICE, tr("Received local voice started event, even thou we're muted! Do not send any voice.")); if(this.handle) { this.handle.enable_voice_send(false); } return; } log.info(LogCategory.VOICE, tr("Local voice started (Native)")); this.handle.enable_voice_send(true); const ch = chandler.getClient(); if(ch) ch.speaking = true; } getConnectionState(): VoiceConnectionStatus { return VoiceConnectionStatus.Connected; } voice_recorder(): RecorderProfile { return this._audio_source; } available_clients(): VoiceClient[] { return this.handle.available_clients().map(e => Object.assign(e, { support_latency_settings() { return true; }, reset_latency_settings() { const stream = this.get_stream(); stream.set_buffer_latency(0.080); stream.set_buffer_max_latency(0.5); return this.latency_settings(); }, latency_settings(settings?: LatencySettings) : LatencySettings { const stream = this.get_stream(); if(typeof settings !== "undefined") { stream.set_buffer_latency(settings.min_buffer / 1000); stream.set_buffer_max_latency(settings.max_buffer / 100); } return { max_buffer: Math.floor(stream.get_buffer_max_latency() * 1000), min_buffer: Math.floor(stream.get_buffer_latency() * 1000) }; }, support_flush() { return true; }, flush() { const stream = this.get_stream(); stream.flush_buffer(); } }) as any); /* cast to any because of: Type 'import("/mnt/d/TeaSpeak/client_linux/client/imports/shared-app/connection/ConnectionBase").voice.PlayerState' is not assignable to type 'import("tc-native/connection").PlayerState' */ } find_client(client_id: number) : VoiceClient | undefined { for(const client of this.available_clients()) if(client.client_id === client_id) return client; return undefined; } unregister_client(client: VoiceClient): Promise { this.handle.unregister_client(client.client_id); return Promise.resolve(); } register_client(client_id: number): VoiceClient { const client = this.handle.register_client(client_id); const c = this.find_client(client_id); c.reset_latency_settings(); return c; } decoding_supported(codec: number): boolean { return this.handle.decoding_supported(codec); } encoding_supported(codec: number): boolean { return this.handle.encoding_supported(codec); } get_encoder_codec(): number { return this.handle.get_encoder_codec(); } set_encoder_codec(codec: number) { return this.handle.set_encoder_codec(codec); } }