A lots of updates :)
This commit is contained in:
parent
bbbd8f6365
commit
89a29a8717
65 changed files with 920 additions and 42 deletions
|
@ -2,6 +2,15 @@
|
|||
* **03.11.18**
|
||||
- Reworked on the basic overlay sizes
|
||||
- Added hostbanner to the UI
|
||||
- Implemented sounds
|
||||
- Implemented poke notification
|
||||
- Added local volume change to music bots
|
||||
- Added move and channel kick to the music bot context menu options
|
||||
- Reworked private chat format
|
||||
- Added several server chat messages
|
||||
- Fixed client double click chat opening
|
||||
- Implemented client drag and drop
|
||||
- Fixed channel select within sub channels
|
||||
|
||||
* **28.10.18**
|
||||
- Restructured the project
|
||||
|
|
16
files.php
16
files.php
|
@ -35,6 +35,22 @@
|
|||
"path" => "css/",
|
||||
"local-path" => "./shared/css/"
|
||||
],
|
||||
[ /* shared sound files */
|
||||
"type" => "wav",
|
||||
"search-pattern" => "/.*\.wav$/",
|
||||
"build-target" => "dev|rel",
|
||||
|
||||
"path" => "audio/",
|
||||
"local-path" => "./shared/audio/"
|
||||
],
|
||||
[ /* shared data sound files */
|
||||
"type" => "json",
|
||||
"search-pattern" => "/.*\.json/",
|
||||
"build-target" => "dev|rel",
|
||||
|
||||
"path" => "audio/",
|
||||
"local-path" => "./shared/audio/"
|
||||
],
|
||||
[ /* shared image files */
|
||||
"type" => "img",
|
||||
"search-pattern" => "/.*\.(svg|png)/",
|
||||
|
|
BIN
shared/audio/effects/message_received.wav
Normal file
BIN
shared/audio/effects/message_received.wav
Normal file
Binary file not shown.
BIN
shared/audio/effects/message_send.wav
Normal file
BIN
shared/audio/effects/message_send.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/away_activated.wav
Normal file
BIN
shared/audio/speech/away_activated.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/away_deactivated.wav
Normal file
BIN
shared/audio/speech/away_deactivated.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.created.wav
Normal file
BIN
shared/audio/speech/channel.created.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.deleted.wav
Normal file
BIN
shared/audio/speech/channel.deleted.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.edited.self.wav
Normal file
BIN
shared/audio/speech/channel.edited.self.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.edited.wav
Normal file
BIN
shared/audio/speech/channel.edited.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.joined.wav
Normal file
BIN
shared/audio/speech/channel.joined.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.kicked.wav
Normal file
BIN
shared/audio/speech/channel.kicked.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/channel.moved.wav
Normal file
BIN
shared/audio/speech/channel.moved.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/connection.banned.wav
Normal file
BIN
shared/audio/speech/connection.banned.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/connection.connected.wav
Normal file
BIN
shared/audio/speech/connection.connected.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/connection.disconnected.timeout.wav
Normal file
BIN
shared/audio/speech/connection.disconnected.timeout.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/connection.disconnected.wav
Normal file
BIN
shared/audio/speech/connection.disconnected.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/connection.refused.wav
Normal file
BIN
shared/audio/speech/connection.refused.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/error.insufficient_permissions.wav
Normal file
BIN
shared/audio/speech/error.insufficient_permissions.wav
Normal file
Binary file not shown.
1
shared/audio/speech/mapping.json
Normal file
1
shared/audio/speech/mapping.json
Normal file
|
@ -0,0 +1 @@
|
|||
[{"file": "sound.test.wav", "key": "sound.test"}, {"file": "sound.egg.wav", "key": "sound.egg"}, {"file": "away_activated.wav", "key": "away_activated"}, {"file": "away_deactivated.wav", "key": "away_deactivated"}, {"file": "connection.connected.wav", "key": "connection.connected"}, {"file": "connection.disconnected.wav", "key": "connection.disconnected"}, {"file": "connection.disconnected.timeout.wav", "key": "connection.disconnected.timeout"}, {"file": "connection.refused.wav", "key": "connection.refused"}, {"file": "connection.banned.wav", "key": "connection.banned"}, {"file": "server.edited.wav", "key": "server.edited"}, {"file": "server.edited.self.wav", "key": "server.edited.self"}, {"file": "server.kicked.wav", "key": "server.kicked"}, {"file": "channel.kicked.wav", "key": "channel.kicked"}, {"file": "channel.moved.wav", "key": "channel.moved"}, {"file": "channel.joined.wav", "key": "channel.joined"}, {"file": "channel.created.wav", "key": "channel.created"}, {"file": "channel.edited.wav", "key": "channel.edited"}, {"file": "channel.edited.self.wav", "key": "channel.edited.self"}, {"file": "channel.deleted.wav", "key": "channel.deleted"}, {"file": "user.moved.wav", "key": "user.moved"}, {"file": "user.moved.self.wav", "key": "user.moved.self"}, {"file": "user.poked.self.wav", "key": "user.poked.self"}, {"file": "user.banned.wav", "key": "user.banned"}, {"file": "user.joined.wav", "key": "user.joined"}, {"file": "user.joined.moved.wav", "key": "user.joined.moved"}, {"file": "user.joined.kicked.wav", "key": "user.joined.kicked"}, {"file": "user.joined.connect.wav", "key": "user.joined.connect"}, {"file": "user.left.wav", "key": "user.left"}, {"file": "user.left.kicked.channel.wav", "key": "user.left.kicked.channel"}, {"file": "user.left.kicked.server.wav", "key": "user.left.kicked.server"}, {"file": "user.left.moved.wav", "key": "user.left.moved"}, {"file": "user.left.disconnect.wav", "key": "user.left.disconnect"}, {"file": "user.left.banned.wav", "key": "user.left.banned"}, {"file": "error.insufficient_permissions.wav", "key": "error.insufficient_permissions"}]
|
BIN
shared/audio/speech/server.edited.self.wav
Normal file
BIN
shared/audio/speech/server.edited.self.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/server.edited.wav
Normal file
BIN
shared/audio/speech/server.edited.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/server.kicked.wav
Normal file
BIN
shared/audio/speech/server.kicked.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/sound.egg.wav
Normal file
BIN
shared/audio/speech/sound.egg.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/sound.test.wav
Normal file
BIN
shared/audio/speech/sound.test.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.banned.wav
Normal file
BIN
shared/audio/speech/user.banned.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.joined.connect.wav
Normal file
BIN
shared/audio/speech/user.joined.connect.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.joined.kicked.wav
Normal file
BIN
shared/audio/speech/user.joined.kicked.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.joined.moved.wav
Normal file
BIN
shared/audio/speech/user.joined.moved.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.joined.wav
Normal file
BIN
shared/audio/speech/user.joined.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.banned.wav
Normal file
BIN
shared/audio/speech/user.left.banned.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.disconnect.wav
Normal file
BIN
shared/audio/speech/user.left.disconnect.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.kicked.channel.wav
Normal file
BIN
shared/audio/speech/user.left.kicked.channel.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.kicked.server.wav
Normal file
BIN
shared/audio/speech/user.left.kicked.server.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.moved.wav
Normal file
BIN
shared/audio/speech/user.left.moved.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.left.wav
Normal file
BIN
shared/audio/speech/user.left.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.moved.self.wav
Normal file
BIN
shared/audio/speech/user.moved.self.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.moved.wav
Normal file
BIN
shared/audio/speech/user.moved.wav
Normal file
Binary file not shown.
BIN
shared/audio/speech/user.poked.self.wav
Normal file
BIN
shared/audio/speech/user.poked.self.wav
Normal file
Binary file not shown.
49
shared/audio/speech_sentences.csv
Normal file
49
shared/audio/speech_sentences.csv
Normal file
|
@ -0,0 +1,49 @@
|
|||
#Sound test
|
||||
sound.test;This is a test sound
|
||||
sound.egg;WolverinDEV is the best and I love TeaSpeak!
|
||||
|
||||
#Away
|
||||
away_activated;See you soon
|
||||
away_deactivated;Welcome back
|
||||
|
||||
#Connection
|
||||
connection.connected;Connected
|
||||
connection.disconnected;Disconnected
|
||||
connection.disconnected.timeout;Connection to server lost
|
||||
connection.refused;Connect failed
|
||||
connection.banned;You got banned from this server
|
||||
|
||||
#Server
|
||||
server.edited;Server edited
|
||||
server.edited.self;You edited the server
|
||||
server.kicked;You got kicked from the server
|
||||
|
||||
#Channel activity
|
||||
channel.kicked;You got kicked from the channel
|
||||
channel.moved;Channel moved
|
||||
channel.joined;Channel joined
|
||||
channel.created;Channel created
|
||||
channel.edited;Channel edited
|
||||
channel.edited.self;You edited the channel
|
||||
channel.deleted;Channel deleted
|
||||
|
||||
#User action
|
||||
user.moved;User moved
|
||||
user.moved.self;You have been moved
|
||||
user.poked.self;Hey wakeup!
|
||||
user.banned;User banned
|
||||
|
||||
user.joined;User joined your channel
|
||||
user.joined.moved;User got moved to your channel
|
||||
user.joined.kicked;User got kicked to your channel
|
||||
user.joined.connect;User connected to your channel
|
||||
|
||||
user.left;User left your channel
|
||||
user.left.kicked.channel;User in your channel got kicked from the channel
|
||||
user.left.kicked.server;User in your channel got kicked from the server
|
||||
user.left.moved;User was moved out of your channel
|
||||
user.left.disconnect;User disconnected from your channel
|
||||
user.left.banned;User in your channel was banned from the server
|
||||
|
||||
#Error
|
||||
error.insufficient_permissions;insufficient permissions
|
|
|
@ -13,6 +13,7 @@
|
|||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex-grow: 1;
|
||||
|
||||
.button-update {
|
||||
width: 100%;
|
||||
|
@ -80,13 +81,14 @@
|
|||
.container-select-info {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
> div {
|
||||
flex-grow: 1;
|
||||
.select_server {
|
||||
> div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -277,6 +277,7 @@ footer .container {
|
|||
}
|
||||
|
||||
min-width: 100px;
|
||||
max-width: 60%; /* tmp chat fix */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
@ -314,4 +315,25 @@ footer .container {
|
|||
width: 40%;
|
||||
border-radius: 0 0 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
#mouse-move {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
border: 2px solid gray;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
min-height: 500px;
|
||||
min-width: 500px;
|
||||
overflow: hidden;
|
||||
}
|
35
shared/css/modal-poke.scss
Normal file
35
shared/css/modal-poke.scss
Normal file
|
@ -0,0 +1,35 @@
|
|||
.container-poke {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.container-information {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container-message {
|
||||
text-align: center;
|
||||
|
||||
.message {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.button-close {
|
||||
margin-top: 5px;
|
||||
width: 150px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
<script class="jsrender-template" id="tmpl_poke_popup" type="text/html">
|
||||
<div class="container-poke">
|
||||
<div class="container-information">
|
||||
<a>You have been poked by </a><node key="invoker"></node><a>:</a>
|
||||
</div>
|
||||
<div class="container-message">
|
||||
<a class="message">{{>message}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
*/
|
|
@ -122,6 +122,10 @@
|
|||
flex-direction: row;
|
||||
top: 0px;
|
||||
left: 16px;
|
||||
|
||||
&.move-selected {
|
||||
border: 1px black solid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
79
shared/generate_voice.py
Normal file
79
shared/generate_voice.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
This should be executed as python 2.7 (because of pydub)
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import csv
|
||||
import shutil
|
||||
from pydub import AudioSegment
|
||||
|
||||
TARGET_DIRECTORY = "audio/speech"
|
||||
SOURCE_FILE = "audio/speech_sentences.csv"
|
||||
|
||||
|
||||
def tts(text, file):
|
||||
voice_id = 4
|
||||
language_id = 1
|
||||
req = requests.post(
|
||||
'https://kfiuqykx63.execute-api.us-east-1.amazonaws.com/Dev/tts?r={}&s={}&l=0&v=aca'.format(voice_id,
|
||||
language_id),
|
||||
stream=True,
|
||||
headers={
|
||||
'origin': 'https://www.naturalreaders.com',
|
||||
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/69.0.3497.100 Safari/537.36 OPR/56.0.3051.52',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'referer': 'https://www.naturalreaders.com/online/',
|
||||
'authority': 'kfiuqykx63.execute-api.us-east-1.amazonaws.com'
|
||||
},
|
||||
data=json.dumps({"t": text})
|
||||
)
|
||||
|
||||
if req.status_code != 200:
|
||||
raise ValueError("Invalid response code {}".format(req.status_code))
|
||||
|
||||
with open(file + ".mp3", "wb") as fstream:
|
||||
for chunk in req.iter_content(chunk_size=128):
|
||||
fstream.write(chunk)
|
||||
fstream.close()
|
||||
|
||||
sound = AudioSegment.from_mp3(file + ".mp3")
|
||||
sound.export(file, format="wav")
|
||||
|
||||
os.remove(file + ".mp3")
|
||||
|
||||
def main():
|
||||
if os.path.exists(TARGET_DIRECTORY):
|
||||
print("Deleting old speach directory (%s)!" % TARGET_DIRECTORY)
|
||||
try:
|
||||
shutil.rmtree(TARGET_DIRECTORY)
|
||||
except e:
|
||||
print("Cant delete old dir!")
|
||||
os.makedirs(TARGET_DIRECTORY)
|
||||
|
||||
mapping = []
|
||||
with open(SOURCE_FILE, 'r') as input:
|
||||
reader = csv.reader(filter(lambda row: len(row) != 0 and row[0] != '#', input), delimiter=';', quotechar='#')
|
||||
for row in reader:
|
||||
if len(row) != 2:
|
||||
continue
|
||||
print("Generating speech for {}: {}".format(row[0], row[1]))
|
||||
try:
|
||||
file = "{}.wav".format(row[0])
|
||||
tts(row[1], TARGET_DIRECTORY + "/" + file)
|
||||
|
||||
mapping.append({'key': row[0], 'file': file})
|
||||
except e:
|
||||
print(e)
|
||||
print("Failed to generate {}", row[0])
|
||||
|
||||
with open("audio/speech/mapping.json", "w") as fstream:
|
||||
fstream.write(json.dumps(mapping))
|
||||
fstream.close()
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -53,6 +53,7 @@
|
|||
<link rel="stylesheet" href="css/modal-banlist.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-bancreate.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-settings.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/modal-poke.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/loader.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css">
|
||||
|
@ -145,6 +146,11 @@
|
|||
|
||||
<div id="music-test"></div>
|
||||
<div id="templates"></div>
|
||||
<div id="sounds"></div>
|
||||
<div id="mouse-move">
|
||||
<div class="container">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<?php
|
||||
|
|
|
@ -431,7 +431,7 @@
|
|||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Transfers</x-tag>
|
||||
<x-content>TeaSpeak does not yet support these variables</x-content>
|
||||
<x-content>This needs to be implemented :)</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Anti-Flood</x-tag>
|
||||
|
@ -1322,9 +1322,7 @@
|
|||
</script>
|
||||
<script class="jsrender-template" id="tmpl_selected_hostbanner" type="text/html">
|
||||
<div class="hostbanner">
|
||||
{{if property_virtualserver_hostbanner_url}}
|
||||
<a href="{{:property_virtualserver_hostbanner_url}}">
|
||||
{{/if}}
|
||||
<a href="{{:property_virtualserver_hostbanner_url}}" style="display: flex; flex-direction: row; justify-content: center;">
|
||||
|
||||
<img src="
|
||||
{{:property_virtualserver_hostbanner_gfx_url}}
|
||||
|
@ -1348,9 +1346,7 @@
|
|||
|
||||
alt="Host banner"
|
||||
>
|
||||
{{if property_virtualserver_hostbanner_url}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</script>
|
||||
<script class="jsrender-template" id="tmpl_selected_server" type="text/html">
|
||||
|
@ -1463,5 +1459,16 @@
|
|||
<node key="bbcode_channel_description"></node>
|
||||
</div>
|
||||
</script>
|
||||
<script class="jsrender-template" id="tmpl_poke_popup" type="text/html">
|
||||
<div class="container-poke">
|
||||
<div class="container-information">
|
||||
<a>You have been poked by </a><node key="invoker"></node><a>:</a>
|
||||
</div>
|
||||
<div class="container-message">
|
||||
<a class="message">{{>message}}</a>
|
||||
</div>
|
||||
<button class="button-close">close</button>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -65,7 +65,8 @@ namespace MessageHelper {
|
|||
console.warn("Message to format contains invalid index (" + number + ")");
|
||||
|
||||
result.push(...this.formatElement(objects[number]));
|
||||
begin = found = found + 2 + offset;
|
||||
found = found + 1 + offset;
|
||||
begin = found + 1;
|
||||
console.log("Offset: " + offset + " Number: " + number);
|
||||
} while(found++);
|
||||
|
||||
|
@ -237,6 +238,11 @@ class ChatEntry {
|
|||
return tag;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.handle.activeChat = this;
|
||||
this.handle.htmlTag.find(".input_box").focus();
|
||||
}
|
||||
|
||||
set name(newName : string) {
|
||||
console.log("Change name!");
|
||||
this._name = newName;
|
||||
|
|
|
@ -98,7 +98,7 @@ class TSClient {
|
|||
helpers.hashPassword(password.password).then(password => {
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password));
|
||||
}).catch(error => {
|
||||
createErrorModal("Error while hasing password", "Faield to hash server password!").open();
|
||||
createErrorModal("Error while hashing password", "Failed to hash server password!<br>" + error).open();
|
||||
})
|
||||
} else
|
||||
this.serverConnection.startConnection({host, port}, new HandshakeHandler(identity, name, password ? password.password : undefined));
|
||||
|
@ -182,6 +182,7 @@ class TSClient {
|
|||
"Click <a href='" + this.certAcceptUrl() + "'>here</a> to accept the remote certificate"
|
||||
).open();
|
||||
}
|
||||
sound.play(Sound.CONNECTION_REFUSED);
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_CLOSED:
|
||||
console.error("Lost connection to remote server!");
|
||||
|
@ -189,9 +190,11 @@ class TSClient {
|
|||
"Connection closed",
|
||||
"The connection was closed by remote host"
|
||||
).open();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
break;
|
||||
case DisconnectReason.CONNECTION_PING_TIMEOUT:
|
||||
console.error("Connection ping timeout");
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED_TIMEOUT);
|
||||
createErrorModal(
|
||||
"Connection lost",
|
||||
"Lost connection to remote host (Ping timeout)<br>Even possible?"
|
||||
|
@ -204,6 +207,7 @@ class TSClient {
|
|||
"The server is closed.<br>" +
|
||||
"Reason: " + data.reasonmsg
|
||||
).open();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
break;
|
||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||
chat.serverChat().appendError("Server requires password");
|
||||
|
@ -214,6 +218,19 @@ class TSClient {
|
|||
this.serverConnection._handshakeHandler.name,
|
||||
{password: password as string, hashed: false});
|
||||
}).open();
|
||||
break;
|
||||
case DisconnectReason.CLIENT_KICKED:
|
||||
chat.serverChat().appendError("You got kicked from the server by {0}{1}",
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
sound.play(Sound.SERVER_KICKED);
|
||||
break;
|
||||
case DisconnectReason.CLIENT_BANNED:
|
||||
chat.serverChat().appendError("You got banned from the server by {0}{1}",
|
||||
ClientEntry.chatTag(data["invokerid"], data["invokername"], data["invokeruid"]),
|
||||
data["reasonmsg"] ? " (" + data["reasonmsg"] + ")" : "");
|
||||
sound.play(Sound.CONNECTION_BANNED); //TODO findout if it was a disconnect or a connect refuse
|
||||
break;
|
||||
default:
|
||||
console.error("Got uncaught disconnect!");
|
||||
console.error("Type: " + type + " Data:");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/// <reference path="ui/channel.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="sound/Sounds.ts" />
|
||||
/// <reference path="ui/modal/ModalPoke.ts" />
|
||||
|
||||
class CommandResult {
|
||||
success: boolean;
|
||||
|
@ -227,6 +229,7 @@ class ServerConnection {
|
|||
if(res.id == 2568) { //Permission error
|
||||
res.message = "Insufficient client permissions. Failed on permission " + this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
|
||||
chat.serverChat().appendError("Insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
|
||||
sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS);
|
||||
} else {
|
||||
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
|
||||
}
|
||||
|
@ -473,6 +476,8 @@ class ConnectionCommandHandler {
|
|||
this["notifyserveredited"] = this.handleNotifyServerEdited;
|
||||
this["notifyserverupdated"] = this.handleNotifyServerUpdated;
|
||||
|
||||
this["notifyclientpoke"] = this.handleNotifyClientPoke;
|
||||
|
||||
this["notifymusicplayerinfo"] = this.handleNotifyMusicPlayerInfo;
|
||||
}
|
||||
|
||||
|
@ -524,6 +529,7 @@ class ConnectionCommandHandler {
|
|||
|
||||
chat.serverChat().name = this.connection._client.channelTree.server.properties["virtualserver_name"];
|
||||
chat.serverChat().appendMessage("Connected as {0}", true, this.connection._client.getClient().createChatTag(true));
|
||||
sound.play(Sound.CONNECTION_CONNECTED);
|
||||
globalClient.onConnected();
|
||||
}
|
||||
|
||||
|
@ -637,14 +643,42 @@ class ConnectionCommandHandler {
|
|||
chat.channelChat().name = channel.channelName();
|
||||
tree.moveClient(client, channel);
|
||||
}
|
||||
|
||||
const own_channel = this.connection._client.getClient().currentChannel();
|
||||
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
if(own_channel == channel)
|
||||
if(old_channel)
|
||||
sound.play(Sound.USER_ENTERED);
|
||||
else
|
||||
sound.play(Sound.USER_ENTERED_CONNECT);
|
||||
if(old_channel) {
|
||||
chat.serverChat().appendMessage("{0} appeared from {1} to {2}", true, client.createChatTag(true), old_channel.createChatTag(true), channel.createChatTag(true));
|
||||
} else {
|
||||
chat.serverChat().appendMessage("{0} connected to channel {1}", true, client.createChatTag(true), channel.createChatTag(true));
|
||||
}
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
||||
if(own_channel == channel)
|
||||
sound.play(Sound.USER_ENTERED_MOVED);
|
||||
|
||||
chat.serverChat().appendMessage("{0} appeared from {1} to {2}, moved by {3}", true,
|
||||
client.createChatTag(true),
|
||||
old_channel ? old_channel.createChatTag(true) : undefined,
|
||||
channel.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
if(own_channel == channel)
|
||||
sound.play(Sound.USER_ENTERED_KICKED);
|
||||
|
||||
chat.serverChat().appendMessage("{0} appeared from {1} to {2}, kicked by {3}{4}", true,
|
||||
client.createChatTag(true),
|
||||
old_channel ? old_channel.createChatTag(true) : undefined,
|
||||
channel.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"] > 0 ? " (" + json["msg"] + ")" : ""
|
||||
);
|
||||
} else {
|
||||
console.warn("Unknown reasonid for " + json["reasonid"]);
|
||||
}
|
||||
|
||||
let updates: {
|
||||
|
@ -678,44 +712,69 @@ class ConnectionCommandHandler {
|
|||
return 0;
|
||||
}
|
||||
if(client == this.connection._client.getClient()) {
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_BAN)
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
|
||||
this.connection._client.handleDisconnect(DisconnectReason.CLIENT_BANNED, json);
|
||||
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK)
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
|
||||
this.connection._client.handleDisconnect(DisconnectReason.CLIENT_KICKED, json);
|
||||
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN)
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_SHUTDOWN) {
|
||||
this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
|
||||
else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED)
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_STOPPED) {
|
||||
this.connection._client.handleDisconnect(DisconnectReason.SERVER_CLOSED, json);
|
||||
else
|
||||
} else
|
||||
this.connection._client.handleDisconnect(DisconnectReason.UNKNOWN, json);
|
||||
return;
|
||||
}
|
||||
|
||||
const own_channel = this.connection._client.getClient().currentChannel();
|
||||
let channel_from = tree.findChannel(json["cfid"]);
|
||||
let channel_to = tree.findChannel(json["ctid"]);
|
||||
|
||||
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
chat.serverChat().appendMessage("{0} disappeared from {1} to {2}", true, client.createChatTag(true), channel_from.createChatTag(true), channel_to.createChatTag(true));
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_LEFT) {
|
||||
chat.serverChat().appendMessage("{0} left the server ({1})", true, client.createChatTag(true), json["reasonmsg"]);
|
||||
chat.serverChat().appendMessage("{0} left the server{1}", true,
|
||||
client.createChatTag(true),
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_DISCONNECT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_SERVER_KICK) {
|
||||
chat.serverChat().appendError("{0} was kicked from the server by {1}. ({2})",
|
||||
chat.serverChat().appendError("{0} was kicked from the server by {1}.{2}",
|
||||
client.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"]
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_KICKED_SERVER);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
chat.serverChat().appendError("{0} was kicked from your channel by {1}.{2}",
|
||||
client.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_BAN) {
|
||||
//"Mulus" was banned for 1 second from the server by "WolverinDEV" (Sry brauchte kurz ein opfer :P <3 (Nohomo))
|
||||
let duration = "permanently";
|
||||
if(json["bantime"])
|
||||
duration = "for " + formatDate(Number.parseInt(json["bantime"]));
|
||||
chat.serverChat().appendError("{0} was banned {1} by {2}. ({3})",
|
||||
|
||||
chat.serverChat().appendError("{0} was banned {1} by {2}.{3}",
|
||||
client.createChatTag(true),
|
||||
duration,
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
json["reasonmsg"]
|
||||
json["reasonmsg"] ? " (" + json["reasonmsg"] + ")" : ""
|
||||
);
|
||||
|
||||
if(channel_from == own_channel)
|
||||
sound.play(Sound.USER_LEFT_BANNED);
|
||||
} else {
|
||||
console.error("Unknown client left reason!");
|
||||
}
|
||||
|
@ -749,8 +808,10 @@ class ConnectionCommandHandler {
|
|||
if(entry !== client) entry.getAudioController().stopAudio(true);
|
||||
this.connection._client.controlBar.updateVoice(channel_to);
|
||||
}
|
||||
|
||||
tree.moveClient(client, channel_to);
|
||||
|
||||
const own_channel = this.connection._client.getClient().currentChannel();
|
||||
if(json["reasonid"] == ViewReasonId.VREASON_MOVED) {
|
||||
chat.serverChat().appendMessage(self ? "You was moved by {3} from channel {1} to {2}" : "{0} was moved from channel {1} to {2} by {3}", true,
|
||||
client.createChatTag(true),
|
||||
|
@ -758,12 +819,39 @@ class ConnectionCommandHandler {
|
|||
channel_to.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"])
|
||||
);
|
||||
if(self)
|
||||
sound.play(Sound.USER_MOVED_SELF);
|
||||
else if(own_channel == channel_to)
|
||||
sound.play(Sound.USER_ENTERED_MOVED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT_MOVED);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_USER_ACTION) {
|
||||
chat.serverChat().appendMessage(self ? "You switched from channel {1} to {2}" : "{0} switched from channel {1} to {2}", true,
|
||||
client.createChatTag(true),
|
||||
channel_from ? channel_from.createChatTag(true) : undefined,
|
||||
channel_to.createChatTag(true)
|
||||
);
|
||||
if(self) {} //If we do an action we wait for the error response
|
||||
else if(own_channel == channel_to)
|
||||
sound.play(Sound.USER_ENTERED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT);
|
||||
} else if(json["reasonid"] == ViewReasonId.VREASON_CHANNEL_KICK) {
|
||||
chat.serverChat().appendMessage(self ? "You got kicked out of the channel {1} to channel {2} by {3}{4}" : "{0} got kicked from channel {1} to {2} by {3}{4}", true,
|
||||
client.createChatTag(true),
|
||||
channel_from ? channel_from.createChatTag(true) : undefined,
|
||||
channel_to.createChatTag(true),
|
||||
ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"]),
|
||||
(json["reasonmsg"] || "").length > 0 ? " (" + json["msg"] + ")" : ""
|
||||
);
|
||||
if(self) {
|
||||
sound.play(Sound.CHANNEL_KICKED);
|
||||
} else if(own_channel == channel_to)
|
||||
sound.play(Sound.USER_ENTERED_KICKED);
|
||||
else if(own_channel == channel_from)
|
||||
sound.play(Sound.USER_LEFT_KICKED_CHANNEL);
|
||||
} else {
|
||||
console.warn("Unknown reason id " + json["reasonid"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -836,14 +924,20 @@ class ConnectionCommandHandler {
|
|||
return;
|
||||
}
|
||||
if(invoker == this.connection._client.getClient()) {
|
||||
target.chat(true).appendMessage("<< " + json["msg"]);
|
||||
sound.play(Sound.MESSAGE_SEND, { background_notification: true });
|
||||
target.chat(true).appendMessage("{0}: {1}", true, this.connection._client.getClient().createChatTag(true), json["msg"]);
|
||||
} else {
|
||||
invoker.chat(true).appendMessage(">> " + json["msg"]);
|
||||
sound.play(Sound.MESSAGE_RECEIVED, { background_notification: true });
|
||||
invoker.chat(true).appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
|
||||
}
|
||||
} else if(mode == 2) {
|
||||
chat.channelChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"])
|
||||
if(json["invokerid"] == this.connection._client.clientId)
|
||||
sound.play(Sound.MESSAGE_SEND, { background_notification: true });
|
||||
else
|
||||
sound.play(Sound.MESSAGE_RECEIVED, { background_notification: true });
|
||||
chat.channelChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"])
|
||||
} else if(mode == 3) {
|
||||
chat.serverChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
|
||||
chat.serverChat().appendMessage("{0}: {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -921,4 +1015,15 @@ class ConnectionCommandHandler {
|
|||
|
||||
bot.handlePlayerInfo(json);
|
||||
}
|
||||
|
||||
handleNotifyClientPoke(json) {
|
||||
json = json[0];
|
||||
Modals.spawnPoke({
|
||||
id: parseInt(json["invokerid"]),
|
||||
name: json["invokername"],
|
||||
unique_id: json["invokeruid"]
|
||||
}, json["msg"]);
|
||||
|
||||
sound.play(Sound.USER_POKED_SELF);
|
||||
}
|
||||
}
|
|
@ -394,7 +394,7 @@ namespace sha {
|
|||
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
|
||||
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
|
||||
|
||||
if(/Edge/.test(navigator.userAgent))
|
||||
if(!crypto || !crypto.subtle || !crypto.subtle.digest || /Edge/.test(navigator.userAgent))
|
||||
return new Promise<ArrayBuffer>(resolve => {
|
||||
resolve(_sha1.arrayBuffer(buffer as ArrayBuffer));
|
||||
});
|
||||
|
|
|
@ -129,6 +129,8 @@ function loadDebug() {
|
|||
//Load general API's
|
||||
"js/log.js",
|
||||
|
||||
"js/sound/Sounds.js",
|
||||
|
||||
"js/utils/modal.js",
|
||||
"js/utils/tab.js",
|
||||
"js/utils/helpers.js",
|
||||
|
@ -147,6 +149,7 @@ function loadDebug() {
|
|||
"js/ui/modal/ModalBanCreate.js",
|
||||
"js/ui/modal/ModalBanList.js",
|
||||
"js/ui/modal/ModalYesNo.js",
|
||||
"js/ui/modal/ModalPoke.js",
|
||||
"js/ui/modal/ModalPermissionEdit.js",
|
||||
"js/ui/modal/ModalServerGroupDialog.js",
|
||||
|
||||
|
@ -154,6 +157,7 @@ function loadDebug() {
|
|||
"js/ui/client.js",
|
||||
"js/ui/server.js",
|
||||
"js/ui/view.js",
|
||||
"js/ui/client_move.js",
|
||||
|
||||
"js/ui/frames/SelectedItemInfo.js",
|
||||
"js/ui/frames/ControlBar.js",
|
||||
|
|
|
@ -152,8 +152,14 @@ function main() {
|
|||
*/
|
||||
|
||||
setup_close();
|
||||
|
||||
let _resize_timeout: NodeJS.Timer;
|
||||
$(window).on('resize', () => {
|
||||
globalClient.channelTree.handle_resized();
|
||||
if(_resize_timeout)
|
||||
clearTimeout(_resize_timeout);
|
||||
_resize_timeout = setTimeout(() => {
|
||||
globalClient.channelTree.handle_resized();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ if(typeof ($) !== "undefined") {
|
|||
result = $(result);
|
||||
}
|
||||
result.find("node").each((index, element) => {
|
||||
$(element).replaceWith(values[$(element).attr("key")] || values[0][$(element).attr("key")]);
|
||||
$(element).replaceWith(values[$(element).attr("key")] || (values[0] || [])[$(element).attr("key")]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
|
212
shared/js/sound/Sounds.ts
Normal file
212
shared/js/sound/Sounds.ts
Normal file
|
@ -0,0 +1,212 @@
|
|||
enum Sound {
|
||||
SOUND_TEST = "sound.test",
|
||||
SOUND_EGG = "sound.egg",
|
||||
|
||||
AWAY_ACTIVATED = "away_activated",
|
||||
AWAY_DEACTIVATED = "away_deactivated",
|
||||
|
||||
CONNECTION_CONNECTED = "connection.connected",
|
||||
CONNECTION_DISCONNECTED = "connection.disconnected",
|
||||
CONNECTION_BANNED = "connection.banned",
|
||||
CONNECTION_DISCONNECTED_TIMEOUT = "connection.disconnected.timeout",
|
||||
CONNECTION_REFUSED = "connection.refused",
|
||||
|
||||
SERVER_EDITED = "server.edited",
|
||||
SERVER_EDITED_SELF = "server.edited.self",
|
||||
SERVER_KICKED = "server.kicked",
|
||||
|
||||
CHANNEL_CREATED = "channel.created",
|
||||
CHANNEL_MOVED = "channel.moved",
|
||||
CHANNEL_EDITED = "channel.edited",
|
||||
CHANNEL_EDITED_SELF = "channel.edited.self",
|
||||
CHANNEL_DELETED = "channel.deleted",
|
||||
|
||||
CHANNEL_JOINED = "channel.joined",
|
||||
CHANNEL_KICKED = "channel.kicked", //You got kicked from the channel
|
||||
|
||||
USER_MOVED = "user.moved", //User moved
|
||||
USER_MOVED_SELF = "user.moved.self", //You were moved
|
||||
USER_POKED_SELF = "user.poked.self", //Hey wakeup
|
||||
USER_BANNED = "user.banned",
|
||||
|
||||
USER_ENTERED = "user.joined", //User joined your channel
|
||||
USER_ENTERED_MOVED = "user.joined.moved", //User was moved to your channel
|
||||
USER_ENTERED_KICKED = "user.joined.kicked", //User was kicked to your channel
|
||||
USER_ENTERED_CONNECT = "user.joined.connect",
|
||||
|
||||
USER_LEFT = "user.left", //User left your channel
|
||||
USER_LEFT_MOVED = "user.left.moved", //User was move out of your channel
|
||||
USER_LEFT_KICKED_CHANNEL = "user.left.kicked.server", //User was kicked out of your channel
|
||||
USER_LEFT_KICKED_SERVER = "user.left.kicked.channel", //User is your channel was kicked from the server
|
||||
USER_LEFT_DISCONNECT = "user.left.disconnect",
|
||||
USER_LEFT_BANNED = "user.left.banned",
|
||||
|
||||
ERROR_INSUFFICIENT_PERMISSIONS = "error.insufficient_permissions",
|
||||
|
||||
MESSAGE_SEND = "message.send",
|
||||
MESSAGE_RECEIVED = "message.received"
|
||||
}
|
||||
|
||||
namespace sound {
|
||||
interface SpeechFile {
|
||||
key: string;
|
||||
filename: string;
|
||||
|
||||
not_supported?: boolean;
|
||||
not_supported_timeout?: number;
|
||||
cached?: AudioBuffer;
|
||||
node?: HTMLAudioElement;
|
||||
|
||||
}
|
||||
let warned = false
|
||||
let speech_mapping: {[key: string]:SpeechFile} = {};
|
||||
|
||||
function register_sound(key: string, file: string) {
|
||||
speech_mapping[key] = {key: key, filename: file} as SpeechFile;
|
||||
}
|
||||
|
||||
export function initialize() : Promise<void> {
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings){
|
||||
if (settings.dataType === 'binary'){
|
||||
console.log("Settins binary");
|
||||
settings.xhr().responseType = 'arraybuffer';
|
||||
settings.processData = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
register_sound("message.received", "effects/message_received.wav");
|
||||
register_sound("message.send", "effects/message_send.wav");
|
||||
|
||||
return new Promise<void>(resolve => {
|
||||
$.ajax({
|
||||
url: "audio/speech/mapping.json",
|
||||
success: response => {
|
||||
for(const entry of response)
|
||||
register_sound(entry.key, "speech/" + entry.file);
|
||||
resolve();
|
||||
},
|
||||
error: () => {
|
||||
console.log("error!");
|
||||
console.dir(...arguments);
|
||||
},
|
||||
timeout: 5000,
|
||||
async: true,
|
||||
type: 'GET'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
|
||||
var bufView = new Uint16Array(buf);
|
||||
for (var i = 0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function play(sound: Sound, options?: {
|
||||
background_notification?: boolean
|
||||
}) {
|
||||
console.log("playback sound " + sound);
|
||||
const file: SpeechFile = speech_mapping[sound];
|
||||
if(!file) {
|
||||
console.warn("Missing sound " + sound);
|
||||
return;
|
||||
}
|
||||
if(file.not_supported) {
|
||||
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported isnt may timeouted
|
||||
return;
|
||||
file.not_supported = false;
|
||||
file.not_supported_timeout = undefined;
|
||||
}
|
||||
|
||||
const path = "audio/" + file.filename;
|
||||
const context = audio.player.context();
|
||||
const volume = options && options.background_notification ? .5 : 1;
|
||||
|
||||
if(context.decodeAudioData) {
|
||||
if(file.cached) {
|
||||
console.log("Using cached buffer: %o", file.cached);
|
||||
const player = context.createBufferSource();
|
||||
player.buffer = file.cached;
|
||||
player.start(0);
|
||||
|
||||
if(volume != 1 && context.createGain) {
|
||||
const gain = context.createGain();
|
||||
if(gain.gain.setValueAtTime)
|
||||
gain.gain.setValueAtTime(volume, 0);
|
||||
else
|
||||
gain.gain.value = volume;
|
||||
|
||||
player.connect(gain);
|
||||
gain.connect(audio.player.destination());
|
||||
} else
|
||||
player.connect(audio.player.destination());
|
||||
} else {
|
||||
const decode_data = buffer => {
|
||||
console.log(buffer);
|
||||
try {
|
||||
console.log("Decoding data");
|
||||
context.decodeAudioData(buffer, result => {
|
||||
console.log("Got decoded data");
|
||||
file.cached = result;
|
||||
play(sound, options);
|
||||
}, error => {
|
||||
console.error("Failed to decode audio data for " + sound);
|
||||
console.error(error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
}
|
||||
};
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', path, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
decode_data(this.response);
|
||||
} else {
|
||||
console.error("Failed to load audio file. (Response code " + this.status + ")");
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = error => {
|
||||
console.error("Failed to load audio file " + sound);
|
||||
console.error(error);
|
||||
file.not_supported = true;
|
||||
file.not_supported_timeout = Date.now() + 1000 * 60 * 60; //Try in 2min again!
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
} else {
|
||||
console.log("Replaying " + path);
|
||||
if(file.node) {
|
||||
file.node.currentTime = 0;
|
||||
file.node.play();
|
||||
} else {
|
||||
if(!warned) {
|
||||
warned = true;
|
||||
console.warn("Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!");
|
||||
}
|
||||
const container = $("#sounds");
|
||||
const node = $.spawn("audio").attr("src", path);
|
||||
node.appendTo(container);
|
||||
|
||||
file.node = node[0];
|
||||
file.node.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@ class ChannelEntry {
|
|||
|
||||
private _channelAlign: string;
|
||||
private _formatedChannelName: string;
|
||||
private _family_index: number = 0;
|
||||
|
||||
//HTML DOM elements
|
||||
private _tag_root: JQuery<HTMLElement>;
|
||||
|
@ -161,8 +162,10 @@ class ChannelEntry {
|
|||
|
||||
//Tag channel
|
||||
this._tag_channel = $.spawn("div");
|
||||
this._tag_channel.attr('channel-id', this.channelId);
|
||||
this._tag_channel.addClass("channelLine");
|
||||
this._tag_channel.addClass(this._channelAlign); //For left
|
||||
this._tag_channel.css('z-index', this._family_index);
|
||||
|
||||
let channelType = $.spawn("div");
|
||||
channelType.addClass("channel_only_normal channel_type icon client-channel_green_subscribed");
|
||||
|
@ -373,7 +376,9 @@ class ChannelEntry {
|
|||
}
|
||||
|
||||
perms[0]["cid"] = this.channelId;
|
||||
this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]);
|
||||
this.channelTree.client.serverConnection.sendCommand("channeladdperm", perms, ["continueonerror"]).then(() => {
|
||||
sound.play(Sound.CHANNEL_EDITED_SELF);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -383,7 +388,11 @@ class ChannelEntry {
|
|||
icon: "client-channel_delete",
|
||||
name: "Delete channel",
|
||||
invalidPermission: !flagDelete,
|
||||
callback: () => this.channelTree.client.serverConnection.sendCommand("channeldelete", {cid: this.channelId})
|
||||
callback: () => {
|
||||
this.channelTree.client.serverConnection.sendCommand("channeldelete", {cid: this.channelId}).then(() => {
|
||||
sound.play(Sound.CHANNEL_DELETED);
|
||||
})
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
|
@ -564,8 +573,10 @@ class ChannelEntry {
|
|||
this.updateChannelTypeIcon();
|
||||
});
|
||||
}).open();
|
||||
} else
|
||||
this.channelTree.client.getServerConnection().joinChannel(this, this._cachedPassword).catch(error => {
|
||||
} else if(this.channelTree.client.getClient().currentChannel() != this)
|
||||
this.channelTree.client.getServerConnection().joinChannel(this, this._cachedPassword).then(() => {
|
||||
sound.play(Sound.CHANNEL_JOINED);
|
||||
}).catch(error => {
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == 781) { //Invalid password
|
||||
this._cachedPassword = undefined;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
/// <reference path="modal/ModalServerGroupDialog.ts" />
|
||||
/// <reference path="client_move.ts" />
|
||||
|
||||
enum ClientType {
|
||||
CLIENT_VOICE,
|
||||
|
@ -80,7 +81,7 @@ class ClientEntry {
|
|||
return this._properties;
|
||||
}
|
||||
|
||||
currentChannel() { return this._channel; }
|
||||
currentChannel() : ChannelEntry { return this._channel; }
|
||||
clientNickName(){ return this.properties.client_nickname; }
|
||||
clientUid(){ return this.properties.client_unique_identifier; }
|
||||
clientId(){ return this._clientId; }
|
||||
|
@ -95,6 +96,11 @@ class ClientEntry {
|
|||
_this.channelTree.onSelect(_this);
|
||||
});
|
||||
|
||||
if(this.clientId() != this.channelTree.client.clientId && !(this instanceof MusicClientEntry))
|
||||
this.tag.dblclick(event => {
|
||||
this.chat(true).focus();
|
||||
});
|
||||
|
||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||
this.tag.on("contextmenu", function (event) {
|
||||
event.preventDefault();
|
||||
|
@ -105,6 +111,25 @@ class ClientEntry {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
this.tag.mousedown(event => {
|
||||
this.channelTree.client_mover.activate(this, target => {
|
||||
if(!target) return;
|
||||
if(target == this._channel) return;
|
||||
|
||||
const source = this._channel;
|
||||
const self = this.channelTree.client.getClient();
|
||||
this.channelTree.client.serverConnection.sendCommand("clientmove", {
|
||||
clid: this.clientId(),
|
||||
cid: target.getChannelId()
|
||||
}).then(event => {
|
||||
if(this.clientId() == this.channelTree.client.clientId)
|
||||
sound.play(Sound.CHANNEL_JOINED);
|
||||
else if(target !== source && target != self.currentChannel())
|
||||
sound.play(Sound.USER_MOVED);
|
||||
});
|
||||
}, event);
|
||||
});
|
||||
}
|
||||
|
||||
protected assignment_context() : ContextMenuEntry[] {
|
||||
|
@ -312,7 +337,9 @@ class ClientEntry {
|
|||
uid: this.properties.client_unique_identifier,
|
||||
banreason: data.reason,
|
||||
time: data.length
|
||||
}, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]);
|
||||
}, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]).then(() => {
|
||||
sound.play(Sound.USER_BANNED);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -780,6 +807,48 @@ class MusicClientEntry extends ClientEntry {
|
|||
},
|
||||
MenuEntry.HR(),
|
||||
...super.assignment_context(),
|
||||
MenuEntry.HR(),{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-move_client_to_own_channel",
|
||||
name: "Move client to your channel",
|
||||
callback: () => {
|
||||
this.channelTree.client.serverConnection.sendCommand("clientmove", {
|
||||
clid: this.clientId(),
|
||||
cid: this.channelTree.client.getClient().currentChannel().getChannelId()
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-kick_channel",
|
||||
name: "Kick client from channel",
|
||||
callback: () => {
|
||||
createInputModal("Kick client from channel", "Kick reason:<br>", text => true, result => {
|
||||
if(result) {
|
||||
console.log("Kicking client " + this.clientNickName() + " from channel with reason " + result);
|
||||
this.channelTree.client.serverConnection.sendCommand("clientkick", {
|
||||
clid: this.clientId(),
|
||||
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
||||
reasonmsg: result
|
||||
});
|
||||
|
||||
}
|
||||
}, { width: 400, maxLength: 255 }).open();
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-volume",
|
||||
name: "Change Volume",
|
||||
callback: () => {
|
||||
Modals.spawnChangeVolume(this.audioController.volume, volume => {
|
||||
settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this.audioController.volume = volume;
|
||||
if(globalClient.selectInfo.currentSelected == this)
|
||||
globalClient.selectInfo.update();
|
||||
});
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
name: "Delete bot",
|
||||
|
|
110
shared/js/ui/client_move.ts
Normal file
110
shared/js/ui/client_move.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/// <reference path="client.ts" />
|
||||
|
||||
class ClientMover {
|
||||
static readonly listener_root = $(document);
|
||||
static readonly move_element = $("#mouse-move");
|
||||
readonly channel_tree: ChannelTree;
|
||||
|
||||
selected_client: ClientEntry;
|
||||
|
||||
hovered_channel: HTMLDivElement;
|
||||
callback: (channel?: ChannelEntry) => any;
|
||||
|
||||
private _bound_finish;
|
||||
private _bound_move;
|
||||
private _active: boolean = false;
|
||||
|
||||
private origin_point: {x: number, y: number} = undefined;
|
||||
|
||||
constructor(tree: ChannelTree) {
|
||||
this.channel_tree = tree;
|
||||
}
|
||||
|
||||
activate(client: ClientEntry, callback: (channel?: ChannelEntry) => any, event: any) {
|
||||
this.finish_listener(undefined);
|
||||
|
||||
this.selected_client = client;
|
||||
this.callback = callback;
|
||||
console.log("Starting mouse move");
|
||||
|
||||
ClientMover.listener_root.on('mouseup', this._bound_finish = this.finish_listener.bind(this)).on('mousemove', this._bound_move = this.move_listener.bind(this));
|
||||
|
||||
{
|
||||
const content = ClientMover.move_element.find(".container");
|
||||
content.empty();
|
||||
content.append($.spawn("a").text(client.clientNickName()));
|
||||
}
|
||||
this.move_listener(event);
|
||||
}
|
||||
|
||||
private move_listener(event) {
|
||||
//console.log("Mouse move: " + event.pageX + " - " + event.pageY);
|
||||
if(!event.pageX || !event.pageY) return;
|
||||
if(!this.origin_point)
|
||||
this.origin_point = {x: event.pageX, y: event.pageY};
|
||||
|
||||
ClientMover.move_element.css({
|
||||
"top": (event.pageY - 1) + "px",
|
||||
"left": (event.pageX + 10) + "px"
|
||||
});
|
||||
|
||||
if(!this._active) {
|
||||
const d_x = this.origin_point.x - event.pageX;
|
||||
const d_y = this.origin_point.y - event.pageY;
|
||||
this._active = Math.sqrt(d_x * d_x + d_y * d_y) > 5 * 5;
|
||||
|
||||
if(this._active)
|
||||
ClientMover.move_element.show();
|
||||
}
|
||||
|
||||
const elements = document.elementsFromPoint(event.pageX, event.pageY);
|
||||
while(elements.length > 0) {
|
||||
if(elements[0].classList.contains("channelLine")) break;
|
||||
elements.pop_front();
|
||||
}
|
||||
|
||||
if(this.hovered_channel) {
|
||||
this.hovered_channel.classList.remove("move-selected");
|
||||
this.hovered_channel = undefined;
|
||||
}
|
||||
if(elements.length > 0) {
|
||||
elements[0].classList.add("move-selected");
|
||||
this.hovered_channel = elements[0] as HTMLDivElement;
|
||||
}
|
||||
}
|
||||
|
||||
private finish_listener(event) {
|
||||
ClientMover.move_element.hide();
|
||||
|
||||
const channel_id = this.hovered_channel ? parseInt(this.hovered_channel.getAttribute("channel-id")) : 0;
|
||||
ClientMover.listener_root.unbind('mouseleave', this._bound_finish);
|
||||
ClientMover.listener_root.unbind('mouseup', this._bound_finish);
|
||||
ClientMover.listener_root.unbind('mousemove', this._bound_move);
|
||||
if(this.hovered_channel) {
|
||||
this.hovered_channel.classList.remove("move-selected");
|
||||
this.hovered_channel = undefined;
|
||||
}
|
||||
|
||||
this.origin_point = undefined;
|
||||
if(!this._active) {
|
||||
this.selected_client = undefined;
|
||||
this.callback = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this._active = false;
|
||||
if(this.callback) {
|
||||
if(!channel_id)
|
||||
this.callback(undefined);
|
||||
else {
|
||||
this.callback(this.channel_tree.findChannel(channel_id));
|
||||
}
|
||||
this.callback = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.callback = undefined;
|
||||
this.finish_listener(undefined);
|
||||
}
|
||||
}
|
|
@ -232,6 +232,7 @@ class ControlBar {
|
|||
private onDisconnect() {
|
||||
this.handle.handleDisconnect(DisconnectReason.REQUESTED); //TODO message?
|
||||
this.update_connection_state();
|
||||
sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||
}
|
||||
|
||||
private on_token_use() {
|
||||
|
|
|
@ -530,10 +530,64 @@ class MusicInfoManager extends ClientInfoManager {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
const player = properties["music_player"];
|
||||
const player_transformer = $.spawn("div").append(player);
|
||||
player_transformer.css({
|
||||
'display': 'block',
|
||||
//'width': "100%",
|
||||
'height': '100%'
|
||||
});
|
||||
properties["music_player"] = player_transformer;
|
||||
}
|
||||
|
||||
let rendered = $("#tmpl_selected_music").renderTag([properties]);
|
||||
html_tag.append(rendered);
|
||||
|
||||
{
|
||||
const player = properties["music_player"] as JQuery;
|
||||
const player_width = 400; //player.width();
|
||||
const player_height = 400; //player.height();
|
||||
|
||||
const parent = player.parent();
|
||||
parent.css({
|
||||
'flex-grow': 1,
|
||||
'display': 'flex',
|
||||
'flex-direction': 'row',
|
||||
'justify-content': 'space-around',
|
||||
});
|
||||
|
||||
const padding = 14;
|
||||
const scale_x = Math.min((parent.width() - padding) / player_width, 1.5);
|
||||
const scale_y = Math.min((parent.height() - padding) / player_height, 1.5);
|
||||
let scale = Math.min(scale_x, scale_y);
|
||||
|
||||
let translate_x = 50, translate_y = 50;
|
||||
if(scale_x == scale_y && scale_x == scale) {
|
||||
//Equal scale
|
||||
} else if(scale_x == scale) {
|
||||
//We scale on the x-Axis
|
||||
//We have to adjust the y-Axis
|
||||
} else {
|
||||
//We scale on the y-Axis
|
||||
//We have to adjust the x-Axis
|
||||
|
||||
}
|
||||
//1 => 0 | 0
|
||||
//1.5 => 0 | 25
|
||||
//0.5 => 0 | -25
|
||||
//const translate_x = scale_x != scale ? 0 : undefined || 50 - (50 * ((parent.width() - padding) / player_width));
|
||||
//const translate_y = scale_y != scale || scale_y > 1 ? 0 : undefined || 50 - (50 * ((parent.height() - padding) / player_height));
|
||||
const transform = ("translate(0%, " + (scale * 50 - 50) + "%) scale(" + scale.toPrecision(2) + ")");
|
||||
|
||||
console.log("Parents: %o | %o", parent.width(), parent.height());
|
||||
console.log("Player: %o | %o", player_width, player_height);
|
||||
console.log("Scale: %f => translate: %o | %o", scale, translate_x, translate_y);
|
||||
player.css({
|
||||
transform: transform
|
||||
});
|
||||
console.log("Transform: " + transform);
|
||||
}
|
||||
}
|
||||
|
||||
available<V>(object: V): boolean {
|
||||
|
|
|
@ -6,7 +6,10 @@ namespace Modals {
|
|||
const modal = createModal({
|
||||
header: channel ? "Edit channel" : "Create channel",
|
||||
body: () => {
|
||||
let template = $("#tmpl_channel_edit").renderTag(channel ? channel.properties : new ChannelProperties());
|
||||
let template = $("#tmpl_channel_edit").renderTag(channel ? channel.properties : {
|
||||
channel_flag_maxfamilyclients_unlimited: true,
|
||||
channel_flag_maxclients_unlimited: true
|
||||
} as ChannelProperties);
|
||||
template = $.spawn("div").append(template);
|
||||
return template.tabify();
|
||||
},
|
||||
|
@ -62,6 +65,8 @@ namespace Modals {
|
|||
});
|
||||
|
||||
modal.open();
|
||||
if(!channel)
|
||||
modal.htmlTag.find(".channel_name").focus();
|
||||
}
|
||||
|
||||
function applyGeneralListener(properties: ChannelProperties, tag: JQuery, button: JQuery, create: boolean) {
|
||||
|
@ -71,7 +76,7 @@ namespace Modals {
|
|||
else button.attr("disabled", "true");
|
||||
};
|
||||
|
||||
tag.find(".channel_name").change(function (this: HTMLInputElement) {
|
||||
tag.find(".channel_name").on('change keyup', function (this: HTMLInputElement) {
|
||||
properties.channel_name = this.value;
|
||||
|
||||
$(this).removeClass("input_error");
|
||||
|
@ -101,8 +106,10 @@ namespace Modals {
|
|||
}).prop("disabled", !globalClient.permissions.neededPermission(create ? PermissionType.B_CHANNEL_CREATE_WITH_DESCRIPTION : PermissionType.B_CHANNEL_MODIFY_DESCRIPTION).granted(1));
|
||||
|
||||
if(create) {
|
||||
tag.find(".channel_name").trigger("change");
|
||||
tag.find(".channel_password").trigger('change');
|
||||
setTimeout(() => {
|
||||
tag.find(".channel_name").trigger("change");
|
||||
tag.find(".channel_password").trigger('change');
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
shared/js/ui/modal/ModalPoke.ts
Normal file
30
shared/js/ui/modal/ModalPoke.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnPoke(invoker: {
|
||||
name: string,
|
||||
id: number,
|
||||
unique_id: string
|
||||
}, message) {
|
||||
let modal;
|
||||
modal = createModal({
|
||||
header: "You have been poked!",
|
||||
body: () => {
|
||||
let template = $("#tmpl_poke_popup").renderTag({
|
||||
"invoker": ClientEntry.chatTag(invoker.id, invoker.name, invoker.unique_id, true),
|
||||
"message": message
|
||||
});
|
||||
template = $.spawn("div").append(template);
|
||||
|
||||
template.find(".button-close").on('click', event => modal.close());
|
||||
|
||||
return template;
|
||||
},
|
||||
footer: undefined,
|
||||
width: 750
|
||||
});
|
||||
modal.open();
|
||||
}
|
||||
}
|
|
@ -130,7 +130,9 @@ class ServerEntry {
|
|||
log.info(LogCategory.SERVER, "Changing server properties %o", properties);
|
||||
console.log("Changed properties: %o", properties);
|
||||
if (properties)
|
||||
this.channelTree.client.serverConnection.sendCommand("serveredit", properties);
|
||||
this.channelTree.client.serverConnection.sendCommand("serveredit", properties).then(() => {
|
||||
sound.play(Sound.SERVER_EDITED_SELF);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,9 +13,14 @@ class ChannelTree {
|
|||
channels: ChannelEntry[];
|
||||
clients: ClientEntry[];
|
||||
|
||||
readonly client_mover: ClientMover;
|
||||
|
||||
constructor(client, htmlTree) {
|
||||
document.addEventListener("touchstart", function(){}, true);
|
||||
|
||||
this.client = client;
|
||||
this.htmlTree = htmlTree;
|
||||
this.client_mover = new ClientMover(this);
|
||||
this.reset();
|
||||
|
||||
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
|
||||
|
@ -122,6 +127,7 @@ class ChannelTree {
|
|||
elm.after(entry);
|
||||
|
||||
channel.adjustSize(true);
|
||||
|
||||
channel.initializeListener();
|
||||
}
|
||||
|
||||
|
@ -164,8 +170,12 @@ class ChannelTree {
|
|||
}
|
||||
|
||||
|
||||
if(oldParent) oldParent.adjustSize();
|
||||
if(channel) channel.adjustSize();
|
||||
if(oldParent) {
|
||||
oldParent.adjustSize();
|
||||
}
|
||||
if(channel) {
|
||||
channel.adjustSize();
|
||||
}
|
||||
}
|
||||
|
||||
deleteClient(client: ClientEntry) {
|
||||
|
@ -306,6 +316,7 @@ class ChannelTree {
|
|||
return new Promise<ChannelEntry>(resolve => { resolve(channel); })
|
||||
}).then(channel => {
|
||||
chat.serverChat().appendMessage("Channel {} successfully created!", true, channel.createChatTag());
|
||||
sound.play(Sound.CHANNEL_CREATED);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ class AudioController {
|
|||
static initializeAudioController() {
|
||||
if(!audio.player.initialize())
|
||||
console.warn("Failed to initialize audio controller!");
|
||||
sound.initialize().then(() => {
|
||||
console.log("Sounds initialitzed");
|
||||
});
|
||||
//this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue