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;