TeaWeb/shared/js/voice/VoiceHandler.ts

479 lines
18 KiB
TypeScript
Raw Normal View History

2018-03-07 19:06:52 +01:00
/// <reference path="../client.ts" />
2018-03-24 23:38:01 +01:00
/// <reference path="../codec/Codec.ts" />
2018-03-07 19:06:52 +01:00
/// <reference path="VoiceRecorder.ts" />
2018-04-11 17:56:09 +02:00
class CodecPoolEntry {
instance: BasicCodec;
owner: number;
last_access: number;
}
class CodecPool {
handle: VoiceConnection;
codecIndex: number;
2018-04-19 18:42:34 +02:00
name: string;
type: CodecType;
2018-04-11 17:56:09 +02:00
entries: CodecPoolEntry[] = [];
maxInstances: number = 2;
2018-04-19 18:42:34 +02:00
private _supported: boolean = true;
2018-04-16 20:38:35 +02:00
initialize(cached: number) {
Implemented the Material Design and fixed some bugs (#33) * cleaned up some files * Fundamental style update * Redesigned some style * fixed hostbanner popup * Removed old identity stuff * fixed close listener * Fixed changelog date * fixed release chat icons * fixed url * Fixed hostbanner * Uploaded missing images * Improved update handling * Improved script files * Fixed loading error and icon error * fixed Yes/No modal * Fixed loader issues with MS Edge * fixed modal style bug * Fixed control bar overflow for small devices * Improved error handling on identity creation * Logging generate error to terminal * fixed possible php error * fixed some possible loading errors when other files have'nt been already loaded. * removed debug message * Changed emsrcypten flags * Improved codec error handling * removed webassembly as required dependency * Improved and fixed channel tree issues * Improved the sliders * Removed unneeded files * fixed loader versions cache * second slight performance improved (dont animate elements anymore if they are not shown) * Fixed query visibility setting * not showing useless client infos for query clients * Added an auto reconnect system * Added a canceled message and increased reconnect interval * removed implemented todo * fixed repetitive channel names * Reworked the channel tree selected lines * Fixed channel tree names * Fixed name alignment * fixed the native client * added min width to the server select groups to avoid a disappearing effect on shrink * fixed bugged downloaded icons
2019-02-17 16:08:10 +01:00
/* test if we're able to use this codec */
const dummy_client_id = 0xFFEF;
this.ownCodec(dummy_client_id).then(codec => {
console.log(tr("Release again! (%o)"), codec);
this.releaseCodec(dummy_client_id);
}).catch(error => {
if(this._supported) {
console.warn(tr("Disabling codec support for "), this.name);
Implemented the Material Design and fixed some bugs (#33) * cleaned up some files * Fundamental style update * Redesigned some style * fixed hostbanner popup * Removed old identity stuff * fixed close listener * Fixed changelog date * fixed release chat icons * fixed url * Fixed hostbanner * Uploaded missing images * Improved update handling * Improved script files * Fixed loading error and icon error * fixed Yes/No modal * Fixed loader issues with MS Edge * fixed modal style bug * Fixed control bar overflow for small devices * Improved error handling on identity creation * Logging generate error to terminal * fixed possible php error * fixed some possible loading errors when other files have'nt been already loaded. * removed debug message * Changed emsrcypten flags * Improved codec error handling * removed webassembly as required dependency * Improved and fixed channel tree issues * Improved the sliders * Removed unneeded files * fixed loader versions cache * second slight performance improved (dont animate elements anymore if they are not shown) * Fixed query visibility setting * not showing useless client infos for query clients * Added an auto reconnect system * Added a canceled message and increased reconnect interval * removed implemented todo * fixed repetitive channel names * Reworked the channel tree selected lines * Fixed channel tree names * Fixed name alignment * fixed the native client * added min width to the server select groups to avoid a disappearing effect on shrink * fixed bugged downloaded icons
2019-02-17 16:08:10 +01:00
createErrorModal(tr("Could not load codec driver"), tr("Could not load or initialize codec ") + this.name + "<br>" +
"Error: <code>" + JSON.stringify(error) + "</code>").open();
console.error(tr("Failed to initialize the opus codec. Error: %o"), error);
} else {
console.debug(tr("Failed to initialize already disabled codec. Error: %o"), error);
}
this._supported = false;
});
2018-04-16 20:38:35 +02:00
}
supported() { return this._supported; }
2018-04-16 20:38:35 +02:00
2018-04-18 20:12:10 +02:00
ownCodec?(clientId: number, create: boolean = true) : Promise<BasicCodec | undefined> {
return new Promise<BasicCodec>((resolve, reject) => {
if(!this._supported) {
reject(tr("unsupported codec!"));
2018-04-18 20:12:10 +02:00
return;
}
2018-04-11 17:56:09 +02:00
2018-04-18 20:12:10 +02:00
let freeSlot = 0;
for(let index = 0; index < this.entries.length; index++) {
if(this.entries[index].owner == clientId) {
this.entries[index].last_access = new Date().getTime();
if(this.entries[index].instance.initialized()) resolve(this.entries[index].instance);
2018-04-19 18:42:34 +02:00
else {
this.entries[index].instance.initialise().then((flag) => {
//TODO test success flag
this.ownCodec(clientId, false).then(resolve).catch(reject);
}).catch(error => {
console.error(tr("Could not initialize codec!\nError: %o"), error);
Implemented the Material Design and fixed some bugs (#33) * cleaned up some files * Fundamental style update * Redesigned some style * fixed hostbanner popup * Removed old identity stuff * fixed close listener * Fixed changelog date * fixed release chat icons * fixed url * Fixed hostbanner * Uploaded missing images * Improved update handling * Improved script files * Fixed loading error and icon error * fixed Yes/No modal * Fixed loader issues with MS Edge * fixed modal style bug * Fixed control bar overflow for small devices * Improved error handling on identity creation * Logging generate error to terminal * fixed possible php error * fixed some possible loading errors when other files have'nt been already loaded. * removed debug message * Changed emsrcypten flags * Improved codec error handling * removed webassembly as required dependency * Improved and fixed channel tree issues * Improved the sliders * Removed unneeded files * fixed loader versions cache * second slight performance improved (dont animate elements anymore if they are not shown) * Fixed query visibility setting * not showing useless client infos for query clients * Added an auto reconnect system * Added a canceled message and increased reconnect interval * removed implemented todo * fixed repetitive channel names * Reworked the channel tree selected lines * Fixed channel tree names * Fixed name alignment * fixed the native client * added min width to the server select groups to avoid a disappearing effect on shrink * fixed bugged downloaded icons
2019-02-17 16:08:10 +01:00
reject(typeof(error) === 'string' ? error : tr("Could not initialize codec!"));
2018-04-19 18:42:34 +02:00
});
}
2018-04-18 20:12:10 +02:00
return;
} else if(freeSlot == 0 && this.entries[index].owner == 0) {
freeSlot = index;
}
2018-04-11 17:56:09 +02:00
}
2018-04-18 20:12:10 +02:00
if(!create) {
resolve(undefined);
return;
}
if(freeSlot == 0){
freeSlot = this.entries.length;
let entry = new CodecPoolEntry();
entry.instance = audio.codec.new_instance(this.type);
2018-06-19 20:31:05 +02:00
entry.instance.on_encoded_data = buffer => this.handle.handleEncodedVoicePacket(buffer, this.codecIndex);
2018-04-18 20:12:10 +02:00
this.entries.push(entry);
}
this.entries[freeSlot].owner = clientId;
this.entries[freeSlot].last_access = new Date().getTime();
if(this.entries[freeSlot].instance.initialized())
this.entries[freeSlot].instance.reset();
else {
2018-04-19 18:42:34 +02:00
this.ownCodec(clientId, false).then(resolve).catch(reject);
2018-04-18 20:12:10 +02:00
return;
}
resolve(this.entries[freeSlot].instance);
});
2018-04-11 17:56:09 +02:00
}
releaseCodec(clientId: number) {
2018-04-19 18:42:34 +02:00
for(let index = 0; index < this.entries.length; index++)
2018-04-11 17:56:09 +02:00
if(this.entries[index].owner == clientId) this.entries[index].owner = 0;
}
constructor(handle: VoiceConnection, index: number, name: string, type: CodecType){
2018-04-11 17:56:09 +02:00
this.handle = handle;
this.codecIndex = index;
2018-04-19 18:42:34 +02:00
this.name = name;
this.type = type;
this._supported = this.type !== undefined && audio.codec.supported(this.type);
2018-04-11 17:56:09 +02:00
}
}
2018-09-21 23:25:03 +02:00
enum VoiceConnectionType {
JS_ENCODE,
NATIVE_ENCODE
}
/* funny fact that typescript dosn't find this */
interface RTCPeerConnection {
addStream(stream: MediaStream): void;
getLocalStreams(): MediaStream[];
getStreamById(streamId: string): MediaStream | null;
removeStream(stream: MediaStream): void;
createOffer(successCallback?: RTCSessionDescriptionCallback, failureCallback?: RTCPeerConnectionErrorCallback, options?: RTCOfferOptions): Promise<RTCSessionDescription>;
}
2018-03-07 19:06:52 +01:00
class VoiceConnection {
client: TSClient;
rtcPeerConnection: RTCPeerConnection;
dataChannel: RTCDataChannel;
voiceRecorder: VoiceRecorder;
private _type: VoiceConnectionType = VoiceConnectionType.NATIVE_ENCODE;
2018-09-21 23:25:03 +02:00
local_audio_stream: any;
2018-03-07 19:06:52 +01:00
2018-08-08 19:32:12 +02:00
private codec_pool: CodecPool[] = [
new CodecPool(this,0,tr("Speex Narrowband"), CodecType.SPEEX_NARROWBAND),
new CodecPool(this,1,tr("Speex Wideband"), CodecType.SPEEX_WIDEBAND),
new CodecPool(this,2,tr("Speex Ultra Wideband"), CodecType.SPEEX_ULTRA_WIDEBAND),
new CodecPool(this,3,tr("CELT Mono"), CodecType.CELT_MONO),
new CodecPool(this,4,tr("Opus Voice"), CodecType.OPUS_VOICE),
new CodecPool(this,5,tr("Opus Music"), CodecType.OPUS_MUSIC)
2018-03-07 19:06:52 +01:00
];
private vpacketId: number = 0;
private chunkVPacketId: number = 0;
private send_task: NodeJS.Timer;
2019-03-07 15:30:53 +01:00
private _tag_favicon: JQuery;
2018-03-07 19:06:52 +01:00
constructor(client) {
this.client = client;
2018-09-23 15:24:07 +02:00
this._type = settings.static_global("voice_connection_type", this._type);
2018-03-07 19:06:52 +01:00
this.voiceRecorder = new VoiceRecorder(this);
this.voiceRecorder.on_end = this.handleVoiceEnded.bind(this);
2018-09-22 15:14:39 +02:00
this.voiceRecorder.on_start = this.handleVoiceStarted.bind(this);
2018-04-11 17:56:09 +02:00
this.voiceRecorder.reinitialiseVAD();
2018-04-16 20:38:35 +02:00
audio.player.on_ready(() => {
log.info(LogCategory.VOICE, tr("Initializing voice handler after AudioController has been initialized!"));
if(native_client) {
this.codec_pool[0].initialize(2);
this.codec_pool[1].initialize(2);
this.codec_pool[2].initialize(2);
this.codec_pool[3].initialize(2);
}
2018-08-08 19:32:12 +02:00
this.codec_pool[4].initialize(2);
this.codec_pool[5].initialize(2);
2018-09-21 23:25:03 +02:00
2018-09-23 15:24:07 +02:00
if(this.type == VoiceConnectionType.NATIVE_ENCODE)
this.setup_native();
else
this.setup_js();
});
2018-05-07 11:51:50 +02:00
2018-06-19 20:31:05 +02:00
this.send_task = setInterval(this.sendNextVoicePacket.bind(this), 20);
2019-03-07 15:30:53 +01:00
this._tag_favicon = $("head link[rel='icon']");
2018-04-16 20:38:35 +02:00
}
2018-09-25 17:39:38 +02:00
native_encoding_supported() : boolean {
2018-09-26 15:30:22 +02:00
if(!(window.webkitAudioContext || window.AudioContext || {prototype: {}} as typeof AudioContext).prototype.createMediaStreamDestination) return false; //Required, but not available within edge
2018-09-25 17:39:38 +02:00
return true;
}
javascript_encoding_supported() : boolean {
2018-09-26 15:30:22 +02:00
if(!(window.RTCPeerConnection || {prototype: {}} as typeof RTCPeerConnection).prototype.createDataChannel) return false;
2018-09-25 17:39:38 +02:00
return true;
}
current_encoding_supported() : boolean {
switch (this._type) {
case VoiceConnectionType.JS_ENCODE:
return this.javascript_encoding_supported();
case VoiceConnectionType.NATIVE_ENCODE:
return this.native_encoding_supported();
}
return false;
}
2018-09-23 15:24:07 +02:00
private setup_native() {
log.info(LogCategory.VOICE, tr("Setting up native voice stream!"));
2018-09-26 15:30:22 +02:00
if(!this.native_encoding_supported()) {
log.warn(LogCategory.VOICE, tr("Native codec isnt supported!"));
2018-09-26 15:30:22 +02:00
return;
}
2018-09-25 17:39:38 +02:00
2018-09-23 15:24:07 +02:00
this.voiceRecorder.on_data = undefined;
let stream = this.voiceRecorder.get_output_stream();
stream.disconnect();
if(!this.local_audio_stream)
this.local_audio_stream = audio.player.context().createMediaStreamDestination();
2018-09-23 15:24:07 +02:00
stream.connect(this.local_audio_stream);
}
private setup_js() {
2018-09-25 17:39:38 +02:00
if(!this.javascript_encoding_supported()) return;
2018-09-23 15:24:07 +02:00
this.voiceRecorder.on_data = this.handleVoiceData.bind(this);
}
get type() : VoiceConnectionType { return this._type; }
set type(target: VoiceConnectionType) {
if(target == this.type) return;
this._type = target;
if(this.type == VoiceConnectionType.NATIVE_ENCODE)
this.setup_native();
else
this.setup_js();
this.createSession();
}
2018-04-16 20:38:35 +02:00
codecSupported(type: number) : boolean {
2018-08-08 19:32:12 +02:00
return this.codec_pool.length > type && this.codec_pool[type].supported();
}
2018-09-22 15:14:39 +02:00
voice_playback_support() : boolean {
2018-08-08 19:32:12 +02:00
return this.dataChannel && this.dataChannel.readyState == "open";
2018-03-07 19:06:52 +01:00
}
2018-09-22 15:14:39 +02:00
voice_send_support() : boolean {
if(this.type == VoiceConnectionType.NATIVE_ENCODE)
2018-09-25 17:39:38 +02:00
return this.native_encoding_supported() && this.rtcPeerConnection.getLocalStreams().length > 0;
2018-09-22 15:14:39 +02:00
else
return this.voice_playback_support();
}
2018-06-19 20:31:05 +02:00
private voice_send_queue: {data: Uint8Array, codec: number}[] = [];
2018-05-07 11:51:50 +02:00
handleEncodedVoicePacket(data: Uint8Array, codec: number){
2018-06-19 20:31:05 +02:00
this.voice_send_queue.push({data: data, codec: codec});
}
private sendNextVoicePacket() {
let buffer = this.voice_send_queue.pop_front();
if(!buffer) return;
this.sendVoicePacket(buffer.data, buffer.codec);
2018-05-07 11:51:50 +02:00
}
2018-03-07 19:06:52 +01:00
sendVoicePacket(data: Uint8Array, codec: number) {
if(this.dataChannel) {
this.vpacketId++;
if(this.vpacketId > 65535) this.vpacketId = 0;
let packet = new Uint8Array(data.byteLength + 2 + 3);
packet[0] = this.chunkVPacketId++ < 5 ? 1 : 0; //Flag header
2018-04-11 17:56:09 +02:00
packet[1] = 0; //Flag fragmented
packet[2] = (this.vpacketId >> 8) & 0xFF; //HIGHT (voiceID)
packet[3] = (this.vpacketId >> 0) & 0xFF; //LOW (voiceID)
2018-03-07 19:06:52 +01:00
packet[4] = codec; //Codec
packet.set(data, 5);
2018-06-19 20:31:05 +02:00
try {
this.dataChannel.send(packet);
} catch (e) {
//TODO may handle error?
}
2018-03-07 19:06:52 +01:00
} else {
console.warn(tr("Could not transfer audio (not connected)"));
2018-03-07 19:06:52 +01:00
}
}
createSession() {
if(!audio.player.initialized()) {
console.log(tr("Audio player isn't initialized yet. Waiting for gesture."));
audio.player.on_ready(() => this.createSession());
return;
}
2018-09-25 17:39:38 +02:00
if(!this.current_encoding_supported()) return false;
2018-09-23 15:24:07 +02:00
if(this.rtcPeerConnection) {
this.dropSession();
}
2018-08-08 19:32:12 +02:00
this._ice_use_cache = true;
2018-09-21 23:25:03 +02:00
2018-08-08 19:32:12 +02:00
let config: RTCConfiguration = {};
config.iceServers = [];
config.iceServers.push({ urls: 'stun:stun.l.google.com:19302' });
2018-03-07 19:06:52 +01:00
this.rtcPeerConnection = new RTCPeerConnection(config);
2018-08-12 22:31:40 +02:00
const dataChannelConfig = { ordered: true, maxRetransmits: 0 };
2018-03-07 19:06:52 +01:00
this.dataChannel = this.rtcPeerConnection.createDataChannel('main', dataChannelConfig);
this.dataChannel.onmessage = this.onDataChannelMessage.bind(this);
this.dataChannel.onopen = this.onDataChannelOpen.bind(this);
2018-08-08 19:32:12 +02:00
this.dataChannel.binaryType = "arraybuffer";
2018-03-07 19:06:52 +01:00
let sdpConstraints : RTCOfferOptions = {};
2018-09-23 15:24:07 +02:00
sdpConstraints.offerToReceiveAudio = this._type == VoiceConnectionType.NATIVE_ENCODE;
sdpConstraints.offerToReceiveVideo = false;
2018-03-07 19:06:52 +01:00
this.rtcPeerConnection.onicecandidate = this.onIceCandidate.bind(this);
2018-09-21 23:25:03 +02:00
if(this.local_audio_stream) { //May a typecheck?
this.rtcPeerConnection.addStream(this.local_audio_stream.stream);
console.log(tr("Adding stream (%o)!"), this.local_audio_stream.stream);
2018-09-21 23:25:03 +02:00
}
2018-03-07 19:06:52 +01:00
this.rtcPeerConnection.createOffer(this.onOfferCreated.bind(this), () => {
console.error(tr("Could not create ice offer!"));
2018-03-07 19:06:52 +01:00
}, sdpConstraints);
}
dropSession() {
if(this.dataChannel) this.dataChannel.close();
if(this.rtcPeerConnection) this.rtcPeerConnection.close();
//TODO here!
}
2018-08-08 19:32:12 +02:00
_ice_use_cache: boolean = true;
_ice_cache: any[] = [];
2018-03-07 19:06:52 +01:00
handleControlPacket(json) {
if(json["request"] === "answer") {
console.log(tr("Set remote sdp! (%o)"), json["msg"]);
2018-09-23 20:49:47 +02:00
this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(json["msg"])).catch(error => {
console.log(tr("Failed to apply remote description: %o"), error); //FIXME error handling!
2018-09-23 20:49:47 +02:00
});
2018-08-08 19:32:12 +02:00
this._ice_use_cache = false;
for(let msg of this._ice_cache) {
2018-09-23 20:49:47 +02:00
this.rtcPeerConnection.addIceCandidate(new RTCIceCandidate(msg)).catch(error => {
console.log(tr("Failed to add remote cached ice candidate %s: %o"), msg, error);
2018-09-23 20:49:47 +02:00
});
2018-08-08 19:32:12 +02:00
}
2018-03-07 19:06:52 +01:00
} else if(json["request"] === "ice") {
2018-08-08 19:32:12 +02:00
if(!this._ice_use_cache) {
console.log(tr("Add remote ice! (%s | %o)"), json["msg"], json);
2018-09-23 20:49:47 +02:00
this.rtcPeerConnection.addIceCandidate(new RTCIceCandidate(json["msg"])).catch(error => {
console.log(tr("Failed to add remote ice candidate %s: %o"), json["msg"], error);
2018-09-23 20:49:47 +02:00
});
2018-08-08 19:32:12 +02:00
} else {
console.log(tr("Cache remote ice! (%s | %o)"), json["msg"], json);
2018-08-08 19:32:12 +02:00
this._ice_cache.push(json["msg"]);
}
} else if(json["request"] == "status") {
if(json["state"] == "failed") {
chat.serverChat().appendError(tr("Failed to setup voice bridge ({}). Allow reconnect: {}"), json["reason"], json["allow_reconnect"]);
log.error(LogCategory.NETWORKING, tr("Failed to setup voice bridge (%s). Allow reconnect: %s"), json["reason"], json["allow_reconnect"]);
2018-08-08 19:32:12 +02:00
if(json["allow_reconnect"] == true) {
this.createSession();
}
//TODO handle fail specially when its not allowed to reconnect
}
2018-03-07 19:06:52 +01:00
}
}
//Listeners
onIceCandidate(event) {
console.log(tr("Got ice candidate! Event:"));
2018-03-07 19:06:52 +01:00
console.log(event);
2018-09-23 20:49:47 +02:00
if (event) {
if(event.candidate)
this.client.serverConnection.sendData(JSON.stringify({
type: 'WebRTC',
request: "ice",
msg: event.candidate,
}));
else {
this.client.serverConnection.sendData(JSON.stringify({
type: 'WebRTC',
request: "ice_finish"
}));
}
2018-03-07 19:06:52 +01:00
}
}
onOfferCreated(localSession) {
console.log(tr("Offer created and accepted"));
2018-09-23 20:49:47 +02:00
this.rtcPeerConnection.setLocalDescription(localSession).catch(error => {
console.log(tr("Failed to apply local description: %o"), error);
2018-09-23 20:49:47 +02:00
//FIXME error handling
});
2018-03-07 19:06:52 +01:00
console.log(tr("Send offer: %o"), localSession);
2018-08-08 19:32:12 +02:00
this.client.serverConnection.sendData(JSON.stringify({type: 'WebRTC', request: "create", msg: localSession}));
2018-03-07 19:06:52 +01:00
}
onDataChannelOpen(channel) {
console.log(tr("Got new data channel! (%s)"), this.dataChannel.readyState);
2018-08-08 19:32:12 +02:00
this.client.controlBar.updateVoice();
2018-03-07 19:06:52 +01:00
}
onDataChannelMessage(message) {
2018-04-11 17:56:09 +02:00
if(this.client.controlBar.muteOutput) return;
2018-03-07 19:06:52 +01:00
let bin = new Uint8Array(message.data);
let clientId = bin[2] << 8 | bin[3];
let packetId = bin[0] << 8 | bin[1];
let codec = bin[4];
//console.log("Client id " + clientId + " PacketID " + packetId + " Codec: " + codec);
let client = this.client.channelTree.findClient(clientId);
if(!client) {
console.error(tr("Having voice from unknown client? (ClientID: %o)"), clientId);
2018-03-07 19:06:52 +01:00
return;
}
2018-04-11 17:56:09 +02:00
2018-08-08 19:32:12 +02:00
let codecPool = this.codec_pool[codec];
2018-04-11 17:56:09 +02:00
if(!codecPool) {
console.error(tr("Could not playback codec %o"), codec);
2018-03-07 19:06:52 +01:00
return;
}
2018-04-11 17:56:09 +02:00
2018-03-07 19:06:52 +01:00
let encodedData;
if(message.data.subarray)
encodedData = message.data.subarray(5);
else encodedData = new Uint8Array(message.data, 5);
2018-03-07 20:14:36 +01:00
if(encodedData.length == 0) {
client.getAudioController().stopAudio();
2018-04-11 17:56:09 +02:00
codecPool.releaseCodec(clientId);
2018-03-07 20:14:36 +01:00
} else {
2018-04-18 20:12:10 +02:00
codecPool.ownCodec(clientId)
.then(decoder => decoder.decodeSamples(client.getAudioController().codecCache(codec), encodedData))
.then(buffer => client.getAudioController().playBuffer(buffer)).catch(error => {
console.error(tr("Could not playback client's (%o) audio (%o)"), clientId, error);
2018-08-12 20:19:44 +02:00
if(error instanceof Error)
console.error(error.stack);
2018-08-08 19:32:12 +02:00
});
2018-03-07 20:14:36 +01:00
}
2018-03-07 19:06:52 +01:00
}
private current_channel_codec() : number {
return (this.client.getClient().currentChannel() || {properties: { channel_codec: 4}}).properties.channel_codec;
}
2018-03-07 19:06:52 +01:00
private handleVoiceData(data: AudioBuffer, head: boolean) {
2018-04-11 17:56:09 +02:00
if(!this.voiceRecorder) return;
if(!this.client.connected) return false;
if(this.client.controlBar.muteInput) return;
2018-04-11 17:56:09 +02:00
2018-03-07 19:06:52 +01:00
if(head) {
this.chunkVPacketId = 0;
this.client.getClient().speaking = true;
}
2018-04-18 20:12:10 +02:00
//TODO Use channel codec!
const codec = this.current_channel_codec();
this.codec_pool[codec].ownCodec(this.client.getClientId())
.then(encoder => encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(codec), data));
2018-03-07 19:06:52 +01:00
}
private handleVoiceEnded() {
2018-09-22 15:14:39 +02:00
if(this.client && this.client.getClient())
this.client.getClient().speaking = false;
2018-04-11 17:56:09 +02:00
if(!this.voiceRecorder) return;
2018-06-19 20:31:05 +02:00
if(!this.client.connected) return;
console.log(tr("Local voice ended"));
2018-04-11 17:56:09 +02:00
if(this.dataChannel)
this.sendVoicePacket(new Uint8Array(0), this.current_channel_codec()); //TODO Use channel codec!
2019-03-07 15:30:53 +01:00
this._tag_favicon.attr('href', "img/favicon/teacup.png");
2018-03-07 19:06:52 +01:00
}
2018-09-22 15:14:39 +02:00
private handleVoiceStarted() {
console.log(tr("Local voice started"));
2018-09-22 15:14:39 +02:00
if(this.client && this.client.getClient())
this.client.getClient().speaking = true;
2019-03-07 15:30:53 +01:00
this._tag_favicon.attr('href', "img/favicon/speaking.png");
2018-09-22 15:14:39 +02:00
}
2018-03-07 19:06:52 +01:00
}