diff --git a/ChangeLog.md b/ChangeLog.md index 95a1b574..0aeef9f2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,7 @@ # Changelog: +* **26.09.18**: + - Added Safari support + * **25.09.18**: - Added support for token use - Added support for away messages diff --git a/js/FileManager.ts b/js/FileManager.ts index 874a213e..59fad291 100644 --- a/js/FileManager.ts +++ b/js/FileManager.ts @@ -492,8 +492,6 @@ class AvatarManager { else img.attr("src", "data:image/png;base64," + avatar.base64); console.debug("Avatar " + client.clientNickName() + " loaded :)"); - console.log(avatar.base64); - console.log(avatar.url); img.css("opacity", 0); tag.append(img); diff --git a/js/codec/BasicCodec.ts b/js/codec/BasicCodec.ts index 7cb47b9d..9865e126 100644 --- a/js/codec/BasicCodec.ts +++ b/js/codec/BasicCodec.ts @@ -18,6 +18,7 @@ class AVGCalculator { } } +declare class webkitOfflineAudioContext extends OfflineAudioContext {} abstract class BasicCodec implements Codec { protected _audioContext: OfflineAudioContext; protected _decodeResampler: AudioResampler; @@ -32,7 +33,7 @@ abstract class BasicCodec implements Codec { constructor(codecSampleRate: number) { this.channelCount = 1; this.samplesPerUnit = 960; - this._audioContext = new OfflineAudioContext(AudioController.globalContext.destination.channelCount, 1024,AudioController.globalContext.sampleRate ); + this._audioContext = new (webkitOfflineAudioContext || OfflineAudioContext)(AudioController.globalContext.destination.channelCount, 1024,AudioController.globalContext.sampleRate ); this._codecSampleRate = codecSampleRate; this._decodeResampler = new AudioResampler(AudioController.globalContext.sampleRate); this._encodeResampler = new AudioResampler(codecSampleRate); diff --git a/js/main.ts b/js/main.ts index 2e9735bd..bba92870 100644 --- a/js/main.ts +++ b/js/main.ts @@ -97,6 +97,8 @@ app.loadedListener.push(() => { $(document).one('click', event => AudioController.initializeFromGesture()); } } catch (ex) { + if(ex instanceof ReferenceError) + ex = ex.message + ":
" + ex.stack; displayCriticalError("Failed to invoke main function:
" + ex, false); } }); \ No newline at end of file diff --git a/js/ui/client.ts b/js/ui/client.ts index 49a450aa..67d0d7b9 100644 --- a/js/ui/client.ts +++ b/js/ui/client.ts @@ -211,6 +211,24 @@ class ClientEntry { } }, MenuEntry.HR(), + /* + { + type: MenuEntryType.ENTRY, + icon: "client-kick_server", + name: "Add group to client", + invalidPermission: true, //!this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1), + callback: () => { + Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => { + this.channelTree.client.serverConnection.sendCommand("banclient", { + uid: this.properties.client_unique_identifier, + banreason: reason, + time: duration + }); + }); + } + }, + MenuEntry.HR(), + */ { type: MenuEntryType.ENTRY, icon: "client-volume", diff --git a/js/voice/AudioController.ts b/js/voice/AudioController.ts index 584c9b7f..44ffecbd 100644 --- a/js/voice/AudioController.ts +++ b/js/voice/AudioController.ts @@ -11,6 +11,8 @@ interface Navigator { webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void; } +declare class webkitAudioContext extends AudioContext {} + class AudioController { private static getUserMediaFunction() { if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) @@ -30,7 +32,7 @@ class AudioController { if(this._globalContext && this._globalContext.state != "suspended") return this._globalContext; if(!this._globalContext) - this._globalContext = new AudioContext(); + this._globalContext = new (webkitAudioContext || AudioContext)(); if(this._globalContext.state == "suspended") { if(!this._globalContextPromise) { (this._globalContextPromise = this._globalContext.resume()).then(() => { diff --git a/js/voice/AudioResampler.ts b/js/voice/AudioResampler.ts index 3424908e..ed5fe2c4 100644 --- a/js/voice/AudioResampler.ts +++ b/js/voice/AudioResampler.ts @@ -1,5 +1,6 @@ class AudioResampler { targetSampleRate: number; + private _use_promise: boolean; constructor(targetSampleRate: number = 44100){ this.targetSampleRate = targetSampleRate; @@ -7,18 +8,37 @@ class AudioResampler { } resample(buffer: AudioBuffer) : Promise { + if(!buffer) { + console.warn("Received empty buffer as input! Returning empty output!"); + return new Promise(resolve => resolve(undefined)); + } //console.log("Encode from %i to %i", buffer.sampleRate, this.targetSampleRate); if(buffer.sampleRate == this.targetSampleRate) return new Promise(resolve => resolve(buffer)); let context; - context = new OfflineAudioContext(buffer.numberOfChannels, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate); + context = new (webkitOfflineAudioContext || OfflineAudioContext)(buffer.numberOfChannels, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate); let source = context.createBufferSource(); source.buffer = buffer; - source.connect(context.destination); source.start(0); + source.connect(context.destination); - return context.startRendering(); + if(typeof(this._use_promise) === "undefined") { + this._use_promise = navigator.browserSpecs.name != 'Safari'; + } + + if(this._use_promise) + return context.startRendering(); + else { + return new Promise((resolve, reject) => { + context.oncomplete = event => resolve(event.renderedBuffer); + try { + context.startRendering(); + } catch (ex) { + reject(ex); + } + }) + } } } \ No newline at end of file diff --git a/js/voice/VoiceHandler.ts b/js/voice/VoiceHandler.ts index a3aab0da..cdd96da2 100644 --- a/js/voice/VoiceHandler.ts +++ b/js/voice/VoiceHandler.ts @@ -163,7 +163,7 @@ class VoiceConnection { } native_encoding_supported() : boolean { - if(!AudioContext.prototype.createMediaStreamDestination) return false; //Required, but not available within edge + if(!(webkitAudioContext || AudioContext).prototype.createMediaStreamDestination) return false; //Required, but not available within edge return true; } diff --git a/js/voice/VoiceRecorder.ts b/js/voice/VoiceRecorder.ts index b956fc0d..33f1836e 100644 --- a/js/voice/VoiceRecorder.ts +++ b/js/voice/VoiceRecorder.ts @@ -23,6 +23,17 @@ interface MediaStreamConstraints { groupId?: string; } +if(!AudioBuffer.prototype.copyToChannel) { //Webkit does not implement this function + AudioBuffer.prototype.copyToChannel = function (source: Float32Array, channelNumber: number, startInChannel?: number) { + if(!startInChannel) startInChannel = 0; + + let destination = this.getChannelData(channelNumber); + for(let index = 0; index < source.length; index++) + if(destination.length < index + startInChannel) + destination[index + startInChannel] = source[index]; + } +} + class VoiceRecorder { private static readonly CHANNEL = 0; private static readonly CHANNELS = 1;