Implemented a load animation
This commit is contained in:
parent
42ed493db0
commit
2506923178
12 changed files with 601 additions and 129 deletions
300
css/loader.scss
Normal file
300
css/loader.scss
Normal file
|
@ -0,0 +1,300 @@
|
|||
$thickness : 5px;
|
||||
$duration : 2500;
|
||||
$delay : $duration/6;
|
||||
$background: #222222;
|
||||
|
||||
@mixin polka($size, $dot, $base, $accent){
|
||||
background: $base;
|
||||
background-image: radial-gradient($accent $dot, transparent 0);
|
||||
background-size:$size $size;
|
||||
background-position: 0 -2.5px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 0;
|
||||
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
z-index: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loader .half {
|
||||
position: fixed;
|
||||
background: #222222;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loader .half.right {
|
||||
right: 0;
|
||||
}
|
||||
.loader .half.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bookshelf_wrapper {
|
||||
position: relative;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.books_list {
|
||||
margin: 0 auto;
|
||||
width: 300px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.book_item {
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
width: 40px;
|
||||
height: 120px;
|
||||
opacity: 0;
|
||||
background-color: #1e6cc7;
|
||||
border: $thickness solid white;
|
||||
transform-origin: bottom left;
|
||||
transform: translateX(300px);
|
||||
animation: travel #{$duration}ms linear infinite;
|
||||
|
||||
&.first {
|
||||
top: -140px;
|
||||
height: 140px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content:'';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.second,
|
||||
&.fifth {
|
||||
&:before,
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content:'';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness*3.5;
|
||||
border-top: $thickness solid white;
|
||||
border-bottom: $thickness solid white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.third {
|
||||
&:before,
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content:'';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 9px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: $thickness solid white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: initial;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fourth {
|
||||
top: -130px;
|
||||
height: 130px;
|
||||
|
||||
&:before {
|
||||
box-sizing: border-box;
|
||||
content:'';
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $thickness*3.5;
|
||||
border-top: $thickness solid white;
|
||||
border-bottom: $thickness solid white;
|
||||
}
|
||||
}
|
||||
|
||||
&.fifth {
|
||||
top: -100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&.sixth {
|
||||
top: -140px;
|
||||
height: 140px;
|
||||
|
||||
&:before {
|
||||
box-sizing: border-box;
|
||||
content:'';
|
||||
position: absolute;
|
||||
bottom: 31px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: $thickness;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
box-sizing: border-box;
|
||||
content:'';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 9px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: $thickness solid white;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: #{$delay*1}ms;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: #{$delay*2}ms;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
animation-delay: #{$delay*3}ms;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
animation-delay: #{$delay*4}ms;
|
||||
}
|
||||
|
||||
&:nth-child(6) {
|
||||
animation-delay: #{$delay*5}ms;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.shelf {
|
||||
width: 300px;
|
||||
height: $thickness;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
position: relative;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content:'';
|
||||
position : absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include polka(10px, 30%, $background, rgba(255,255,255,0.5));
|
||||
top: 200%;
|
||||
left: 5%;
|
||||
animation: move #{$duration/10}ms linear infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: 400%;
|
||||
left: 7.5%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
|
||||
from {
|
||||
background-position-x: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position-x: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes travel {
|
||||
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(300px) rotateZ(0deg) scaleY(1);
|
||||
}
|
||||
|
||||
6.5% {
|
||||
transform: translateX(279.5px) rotateZ(0deg) scaleY(1.1);
|
||||
}
|
||||
|
||||
8.8% {
|
||||
transform: translateX(273.6px) rotateZ(0deg) scaleY(1);
|
||||
}
|
||||
|
||||
10% {
|
||||
opacity: 1;
|
||||
transform: translateX(270px) rotateZ(0deg);
|
||||
}
|
||||
|
||||
17.6% {
|
||||
transform: translateX(247.2px) rotateZ(-30deg);
|
||||
}
|
||||
|
||||
45% {
|
||||
transform: translateX(165px) rotateZ(-30deg);
|
||||
}
|
||||
|
||||
49.5% {
|
||||
transform: translateX(151.5px) rotateZ(-45deg);
|
||||
}
|
||||
|
||||
61.5% {
|
||||
transform: translateX(115.5px) rotateZ(-45deg);
|
||||
}
|
||||
|
||||
67% {
|
||||
transform: translateX(99px) rotateZ(-60deg);
|
||||
}
|
||||
|
||||
76% {
|
||||
transform: translateX(72px) rotateZ(-60deg);
|
||||
}
|
||||
|
||||
83.5% {
|
||||
opacity: 1;
|
||||
transform: translateX(49.5px) rotateZ(-90deg);
|
||||
}
|
||||
|
||||
90% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(0px) rotateZ(-90deg);
|
||||
}
|
||||
|
||||
}
|
|
@ -682,65 +682,95 @@ class PushToTalkVAD extends VoiceActivityDetector {
|
|||
class CodecPoolEntry {
|
||||
}
|
||||
class CodecPool {
|
||||
constructor(handle, index, creator) {
|
||||
constructor(handle, index, name, creator) {
|
||||
this.entries = [];
|
||||
this.maxInstances = 2;
|
||||
this._supported = true;
|
||||
this.creator = creator;
|
||||
this.handle = handle;
|
||||
this.codecIndex = index;
|
||||
this.name = name;
|
||||
}
|
||||
initialize(cached) {
|
||||
for (let i = 0; i < cached; i++)
|
||||
this.ownCodec(i);
|
||||
for (let i = 0; i < cached; i++)
|
||||
this.releaseCodec(i);
|
||||
this.ownCodec(i + 1).then(codec => {
|
||||
console.log("Release again! (%o)", codec);
|
||||
this.releaseCodec(i + 1);
|
||||
}).catch(error => {
|
||||
if (this._supported) {
|
||||
createErrorModal("Could not load codec driver", "Could not load or initialize codec " + this.name + "<br>" +
|
||||
"Error: <code>" + JSON.stringify(error) + "</code>").open();
|
||||
}
|
||||
this._supported = false;
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
supported() { return this.creator != undefined; }
|
||||
supported() { return this.creator != undefined && this._supported; }
|
||||
ownCodec(clientId, create = true) {
|
||||
if (!this.creator)
|
||||
return null;
|
||||
let free = 0;
|
||||
for (let index = 0; index < this.entries.length; index++) {
|
||||
if (this.entries[index].owner == clientId) {
|
||||
this.entries[index].last_access = new Date().getTime();
|
||||
return this.entries[index].instance;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.creator || !this._supported) {
|
||||
reject("unsupported codec!");
|
||||
return;
|
||||
}
|
||||
else if (free == 0 && this.entries[index].owner == 0) {
|
||||
free = index;
|
||||
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);
|
||||
else {
|
||||
this.entries[index].instance.initialise().then((flag) => {
|
||||
//TODO test success flag
|
||||
console.error(flag);
|
||||
this.ownCodec(clientId, false).then(resolve).catch(reject);
|
||||
}).catch(error => {
|
||||
console.error("Could not initialize codec!\nError: %o", error);
|
||||
reject("Could not initialize codec!");
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (freeSlot == 0 && this.entries[index].owner == 0) {
|
||||
freeSlot = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!create)
|
||||
return null;
|
||||
if (free == 0) {
|
||||
free = this.entries.length;
|
||||
let entry = new CodecPoolEntry();
|
||||
entry.instance = this.creator();
|
||||
entry.instance.initialise();
|
||||
entry.instance.on_encoded_data = buffer => this.handle.sendVoicePacket(buffer, this.codecIndex);
|
||||
this.entries.push(entry);
|
||||
}
|
||||
this.entries[free].owner = clientId;
|
||||
this.entries[free].last_access = new Date().getTime();
|
||||
this.entries[free].instance.reset();
|
||||
return this.entries[free].instance;
|
||||
if (!create) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
if (freeSlot == 0) {
|
||||
freeSlot = this.entries.length;
|
||||
let entry = new CodecPoolEntry();
|
||||
entry.instance = this.creator();
|
||||
entry.instance.on_encoded_data = buffer => this.handle.sendVoicePacket(buffer, this.codecIndex);
|
||||
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 {
|
||||
this.ownCodec(clientId, false).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
resolve(this.entries[freeSlot].instance);
|
||||
});
|
||||
}
|
||||
releaseCodec(clientId) {
|
||||
for (let index = 0; index < this.entries.length; index++) {
|
||||
for (let index = 0; index < this.entries.length; index++)
|
||||
if (this.entries[index].owner == clientId)
|
||||
this.entries[index].owner = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
class VoiceConnection {
|
||||
constructor(client) {
|
||||
this.codecPool = [
|
||||
new CodecPool(this, 0, undefined),
|
||||
new CodecPool(this, 1, undefined),
|
||||
new CodecPool(this, 2, undefined),
|
||||
new CodecPool(this, 3, undefined),
|
||||
new CodecPool(this, 4, () => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 1); }),
|
||||
new CodecPool(this, 5, () => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 2); }) //opus music
|
||||
//FIXME Why is it at index 5 currently only 1?
|
||||
new CodecPool(this, 0, "Spex A", undefined),
|
||||
new CodecPool(this, 1, "Spex B", undefined),
|
||||
new CodecPool(this, 2, "Spex C", undefined),
|
||||
new CodecPool(this, 3, "CELT Mono", undefined),
|
||||
new CodecPool(this, 4, "Opus Voice", () => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 1); }),
|
||||
new CodecPool(this, 5, "Opus Music", () => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 2); }) //opus music
|
||||
];
|
||||
this.vpacketId = 0;
|
||||
this.chunkVPacketId = 0;
|
||||
|
@ -854,10 +884,9 @@ class VoiceConnection {
|
|||
codecPool.releaseCodec(clientId);
|
||||
}
|
||||
else {
|
||||
let decoder = codecPool.ownCodec(clientId);
|
||||
decoder.decodeSamples(client.getAudioController().codecCache(codec), encodedData).then(buffer => {
|
||||
client.getAudioController().playBuffer(buffer);
|
||||
}).catch(error => {
|
||||
codecPool.ownCodec(clientId)
|
||||
.then(decoder => decoder.decodeSamples(client.getAudioController().codecCache(codec), encodedData))
|
||||
.then(buffer => client.getAudioController().playBuffer(buffer)).catch(error => {
|
||||
console.error("Could not playback client's (" + clientId + ") audio (" + error + ")");
|
||||
});
|
||||
}
|
||||
|
@ -865,16 +894,17 @@ class VoiceConnection {
|
|||
handleVoiceData(data, head) {
|
||||
if (!this.voiceRecorder)
|
||||
return;
|
||||
if (!this.client.connected)
|
||||
return false;
|
||||
if (this.client.controlBar.muteInput)
|
||||
return;
|
||||
if (head) {
|
||||
this.chunkVPacketId = 0;
|
||||
this.client.getClient().speaking = true;
|
||||
}
|
||||
let encoder = this.codecPool[4].ownCodec(this.client.getClientId());
|
||||
if (!encoder) {
|
||||
console.error("Could not reserve encoder!");
|
||||
return;
|
||||
}
|
||||
encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(4), data); //TODO Use channel codec!
|
||||
//TODO Use channel codec!
|
||||
this.codecPool[4].ownCodec(this.client.getClientId())
|
||||
.then(encoder => encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(4), data));
|
||||
//this.client.getClient().getAudioController().play(data);
|
||||
}
|
||||
handleVoiceEnded() {
|
||||
|
@ -982,7 +1012,7 @@ function spawnMenu(x, y, ...entries) {
|
|||
var sha;
|
||||
(function (sha) {
|
||||
function sha1(message) {
|
||||
let buffer = message instanceof ArrayBuffer ? message : new TextEncoder("utf-8").encode(message);
|
||||
let buffer = message instanceof ArrayBuffer ? message : new TextEncoder().encode(message);
|
||||
return crypto.subtle.digest("SHA-1", buffer);
|
||||
}
|
||||
sha.sha1 = sha1;
|
||||
|
@ -2330,6 +2360,7 @@ class ServerConnection {
|
|||
constructor(client) {
|
||||
this._connectionState = ConnectionState.UNCONNECTED;
|
||||
this._connectTimeoutHandler = undefined;
|
||||
this._connected = false;
|
||||
this.on_connect = () => {
|
||||
console.log("Socket connected");
|
||||
chat.serverChat().appendMessage("Logging in...");
|
||||
|
@ -2355,6 +2386,7 @@ class ServerConnection {
|
|||
this._remotePort = port;
|
||||
this._handshakeHandler = handshake;
|
||||
this._handshakeHandler.setConnection(this);
|
||||
this._connected = false;
|
||||
chat.serverChat().appendMessage("Connecting to " + host + ":" + port);
|
||||
const self = this;
|
||||
try {
|
||||
|
@ -2371,12 +2403,13 @@ class ServerConnection {
|
|||
this._socket.onopen = () => {
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
this._connected = true;
|
||||
this.on_connect();
|
||||
};
|
||||
this._socket.onclose = event => {
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
this._client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
|
||||
this._client.handleDisconnect(this._connected ? DisconnectReason.CONNECTION_CLOSED : DisconnectReason.CONNECT_FAILURE, {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
event: event
|
||||
|
@ -2414,6 +2447,7 @@ class ServerConnection {
|
|||
future.reject("Connection closed");
|
||||
this._retListener = [];
|
||||
this._retCodeIdx = 0;
|
||||
this._connected = false;
|
||||
return true;
|
||||
}
|
||||
handleWebSocketMessage(data) {
|
||||
|
@ -2973,20 +3007,22 @@ class Settings {
|
|||
initializeStatic() {
|
||||
location.search.substr(1).split("&").forEach(part => {
|
||||
let item = part.split("=");
|
||||
$.spawn("div")
|
||||
$("<x-property></x-property>")
|
||||
.attr("key", item[0])
|
||||
.attr("value", item[1])
|
||||
.appendTo(this._staticPropsTag);
|
||||
});
|
||||
}
|
||||
static transformStO(input, _default) {
|
||||
if (typeof input === "undefined")
|
||||
return _default;
|
||||
if (typeof _default === "string")
|
||||
return input;
|
||||
else if (typeof _default === "number")
|
||||
return parseInt(input);
|
||||
else if (typeof _default === "boolean")
|
||||
return (input == "1" || input == "true");
|
||||
else if (typeof _default == "undefined")
|
||||
else if (typeof _default === "undefined")
|
||||
return input;
|
||||
return JSON.parse(input);
|
||||
}
|
||||
|
@ -3011,7 +3047,8 @@ class Settings {
|
|||
}
|
||||
static(key, _default) {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
return Settings.transformStO(result.length > 0 ? decodeURIComponent(result.attr("value")) : undefined, _default);
|
||||
console.log("%d | %o", result.length, result);
|
||||
return Settings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
|
||||
}
|
||||
changeGlobal(key, value) {
|
||||
if (this.cacheGlobal[key] == value)
|
||||
|
@ -4218,6 +4255,9 @@ class TSClient {
|
|||
this.groups.requestGroups();
|
||||
this.controlBar.updateProperties();
|
||||
}
|
||||
get connected() {
|
||||
return !!this.serverConnection && this.serverConnection.connected;
|
||||
}
|
||||
handleDisconnect(type, data = {}) {
|
||||
switch (type) {
|
||||
case DisconnectReason.REQUESTED:
|
||||
|
@ -4225,7 +4265,10 @@ class TSClient {
|
|||
case DisconnectReason.CONNECT_FAILURE:
|
||||
console.error("Could not connect to remote host! Exception");
|
||||
console.error(data);
|
||||
createErrorModal("Could not connect", "Could not connect to remote host (Connection refused)").open();
|
||||
//TODO test for status 1006
|
||||
createErrorModal("Could not connect", "Could not connect to remote host (Connection refused)<br>" +
|
||||
"If you're shure that the remot host is up, than you may not allow unsigned certificates.<br>" +
|
||||
"Click <a href='https://" + this.serverConnection._remoteHost + ":" + this.serverConnection._remotePort + "'>here</a> to accept the remote certificate").open();
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error("Lost connection to remote server!");
|
||||
|
@ -5308,7 +5351,7 @@ class BasicCodec {
|
|||
this.samplesPerUnit = 960;
|
||||
this._audioContext = new OfflineAudioContext(1, 1024, 44100);
|
||||
this._codecSampleRate = codecSampleRate;
|
||||
this._decodeResampler = new AudioResampler();
|
||||
this._decodeResampler = new AudioResampler(AudioController.globalContext.sampleRate);
|
||||
this._encodeResampler = new AudioResampler(codecSampleRate);
|
||||
}
|
||||
encodeSamples(cache, pcm) {
|
||||
|
@ -5328,9 +5371,13 @@ class BasicCodec {
|
|||
if (buf.index == buf.buffer.length)
|
||||
cache._chunks.pop_front();
|
||||
}
|
||||
let encodeBegin = new Date().getTime();
|
||||
this.encode(buffer).then(result => {
|
||||
if (result instanceof Uint8Array)
|
||||
if (result instanceof Uint8Array) {
|
||||
if (new Date().getTime() - 20 > encodeBegin)
|
||||
console.error("Required time: %d", new Date().getTime() - encodeBegin);
|
||||
this.on_encoded_data(result);
|
||||
}
|
||||
else
|
||||
console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result);
|
||||
});
|
||||
|
@ -5352,6 +5399,7 @@ class CodecWrapper extends BasicCodec {
|
|||
this._workerListener = [];
|
||||
this._workerCallbackToken = "callback_token";
|
||||
this._workerTokeIndex = 0;
|
||||
this._initialized = false;
|
||||
this.type = type;
|
||||
this.channelCount = channelCount;
|
||||
}
|
||||
|
@ -5359,12 +5407,31 @@ class CodecWrapper extends BasicCodec {
|
|||
return "Worker for " + CodecWorkerType[this.type] + " Channels " + this.channelCount;
|
||||
}
|
||||
initialise() {
|
||||
this.spawnWorker();
|
||||
this.sendWorkerMessage({
|
||||
command: "initialise",
|
||||
type: this.type,
|
||||
channelCount: this.channelCount
|
||||
});
|
||||
if (this._initializePromise)
|
||||
return this._initializePromise;
|
||||
return this._initializePromise = this.spawnWorker().then(() => new Promise((resolve, reject) => {
|
||||
const token = this.generateToken();
|
||||
this.sendWorkerMessage({
|
||||
command: "initialise",
|
||||
type: this.type,
|
||||
channelCount: this.channelCount,
|
||||
token: token
|
||||
});
|
||||
this._workerListener.push({
|
||||
token: token,
|
||||
resolve: data => {
|
||||
console.log("Init result: %o", data);
|
||||
this._initialized = data["success"] == true;
|
||||
if (data["success"] == true)
|
||||
resolve();
|
||||
else
|
||||
reject(data.message);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
initialized() {
|
||||
return this._initialized;
|
||||
}
|
||||
deinitialise() {
|
||||
this.sendWorkerMessage({
|
||||
|
@ -5372,7 +5439,7 @@ class CodecWrapper extends BasicCodec {
|
|||
});
|
||||
}
|
||||
decode(data) {
|
||||
let token = this._workerTokeIndex++ + "_token";
|
||||
let token = this.generateToken();
|
||||
let result = new Promise((resolve, reject) => {
|
||||
this._workerListener.push({
|
||||
token: token,
|
||||
|
@ -5402,7 +5469,7 @@ class CodecWrapper extends BasicCodec {
|
|||
return result;
|
||||
}
|
||||
encode(data) {
|
||||
let token = this._workerTokeIndex++ + "_token";
|
||||
let token = this.generateToken();
|
||||
let result = new Promise((resolve, reject) => {
|
||||
this._workerListener.push({
|
||||
token: token,
|
||||
|
@ -5439,16 +5506,35 @@ class CodecWrapper extends BasicCodec {
|
|||
});
|
||||
return true;
|
||||
}
|
||||
generateToken() {
|
||||
return this._workerTokeIndex++ + "_token";
|
||||
}
|
||||
sendWorkerMessage(message, transfare) {
|
||||
//console.log("Send worker: %o", message);
|
||||
this._worker.postMessage(JSON.stringify(message), transfare);
|
||||
}
|
||||
onWorkerMessage(message) {
|
||||
//console.log("Worker message: %o", message);
|
||||
if (!message["token"]) {
|
||||
console.error("Invalid worker token!");
|
||||
return;
|
||||
}
|
||||
if (message["token"] == this._workerCallbackToken) {
|
||||
console.log("Callback data!");
|
||||
if (message["type"] == "loaded") {
|
||||
console.log("[Codec] Got worker init response: Success: %o Message: %o", message["success"], message["message"]);
|
||||
if (message["success"]) {
|
||||
if (this._workerCallbackResolve)
|
||||
this._workerCallbackResolve();
|
||||
}
|
||||
else {
|
||||
if (this._workerCallbackReject)
|
||||
this._workerCallbackReject(message["message"]);
|
||||
}
|
||||
this._workerCallbackReject = undefined;
|
||||
this._workerCallbackResolve = undefined;
|
||||
return;
|
||||
}
|
||||
console.log("Costume callback! (%o)", message);
|
||||
return;
|
||||
}
|
||||
for (let entry of this._workerListener) {
|
||||
|
@ -5461,8 +5547,12 @@ class CodecWrapper extends BasicCodec {
|
|||
console.error("Could not find worker token entry! (" + message["token"] + ")");
|
||||
}
|
||||
spawnWorker() {
|
||||
this._worker = new Worker("js/codec/CompiledCodecWorker.js");
|
||||
this._worker.onmessage = event => this.onWorkerMessage(JSON.parse(event.data));
|
||||
return new Promise((resolve, reject) => {
|
||||
this._workerCallbackReject = reject;
|
||||
this._workerCallbackResolve = resolve;
|
||||
this._worker = new Worker(settings.static("worker_directory", "js/workers/") + "WorkerCodec.js");
|
||||
this._worker.onmessage = event => this.onWorkerMessage(JSON.parse(event.data));
|
||||
});
|
||||
}
|
||||
}
|
||||
/// <reference path="chat.ts" />
|
||||
|
@ -5478,7 +5568,7 @@ let settings;
|
|||
let globalClient;
|
||||
let chat;
|
||||
let forumIdentity;
|
||||
function invokeMain() {
|
||||
function main() {
|
||||
//localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost
|
||||
AudioController.initializeAudioController();
|
||||
if (!TSIdentityHelper.setup()) {
|
||||
|
@ -5512,6 +5602,7 @@ function invokeMain() {
|
|||
Modals.spawnConnectModal(settings.static("default_connect_url"));
|
||||
}
|
||||
}
|
||||
app.loadedListener.push(() => main());
|
||||
/// <reference path="BasicCodec.ts"/>
|
||||
class RawCodec extends BasicCodec {
|
||||
constructor(codecSampleRate) {
|
||||
|
@ -5524,6 +5615,10 @@ class RawCodec extends BasicCodec {
|
|||
initialise() {
|
||||
this.converterRaw = Module._malloc(this.bufferSize);
|
||||
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
|
||||
return new Promise(resolve => resolve());
|
||||
}
|
||||
initialized() {
|
||||
return true;
|
||||
}
|
||||
deinitialise() { }
|
||||
decode(data) {
|
||||
|
@ -5648,10 +5743,11 @@ class AudioResampler {
|
|||
throw "The target sample rate is outside the range [3000, 384000].";
|
||||
}
|
||||
resample(buffer) {
|
||||
//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.floor(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
context = new OfflineAudioContext(buffer.numberOfChannels, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
let source = context.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.connect(context.destination);
|
||||
|
|
File diff suppressed because one or more lines are too long
2
generated/js/client.min.js
vendored
2
generated/js/client.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18
index.php
18
index.php
|
@ -29,6 +29,7 @@
|
|||
<link rel="stylesheet" href="css/ts/icons.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/general.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modals.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/loader.css" type="text/css">
|
||||
|
||||
<!-- PHP generated properies -->
|
||||
<!-- localhost:63342/TeaSpeak-Web/index.php?_ijt=o48hmliefjoa8cer8v7mpl98pj&connect_default_host=192.168.43.141 -->
|
||||
|
@ -70,6 +71,23 @@
|
|||
elements.item(0).remove();
|
||||
</script>
|
||||
|
||||
<!-- Loading screen -->
|
||||
<div class="loader">
|
||||
<div class="half right"></div>
|
||||
<div class="half left"></div>
|
||||
<div class="bookshelf_wrapper">
|
||||
<ul class="books_list">
|
||||
<li class="book_item first"></li>
|
||||
<li class="book_item second"></li>
|
||||
<li class="book_item third"></li>
|
||||
<li class="book_item fourth"></li>
|
||||
<li class="book_item fifth"></li>
|
||||
<li class="book_item sixth"></li>
|
||||
</ul>
|
||||
<div class="shelf"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Critical load error -->
|
||||
<div style="display: none; position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px; background-color: gray; z-index: 1000; text-align: center;" id="critical-load">
|
||||
<div style="position: relative; display: inline-block; top: 30%">
|
||||
|
|
39
js/load.ts
39
js/load.ts
|
@ -9,6 +9,7 @@ namespace app {
|
|||
let applicationLoaded: boolean;
|
||||
export let type: Type = Type.UNDEFINED;
|
||||
export let loadedListener: (() => any)[];
|
||||
export const appLoaded = Date.now();
|
||||
|
||||
export function initialized() : boolean {
|
||||
return moduleInitialized && applicationLoaded;
|
||||
|
@ -59,6 +60,7 @@ namespace app {
|
|||
if(typeof Module === "undefined")
|
||||
this["Module"] = {};
|
||||
app.initialize();
|
||||
app.loadedListener.push(fadeoutLoader);
|
||||
|
||||
|
||||
function loadScripts(paths: (string | string[])[]) : {path: string, promise: Promise<Boolean>}[] {
|
||||
|
@ -213,6 +215,7 @@ function displayCriticalError(message: string, closeable: boolean = true) {
|
|||
|
||||
tag.style.display = "block";
|
||||
}
|
||||
fadeoutLoader();
|
||||
}
|
||||
|
||||
function loadTemplates() {
|
||||
|
@ -240,6 +243,10 @@ function loadTemplates() {
|
|||
|
||||
//TODO release config!
|
||||
function loadSide() {
|
||||
if(typeof (WebAssembly) === "undefined" || typeof (WebAssembly.compile) === "undefined") {
|
||||
displayCriticalError("You require WebAssembly for TeaSpeak-Web!");
|
||||
return;
|
||||
}
|
||||
//Load the general scripts and required scripts
|
||||
awaitLoad(loadScripts([
|
||||
["vendor/jquery/jquery.min.js", /*"https://code.jquery.com/jquery-latest.min.js"*/],
|
||||
|
@ -254,4 +261,34 @@ function loadSide() {
|
|||
});
|
||||
}
|
||||
|
||||
loadSide();
|
||||
loadSide();
|
||||
|
||||
//FUN: loader_ignore_age=0&loader_default_duration=1500&loader_default_age=5000
|
||||
function fadeoutLoader(duration = undefined, minAge = undefined, ignoreAge = undefined) {
|
||||
let settingsDefined = typeof(StaticSettings) !== "undefined";
|
||||
if(!duration) {
|
||||
if(settingsDefined)
|
||||
duration = StaticSettings.instance.static("loader_default_duration", 750);
|
||||
else duration = 750;
|
||||
}
|
||||
if(!minAge) {
|
||||
if(settingsDefined)
|
||||
minAge = StaticSettings.instance.static("loader_default_age", 1750);
|
||||
else minAge = 750;
|
||||
}
|
||||
if(!ignoreAge) {
|
||||
if(settingsDefined)
|
||||
ignoreAge = StaticSettings.instance.static("loader_ignore_age", false);
|
||||
else ignoreAge = false;
|
||||
}
|
||||
|
||||
let age = Date.now() - app.appLoaded;
|
||||
if(age < minAge && !ignoreAge) {
|
||||
setTimeout(() => fadeoutLoader(duration, 0, true), minAge - age);
|
||||
return;
|
||||
}
|
||||
$(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, duration);
|
||||
$(".loader .half").animate({width: 0}, duration, () => {
|
||||
$(".loader").detach();
|
||||
});
|
||||
}
|
118
js/settings.ts
118
js/settings.ts
|
@ -8,30 +8,41 @@ if(typeof(customElements) !== "undefined") {
|
|||
customElements.define('x-property', X_Property, { extends: 'div' });
|
||||
}
|
||||
|
||||
class Settings {
|
||||
static readonly KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
|
||||
static readonly KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
|
||||
class StaticSettings {
|
||||
private static _instance: StaticSettings;
|
||||
static get instance() : StaticSettings {
|
||||
if(!this._instance)
|
||||
this._instance = new StaticSettings(true);
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
private static readonly UPDATE_DIRECT: boolean = true;
|
||||
private cacheGlobal = {};
|
||||
private cacheServer = {};
|
||||
private currentServer: ServerEntry;
|
||||
private saveWorker: NodeJS.Timer;
|
||||
private updated: boolean = false;
|
||||
private _staticPropsTag: JQuery;
|
||||
protected static transformStO?<T>(input?: string, _default?: T) : T {
|
||||
if (typeof input === "undefined") return _default;
|
||||
if (typeof _default === "string") return input as any;
|
||||
else if (typeof _default === "number") return parseInt(input) as any;
|
||||
else if (typeof _default === "boolean") return (input == "1" || input == "true") as any;
|
||||
else if (typeof _default === "undefined") return input as any;
|
||||
return JSON.parse(input) as any;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._staticPropsTag = $("#properties");
|
||||
protected static transformOtS?<T>(input: T) : string {
|
||||
if (typeof input === "string") return input as string;
|
||||
else if (typeof input === "number") return input.toString();
|
||||
else if (typeof input === "boolean") return input ? "1" : "0";
|
||||
else if (typeof input == "undefined") return undefined;
|
||||
return JSON.stringify(input);
|
||||
}
|
||||
|
||||
this.cacheGlobal = JSON.parse(localStorage.getItem("settings.global"));
|
||||
if(!this.cacheGlobal) this.cacheGlobal = {};
|
||||
const _this = this;
|
||||
this.saveWorker = setInterval(() => {
|
||||
if(_this.updated)
|
||||
_this.save();
|
||||
}, 5 * 1000);
|
||||
protected _handle: StaticSettings;
|
||||
protected _staticPropsTag: JQuery;
|
||||
|
||||
this.initializeStatic();
|
||||
protected constructor(_reserved = undefined) {
|
||||
if(_reserved && !StaticSettings._instance) {
|
||||
this._staticPropsTag = $("#properties");
|
||||
this.initializeStatic();
|
||||
} else {
|
||||
this._handle = StaticSettings.instance;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeStatic() {
|
||||
|
@ -44,45 +55,59 @@ class Settings {
|
|||
});
|
||||
}
|
||||
|
||||
private static transformStO?<T>(input?: string, _default?: T) : T {
|
||||
if (typeof input === "undefined") return _default;
|
||||
if (typeof _default === "string") return input as any;
|
||||
else if (typeof _default === "number") return parseInt(input) as any;
|
||||
else if (typeof _default === "boolean") return (input == "1" || input == "true") as any;
|
||||
else if (typeof _default === "undefined") return input as any;
|
||||
return JSON.parse(input) as any;
|
||||
static?<T>(key: string, _default?: T) : T {
|
||||
if(this._handle) return this._handle.static<T>(key, _default);
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
console.log("%d | %o", result.length, result);
|
||||
return StaticSettings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
|
||||
}
|
||||
|
||||
private static transformOtS?<T>(input: T) : string {
|
||||
if (typeof input === "string") return input as string;
|
||||
else if (typeof input === "number") return input.toString();
|
||||
else if (typeof input === "boolean") return input ? "1" : "0";
|
||||
else if (typeof input == "undefined") return undefined;
|
||||
return JSON.stringify(input);
|
||||
deleteStatic(key: string) {
|
||||
if(this._handle) {
|
||||
this._handle.deleteStatic(key);
|
||||
return;
|
||||
}
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if(result.length != 0) result.detach();
|
||||
}
|
||||
}
|
||||
|
||||
class Settings extends StaticSettings {
|
||||
static readonly KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
|
||||
static readonly KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
|
||||
|
||||
private static readonly UPDATE_DIRECT: boolean = true;
|
||||
private cacheGlobal = {};
|
||||
private cacheServer = {};
|
||||
private currentServer: ServerEntry;
|
||||
private saveWorker: NodeJS.Timer;
|
||||
private updated: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.cacheGlobal = JSON.parse(localStorage.getItem("settings.global"));
|
||||
if(!this.cacheGlobal) this.cacheGlobal = {};
|
||||
this.saveWorker = setInterval(() => {
|
||||
if(this.updated)
|
||||
this.save();
|
||||
}, 5 * 1000);
|
||||
}
|
||||
|
||||
global?<T>(key: string, _default?: T) : T {
|
||||
let result = this.cacheGlobal[key];
|
||||
return Settings.transformStO(result, _default);
|
||||
return StaticSettings.transformStO(result, _default);
|
||||
}
|
||||
|
||||
server?<T>(key: string, _default?: T) : T {
|
||||
let result = this.cacheServer[key];
|
||||
return Settings.transformStO(result, _default);
|
||||
return StaticSettings.transformStO(result, _default);
|
||||
}
|
||||
|
||||
static?<T>(key: string, _default?: T) : T {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
console.log("%d | %o", result.length, result);
|
||||
return Settings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
|
||||
}
|
||||
|
||||
|
||||
changeGlobal<T>(key: string, value?: T){
|
||||
if(this.cacheGlobal[key] == value) return;
|
||||
|
||||
this.updated = true;
|
||||
this.cacheGlobal[key] = Settings.transformOtS(value);
|
||||
this.cacheGlobal[key] = StaticSettings.transformOtS(value);
|
||||
|
||||
if(Settings.UPDATE_DIRECT)
|
||||
this.save();
|
||||
|
@ -92,7 +117,7 @@ class Settings {
|
|||
if(this.cacheServer[key] == value) return;
|
||||
|
||||
this.updated = true;
|
||||
this.cacheServer[key] = Settings.transformOtS(value);
|
||||
this.cacheServer[key] = StaticSettings.transformOtS(value);
|
||||
|
||||
if(Settings.UPDATE_DIRECT)
|
||||
this.save();
|
||||
|
@ -126,9 +151,4 @@ class Settings {
|
|||
let global = JSON.stringify(this.cacheGlobal);
|
||||
localStorage.setItem("settings.global", global);
|
||||
}
|
||||
|
||||
deleteStatic(key: string) {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if(result.length != 0) result.detach();
|
||||
}
|
||||
}
|
|
@ -80,6 +80,7 @@ class AudioController {
|
|||
private _volume: number = 1;
|
||||
private _codecCache: CodecClientCache[] = [];
|
||||
private _timeIndex: number = 0;
|
||||
private _latencyBufferLength: number = 3;
|
||||
allowBuffering: boolean = true;
|
||||
|
||||
//Events
|
||||
|
@ -105,7 +106,7 @@ class AudioController {
|
|||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")");
|
||||
|
||||
this.applayVolume(buffer);
|
||||
this.applyVolume(buffer);
|
||||
this.audioCache.push(buffer);
|
||||
if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
|
||||
console.log("[Audio] Starting new playback");
|
||||
|
@ -117,7 +118,7 @@ class AudioController {
|
|||
switch (this.playerState) {
|
||||
case PlayerState.PREBUFFERING:
|
||||
case PlayerState.BUFFERING:
|
||||
if(this.audioCache.length < 3) {
|
||||
if(this.audioCache.length <= this._latencyBufferLength) {
|
||||
if(this.playerState == PlayerState.BUFFERING) {
|
||||
if(this.allowBuffering) break;
|
||||
} else break;
|
||||
|
@ -192,10 +193,10 @@ class AudioController {
|
|||
if(this._volume == val) return;
|
||||
this._volume = val;
|
||||
for(let buffer of this.audioCache)
|
||||
this.applayVolume(buffer);
|
||||
this.applyVolume(buffer);
|
||||
}
|
||||
|
||||
private applayVolume(buffer: AudioBuffer) {
|
||||
private applyVolume(buffer: AudioBuffer) {
|
||||
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for(let sample = 0; sample < data.length; sample++) {
|
||||
|
|
|
@ -52,7 +52,6 @@ class CodecPool {
|
|||
else {
|
||||
this.entries[index].instance.initialise().then((flag) => {
|
||||
//TODO test success flag
|
||||
console.error(flag);
|
||||
this.ownCodec(clientId, false).then(resolve).catch(reject);
|
||||
}).catch(error => {
|
||||
console.error("Could not initialize codec!\nError: %o", error);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
},
|
||||
"exclude": [
|
||||
"../node_modules",
|
||||
"../js/codec/workers"
|
||||
"../js/codec/workers",
|
||||
"../js/workers"
|
||||
]
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
"exclude": [
|
||||
"../js/load.ts",
|
||||
"../node_modules",
|
||||
"../js/codec/workers"
|
||||
"../js/workers"
|
||||
],
|
||||
"include": [
|
||||
"../js/**/*"
|
||||
|
|
Loading…
Add table
Reference in a new issue