A mass of changes :D
This commit is contained in:
parent
ca1accad57
commit
e357cf5aa6
92 changed files with 275276 additions and 99285 deletions
.idea
WSSTest.htmlWSSTest.tsanimates
asm
css
index.phpjs
Identity.tsInfoBar.jsInfoBar.js.mapInfoBar.tschat.jschat.js.mapchat.tsclient.jsclient.js.mapclient.ts
templates.htmlcodec
connection.jsconnection.js.mapconnection.tscontextMenu.jscontextMenu.js.mapcontextMenu.tsload.tsmain.jsmain.js.mapmain.tsproto.jsproto.js.mapproto.tssettings.jssettings.js.mapsettings.tsui
ControlBar.jsControlBar.js.mapControlBar.tschannel.jschannel.js.mapchannel.tsclient.jsclient.js.mapclient.ts
modal
ModalChangeVolume.tsModalConnect.jsModalConnect.js.mapModalConnect.tsModalSettings.jsModalSettings.js.mapModalSettings.ts
server.jsserver.js.mapserver.tsvoice
test
tsconfig.jsontsconfig
6
.idea/Web-Client.iml
generated
6
.idea/Web-Client.iml
generated
|
@ -1,7 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/asm/generated" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/enviroment" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/generated" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="adapter-latest" level="application" />
|
||||
|
|
1227
.idea/workspace.xml
generated
1227
.idea/workspace.xml
generated
File diff suppressed because it is too large
Load diff
11
WSSTest.html
11
WSSTest.html
|
@ -1,11 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WSSTest</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <iframe src="https://localhost:19974"></iframe> -->
|
||||
<script src="WSSTest.js"></script>
|
||||
</body>
|
||||
</html>
|
23
WSSTest.ts
23
WSSTest.ts
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
let oldError = console.error;
|
||||
console.error = function () {
|
||||
oldError("Got error:");
|
||||
oldError(arguments);
|
||||
};
|
||||
|
||||
let wss = new WebSocket("wss:localhost:4433");
|
||||
wss.onclose = ev => console.log(ev);
|
||||
wss.onerror = ev => console.log(ev);
|
||||
wss.onmessage = ev => console.log(ev);
|
||||
wss.onopen = ev => console.log(ev);
|
||||
|
||||
setTimeout(() => {
|
||||
//document.location.assign("https://localhost:44330");
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
let win = window.open("https://localhost:44330");
|
||||
if(win) {
|
||||
win.close();
|
||||
}
|
||||
}, 1000);
|
||||
*/
|
27
animates/A.html
Normal file
27
animates/A.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=windows-1251">
|
||||
<script type="text/javascript" src="https://adsterra.com/libs/header_index/jquery-2.2.4.min.js"></script>
|
||||
<script type="text/javascript" src="https://adsterra.com/libs/header_index/pixi.min.js"></script>
|
||||
<script type="text/javascript" src="https://adsterra.com/libs/header_index/animationFrame.js?ver=201703051115"></script>
|
||||
<script type="text/javascript" src="https://adsterra.com/libs/header_index/nodes.js?ver=201703051115"></script>
|
||||
</head>
|
||||
<style>body {
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
}</style>
|
||||
<body>
|
||||
<header>
|
||||
<script>
|
||||
Nodes.multipleInit([{
|
||||
"post_name": "ADSTERRA",
|
||||
"drawnImage": "TeaSpeak32b.png", //https://orig00.deviantart.net/45aa/f/2015/323/9/9/letter_a_by_hillygon-d9h8c6a.jpg | TeaSpeak32b.png
|
||||
"linkTitle": "ADSTERRA",
|
||||
"particleDensity": "5",
|
||||
"particleWidth": "0.4",
|
||||
"particleHeight": "0.4"
|
||||
}]);
|
||||
</script>
|
||||
</header>
|
||||
</body>
|
||||
</html>
|
|
@ -3,16 +3,16 @@ project(TeaWeb-Native)
|
|||
set(CMAKE_CXX_COMPILER "emcc")
|
||||
set(CMAKE_C_COMPILER "emcc")
|
||||
set(CMAKE_C_LINK_EXECUTABLE "emcc")
|
||||
set(CMAKE_CXX_FLAGS "-s ASSERTIONS=2") #-s WASM=1
|
||||
set(CMAKE_CXX_FLAGS "-s ASSERTIONS=2 -s ALLOW_MEMORY_GROWTH=1") #-s WASM=1 -O2
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") #
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\", \"Pointer_stringify\"]'") #
|
||||
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
|
||||
|
||||
add_definitions(-DLTM_DESC)
|
||||
include_directories(libraries/opus/include/)
|
||||
include_directories(libraries/tommath/)
|
||||
include_directories(libraries/tomcrypt/src/headers)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/generated/")
|
||||
add_executable(TeaWeb-Native.js src/WebASMTest.cpp)
|
||||
target_link_libraries(TeaWeb-Native.js ${CMAKE_CURRENT_SOURCE_DIR}/libs/opus/.libs/libopus.a)
|
||||
#Adding directories to PATH:
|
||||
#PATH += /home/wolverindev/wgit/emscripten-sdk
|
||||
|
||||
#Setting environment variables:
|
||||
#EMSDK = /home/wolverindev/wgit/emscripten-sdk
|
||||
#EM_CONFIG = /home/wolverindev/.emscripten
|
||||
add_executable(TeaWeb-Native.js src/CodecOpus.cpp src/TeamSpeakIdentity.cpp src/Identity.cpp src/INIReader.h)
|
||||
target_link_libraries(TeaWeb-Native.js ${CMAKE_CURRENT_SOURCE_DIR}/libraries/opus/.libs/libopus.a ${CMAKE_CURRENT_SOURCE_DIR}/libraries/tomcrypt/libtomcrypt.a ${CMAKE_CURRENT_SOURCE_DIR}/libraries/tommath/build/libtommathStatic.a)
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
Subproject commit 655cc54c564b84ef2827f0b2152ce3811046201e
|
|
@ -1,4 +1,4 @@
|
|||
#include <libs/opus/include/opus.h>
|
||||
#include <opus.h>
|
||||
#include <emscripten.h>
|
||||
#include <string>
|
||||
|
||||
|
@ -9,6 +9,7 @@ extern "C" {
|
|||
OpusDecoder* decoder = nullptr;
|
||||
|
||||
size_t channelCount = 1;
|
||||
int opusType = OPUS_APPLICATION_AUDIO;
|
||||
};
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
|
@ -17,9 +18,8 @@ extern "C" {
|
|||
auto codec = new OpusHandle{};
|
||||
int error = 0;
|
||||
codec->decoder = opus_decoder_create(48000, channelCount, &error);
|
||||
printf("Status %d\n", error);
|
||||
codec->encoder = opus_encoder_create(48000, channelCount, type, &error);
|
||||
printf("Status %d Channel %d\n", error, codec->channelCount);
|
||||
codec->opusType = type;
|
||||
return codec;
|
||||
}
|
||||
|
||||
|
@ -52,11 +52,22 @@ extern "C" {
|
|||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int codec_opus_changeApplication(OpusHandle* handle, int type) {
|
||||
handle->opusType = type;
|
||||
if(type != OPUS_APPLICATION_VOIP && type != OPUS_APPLICATION_AUDIO && type != OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
return 1;
|
||||
return opus_encoder_ctl(handle->encoder, OPUS_SET_APPLICATION(type));
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int codec_opus_reset(OpusHandle* handle) {
|
||||
if(handle->encoder) opus_encoder_destroy(handle->encoder);
|
||||
if(handle->decoder) opus_decoder_destroy(handle->decoder);
|
||||
|
||||
int error = 0;
|
||||
handle->decoder = opus_decoder_create(48000, handle->channelCount, &error);
|
||||
handle->encoder = opus_encoder_create(48000, handle->channelCount, handle->opusType, &error);
|
||||
return 1;
|
||||
}
|
||||
/*
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
||||
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
458
asm/src/INIReader.h
Normal file
458
asm/src/INIReader.h
Normal file
|
@ -0,0 +1,458 @@
|
|||
// Read an INI file into easy-to-access name/value pairs.
|
||||
|
||||
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
|
||||
// Go to the project home page for more info:
|
||||
//
|
||||
// https://github.com/benhoyt/inih
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value);
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||
|
||||
inline int ini_parse_message(const std::string& message, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||
#ifndef INI_ALLOW_BOM
|
||||
#define INI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack, zero to use heap (malloc/free). */
|
||||
#ifndef INI_USE_STACK
|
||||
#define INI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Stop parsing on first error (default is to keep parsing). */
|
||||
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||
#define INI_STOP_ON_FIRST_ERROR 0
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file. */
|
||||
#ifndef INI_MAX_LINE
|
||||
#define INI_MAX_LINE 200
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <sstream>
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
inline static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p)))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
inline static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace((unsigned char)(*s)))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to null at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
inline static char* find_chars_or_comment(const char* s, const char* chars)
|
||||
{
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
inline static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
strncpy(dest, src, size);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if INI_USE_STACK
|
||||
char line[INI_MAX_LINE];
|
||||
#else
|
||||
char* line;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !INI_USE_STACK
|
||||
line = (char*)malloc(INI_MAX_LINE);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, INI_MAX_LINE, stream) != NULL) {
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if INI_ALLOW_BOM
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (*start == ';' || *start == '#') {
|
||||
/* Per Python configparser, allow both ; and # comments at the
|
||||
start of a line */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(start, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
rstrip(start);
|
||||
#endif
|
||||
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!handler(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = lskip(end + 1);
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
#endif
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!handler(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
|
||||
#if INI_STOP_ON_FIRST_ERROR
|
||||
if (error)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !INI_USE_STACK
|
||||
free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
inline int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||
{
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
inline int ini_parse_message(const std::string& message, ini_handler handler, void* user)
|
||||
{
|
||||
std::istringstream stream(message);
|
||||
return ini_parse_stream((ini_reader) [](char* str, int num, void* ptrStream) {
|
||||
auto stream = (std::istringstream*) ptrStream;
|
||||
stream->getline(str, num);
|
||||
return !!*stream ? str : nullptr;
|
||||
}, &stream, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
inline int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif /* __INI_H__ */
|
||||
|
||||
|
||||
#ifndef __INIREADER_H__
|
||||
#define __INIREADER_H__
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
|
||||
// for simplicity here rather than speed, but it should be pretty decent.)
|
||||
class INIReader
|
||||
{
|
||||
public:
|
||||
// Empty Constructor
|
||||
INIReader() {};
|
||||
|
||||
// Construct INIReader and parse given filename. See ini.h for more info
|
||||
// about the parsing.
|
||||
INIReader(std::string filename, bool raw = false);
|
||||
|
||||
// Return the result of ini_parse(), i.e., 0 on success, line number of
|
||||
// first error on parse error, or -1 on file open error.
|
||||
int ParseError() const;
|
||||
|
||||
// Return the list of sections found in ini file
|
||||
std::set<std::string> Sections();
|
||||
|
||||
// Get a string value from INI file, returning default_value if not found.
|
||||
std::string Get(std::string section, std::string name,
|
||||
std::string default_value);
|
||||
|
||||
// Get an integer (long) value from INI file, returning default_value if
|
||||
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
|
||||
long GetInteger(std::string section, std::string name, long default_value);
|
||||
|
||||
// Get a real (floating point double) value from INI file, returning
|
||||
// default_value if not found or not a valid floating point value
|
||||
// according to strtod().
|
||||
double GetReal(std::string section, std::string name, double default_value);
|
||||
|
||||
// Get a boolean value from INI file, returning default_value if not found or if
|
||||
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
|
||||
// and valid false values are "false", "no", "off", "0" (not case sensitive).
|
||||
bool GetBoolean(std::string section, std::string name, bool default_value);
|
||||
|
||||
private:
|
||||
int _error;
|
||||
std::map<std::string, std::string> _values;
|
||||
std::set<std::string> _sections;
|
||||
static std::string MakeKey(std::string section, std::string name);
|
||||
static int ValueHandler(void* user, const char* section, const char* name,
|
||||
const char* value);
|
||||
};
|
||||
|
||||
#endif // __INIREADER_H__
|
||||
|
||||
|
||||
#ifndef __INIREADER__
|
||||
#define __INIREADER__
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
|
||||
using std::string;
|
||||
|
||||
inline INIReader::INIReader(string data, bool raw)
|
||||
{
|
||||
if(raw)
|
||||
_error = ini_parse_message(data, ValueHandler, this);
|
||||
else
|
||||
_error = ini_parse(data.c_str(), ValueHandler, this);
|
||||
}
|
||||
|
||||
inline int INIReader::ParseError() const
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
|
||||
inline std::set<string> INIReader::Sections()
|
||||
{
|
||||
return _sections;
|
||||
}
|
||||
|
||||
inline string INIReader::Get(string section, string name, string default_value)
|
||||
{
|
||||
string key = MakeKey(section, name);
|
||||
return _values.count(key) ? _values[key] : default_value;
|
||||
}
|
||||
|
||||
inline long INIReader::GetInteger(string section, string name, long default_value)
|
||||
{
|
||||
string valstr = Get(section, name, "");
|
||||
const char* value = valstr.c_str();
|
||||
char* end;
|
||||
// This parses "1234" (decimal) and also "0x4D2" (hex)
|
||||
long n = strtol(value, &end, 0);
|
||||
return end > value ? n : default_value;
|
||||
}
|
||||
|
||||
inline double INIReader::GetReal(string section, string name, double default_value)
|
||||
{
|
||||
string valstr = Get(section, name, "");
|
||||
const char* value = valstr.c_str();
|
||||
char* end;
|
||||
double n = strtod(value, &end);
|
||||
return end > value ? n : default_value;
|
||||
}
|
||||
|
||||
inline bool INIReader::GetBoolean(string section, string name, bool default_value)
|
||||
{
|
||||
string valstr = Get(section, name, "");
|
||||
// Convert to lower case to make string comparisons case-insensitive
|
||||
std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
|
||||
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
|
||||
return true;
|
||||
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
|
||||
return false;
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
inline string INIReader::MakeKey(string section, string name)
|
||||
{
|
||||
string key = section + "=" + name;
|
||||
// Convert to lower case to make section/name lookups case-insensitive
|
||||
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
|
||||
return key;
|
||||
}
|
||||
|
||||
inline int INIReader::ValueHandler(void* user, const char* section, const char* name,
|
||||
const char* value)
|
||||
{
|
||||
INIReader* reader = (INIReader*)user;
|
||||
string key = MakeKey(section, name);
|
||||
if (reader->_values[key].size() > 0)
|
||||
reader->_values[key] += "\n";
|
||||
reader->_values[key] += value;
|
||||
reader->_sections.insert(section);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif // __INIREADER__
|
283
asm/src/Identity.cpp
Normal file
283
asm/src/Identity.cpp
Normal file
|
@ -0,0 +1,283 @@
|
|||
#include "Identity.h"
|
||||
#include <iostream>
|
||||
#include "base64.h"
|
||||
|
||||
#define SHA_DIGEST_LENGTH 20
|
||||
#define ECC_TYPE_INDEX 5
|
||||
|
||||
static const char *TSKEY =
|
||||
"b9dfaa7bee6ac57ac7b65f1094a1c155"
|
||||
"e747327bc2fe5d51c512023fe54a2802"
|
||||
"01004e90ad1daaae1075d53b7d571c30"
|
||||
"e063b5a62a4a017bb394833aa0983e6e";
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline int SHA1(const char* input, size_t length, char* result) {
|
||||
hash_state ctx = {};
|
||||
if (sha1_init(&ctx) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_process(&ctx, (uint8_t*) input, length) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_done(&ctx, (uint8_t*) result) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decriptIdentity(char *data, uint32_t length) {
|
||||
int dataSize = std::min((uint32_t) 100, length);
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
data[i] ^= TSKEY[i];
|
||||
}
|
||||
|
||||
char hash[SHA_DIGEST_LENGTH];
|
||||
//if(SHA1(data + 20, strlen(data + 20), hash) < 0) return -1;
|
||||
|
||||
hash_state ctx = {};
|
||||
if (sha1_init(&ctx) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_process(&ctx, (uint8_t*)data + 20, strlen(data + 20)) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_done(&ctx, (uint8_t*)hash) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
data[i] ^= hash[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int encriptIdentity(char *data, uint32_t length) {
|
||||
char hash[SHA_DIGEST_LENGTH];
|
||||
//if(SHA1(data, length, hash) < 0) return -1;
|
||||
|
||||
hash_state ctx;
|
||||
if (sha1_init(&ctx) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_process(&ctx, (uint8_t*)data + 20, strlen(data + 20)) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_done(&ctx, (uint8_t*)hash) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
data[i] ^= hash[i];
|
||||
}
|
||||
|
||||
int dataSize = std::min((uint32_t) 100, length);
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
data[i] ^= TSKEY[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
Identity* Identity::createNew() {
|
||||
auto result = new Identity();
|
||||
|
||||
prng_state rndState = {};
|
||||
memset(&rndState, 0, sizeof(prng_state));
|
||||
int err;
|
||||
|
||||
result->keyPair = new ecc_key;
|
||||
|
||||
//cout << " -> " << find_prng("sprng") << endl;
|
||||
if((err = ecc_make_key_ex(&rndState, find_prng("sprng"), result->keyPair, <c_ecc_sets[ECC_TYPE_INDEX])) != CRYPT_OK){
|
||||
printf("Cant create a new identity (Keygen)\n");
|
||||
printf("Message: %s\n", error_to_string(err));
|
||||
delete result;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Identity* Identity::parse(const std::string& data, std::string& error) {
|
||||
int vindex = data.find('V');
|
||||
if(vindex <= 0) {
|
||||
error = "Invalid structure";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto slevel = data.substr(0, vindex);
|
||||
if(slevel.find_first_not_of("0123456789") != std::string::npos) {
|
||||
error = "Invalid offset (" + slevel + ")";
|
||||
return nullptr;
|
||||
}
|
||||
mp_int keyOffset{};
|
||||
mp_init(&keyOffset);
|
||||
mp_read_radix(&keyOffset, slevel.data(), 10);
|
||||
|
||||
auto keyData = data.substr(vindex + 1);
|
||||
keyData = base64::decode(keyData);
|
||||
if(encriptIdentity(&keyData[0], keyData.length()) < 0) {
|
||||
error = "Could not decrypt key";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto identity = new Identity(base64::decode(keyData), keyOffset, keyOffset);
|
||||
if(!identity->keyPair) {
|
||||
error = "Could not load key";
|
||||
delete identity;
|
||||
return nullptr;
|
||||
}
|
||||
printf("X: %s | %s\n", slevel.c_str(), identity->lastValidKeyOffsetString().c_str());
|
||||
return identity;
|
||||
}
|
||||
|
||||
Identity::Identity(const std::string& asnStruct, mp_int keyOffset, mp_int lastCheckedOffset) {
|
||||
this->keyOffset = keyOffset;
|
||||
this->lastCheckedOffset = lastCheckedOffset;
|
||||
importKey(asnStruct);
|
||||
|
||||
mp_init_copy(&this->keyOffset, &keyOffset);
|
||||
mp_init_copy(&this->lastCheckedOffset, &lastCheckedOffset);
|
||||
}
|
||||
|
||||
Identity::Identity() {
|
||||
mp_init_multi(&this->keyOffset, &this->lastCheckedOffset, nullptr);
|
||||
this->keyPair = nullptr;
|
||||
}
|
||||
|
||||
Identity::~Identity() {
|
||||
delete this->keyPair;
|
||||
this->keyPair = nullptr;
|
||||
|
||||
mp_clear_multi(&this->keyOffset, &this->lastCheckedOffset, nullptr);
|
||||
}
|
||||
|
||||
void Identity::importKey(std::string asnStruct) {
|
||||
this->keyPair = new ecc_key;
|
||||
int err;
|
||||
if((err = ecc_import_ex((const unsigned char *) asnStruct.data(), asnStruct.length(), this->keyPair, <c_ecc_sets[ECC_TYPE_INDEX])) != CRYPT_OK){
|
||||
delete this->keyPair;
|
||||
this->keyPair = nullptr;
|
||||
|
||||
printf("Cant import identity from asn structure\n");
|
||||
printf("Message: %s\n", error_to_string(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Identity::exportIdentity() {
|
||||
std::string data = privateKey();
|
||||
decriptIdentity((char *) data.data(), data.length());
|
||||
return this->lastValidKeyOffsetString() + "V" + base64_encode(data);
|
||||
}
|
||||
|
||||
std::string Identity::uid() {
|
||||
char buffer[SHA_DIGEST_LENGTH];
|
||||
auto key = this->publicKey();
|
||||
SHA1(key.data(), key.length(), buffer);
|
||||
return base64::encode(buffer, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
inline string hex(string input, char beg, char end){
|
||||
assert(end - beg == 16);
|
||||
|
||||
int len = input.length() * 2;
|
||||
char output[len];
|
||||
int idx = 0;
|
||||
for(int index = 0; index < input.length(); index++){
|
||||
char elm = input[index];
|
||||
output[idx++] = static_cast<char>(beg + ((elm >> 4) & 0x0F));
|
||||
output[idx++] = static_cast<char>(beg + ((elm & 0x0F) >> 0));
|
||||
}
|
||||
|
||||
return string(output, len);
|
||||
}
|
||||
|
||||
std::string Identity::avatarId() {
|
||||
return hex(base64::decode(this->uid()), 'a', 'q');
|
||||
}
|
||||
|
||||
std::string Identity::publicKey() {
|
||||
assert(this->keyPair);
|
||||
|
||||
ulong32 bufferLength = 1028;
|
||||
char buffer[bufferLength];
|
||||
ecc_export((unsigned char *) buffer, &bufferLength, PK_PUBLIC, this->keyPair);
|
||||
|
||||
return base64_encode(std::string(buffer, bufferLength));
|
||||
}
|
||||
|
||||
std::string Identity::privateKey() {
|
||||
assert(this->keyPair);
|
||||
|
||||
ulong32 bufferLength = 1028;
|
||||
char buffer[bufferLength];
|
||||
ecc_export((unsigned char *) buffer, &bufferLength, PK_PRIVATE, this->keyPair);
|
||||
|
||||
return base64_encode(std::string(buffer, bufferLength));
|
||||
}
|
||||
|
||||
ecc_key& Identity::getPrivateKey() {
|
||||
return *keyPair;
|
||||
}
|
||||
|
||||
#define MaxUlongString 20
|
||||
|
||||
bool Identity::improveSecurityLevel(int target) {
|
||||
auto publicKey = this->publicKey();
|
||||
char hashBuffer[publicKey.length() + MaxUlongString];
|
||||
memcpy(hashBuffer, publicKey.data(), publicKey.length());
|
||||
|
||||
if(mp_cmp(&this->lastCheckedOffset, &this->keyOffset) < 0)
|
||||
mp_copy(&this->keyOffset, &this->lastCheckedOffset);
|
||||
int best = getSecurityLevel(hashBuffer, publicKey.length(), this->lastCheckedOffset);
|
||||
while(true){
|
||||
if(best >= target) return true;
|
||||
|
||||
int currentLevel = getSecurityLevel(hashBuffer, publicKey.length(), this->lastCheckedOffset);
|
||||
if(currentLevel >= best){
|
||||
this->keyOffset = this->lastCheckedOffset;
|
||||
best = currentLevel;
|
||||
}
|
||||
mp_add_d(&this->lastCheckedOffset, 1, &this->lastCheckedOffset);
|
||||
}
|
||||
}
|
||||
|
||||
int Identity::getSecurityLevel() {
|
||||
auto length = publicKey().length();
|
||||
char hashBuffer[length + MaxUlongString];
|
||||
|
||||
auto publicKey = this->publicKey();
|
||||
memcpy(hashBuffer, publicKey.data(), publicKey.length());
|
||||
|
||||
return getSecurityLevel(hashBuffer, publicKey.length(), this->keyOffset);
|
||||
}
|
||||
|
||||
int Identity::getSecurityLevel(char *hashBuffer, size_t keyLength, mp_int offset) {
|
||||
char numBuffer[MaxUlongString];
|
||||
mp_todecimal(&offset, numBuffer);
|
||||
/*
|
||||
int numLen = 0;
|
||||
do {
|
||||
numBuffer[numLen] = '0' + (offset % 10);
|
||||
offset /= 10;
|
||||
numLen++;
|
||||
} while(offset > 0);
|
||||
for(int i = 0; i < numLen; i++)
|
||||
hashBuffer[keyLength + i] = numBuffer[numLen - (i + 1)];
|
||||
*/
|
||||
auto numLen = strlen(numBuffer);
|
||||
memcpy(&hashBuffer[keyLength], numBuffer, numLen);
|
||||
|
||||
char shaBuffer[SHA_DIGEST_LENGTH];
|
||||
SHA1(hashBuffer, keyLength + numLen, shaBuffer);
|
||||
|
||||
//Leading zero bits
|
||||
int zeroBits = 0;
|
||||
int i;
|
||||
for(i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
if(shaBuffer[i] == 0) zeroBits += 8;
|
||||
else break;
|
||||
if(i < SHA_DIGEST_LENGTH)
|
||||
for(int bit = 0; bit < 8; bit++)
|
||||
if((shaBuffer[i] & (1 << bit)) == 0) zeroBits++;
|
||||
else break;
|
||||
return zeroBits;
|
||||
}
|
||||
}
|
59
asm/src/Identity.h
Normal file
59
asm/src/Identity.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <tomcrypt.h>
|
||||
#include <string>
|
||||
#include <tommath.h>
|
||||
|
||||
namespace ts {
|
||||
class Identity {
|
||||
inline std::string toString(const mp_int& num) {
|
||||
char buffer[1024];
|
||||
mp_todecimal(&num, buffer);
|
||||
return std::string(buffer);
|
||||
}
|
||||
public:
|
||||
static Identity* createNew();
|
||||
static Identity* parse(const std::string&, std::string&);
|
||||
|
||||
Identity(const std::string& asnStruct,mp_int keyOffset,mp_int lastCheckedOffset);
|
||||
~Identity();
|
||||
|
||||
bool valid(){ return keyPair != nullptr; }
|
||||
|
||||
std::string uid();
|
||||
std::string avatarId();
|
||||
|
||||
std::string publicKey();
|
||||
std::string privateKey();
|
||||
std::string exportIdentity();
|
||||
|
||||
ecc_key* getKeyPair(){
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
ecc_key& getPrivateKey();
|
||||
|
||||
bool improveSecurityLevel(int target);
|
||||
int getSecurityLevel();
|
||||
|
||||
mp_int lastValidKeyOffset(){ return keyOffset; }
|
||||
mp_int lastTestedKeyOffset(){ return lastCheckedOffset; }
|
||||
|
||||
std::string lastValidKeyOffsetString(){
|
||||
return toString(this->lastValidKeyOffset());
|
||||
}
|
||||
|
||||
std::string lastTestedKeyOffsetString(){
|
||||
return toString(this->lastTestedKeyOffset());
|
||||
}
|
||||
private:
|
||||
Identity();
|
||||
|
||||
int getSecurityLevel(char* hasBuffer, size_t keyLength, mp_int offset);
|
||||
void importKey(std::string asn1);
|
||||
|
||||
ecc_key* keyPair = nullptr;
|
||||
mp_int keyOffset;
|
||||
mp_int lastCheckedOffset;
|
||||
};
|
||||
}
|
136
asm/src/TeamSpeakIdentity.cpp
Normal file
136
asm/src/TeamSpeakIdentity.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include <cstdio>
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <tomcrypt.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "Identity.h"
|
||||
#include "base64.h"
|
||||
#define INI_MAX_LINE 1024
|
||||
#include "INIReader.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace std;
|
||||
extern "C" {
|
||||
std::string errorMessage = "";
|
||||
|
||||
inline const char* cstr(const std::string& message) {
|
||||
auto buffer = (char*) malloc(message.length() + 1);
|
||||
cout << "Allocating at " << (void*) buffer << endl;
|
||||
buffer[message.length()] = '\0';
|
||||
memcpy(buffer, message.data(), message.length());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* last_error_message() {
|
||||
return cstr(errorMessage);
|
||||
};
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void destroy_string(const char* str) {
|
||||
cout << "Deallocating at " << (void*) str << endl;
|
||||
if(str) free((void *) str);
|
||||
};
|
||||
|
||||
inline void clear_error() { errorMessage = ""; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int tomcrypt_initialize() {
|
||||
init_LTM();
|
||||
if(register_prng(&sprng_desc) == -1) {
|
||||
printf("could not setup prng\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (register_cipher(&rijndael_desc) == -1) {
|
||||
printf("could not setup rijndael\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
cout << "Initialized!" << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void* parse_identity(const char* input) {
|
||||
cout << "Got messsage: " << input << endl;
|
||||
clear_error();
|
||||
return ts::Identity::parse(input, errorMessage);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void* parse_identity_file(const char* input) {
|
||||
clear_error();
|
||||
INIReader reader(input, true);
|
||||
if(reader.ParseError() != 0) {
|
||||
errorMessage = "Could not parse file " + to_string(reader.ParseError());
|
||||
return nullptr;
|
||||
}
|
||||
auto identity = reader.Get("Identity", "identity", "");
|
||||
if(!identity.empty() && identity[0] == '"')
|
||||
identity = identity.substr(1);
|
||||
if(!identity.empty() && identity.back() == '"')
|
||||
identity = identity.substr(0, identity.length() - 1);
|
||||
if(identity.empty()) {
|
||||
errorMessage = "Mussing identity value at Identity::identity";
|
||||
return nullptr;
|
||||
}
|
||||
return ts::Identity::parse(identity, errorMessage);
|
||||
}
|
||||
|
||||
#define IDENTITIEFY(_ret) \
|
||||
auto identity = dynamic_cast<ts::Identity*>((ts::Identity*) ptrIdentity); \
|
||||
if(!identity) { \
|
||||
errorMessage = "Invalid identity pointer!"; \
|
||||
return _ret; \
|
||||
}
|
||||
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void delete_identity(void* ptrIdentity) {
|
||||
IDENTITIEFY(;);
|
||||
delete identity;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* identity_security_level(void* ptrIdentity) {
|
||||
IDENTITIEFY("");
|
||||
return cstr(std::to_string(identity->getSecurityLevel()));
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* identity_export(void* ptrIdentity) {
|
||||
IDENTITIEFY("");
|
||||
return cstr(identity->exportIdentity());
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* identity_key_public(void* ptrIdentity) {
|
||||
IDENTITIEFY("");
|
||||
return cstr(identity->publicKey());
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* identity_uid(void* ptrIdentity) {
|
||||
IDENTITIEFY("");
|
||||
return cstr(identity->uid());
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char* identity_sign(void* ptrIdentity, const char* message, int length) {
|
||||
IDENTITIEFY("");
|
||||
|
||||
ulong32 bufferLength = 128;
|
||||
char signBuffer[bufferLength];
|
||||
|
||||
prng_state rndState = {};
|
||||
memset(&rndState, 0, sizeof(prng_state));
|
||||
|
||||
auto state = ecc_sign_hash((const unsigned char*) message, length, reinterpret_cast<unsigned char *>(signBuffer), &bufferLength, &rndState, find_prng("sprng"), identity->getKeyPair());
|
||||
if(state != CRYPT_OK) {
|
||||
errorMessage = "Could not sign message (" + std::string(error_to_string(state)) + "|" + std::to_string(state) + ")";
|
||||
return "";
|
||||
}
|
||||
|
||||
return cstr(base64::encode(signBuffer, bufferLength));
|
||||
}
|
||||
}
|
59
asm/src/base64.h
Normal file
59
asm/src/base64.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tomcrypt.h>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace base64 {
|
||||
/**
|
||||
* Encodes a given string in Base64
|
||||
* @param input The input string to Base64-encode
|
||||
* @param inputSize The size of the input to decode
|
||||
* @return A Base64-encoded version of the encoded string
|
||||
*/
|
||||
inline std::string encode(const char* input, const unsigned long inputSize) {
|
||||
auto outlen = static_cast<unsigned long>(inputSize + (inputSize / 3.0) + 16);
|
||||
auto outbuf = new unsigned char[outlen]; //Reserve output memory
|
||||
if(base64_encode((unsigned char*) input, inputSize, outbuf, &outlen) != CRYPT_OK){
|
||||
std::cerr << "Invalid input '" << input << "'" << std::endl;
|
||||
return "";
|
||||
}
|
||||
std::string ret((char*) outbuf, outlen);
|
||||
delete[] outbuf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a given string in Base64
|
||||
* @param input The input string to Base64-encode
|
||||
* @return A Base64-encoded version of the encoded string
|
||||
*/
|
||||
inline std::string encode(const std::string& input) { return encode(input.c_str(), input.size()); }
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a Base64-encoded string.
|
||||
* @param input The input string to decode
|
||||
* @return A string (binary) that represents the Base64-decoded data of the input
|
||||
*/
|
||||
inline std::string decode(const char* input, ulong32 size) {
|
||||
auto out = new unsigned char[size];
|
||||
if(base64_strict_decode((unsigned char*) input, size, out, &size) != CRYPT_OK){
|
||||
std::cerr << "Invalid base 64 string '" << input << "'" << std::endl;
|
||||
return "";
|
||||
}
|
||||
std::string ret((char*) out, size);
|
||||
delete[] out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a Base64-encoded string.
|
||||
* @param input The input string to decode
|
||||
* @return A string (binary) that represents the Base64-decoded data of the input
|
||||
*/
|
||||
inline std::string decode(const std::string& input) { return decode(input.c_str(), input.size()); }
|
||||
}
|
||||
inline std::string base64_encode(const char* input, const unsigned long inputSize) { return base64::encode(input, inputSize); }
|
||||
inline std::string base64_encode(const std::string& input) { return base64::encode(input.c_str(), input.size()); }
|
|
@ -4,6 +4,12 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.group_box {
|
||||
border: lightgray solid 1px;
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.contextMenu {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
|
@ -42,10 +48,15 @@
|
|||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.contextMenu li:hover {
|
||||
.contextMenu li:hover:not(.disabled) {
|
||||
background-color: #DEF;
|
||||
}
|
||||
|
||||
.contextMenu li.disabled {
|
||||
background-color: lightgray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.icon_loading {
|
||||
border: 2px solid #f3f3f3; /* Light grey */
|
||||
border-top: 2px solid #3498db; /* Blue */
|
||||
|
|
28
index.php
28
index.php
|
@ -1,20 +1,14 @@
|
|||
<?php
|
||||
$host = gethostname();
|
||||
$localhost = false;
|
||||
$testXF = true;
|
||||
|
||||
if($host == "WolverinDEV")
|
||||
$localhost = true;
|
||||
|
||||
if(file_exists('auth.php'))
|
||||
include_once('auth.php');
|
||||
else if(file_exists('auth/auth.php'))
|
||||
include_once('auth/auth.php');
|
||||
else die("Could not resolve auth.php!");
|
||||
if(!$localhost || $testXF){
|
||||
if(file_exists('auth.php'))
|
||||
include_once('auth.php');
|
||||
else if(file_exists('auth/auth.php'))
|
||||
include_once('auth/auth.php');
|
||||
else die("Could not resolve auth.php!");
|
||||
redirectOnInvalidSession();
|
||||
} else {
|
||||
function webPath() { return "auth/"; }
|
||||
}
|
||||
?>
|
||||
|
||||
|
@ -34,11 +28,13 @@
|
|||
<x-properties id="properties">
|
||||
<!-- <x-property key="" value=""/> -->
|
||||
<?php
|
||||
if($localhost) {
|
||||
echo '<x-property key="connect_default_host" value="localhost"/>';
|
||||
} else {
|
||||
echo '<x-property key="connect_default_host" value="ts.TeaSpeak.de"/>';
|
||||
function spawnProperty($name, $value) {
|
||||
echo '<x-property key="' . $name . '" value="' . urlencode($value) . '"/>';
|
||||
}
|
||||
|
||||
spawnProperty('connect_default_host', $localhost ? "localhost" : "ts.TeaSpeak.de");
|
||||
spawnProperty('forum_user_data', $_COOKIE[$GLOBALS["COOKIE_NAME_USER_DATA"]]);
|
||||
spawnProperty('forum_user_sign', $_COOKIE[$GLOBALS["COOKIE_NAME_USER_SIGN"]]);
|
||||
?>
|
||||
</x-properties>
|
||||
|
||||
|
@ -132,7 +128,7 @@
|
|||
<footer>
|
||||
<div class="container">
|
||||
<div style="align-self: center;">TeaSpeak Web client by WolverinDEV</div>
|
||||
<div style="align-self: center; position: fixed; right: 5px;"><a href="<?php echo webPath() . "auth.php?type=logout"; ?>">logout</a></div>
|
||||
<div style="align-self: center; position: fixed; right: 5px;"><a href="<?php echo authPath() . "auth.php?type=logout"; ?>">logout</a></div>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
116
js/Identity.ts
Normal file
116
js/Identity.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK
|
||||
}
|
||||
|
||||
namespace TSIdentityHelper {
|
||||
import Pointer_stringify = Module.Pointer_stringify;
|
||||
export let funcationParseIdentity: any;
|
||||
export let funcationParseIdentityByFile: any;
|
||||
export let funcationCalculateSecurityLevel: any;
|
||||
export let functionUid: any;
|
||||
export let funcationExportIdentity: any;
|
||||
export let funcationPublicKey: any;
|
||||
export let funcationSignMessage: any;
|
||||
|
||||
let functionLastError: any;
|
||||
let functionClearLastError: any;
|
||||
|
||||
let functionDestroyString: any;
|
||||
let functionDestroyIdentity: any;
|
||||
|
||||
export function setup() : boolean {
|
||||
functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
|
||||
functionLastError = Module.cwrap("last_error_message", null, ["string"]);
|
||||
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
|
||||
funcationParseIdentityByFile = Module.cwrap("parse_identity_file", "pointer", ["string"]);
|
||||
functionDestroyIdentity = Module.cwrap("delete_identity", null, ["pointer"]);
|
||||
|
||||
funcationCalculateSecurityLevel = Module.cwrap("identity_security_level", "pointer", ["pointer"]);
|
||||
funcationExportIdentity = Module.cwrap("identity_export", "pointer", ["pointer"]);
|
||||
funcationPublicKey = Module.cwrap("identity_key_public", "pointer", ["pointer"]);
|
||||
funcationSignMessage = Module.cwrap("identity_sign", "pointer", ["pointer", "string", "number"]);
|
||||
functionUid = Module.cwrap("identity_uid", "pointer", ["pointer"]);
|
||||
|
||||
return Module.cwrap("tomcrypt_initialize", "number", [])() == 0;
|
||||
}
|
||||
|
||||
export function last_error() : string {
|
||||
return unwarpString(functionLastError());
|
||||
}
|
||||
|
||||
export function unwarpString(str) : string {
|
||||
if(str == "") return "";
|
||||
let message: string = Pointer_stringify(str);
|
||||
functionDestroyString(str);
|
||||
return message;
|
||||
}
|
||||
|
||||
export function loadIdentity(key: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentity(key);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
|
||||
export function loadIdentityFromFileContains(contains: string) : TeamSpeakIdentity {
|
||||
let handle = funcationParseIdentityByFile(contains);
|
||||
if(!handle) return undefined;
|
||||
return new TeamSpeakIdentity(handle, "TeaWeb user");
|
||||
}
|
||||
}
|
||||
|
||||
interface Identity {
|
||||
name() : string;
|
||||
uid() : string;
|
||||
type() : IdentitifyType;
|
||||
}
|
||||
|
||||
class TeamSpeakIdentity implements Identity {
|
||||
private handle: any;
|
||||
private _name: string;
|
||||
|
||||
constructor(handle: any, name: string) {
|
||||
this.handle = handle;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
securityLevel() : number | undefined {
|
||||
return parseInt(TSIdentityHelper.unwarpString(TSIdentityHelper.funcationCalculateSecurityLevel(this.handle)));
|
||||
}
|
||||
|
||||
name() : string { return this._name; }
|
||||
|
||||
uid() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.functionUid(this.handle));
|
||||
}
|
||||
|
||||
type() : IdentitifyType { return IdentitifyType.TEAMSPEAK; }
|
||||
|
||||
signMessage(message: string): string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationSignMessage(this.handle, message, message.length));
|
||||
}
|
||||
|
||||
exported() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationExportIdentity(this.handle));
|
||||
}
|
||||
|
||||
publicKey() : string {
|
||||
return TSIdentityHelper.unwarpString(TSIdentityHelper.funcationPublicKey(this.handle));
|
||||
}
|
||||
}
|
||||
|
||||
class TeaForumIdentity implements Identity {
|
||||
readonly identityData: string;
|
||||
readonly identityDataJson: string;
|
||||
readonly identitySign: string;
|
||||
|
||||
constructor(data: string, sign: string) {
|
||||
this.identityDataJson = data;
|
||||
this.identityData = JSON.parse(this.identityDataJson);
|
||||
this.identitySign = sign;
|
||||
}
|
||||
|
||||
name() : string { return this.identityData["user_name"]; }
|
||||
uid() : string { return "TeaForo#" + this.identityData["user_id"]; }
|
||||
type() : IdentitifyType { return IdentitifyType.TEAFORO; }
|
||||
}
|
|
@ -86,7 +86,7 @@ class InfoBar {
|
|||
else if (this._currentSelected instanceof ChannelEntry) {
|
||||
let props = this._currentSelected.properties;
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Topic": this._currentSelected.properties.channel_topic,
|
||||
"Codec": this._currentSelected.properties.channel_codec,
|
||||
"Codec Quality": this._currentSelected.properties.channel_codec_quality,
|
||||
|
@ -98,12 +98,20 @@ class InfoBar {
|
|||
}
|
||||
else if (this._currentSelected instanceof ClientEntry) {
|
||||
this._currentSelected.updateVariables();
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
let version = this._currentSelected.properties.client_version;
|
||||
if (!version)
|
||||
version = "";
|
||||
let infos = {
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Description": this._currentSelected.properties.client_description,
|
||||
"Version": this._currentSelected.properties.client_version + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>"
|
||||
}));
|
||||
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>",
|
||||
"Volume": this._currentSelected.audioController.volume * 100 + " %"
|
||||
};
|
||||
if (this._currentSelected.properties["client_teaforum_id"] > 0) {
|
||||
infos["TeaSpeak Account"] = "<a href='//forum.teaspeak.de/index.php?members/{1}/' target='_blank'>{0}</a>".format(this._currentSelected.properties["client_teaforum_name"], this._currentSelected.properties["client_teaforum_id"]);
|
||||
}
|
||||
this._htmlTag.append(this.createInfoTable(infos));
|
||||
{
|
||||
let serverGroups = $.spawn("div");
|
||||
serverGroups
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -106,7 +106,7 @@ class InfoBar {
|
|||
} else if(this._currentSelected instanceof ChannelEntry) {
|
||||
let props = this._currentSelected.properties;
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Topic": this._currentSelected.properties.channel_topic,
|
||||
"Codec": this._currentSelected.properties.channel_codec,
|
||||
"Codec Quality": this._currentSelected.properties.channel_codec_quality,
|
||||
|
@ -117,12 +117,20 @@ class InfoBar {
|
|||
}));
|
||||
} else if(this._currentSelected instanceof ClientEntry) {
|
||||
this._currentSelected.updateVariables();
|
||||
this._htmlTag.append(this.createInfoTable({
|
||||
"Name": this._currentSelected.createChatTag(),
|
||||
|
||||
let version: string = this._currentSelected.properties.client_version;
|
||||
if(!version) version = "";
|
||||
let infos = {
|
||||
"Name": this._currentSelected.createChatTag().html(),
|
||||
"Description": this._currentSelected.properties.client_description,
|
||||
"Version": this._currentSelected.properties.client_version + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>"
|
||||
}));
|
||||
"Version": "<a title='" + ChatMessage.formatMessage(version) + "'>" + version.split(" ")[0] + "</a>" + " on " + this._currentSelected.properties.client_platform,
|
||||
"Online since": "<a class='online'>" + formatDate(this._currentSelected.calculateOnlineTime()) + "</a>",
|
||||
"Volume": this._currentSelected.audioController.volume * 100 + " %"
|
||||
};
|
||||
if(this._currentSelected.properties["client_teaforum_id"] > 0) {
|
||||
infos["TeaSpeak Account"] = "<a href='//forum.teaspeak.de/index.php?members/{1}/' target='_blank'>{0}</a>".format(this._currentSelected.properties["client_teaforum_name"], this._currentSelected.properties["client_teaforum_id"]);
|
||||
}
|
||||
this._htmlTag.append(this.createInfoTable(infos));
|
||||
|
||||
{
|
||||
let serverGroups = $.spawn("div");
|
||||
|
|
31
js/chat.js
31
js/chat.js
|
@ -35,9 +35,16 @@ class ChatMessage {
|
|||
return tag;
|
||||
}
|
||||
static formatMessage(message) {
|
||||
return message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
/*
|
||||
message = message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
*/
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
console.log(message + "->" + div.innerHTML);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
class ChatEntry {
|
||||
|
@ -49,12 +56,26 @@ class ChatEntry {
|
|||
this.history = [];
|
||||
this.onClose = function () { return true; };
|
||||
}
|
||||
static tagify(message, ...args) {
|
||||
}
|
||||
appendError(message, ...args) {
|
||||
this.appendMessage("<a style='color: red'>{0}</a>".format(ChatMessage.formatMessage(message).format(...args)), false);
|
||||
}
|
||||
appendMessage(message, fmt = true, ...args) {
|
||||
let parms = [];
|
||||
for (let index = 2; index < arguments.length; index++) {
|
||||
if (typeof arguments[index] == "string")
|
||||
arguments[index] = ChatMessage.formatMessage(arguments[index]);
|
||||
else if (arguments[index] instanceof jQuery)
|
||||
arguments[index] = arguments[index].html();
|
||||
else {
|
||||
console.error("Invalid type " + typeof arguments[index] + "|" + arguments[index].prototype);
|
||||
arguments[index] = arguments[index].toString();
|
||||
}
|
||||
parms.push(arguments[index]);
|
||||
}
|
||||
let msg = fmt ? ChatMessage.formatMessage(message) : message;
|
||||
msg = msg.format(...args);
|
||||
msg = msg.format(parms);
|
||||
let elm = new ChatMessage(msg);
|
||||
this.history.push(elm);
|
||||
while (this.history.length > 100) {
|
||||
|
@ -91,7 +112,7 @@ class ChatEntry {
|
|||
get htmlTag() {
|
||||
if (this._htmlTag)
|
||||
return this._htmlTag;
|
||||
var tag = $.spawn("div");
|
||||
let tag = $.spawn("div");
|
||||
tag.addClass("chat");
|
||||
tag.append("<div class=\"chatIcon icon clicon " + this.chatIcon() + "\"></div>");
|
||||
tag.append("<a class='name'>" + this._name + "</a>");
|
||||
|
|
File diff suppressed because one or more lines are too long
31
js/chat.ts
31
js/chat.ts
|
@ -46,9 +46,16 @@ class ChatMessage {
|
|||
}
|
||||
|
||||
static formatMessage(message: string) : string {
|
||||
return message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
/*
|
||||
message = message
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, "<br/>");
|
||||
*/
|
||||
const div = document.createElement('div');
|
||||
div.innerText = message;
|
||||
message = div.innerHTML;
|
||||
console.log(message + "->" + div.innerHTML);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,13 +82,27 @@ class ChatEntry {
|
|||
this.onClose = function () { return true; }
|
||||
}
|
||||
|
||||
static tagify(message: string, ...args) {
|
||||
|
||||
}
|
||||
|
||||
appendError(message: string, ...args) {
|
||||
this.appendMessage("<a style='color: red'>{0}</a>".format(ChatMessage.formatMessage(message).format(...args)), false);
|
||||
}
|
||||
|
||||
appendMessage(message : string, fmt: boolean = true, ...args) {
|
||||
let parms: any[] = [];
|
||||
for(let index = 2; index < arguments.length; index++) {
|
||||
if(typeof arguments[index] == "string") arguments[index] = ChatMessage.formatMessage(arguments[index]);
|
||||
else if(arguments[index] instanceof jQuery) arguments[index] = arguments[index].html();
|
||||
else {
|
||||
console.error("Invalid type " + typeof arguments[index] + "|" + arguments[index].prototype);
|
||||
arguments[index] = arguments[index].toString();
|
||||
}
|
||||
parms.push(arguments[index]);
|
||||
}
|
||||
let msg : string = fmt ? ChatMessage.formatMessage(message) : message;
|
||||
msg = msg.format(...args);
|
||||
msg = msg.format(parms);
|
||||
let elm = new ChatMessage(msg);
|
||||
this.history.push(elm);
|
||||
while(this.history.length > 100) {
|
||||
|
@ -119,7 +140,7 @@ class ChatEntry {
|
|||
get htmlTag() {
|
||||
if(this._htmlTag) return this._htmlTag;
|
||||
|
||||
var tag = $.spawn("div");
|
||||
let tag = $.spawn("div");
|
||||
tag.addClass("chat");
|
||||
|
||||
tag.append("<div class=\"chatIcon icon clicon " + this.chatIcon() + "\"></div>");
|
||||
|
|
|
@ -60,9 +60,8 @@ class TSClient {
|
|||
}
|
||||
setup() {
|
||||
this.controlBar.initialise();
|
||||
this.serverConnection.on_connected = this.onConnected.bind(this);
|
||||
}
|
||||
startConnection(addr) {
|
||||
startConnection(addr, identity, name) {
|
||||
if (this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
let idx = addr.lastIndexOf(':');
|
||||
|
@ -78,7 +77,7 @@ class TSClient {
|
|||
}
|
||||
console.log("Start connection to " + host + ":" + port);
|
||||
this.channelTree.initialiseHead(addr);
|
||||
this.serverConnection.startConnection(host, port);
|
||||
this.serverConnection.startConnection(host, port, new HandshakeHandler(identity, name));
|
||||
}
|
||||
getClient() { return this._ownEntry; }
|
||||
getClientId() { return this._clientId; } //TODO here
|
||||
|
@ -97,7 +96,6 @@ class TSClient {
|
|||
console.log("Client connected!");
|
||||
this.channelTree.registerClient(this._ownEntry);
|
||||
this.settings.loadServer();
|
||||
chat.serverChat().appendMessage("Connected");
|
||||
this.serverConnection.sendCommand("channelsubscribeall");
|
||||
this.permissions.requestPermissionList();
|
||||
if (this.groups.serverGroups.length == 0)
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"client.js","sourceRoot":"","sources":["client.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"client.js","sourceRoot":"","sources":["client.ts"],"names":[],"mappings":"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"}
|
|
@ -74,11 +74,9 @@ class TSClient {
|
|||
|
||||
setup() {
|
||||
this.controlBar.initialise();
|
||||
|
||||
this.serverConnection.on_connected = this.onConnected.bind(this);
|
||||
}
|
||||
|
||||
startConnection(addr: string) {
|
||||
startConnection(addr: string, identity: Identity, name?: string) {
|
||||
if(this.serverConnection)
|
||||
this.handleDisconnect(DisconnectReason.REQUESTED);
|
||||
|
||||
|
@ -95,7 +93,7 @@ class TSClient {
|
|||
}
|
||||
console.log("Start connection to " + host + ":" + port);
|
||||
this.channelTree.initialiseHead(addr);
|
||||
this.serverConnection.startConnection(host, port);
|
||||
this.serverConnection.startConnection(host, port, new HandshakeHandler(identity, name));
|
||||
}
|
||||
|
||||
|
||||
|
@ -121,7 +119,6 @@ class TSClient {
|
|||
console.log("Client connected!");
|
||||
this.channelTree.registerClient(this._ownEntry);
|
||||
this.settings.loadServer();
|
||||
chat.serverChat().appendMessage("Connected");
|
||||
this.serverConnection.sendCommand("channelsubscribeall");
|
||||
this.permissions.requestPermissionList();
|
||||
if(this.groups.serverGroups.length == 0)
|
||||
|
|
63
js/codec/BasicCodec.ts
Normal file
63
js/codec/BasicCodec.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/// <reference path="Codec.ts"/>
|
||||
|
||||
abstract class BasicCodec implements Codec {
|
||||
protected _audioContext: OfflineAudioContext;
|
||||
protected _decodeResampler: AudioResampler;
|
||||
protected _encodeResampler: AudioResampler;
|
||||
protected _codecSampleRate: number;
|
||||
|
||||
on_encoded_data: (Uint8Array) => void = $ => {};
|
||||
channelCount: number = 1;
|
||||
samplesPerUnit: number = 960;
|
||||
|
||||
constructor(codecSampleRate: number) {
|
||||
this.channelCount = 1;
|
||||
this.samplesPerUnit = 960;
|
||||
this._audioContext = new OfflineAudioContext(1, 1024,44100 );
|
||||
this._codecSampleRate = codecSampleRate;
|
||||
this._decodeResampler = new AudioResampler();
|
||||
this._encodeResampler = new AudioResampler(codecSampleRate);
|
||||
}
|
||||
|
||||
abstract name() : string;
|
||||
abstract initialise();
|
||||
abstract deinitialise();
|
||||
abstract reset() : boolean;
|
||||
|
||||
protected abstract decode(data: Uint8Array) : Promise<AudioBuffer>;
|
||||
protected abstract encode(data: AudioBuffer) : Promise<Uint8Array | string>;
|
||||
|
||||
|
||||
encodeSamples(cache: CodecClientCache, pcm: AudioBuffer) {
|
||||
this._encodeResampler.resample(pcm).then(buffer => this.encodeSamples0(cache, buffer))
|
||||
.catch(error => console.error("Could not resample PCM data for codec. Error:" + error));
|
||||
|
||||
}
|
||||
|
||||
private encodeSamples0(cache: CodecClientCache, buffer: AudioBuffer) {
|
||||
cache._chunks.push(new BufferChunk(buffer)); //TODO multi channel!
|
||||
|
||||
while(cache.bufferedSamples(this.samplesPerUnit) >= this.samplesPerUnit) {
|
||||
let buffer = this._audioContext.createBuffer(this.channelCount, this.samplesPerUnit, this._codecSampleRate);
|
||||
let index = 0;
|
||||
while(index < this.samplesPerUnit) {
|
||||
let buf = cache._chunks[0];
|
||||
let cpyBytes = buf.copyRangeTo(buffer, this.samplesPerUnit - index, index);
|
||||
index += cpyBytes;
|
||||
buf.index += cpyBytes;
|
||||
if(buf.index == buf.buffer.length)
|
||||
cache._chunks.pop_front();
|
||||
}
|
||||
|
||||
this.encode(buffer).then(result => {
|
||||
if(result instanceof Uint8Array) this.on_encoded_data(result);
|
||||
else console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
decodeSamples(cache: CodecClientCache, data: Uint8Array) : Promise<AudioBuffer> {
|
||||
return this.decode(data).then(buffer => this._decodeResampler.resample(buffer));
|
||||
}
|
||||
}
|
|
@ -1,12 +1,3 @@
|
|||
/// <reference path="../voice/AudioResampler.ts" />
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
class BufferChunk {
|
||||
constructor(buffer) {
|
||||
this.buffer = buffer;
|
||||
|
@ -20,15 +11,9 @@ class BufferChunk {
|
|||
return copy;
|
||||
}
|
||||
}
|
||||
class Codec {
|
||||
constructor(codecSampleRate) {
|
||||
this.on_encoded_data = ($) => { };
|
||||
class CodecClientCache {
|
||||
constructor() {
|
||||
this._chunks = [];
|
||||
this.channelCount = 1;
|
||||
this.samplesPerUnit = 960;
|
||||
this._codecSampleRate = codecSampleRate;
|
||||
this._decodeResampler = new AudioResampler();
|
||||
this._encodeResampler = new AudioResampler(codecSampleRate);
|
||||
}
|
||||
bufferedSamples(max = 0) {
|
||||
let value = 0;
|
||||
|
@ -36,126 +21,5 @@ class Codec {
|
|||
value += this._chunks[i].buffer.length - this._chunks[i].index;
|
||||
return value;
|
||||
}
|
||||
encodeSamples(pcm) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this._encodeResampler.resample(pcm).then(buffer => this.encodeSamples0(buffer))
|
||||
.catch(error => console.error("Could not resample PCM data for codec. Error:" + error));
|
||||
});
|
||||
}
|
||||
encodeSamples0(buffer) {
|
||||
this._chunks.push(new BufferChunk(buffer)); //TODO multi channel!
|
||||
while (this.bufferedSamples(this.samplesPerUnit) >= this.samplesPerUnit) {
|
||||
let buffer = AudioController.globalContext.createBuffer(this.channelCount, this.samplesPerUnit, this._codecSampleRate);
|
||||
let index = 0;
|
||||
while (index < this.samplesPerUnit) {
|
||||
let buf = this._chunks[0];
|
||||
let cpyBytes = buf.copyRangeTo(buffer, this.samplesPerUnit - index, index);
|
||||
index += cpyBytes;
|
||||
buf.index += cpyBytes;
|
||||
if (buf.index == buf.buffer.length)
|
||||
this._chunks.pop_front();
|
||||
}
|
||||
let result = this.encode(buffer);
|
||||
if (result instanceof Uint8Array)
|
||||
this.on_encoded_data(result);
|
||||
else
|
||||
console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
decodeSamples(data) {
|
||||
return this.decode(data).then(buffer => this._decodeResampler.resample(buffer));
|
||||
}
|
||||
}
|
||||
class RawCodec extends Codec {
|
||||
constructor(codecSampleRate) {
|
||||
super(codecSampleRate);
|
||||
this.bufferSize = 4096 * 4;
|
||||
}
|
||||
name() {
|
||||
return "raw";
|
||||
}
|
||||
initialise() {
|
||||
this.converterRaw = Module._malloc(this.bufferSize);
|
||||
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
|
||||
}
|
||||
deinitialise() { }
|
||||
decode(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.converter.set(data);
|
||||
let buf = Module.HEAPF32.slice(this.converter.byteOffset / 4, (this.converter.byteOffset / 4) + data.length / 4);
|
||||
let audioBuf = AudioController.globalContext.createBuffer(1, data.length / 4, this._codecSampleRate);
|
||||
audioBuf.copyToChannel(buf, 0);
|
||||
resolve(audioBuf);
|
||||
});
|
||||
}
|
||||
encode(data) {
|
||||
return new Uint8Array(data.getChannelData(0));
|
||||
}
|
||||
}
|
||||
var OpusType;
|
||||
(function (OpusType) {
|
||||
OpusType[OpusType["VOIP"] = 2048] = "VOIP";
|
||||
OpusType[OpusType["AUDIO"] = 2049] = "AUDIO";
|
||||
OpusType[OpusType["RESTRICTED_LOWDELAY"] = 2051] = "RESTRICTED_LOWDELAY";
|
||||
})(OpusType || (OpusType = {}));
|
||||
class OpusCodec extends Codec {
|
||||
constructor(channelCount, type) {
|
||||
super(48000);
|
||||
this.bufferSize = 4096 * 2;
|
||||
this.channelCount = channelCount;
|
||||
this.type = type;
|
||||
}
|
||||
name() {
|
||||
return "Opus (Type: " + OpusCodec[this.type] + " Channels: " + this.channelCount + ")";
|
||||
}
|
||||
initialise() {
|
||||
this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "pointer", ["number", "number"]);
|
||||
this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
/* codec_opus_decode(handle, buffer, length, maxlength) */
|
||||
this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
|
||||
this.encodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.encodeBuffer = new Float32Array(Module.HEAPF32.buffer, this.encodeBufferRaw, this.bufferSize / 4);
|
||||
this.decodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.decodeBuffer = new Uint8Array(Module.HEAPU8.buffer, this.decodeBufferRaw, this.bufferSize);
|
||||
}
|
||||
deinitialise() { } //TODO
|
||||
decode(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (data.byteLength > this.decodeBuffer.byteLength)
|
||||
throw "Data to long!";
|
||||
this.decodeBuffer.set(data);
|
||||
//console.log("decode(" + data.length + ")");
|
||||
//console.log(data);
|
||||
let result = this.fn_decode(this.nativeHandle, this.decodeBuffer.byteOffset, data.byteLength, this.decodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
reject("invalid result on decode (" + result + ")");
|
||||
return;
|
||||
}
|
||||
//console.log("decoded: " + result);
|
||||
let buf = Module.HEAPF32.slice(this.decodeBuffer.byteOffset / 4, (this.decodeBuffer.byteOffset / 4) + (result * this.channelCount));
|
||||
let audioBuf = AudioController.globalContext.createBuffer(this.channelCount, result, this._codecSampleRate);
|
||||
for (let offset = 0; offset < result; offset++) {
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
audioBuf.getChannelData(channel)[offset] = buf[offset * this.channelCount + channel];
|
||||
}
|
||||
resolve(audioBuf);
|
||||
});
|
||||
}
|
||||
encode(data) {
|
||||
if (data.length * this.channelCount > this.encodeBuffer.length)
|
||||
throw "Data to long!";
|
||||
for (let offset = 0; offset < data.length; offset++) {
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
this.encodeBuffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
|
||||
}
|
||||
let result = this.fn_encode(this.nativeHandle, this.encodeBuffer.byteOffset, data.length, this.encodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
return "invalid result on encode (" + result + ")";
|
||||
}
|
||||
let buf = Module.HEAP8.slice(this.encodeBuffer.byteOffset, this.encodeBuffer.byteOffset + result);
|
||||
return Uint8Array.from(buf);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=Codec.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,6 @@
|
|||
/// <reference path="../voice/AudioResampler.ts" />
|
||||
interface CodecCostructor {
|
||||
new (codecSampleRate: number) : Codec;
|
||||
}
|
||||
|
||||
class BufferChunk {
|
||||
buffer: AudioBuffer;
|
||||
|
@ -21,191 +23,29 @@ class BufferChunk {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class Codec {
|
||||
on_encoded_data: (Uint8Array) => void = ($) => {};
|
||||
class CodecClientCache {
|
||||
_chunks: BufferChunk[] = [];
|
||||
|
||||
protected _decodeResampler: AudioResampler;
|
||||
protected _encodeResampler: AudioResampler;
|
||||
protected _codecSampleRate: number;
|
||||
protected _chunks: BufferChunk[] = [];
|
||||
|
||||
channelCount: number = 1;
|
||||
samplesPerUnit: number = 960;
|
||||
|
||||
protected constructor(codecSampleRate: number){
|
||||
this._codecSampleRate = codecSampleRate;
|
||||
this._decodeResampler = new AudioResampler();
|
||||
this._encodeResampler = new AudioResampler(codecSampleRate);
|
||||
}
|
||||
|
||||
abstract name() : string;
|
||||
abstract initialise();
|
||||
abstract deinitialise();
|
||||
|
||||
protected abstract decode(data: Uint8Array) : Promise<AudioBuffer>;
|
||||
protected abstract encode(data: AudioBuffer) : Uint8Array | string;
|
||||
|
||||
|
||||
protected bufferedSamples(max: number = 0) : number {
|
||||
bufferedSamples(max: number = 0) : number {
|
||||
let value = 0;
|
||||
for(let i = 0; i < this._chunks.length && value < max; i++)
|
||||
value += this._chunks[i].buffer.length - this._chunks[i].index;
|
||||
return value;
|
||||
}
|
||||
|
||||
async encodeSamples(pcm: AudioBuffer) {
|
||||
this._encodeResampler.resample(pcm).then(buffer => this.encodeSamples0(buffer))
|
||||
.catch(error => console.error("Could not resample PCM data for codec. Error:" + error));
|
||||
|
||||
}
|
||||
|
||||
private encodeSamples0(buffer: AudioBuffer) {
|
||||
this._chunks.push(new BufferChunk(buffer)); //TODO multi channel!
|
||||
|
||||
while(this.bufferedSamples(this.samplesPerUnit) >= this.samplesPerUnit) {
|
||||
let buffer = AudioController.globalContext.createBuffer(this.channelCount, this.samplesPerUnit, this._codecSampleRate);
|
||||
let index = 0;
|
||||
while(index < this.samplesPerUnit) {
|
||||
let buf = this._chunks[0];
|
||||
let cpyBytes = buf.copyRangeTo(buffer, this.samplesPerUnit - index, index);
|
||||
index += cpyBytes;
|
||||
buf.index += cpyBytes;
|
||||
if(buf.index == buf.buffer.length)
|
||||
this._chunks.pop_front();
|
||||
}
|
||||
|
||||
let result = this.encode(buffer);
|
||||
if(result instanceof Uint8Array) this.on_encoded_data(result);
|
||||
else console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
decodeSamples(data: Uint8Array) : Promise<AudioBuffer> {
|
||||
return this.decode(data).then(buffer => this._decodeResampler.resample(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
class RawCodec extends Codec {
|
||||
converterRaw: any;
|
||||
converter: Uint8Array;
|
||||
bufferSize: number = 4096 * 4;
|
||||
interface Codec {
|
||||
on_encoded_data: (Uint8Array) => void;
|
||||
|
||||
constructor(codecSampleRate: number){
|
||||
super(codecSampleRate);
|
||||
}
|
||||
channelCount: number;
|
||||
samplesPerUnit: number;
|
||||
|
||||
name(): string {
|
||||
return "raw";
|
||||
}
|
||||
name() : string;
|
||||
initialise();
|
||||
deinitialise();
|
||||
|
||||
initialise() {
|
||||
this.converterRaw = Module._malloc(this.bufferSize);
|
||||
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
|
||||
}
|
||||
decodeSamples(cache: CodecClientCache, data: Uint8Array) : Promise<AudioBuffer>;
|
||||
encodeSamples(cache: CodecClientCache, pcm: AudioBuffer);
|
||||
|
||||
deinitialise() { }
|
||||
|
||||
protected decode(data: Uint8Array): Promise<AudioBuffer> {
|
||||
return new Promise<AudioBuffer>((resolve, reject) => {
|
||||
this.converter.set(data);
|
||||
let buf = Module.HEAPF32.slice(this.converter.byteOffset / 4, (this.converter.byteOffset / 4) + data.length / 4);
|
||||
let audioBuf = AudioController.globalContext.createBuffer(1, data.length / 4, this._codecSampleRate);
|
||||
audioBuf.copyToChannel(buf, 0);
|
||||
resolve(audioBuf);
|
||||
});
|
||||
}
|
||||
|
||||
protected encode(data: AudioBuffer): Uint8Array | string {
|
||||
return new Uint8Array(data.getChannelData(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum OpusType {
|
||||
VOIP = 2048,
|
||||
AUDIO = 2049,
|
||||
RESTRICTED_LOWDELAY = 2051
|
||||
}
|
||||
|
||||
class OpusCodec extends Codec {
|
||||
private nativeHandle: any;
|
||||
private type: OpusType;
|
||||
|
||||
private fn_newHandle: any;
|
||||
private fn_decode: any;
|
||||
private fn_encode: any;
|
||||
|
||||
private bufferSize = 4096 * 2;
|
||||
private encodeBufferRaw: any;
|
||||
private encodeBuffer: Float32Array;
|
||||
private decodeBufferRaw: any;
|
||||
private decodeBuffer: Uint8Array;
|
||||
|
||||
constructor(channelCount: number, type: OpusType) {
|
||||
super(48000);
|
||||
this.channelCount = channelCount;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return "Opus (Type: " + OpusCodec[this.type] + " Channels: " + this.channelCount + ")";
|
||||
}
|
||||
|
||||
initialise() {
|
||||
this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "pointer", ["number", "number"]);
|
||||
this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
/* codec_opus_decode(handle, buffer, length, maxlength) */
|
||||
this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
|
||||
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
|
||||
|
||||
this.encodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.encodeBuffer = new Float32Array(Module.HEAPF32.buffer, this.encodeBufferRaw, this.bufferSize / 4);
|
||||
|
||||
this.decodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.decodeBuffer = new Uint8Array(Module.HEAPU8.buffer, this.decodeBufferRaw, this.bufferSize);
|
||||
}
|
||||
|
||||
deinitialise() { } //TODO
|
||||
|
||||
decode(data: Uint8Array): Promise<AudioBuffer> {
|
||||
return new Promise<AudioBuffer>((resolve, reject) => {
|
||||
if (data.byteLength > this.decodeBuffer.byteLength) throw "Data to long!";
|
||||
this.decodeBuffer.set(data);
|
||||
//console.log("decode(" + data.length + ")");
|
||||
//console.log(data);
|
||||
let result = this.fn_decode(this.nativeHandle, this.decodeBuffer.byteOffset, data.byteLength, this.decodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
reject("invalid result on decode (" + result + ")");
|
||||
return;
|
||||
}
|
||||
//console.log("decoded: " + result);
|
||||
let buf = Module.HEAPF32.slice(this.decodeBuffer.byteOffset / 4, (this.decodeBuffer.byteOffset / 4) + (result * this.channelCount));
|
||||
let audioBuf = AudioController.globalContext.createBuffer(this.channelCount, result, this._codecSampleRate);
|
||||
|
||||
for (let offset = 0; offset < result; offset++) {
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
audioBuf.getChannelData(channel)[offset] = buf[offset * this.channelCount + channel];
|
||||
}
|
||||
|
||||
resolve(audioBuf);
|
||||
});
|
||||
}
|
||||
|
||||
encode(data: AudioBuffer): Uint8Array | string {
|
||||
if (data.length * this.channelCount > this.encodeBuffer.length) throw "Data to long!";
|
||||
|
||||
for (let offset = 0; offset < data.length; offset++) {
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
this.encodeBuffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
|
||||
}
|
||||
|
||||
let result = this.fn_encode(this.nativeHandle, this.encodeBuffer.byteOffset, data.length, this.encodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
return "invalid result on encode (" + result + ")";
|
||||
}
|
||||
let buf = Module.HEAP8.slice(this.encodeBuffer.byteOffset, this.encodeBuffer.byteOffset + result);
|
||||
return Uint8Array.from(buf);
|
||||
}
|
||||
reset() : boolean;
|
||||
}
|
145
js/codec/CodecWrapper.ts
Normal file
145
js/codec/CodecWrapper.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
/// <reference path="BasicCodec.ts"/>
|
||||
|
||||
enum CodecWorkerType {
|
||||
WORKER_OPUS
|
||||
}
|
||||
|
||||
class CodecWrapper extends BasicCodec {
|
||||
private _worker: Worker;
|
||||
private _workerListener: {token: string, resolve: (data: any) => void}[] = [];
|
||||
private _workerCallbackToken = "callback_token";
|
||||
private _workerTokeIndex: number = 0;
|
||||
type: CodecWorkerType;
|
||||
|
||||
name(): string {
|
||||
return "Worker for " + CodecWorkerType[this.type] + " Channels " + this.channelCount;
|
||||
}
|
||||
|
||||
initialise() {
|
||||
this.spawnWorker();
|
||||
this.sendWorkerMessage({
|
||||
command: "initialise",
|
||||
type: this.type,
|
||||
channelCount: this.channelCount
|
||||
});
|
||||
}
|
||||
|
||||
deinitialise() {
|
||||
this.sendWorkerMessage({
|
||||
command: "deinitialise"
|
||||
});
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): Promise<AudioBuffer> {
|
||||
let token = this._workerTokeIndex++ + "_token";
|
||||
let result = new Promise<AudioBuffer>((resolve, reject) => {
|
||||
this._workerListener.push(
|
||||
{
|
||||
token: token,
|
||||
resolve: (data) => {
|
||||
if(data.success) {
|
||||
let array = new Float32Array(data.dataLength);
|
||||
for(let index = 0; index < array.length; index++)
|
||||
array[index] = data.data[index];
|
||||
|
||||
let audioBuf = this._audioContext.createBuffer(this.channelCount, array.length / this.channelCount, this._codecSampleRate);
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
for (let offset = 0; offset < audioBuf.length; offset++)
|
||||
audioBuf.getChannelData(channel)[offset] = array[channel * audioBuf.length + offset];
|
||||
resolve(audioBuf);
|
||||
} else {
|
||||
reject(data.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
this.sendWorkerMessage({
|
||||
command: "decodeSamples",
|
||||
token: token,
|
||||
data: data,
|
||||
dataLength: data.length
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
encode(data: AudioBuffer) : Promise<Uint8Array> {
|
||||
let token = this._workerTokeIndex++ + "_token";
|
||||
let result = new Promise<Uint8Array>((resolve, reject) => {
|
||||
this._workerListener.push(
|
||||
{
|
||||
token: token,
|
||||
resolve: (data) => {
|
||||
if(data.success) {
|
||||
let array = new Uint8Array(data.dataLength);
|
||||
for(let index = 0; index < array.length; index++)
|
||||
array[index] = data.data[index];
|
||||
resolve(array);
|
||||
} else {
|
||||
reject(data.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let buffer = new Float32Array(this.channelCount * data.length);
|
||||
for (let offset = 0; offset < data.length; offset++) {
|
||||
for (let channel = 0; channel < this.channelCount; channel++)
|
||||
buffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
|
||||
}
|
||||
//FIXME test if this is right!
|
||||
|
||||
this.sendWorkerMessage({
|
||||
command: "encodeSamples",
|
||||
token: token,
|
||||
data: buffer,
|
||||
dataLength: buffer.length
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
reset() : boolean {
|
||||
this.sendWorkerMessage({
|
||||
command: "reset"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor(type: CodecWorkerType, channelCount: number) {
|
||||
super(48000);
|
||||
this.type = type;
|
||||
this.channelCount = channelCount;
|
||||
}
|
||||
|
||||
private sendWorkerMessage(message: any, transfare?: any[]) {
|
||||
this._worker.postMessage(JSON.stringify(message), transfare);
|
||||
}
|
||||
|
||||
private onWorkerMessage(message: any) {
|
||||
if(!message["token"]) {
|
||||
console.error("Invalid worker token!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(message["token"] == this._workerCallbackToken) {
|
||||
console.log("Callback data!");
|
||||
return;
|
||||
}
|
||||
|
||||
for(let entry of this._workerListener) {
|
||||
if(entry.token == message["token"]) {
|
||||
entry.resolve(message);
|
||||
this._workerListener.remove(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Could not find worker token entry! (" + message["token"] + ")");
|
||||
}
|
||||
|
||||
private spawnWorker() {
|
||||
this._worker = new Worker("js/codec/CompiledCodecWorker.js");
|
||||
this._worker.onmessage = event => this.onWorkerMessage(JSON.parse(event.data));
|
||||
}
|
||||
}
|
38
js/codec/RawCodec.ts
Normal file
38
js/codec/RawCodec.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/// <reference path="BasicCodec.ts"/>
|
||||
|
||||
class RawCodec extends BasicCodec {
|
||||
converterRaw: any;
|
||||
converter: Uint8Array;
|
||||
bufferSize: number = 4096 * 4;
|
||||
|
||||
constructor(codecSampleRate: number){
|
||||
super(codecSampleRate);
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return "raw";
|
||||
}
|
||||
|
||||
initialise() {
|
||||
this.converterRaw = Module._malloc(this.bufferSize);
|
||||
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
|
||||
}
|
||||
|
||||
deinitialise() { }
|
||||
|
||||
protected decode(data: Uint8Array): Promise<AudioBuffer> {
|
||||
return new Promise<AudioBuffer>((resolve, reject) => {
|
||||
this.converter.set(data);
|
||||
let buf = Module.HEAPF32.slice(this.converter.byteOffset / 4, (this.converter.byteOffset / 4) + data.length / 4);
|
||||
let audioBuf = this._audioContext.createBuffer(1, data.length / 4, this._codecSampleRate);
|
||||
audioBuf.copyToChannel(buf, 0);
|
||||
resolve(audioBuf);
|
||||
});
|
||||
}
|
||||
|
||||
protected encode(data: AudioBuffer): Promise<Uint8Array> {
|
||||
return new Promise<Uint8Array>(resolve => resolve(new Uint8Array(data.getChannelData(0))));
|
||||
}
|
||||
|
||||
reset() : boolean { return true; }
|
||||
}
|
17
js/codec/tsconfig.json
Normal file
17
js/codec/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "none",
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"outFile": "CompiledCodecWorker.js"
|
||||
},
|
||||
"files": [
|
||||
"workers/CodecWorker.ts",
|
||||
"workers/CodecWorker.ts",
|
||||
"workers/OpusCodec.ts",
|
||||
"../proto.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
88
js/codec/workers/CodecWorker.ts
Normal file
88
js/codec/workers/CodecWorker.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
const prefix = "[CodecWorker] ";
|
||||
interface CodecWorker {
|
||||
name();
|
||||
initialise();
|
||||
deinitialise();
|
||||
decode(data: Uint8Array);
|
||||
encode(data: Float32Array) : Uint8Array | string;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
enum CodecWorkerType {
|
||||
WORKER_OPUS
|
||||
}
|
||||
let codecInstance: CodecWorker;
|
||||
|
||||
onmessage = function(e) {
|
||||
let data = JSON.parse(e.data);
|
||||
//console.log(data);
|
||||
|
||||
let res: any = {};
|
||||
res.token = data.token;
|
||||
|
||||
switch (data.command) {
|
||||
case "initialise":
|
||||
console.log(prefix + "Got initialize for type " + CodecWorkerType[data.type as CodecWorkerType]);
|
||||
switch (data.type as CodecWorkerType) {
|
||||
case CodecWorkerType.WORKER_OPUS:
|
||||
codecInstance = new OpusWorker(data.channelCount, data.channelCount == 1 ? OpusType.VOIP : OpusType.AUDIO);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error("Could not resolve opus type!");
|
||||
return;
|
||||
}
|
||||
|
||||
codecInstance.initialise();
|
||||
break;
|
||||
|
||||
case "encodeSamples":
|
||||
let encodeArray = new Float32Array(data.dataLength);
|
||||
for(let index = 0; index < encodeArray.length; index++)
|
||||
encodeArray[index] = data.data[index];
|
||||
|
||||
let encodeResult = codecInstance.encode(encodeArray);
|
||||
|
||||
if(typeof encodeResult === "string") {
|
||||
res.success = false;
|
||||
res.message = encodeResult;
|
||||
} else {
|
||||
res.success = true;
|
||||
res.data = encodeResult;
|
||||
res.dataLength = encodeResult.length;
|
||||
}
|
||||
sendMessage(res, e.origin);
|
||||
break;
|
||||
|
||||
case "decodeSamples":
|
||||
let decodeArray = new Uint8Array(data.dataLength);
|
||||
for(let index = 0; index < decodeArray.length; index++)
|
||||
decodeArray[index] = data.data[index];
|
||||
|
||||
let decodeResult = codecInstance.decode(decodeArray);
|
||||
|
||||
if(typeof decodeResult === "string") {
|
||||
res.success = false;
|
||||
res.message = decodeResult;
|
||||
} else {
|
||||
res.success = true;
|
||||
res.data = decodeResult;
|
||||
res.dataLength = decodeResult.length;
|
||||
}
|
||||
sendMessage(res, e.origin);
|
||||
break;
|
||||
|
||||
case "reset":
|
||||
codecInstance.reset();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(prefix + "Unknown type " + data.command);
|
||||
}
|
||||
};
|
||||
|
||||
declare function postMessage(message: any): void;
|
||||
function sendMessage(message: any, origin: string){
|
||||
postMessage(JSON.stringify(message));
|
||||
}
|
90
js/codec/workers/OpusCodec.ts
Normal file
90
js/codec/workers/OpusCodec.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
/// <reference path="CodecWorker.ts" />
|
||||
|
||||
//"js/TeaWeb-Native.js", "asm/generated/TeaWeb-Native.js"
|
||||
try {
|
||||
importScripts("../TeaWeb-Native.js");
|
||||
} catch (e) {
|
||||
try {
|
||||
importScripts("../../asm/generated/TeaWeb-Native.js");
|
||||
} catch (e) {
|
||||
console.error("Could not load native script!");
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
enum OpusType {
|
||||
VOIP = 2048,
|
||||
AUDIO = 2049,
|
||||
RESTRICTED_LOWDELAY = 2051
|
||||
}
|
||||
|
||||
class OpusWorker implements CodecWorker {
|
||||
private channelCount: number;
|
||||
private nativeHandle: any;
|
||||
private type: OpusType;
|
||||
|
||||
private fn_newHandle: any;
|
||||
private fn_decode: any;
|
||||
private fn_encode: any;
|
||||
private fn_reset: any;
|
||||
|
||||
private bufferSize = 4096 * 2;
|
||||
private encodeBufferRaw: any;
|
||||
private encodeBuffer: Float32Array;
|
||||
private decodeBufferRaw: any;
|
||||
private decodeBuffer: Uint8Array;
|
||||
|
||||
constructor(channelCount: number, type: OpusType) {
|
||||
this.channelCount = channelCount;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
name(): string {
|
||||
return "Opus (Type: " + OpusWorker[this.type] + " Channels: " + this.channelCount + ")";
|
||||
}
|
||||
|
||||
initialise() {
|
||||
this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "pointer", ["number", "number"]);
|
||||
this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
/* codec_opus_decode(handle, buffer, length, maxlength) */
|
||||
this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["pointer", "pointer", "number", "number"]);
|
||||
this.fn_reset = Module.cwrap("codec_opus_reset", "number", ["pointer"]);
|
||||
|
||||
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
|
||||
|
||||
this.encodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.encodeBuffer = new Float32Array(Module.HEAPF32.buffer, this.encodeBufferRaw, this.bufferSize / 4);
|
||||
|
||||
this.decodeBufferRaw = Module._malloc(this.bufferSize);
|
||||
this.decodeBuffer = new Uint8Array(Module.HEAPU8.buffer, this.decodeBufferRaw, this.bufferSize);
|
||||
}
|
||||
|
||||
deinitialise() { } //TODO
|
||||
|
||||
decode(data: Uint8Array): Float32Array | string {
|
||||
if (data.byteLength > this.decodeBuffer.byteLength) return "Data to long!";
|
||||
this.decodeBuffer.set(data);
|
||||
//console.log("decode(" + data.length + ")");
|
||||
//console.log(data);
|
||||
let result = this.fn_decode(this.nativeHandle, this.decodeBuffer.byteOffset, data.byteLength, this.decodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
return "invalid result on decode (" + result + ")";
|
||||
}
|
||||
return Module.HEAPF32.slice(this.decodeBuffer.byteOffset / 4, (this.decodeBuffer.byteOffset / 4) + (result * this.channelCount));
|
||||
}
|
||||
|
||||
encode(data: Float32Array): Uint8Array | string {
|
||||
this.encodeBuffer.set(data);
|
||||
|
||||
let result = this.fn_encode(this.nativeHandle, this.encodeBuffer.byteOffset, data.length, this.encodeBuffer.byteLength);
|
||||
if (result < 0) {
|
||||
return "invalid result on encode (" + result + ")";
|
||||
}
|
||||
let buf = Module.HEAP8.slice(this.encodeBuffer.byteOffset, this.encodeBuffer.byteOffset + result);
|
||||
return Uint8Array.from(buf);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.fn_reset(this.nativeHandle);
|
||||
}
|
||||
}
|
|
@ -18,10 +18,10 @@ class ServerConnection {
|
|||
this._connectionState = ConnectionState.UNCONNECTED;
|
||||
this._connectTimeoutHandler = undefined;
|
||||
this.on_connect = () => {
|
||||
console.log("Client connected!");
|
||||
chat.serverChat().appendMessage("Client connected");
|
||||
console.log("Socket connected");
|
||||
chat.serverChat().appendMessage("Logging in...");
|
||||
this._handshakeHandler.startHandshake();
|
||||
};
|
||||
this.on_connected = () => { };
|
||||
this._client = client;
|
||||
this._socket = null;
|
||||
this.commandHandler = new ConnectionCommandHandler(this);
|
||||
|
@ -31,7 +31,7 @@ class ServerConnection {
|
|||
generateReturnCode() {
|
||||
return (this._retCodeIdx++).toString();
|
||||
}
|
||||
startConnection(host, port, timeout = 1000) {
|
||||
startConnection(host, port, handshake, timeout = 1000) {
|
||||
if (this._connectTimeoutHandler) {
|
||||
clearTimeout(this._connectTimeoutHandler);
|
||||
this._connectTimeoutHandler = null;
|
||||
|
@ -40,23 +40,28 @@ class ServerConnection {
|
|||
this.updateConnectionState(ConnectionState.CONNECTING);
|
||||
this._remoteHost = host;
|
||||
this._remotePort = port;
|
||||
this._handshakeHandler = handshake;
|
||||
this._handshakeHandler.setConnection(this);
|
||||
chat.serverChat().appendMessage("Connecting to " + host + ":" + port);
|
||||
const self = this;
|
||||
try {
|
||||
this._connectTimeoutHandler = setTimeout(() => {
|
||||
this.disconnect();
|
||||
this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE);
|
||||
}, timeout);
|
||||
this._socket = new WebSocket('wss:' + this._remoteHost + ":" + this._remotePort);
|
||||
let sockCpy;
|
||||
this._socket = (sockCpy = new WebSocket('wss:' + this._remoteHost + ":" + this._remotePort));
|
||||
clearTimeout(this._connectTimeoutHandler);
|
||||
this._connectTimeoutHandler = null;
|
||||
const _socketCpy = this._socket;
|
||||
if (this._socket != sockCpy)
|
||||
return; //Connect timeouted
|
||||
this._socket.onopen = () => {
|
||||
if (this._socket != _socketCpy)
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
this.on_connect();
|
||||
};
|
||||
this._socket.onclose = event => {
|
||||
if (this._socket != _socketCpy)
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
this._client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
|
||||
code: event.code,
|
||||
|
@ -65,13 +70,13 @@ class ServerConnection {
|
|||
});
|
||||
};
|
||||
this._socket.onerror = e => {
|
||||
if (this._socket != _socketCpy)
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
console.log("Got error: (" + self._socket.readyState + ")");
|
||||
console.log(e);
|
||||
};
|
||||
this._socket.onmessage = msg => {
|
||||
if (this._socket != _socketCpy)
|
||||
if (this._socket != sockCpy)
|
||||
return;
|
||||
self.handleWebSocketMessage(msg.data);
|
||||
};
|
||||
|
@ -203,6 +208,53 @@ class ServerConnection {
|
|||
return this.sendCommand("clientupdate", data);
|
||||
}
|
||||
}
|
||||
class HandshakeHandler {
|
||||
constructor(identity, name) {
|
||||
this.identity = identity;
|
||||
this.name = name;
|
||||
}
|
||||
setConnection(con) {
|
||||
this.connection = con;
|
||||
this.connection.commandHandler["handshakeidentityproof"] = this.handleCommandHandshakeIdentityProof.bind(this);
|
||||
}
|
||||
startHandshake() {
|
||||
let data = {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type()
|
||||
};
|
||||
if (this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
data.publicKey = this.identity.publicKey();
|
||||
}
|
||||
else if (this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
data.data = this.identity.identityDataJson;
|
||||
}
|
||||
this.connection.sendCommand("handshakebegin", data).catch(error => {
|
||||
console.log(error);
|
||||
//TODO here
|
||||
});
|
||||
}
|
||||
handleCommandHandshakeIdentityProof(json) {
|
||||
let proof;
|
||||
if (this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
proof = this.identity.signMessage(json[0]["message"]);
|
||||
}
|
||||
else if (this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
proof = this.identity.identitySign;
|
||||
}
|
||||
this.connection.sendCommand("handshakeindentityproof", { proof: proof }).then(() => {
|
||||
this.connection.sendCommand("clientinit", {
|
||||
//TODO variables!
|
||||
client_nickname: this.name ? this.name : this.identity.name(),
|
||||
client_platform: navigator.platform,
|
||||
client_version: navigator.userAgent,
|
||||
client_browser_engine: navigator.product
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error("Got login error");
|
||||
console.log(error);
|
||||
}); //TODO handle error
|
||||
}
|
||||
}
|
||||
class ConnectionCommandHandler {
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
|
@ -257,7 +309,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));
|
||||
this.connection.on_connected();
|
||||
globalClient.onConnected();
|
||||
}
|
||||
createChannelFromJson(json, ignoreOrder = false) {
|
||||
let tree = this.connection._client.channelTree;
|
||||
|
@ -483,14 +535,18 @@ class ConnectionCommandHandler {
|
|||
json = json[0]; //Only one bulk
|
||||
//TODO chat format?
|
||||
let mode = json["targetmode"];
|
||||
let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
|
||||
if (!invoker) {
|
||||
console.error("Invalid chat message!");
|
||||
return;
|
||||
}
|
||||
if (mode == 1) {
|
||||
let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
|
||||
let target = this.connection._client.channelTree.findClient(json["target"]);
|
||||
if (!invoker) {
|
||||
console.error("Got private message from invalid client!");
|
||||
return;
|
||||
}
|
||||
if (!target) {
|
||||
console.error("Got private message from invalid client!");
|
||||
return;
|
||||
}
|
||||
if (invoker == this.connection._client.getClient()) {
|
||||
let target = this.connection._client.channelTree.findClient(json["target"]);
|
||||
target.chat(true).appendMessage("<< " + json["msg"]);
|
||||
}
|
||||
else {
|
||||
|
@ -498,10 +554,10 @@ class ConnectionCommandHandler {
|
|||
}
|
||||
}
|
||||
else if (mode == 2) {
|
||||
chat.channelChat().appendMessage("{0} >> " + json["msg"], true, invoker.createChatTag(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} >> " + json["msg"], true, invoker.createChatTag(true));
|
||||
chat.serverChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
|
||||
}
|
||||
}
|
||||
handleNotifyClientUpdated(json) {
|
||||
|
|
File diff suppressed because one or more lines are too long
105
js/connection.ts
105
js/connection.ts
|
@ -35,6 +35,7 @@ class ServerConnection {
|
|||
_remotePort: number;
|
||||
_socket: WebSocket;
|
||||
_connectionState: ConnectionState = ConnectionState.UNCONNECTED;
|
||||
_handshakeHandler: HandshakeHandler;
|
||||
commandHandler: ConnectionCommandHandler;
|
||||
|
||||
private _connectTimeoutHandler: NodeJS.Timer = undefined;
|
||||
|
@ -52,17 +53,16 @@ class ServerConnection {
|
|||
}
|
||||
|
||||
on_connect: () => void = () => {
|
||||
console.log("Client connected!");
|
||||
chat.serverChat().appendMessage("Client connected");
|
||||
console.log("Socket connected");
|
||||
chat.serverChat().appendMessage("Logging in...");
|
||||
this._handshakeHandler.startHandshake();
|
||||
};
|
||||
|
||||
on_connected: () => void = () => {};
|
||||
|
||||
private generateReturnCode() : string {
|
||||
return (this._retCodeIdx++).toString();
|
||||
}
|
||||
|
||||
startConnection(host : string, port : number, timeout: number = 1000) {
|
||||
startConnection(host : string, port : number, handshake: HandshakeHandler, timeout: number = 1000) {
|
||||
if(this._connectTimeoutHandler) {
|
||||
clearTimeout(this._connectTimeoutHandler);
|
||||
this._connectTimeoutHandler = null;
|
||||
|
@ -71,6 +71,9 @@ class ServerConnection {
|
|||
this.updateConnectionState(ConnectionState.CONNECTING);
|
||||
this._remoteHost = host;
|
||||
this._remotePort = port;
|
||||
this._handshakeHandler = handshake;
|
||||
this._handshakeHandler.setConnection(this);
|
||||
chat.serverChat().appendMessage("Connecting to " + host + ":" + port);
|
||||
|
||||
const self = this;
|
||||
try {
|
||||
|
@ -78,18 +81,19 @@ class ServerConnection {
|
|||
this.disconnect();
|
||||
this._client.handleDisconnect(DisconnectReason.CONNECT_FAILURE);
|
||||
}, timeout);
|
||||
this._socket = new WebSocket('wss:' + this._remoteHost + ":" + this._remotePort);
|
||||
let sockCpy;
|
||||
this._socket = (sockCpy = new WebSocket('wss:' + this._remoteHost + ":" + this._remotePort));
|
||||
clearTimeout(this._connectTimeoutHandler);
|
||||
this._connectTimeoutHandler = null;
|
||||
if(this._socket != sockCpy) return; //Connect timeouted
|
||||
|
||||
const _socketCpy = this._socket;
|
||||
this._socket.onopen = () => {
|
||||
if(this._socket != _socketCpy) return;
|
||||
if(this._socket != sockCpy) return;
|
||||
this.on_connect();
|
||||
};
|
||||
|
||||
this._socket.onclose = event => {
|
||||
if(this._socket != _socketCpy) return;
|
||||
if(this._socket != sockCpy) return;
|
||||
this._client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
|
@ -98,13 +102,13 @@ class ServerConnection {
|
|||
};
|
||||
|
||||
this._socket.onerror = e => {
|
||||
if(this._socket != _socketCpy) return;
|
||||
if(this._socket != sockCpy) return;
|
||||
console.log("Got error: (" + self._socket.readyState + ")");
|
||||
console.log(e);
|
||||
};
|
||||
|
||||
this._socket.onmessage = msg => {
|
||||
if(this._socket != _socketCpy) return;
|
||||
if(this._socket != sockCpy) return;
|
||||
self.handleWebSocketMessage(msg.data);
|
||||
};
|
||||
this.updateConnectionState(ConnectionState.INITIALISING);
|
||||
|
@ -242,12 +246,65 @@ class ServerConnection {
|
|||
}
|
||||
}
|
||||
|
||||
class HandshakeHandler {
|
||||
readonly identity: Identity;
|
||||
readonly name?: string;
|
||||
private connection: ServerConnection;
|
||||
|
||||
constructor(identity: Identity, name?: string) {
|
||||
this.identity = identity;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
setConnection(con: ServerConnection) {
|
||||
this.connection = con;
|
||||
this.connection.commandHandler["handshakeidentityproof"] = this.handleCommandHandshakeIdentityProof.bind(this);
|
||||
}
|
||||
|
||||
startHandshake() {
|
||||
let data: any = {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type()
|
||||
};
|
||||
if(this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
data.publicKey = (this.identity as TeamSpeakIdentity).publicKey();
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
data.data = (this.identity as TeaForumIdentity).identityDataJson;
|
||||
}
|
||||
|
||||
this.connection.sendCommand("handshakebegin", data).catch(error => {
|
||||
console.log(error);
|
||||
//TODO here
|
||||
});
|
||||
}
|
||||
|
||||
private handleCommandHandshakeIdentityProof(json) {
|
||||
let proof: string;
|
||||
if(this.identity.type() == IdentitifyType.TEAMSPEAK) {
|
||||
proof = (this.identity as TeamSpeakIdentity).signMessage(json[0]["message"]);
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
proof = (this.identity as TeaForumIdentity).identitySign;
|
||||
}
|
||||
this.connection.sendCommand("handshakeindentityproof", {proof: proof}).then(() => {
|
||||
this.connection.sendCommand("clientinit", {
|
||||
//TODO variables!
|
||||
client_nickname: this.name ? this.name : this.identity.name(),
|
||||
client_platform: navigator.platform,
|
||||
client_version: navigator.userAgent,
|
||||
client_browser_engine: navigator.product
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error("Got login error");
|
||||
console.log(error);
|
||||
}); //TODO handle error
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionCommandHandler {
|
||||
readonly connection: ServerConnection;
|
||||
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
|
||||
this["error"] = this.handleCommandResult;
|
||||
this["channellist"] = this.handleCommandChannelList;
|
||||
this["notifychannelcreated"] = this.handleCommandChannelCreate;
|
||||
|
@ -278,7 +335,6 @@ class ConnectionCommandHandler {
|
|||
for(let e of retListeners) {
|
||||
if(e.code != code) continue;
|
||||
retListeners.remove(e);
|
||||
|
||||
let result = new CommandResult(json);
|
||||
if(result.success)
|
||||
e.resolve(result);
|
||||
|
@ -306,8 +362,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));
|
||||
|
||||
this.connection.on_connected();
|
||||
globalClient.onConnected();
|
||||
}
|
||||
|
||||
private createChannelFromJson(json, ignoreOrder: boolean = false) {
|
||||
|
@ -562,22 +617,26 @@ class ConnectionCommandHandler {
|
|||
|
||||
//TODO chat format?
|
||||
let mode = json["targetmode"];
|
||||
let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
|
||||
if(!invoker) { //TODO ignore?
|
||||
console.error("Invalid chat message!");
|
||||
return;
|
||||
}
|
||||
if(mode == 1){
|
||||
let invoker = this.connection._client.channelTree.findClient(json["invokerid"]);
|
||||
let target = this.connection._client.channelTree.findClient(json["target"]);
|
||||
if(!invoker) { //TODO spawn chat (Client is may invisible)
|
||||
console.error("Got private message from invalid client!");
|
||||
return;
|
||||
}
|
||||
if(!target) { //TODO spawn chat (Client is may invisible)
|
||||
console.error("Got private message from invalid client!");
|
||||
return;
|
||||
}
|
||||
if(invoker == this.connection._client.getClient()) {
|
||||
let target = this.connection._client.channelTree.findClient(json["target"]);
|
||||
target.chat(true).appendMessage("<< " + json["msg"]);
|
||||
} else {
|
||||
invoker.chat(true).appendMessage(">> " + json["msg"]);
|
||||
}
|
||||
} else if(mode == 2) {
|
||||
chat.channelChat().appendMessage("{0} >> " + json["msg"], true, invoker.createChatTag(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} >> " + json["msg"], true, invoker.createChatTag(true));
|
||||
chat.serverChat().appendMessage("{0} >> {1}", true, ClientEntry.chatTag(json["invokerid"], json["invokername"], json["invokeruid"], true), json["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,13 +50,11 @@ class MenuEntry {
|
|||
};
|
||||
}
|
||||
}
|
||||
;
|
||||
function spawnMenu(x, y, ...entries) {
|
||||
var menu = $("#contextMenu");
|
||||
const menu = $("#contextMenu");
|
||||
menu.empty();
|
||||
menu.hide();
|
||||
contextMenuCloseFn = undefined;
|
||||
console.log(" -> " + ($.isArray(entries) ? "yes" : "no"));
|
||||
let index = 0;
|
||||
for (let entry of entries) {
|
||||
if (entry.type == MenuEntryType.HR) {
|
||||
|
@ -75,11 +73,15 @@ function spawnMenu(x, y, ...entries) {
|
|||
tag.append("<div class='" + icon + "'></div>");
|
||||
tag.append("<div>" + ($.isFunction(entry.name) ? entry.name() : entry.name) + "</div>");
|
||||
menu.append(tag);
|
||||
tag.click(function () {
|
||||
if ($.isFunction(entry.callback))
|
||||
entry.callback();
|
||||
despawnContextMenu();
|
||||
});
|
||||
if (entry.disabled)
|
||||
tag.addClass("disabled");
|
||||
else {
|
||||
tag.click(function () {
|
||||
if ($.isFunction(entry.callback))
|
||||
entry.callback();
|
||||
despawnContextMenu();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.show(100);
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"contextMenu.js","sourceRoot":"","sources":["contextMenu.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"contextMenu.js","sourceRoot":"","sources":["contextMenu.ts"],"names":[],"mappings":"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"}
|
|
@ -49,21 +49,21 @@ class MenuEntry {
|
|||
icon: ""
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function spawnMenu(x, y, ...entries: {
|
||||
callback: () => void;
|
||||
type: MenuEntryType;
|
||||
name: (() => string) | string;
|
||||
icon: (() => string) | string;
|
||||
disabled?: boolean;
|
||||
}[]) {
|
||||
var menu = $("#contextMenu");
|
||||
const menu = $("#contextMenu");
|
||||
menu.empty();
|
||||
menu.hide();
|
||||
|
||||
contextMenuCloseFn = undefined;
|
||||
|
||||
console.log(" -> " + ($.isArray(entries) ? "yes" : "no"));
|
||||
let index = 0;
|
||||
|
||||
for(let entry of entries){
|
||||
|
@ -79,12 +79,16 @@ function spawnMenu(x, y, ...entries: {
|
|||
let tag = $.spawn("li");
|
||||
tag.append("<div class='" + icon + "'></div>");
|
||||
tag.append("<div>" + ($.isFunction(entry.name) ? entry.name() : entry.name) + "</div>");
|
||||
|
||||
menu.append(tag);
|
||||
|
||||
tag.click(function () {
|
||||
if($.isFunction(entry.callback)) entry.callback();
|
||||
despawnContextMenu();
|
||||
});
|
||||
if(entry.disabled) tag.addClass("disabled");
|
||||
else {
|
||||
tag.click(function () {
|
||||
if($.isFunction(entry.callback)) entry.callback();
|
||||
despawnContextMenu();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
19
js/load.ts
19
js/load.ts
|
@ -77,6 +77,7 @@ function loadDebug() {
|
|||
"js/ui/modal/ModalSettings.js",
|
||||
"js/ui/modal/ModalCreateChannel.js",
|
||||
"js/ui/modal/ModalConnect.js",
|
||||
"js/ui/modal/ModalChangeVolume.js",
|
||||
"js/ui/channel.js",
|
||||
"js/ui/client.js",
|
||||
"js/ui/server.js",
|
||||
|
@ -95,6 +96,8 @@ function loadDebug() {
|
|||
|
||||
//Load codec
|
||||
"js/codec/Codec.js",
|
||||
"js/codec/BasicCodec.js",
|
||||
"js/codec/CodecWrapper.js",
|
||||
|
||||
//Load general stuff
|
||||
"js/settings.js",
|
||||
|
@ -103,7 +106,8 @@ function loadDebug() {
|
|||
"js/FileManager.js",
|
||||
"js/client.js",
|
||||
"js/chat.js",
|
||||
"js/InfoBar.js"
|
||||
"js/InfoBar.js",
|
||||
"js/Identity.js"
|
||||
])).then(() => {
|
||||
awaitLoad(loadScripts(["js/main.js"])).then(() => {
|
||||
console.log("Loaded successfully all scripts!");
|
||||
|
@ -120,7 +124,14 @@ function awaitLoad(promises: {path: string, promise: Promise<Boolean>}[]) : Prom
|
|||
awaiting--;
|
||||
if(awaiting == 0) resolve();
|
||||
}).catch(error => {
|
||||
console.error("Failed to load script " + entry.path);
|
||||
if(error instanceof TypeError) {
|
||||
console.error(error);
|
||||
let name = (error as any).fileName + "@" + (error as any).lineNumber + ":" + (error as any).columnNumber;
|
||||
displayCriticalError("Failed to execute script <code>" + name + "</code>.<hr>If you believe that it isn't you're mistake<br>then please contact an administrator!", false);
|
||||
return;
|
||||
} else {
|
||||
console.error("Failed to load script " + entry.path);
|
||||
}
|
||||
displayCriticalError("Failed to load script " + formatPath(entry.path) + ".<hr>If you believe that it isn't you're mistake<br>then please contact an administrator!", false);
|
||||
})
|
||||
}
|
||||
|
@ -170,8 +181,8 @@ function loadSide() {
|
|||
["vendor/jquery/jquery.min.js", /*"https://code.jquery.com/jquery-latest.min.js"*/],
|
||||
["https://webrtc.github.io/adapter/adapter-latest.js"]
|
||||
])).then(() => awaitLoad(loadScripts([
|
||||
["https://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"],
|
||||
["js/TeaWeb-Native.js", "asm/generated/TeaWeb-Native.js"]
|
||||
["asm/generated/TeaWeb-Native.js", "js/TeaWeb-Native.js"],
|
||||
["https://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"]
|
||||
]))).then(() => {
|
||||
//Load the teaweb scripts
|
||||
loadScript("js/proto.js").then(loadDebug).catch(error => {
|
||||
|
|
15
js/main.js
15
js/main.js
|
@ -1,11 +1,24 @@
|
|||
/// <reference path="chat.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="Identity.ts" />
|
||||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="codec/CodecWrapper.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
let globalClient;
|
||||
let chat;
|
||||
let forumIdentity;
|
||||
function invokeMain() {
|
||||
AudioController.initializeAudioController();
|
||||
if (!TSIdentityHelper.setup()) {
|
||||
console.error("Could not setup the TeamSpeak identity parser!");
|
||||
return;
|
||||
}
|
||||
globalClient = new TSClient();
|
||||
/** Setup the XF forum identity **/
|
||||
if (globalClient.settings.static("forum_user_data")) {
|
||||
forumIdentity = new TeaForumIdentity(globalClient.settings.static("forum_user_data"), globalClient.settings.static("forum_user_sign"));
|
||||
}
|
||||
chat = new ChatBox($("#chat"));
|
||||
globalClient.setup();
|
||||
//globalClient.startConnection("localhost:19974"); //TODO remove only for testing
|
||||
|
@ -16,5 +29,7 @@ function invokeMain() {
|
|||
event.returnValue = "Are you really sure?<br>You're still connected!";
|
||||
//event.preventDefault();
|
||||
});
|
||||
//console.log("XF: " + globalClient.settings.static("forum_user_data"));
|
||||
//console.log("XF: " + globalClient.settings.static("forum_user_sign"));
|
||||
}
|
||||
//# sourceMappingURL=main.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,kCAAkC;AAClC,uCAAuC;AACvC,iDAAiD;AAEjD,IAAI,YAAY,CAAC;AACjB,IAAI,IAAI,CAAC;AAET;IACI,YAAY,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC9B,IAAI,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,YAAY,CAAC,KAAK,EAAE,CAAC;IACrB,iFAAiF;IAEjF,6BAA6B;IAC7B,8BAA8B;IAE9B,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,UAAU,KAAK;QACnD,EAAE,CAAA,CAAC,YAAY,CAAC,gBAAgB,IAAI,YAAY,CAAC,gBAAgB,CAAC,SAAS,CAAC;YACxE,KAAK,CAAC,WAAW,GAAG,iDAAiD,CAAC;QAC1E,yBAAyB;IAC7B,CAAC,CAAC,CAAC;AACP,CAAC"}
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,kCAAkC;AAClC,oCAAoC;AACpC,uCAAuC;AACvC,iDAAiD;AACjD,8CAA8C;AAC9C,oCAAoC;AAEpC,IAAI,YAAsB,CAAC;AAC3B,IAAI,IAAa,CAAC;AAElB,IAAI,aAA+B,CAAC;AAEpC;IACI,eAAe,CAAC,yBAAyB,EAAE,CAAC;IAC5C,EAAE,CAAA,CAAC,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAAC,MAAM,CAAC;IAAC,CAAC;IAE1G,YAAY,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC9B,mCAAmC;IACnC,EAAE,CAAA,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACjD,aAAa,GAAG,IAAI,gBAAgB,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC3I,CAAC;IAED,IAAI,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,YAAY,CAAC,KAAK,EAAE,CAAC;IACrB,iFAAiF;IAEjF,6BAA6B;IAC7B,8BAA8B;IAE9B,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,UAAU,KAAK;QACnD,EAAE,CAAA,CAAC,YAAY,CAAC,gBAAgB,IAAI,YAAY,CAAC,gBAAgB,CAAC,SAAS,CAAC;YACxE,KAAK,CAAC,WAAW,GAAG,iDAAiD,CAAC;QAC1E,yBAAyB;IAC7B,CAAC,CAAC,CAAC;IAGH,wEAAwE;IACxE,wEAAwE;AAC5E,CAAC"}
|
21
js/main.ts
21
js/main.ts
|
@ -1,13 +1,26 @@
|
|||
/// <reference path="chat.ts" />
|
||||
/// <reference path="client.ts" />
|
||||
/// <reference path="Identity.ts" />
|
||||
/// <reference path="utils/modal.ts" />
|
||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
||||
/// <reference path="codec/CodecWrapper.ts" />
|
||||
/// <reference path="settings.ts" />
|
||||
|
||||
let globalClient;
|
||||
let chat;
|
||||
let globalClient: TSClient;
|
||||
let chat: ChatBox;
|
||||
|
||||
let forumIdentity: TeaForumIdentity;
|
||||
|
||||
function invokeMain() {
|
||||
AudioController.initializeAudioController();
|
||||
if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; }
|
||||
|
||||
globalClient = new TSClient();
|
||||
/** Setup the XF forum identity **/
|
||||
if(globalClient.settings.static("forum_user_data")) {
|
||||
forumIdentity = new TeaForumIdentity(globalClient.settings.static("forum_user_data"), globalClient.settings.static("forum_user_sign"));
|
||||
}
|
||||
|
||||
chat = new ChatBox($("#chat"));
|
||||
globalClient.setup();
|
||||
//globalClient.startConnection("localhost:19974"); //TODO remove only for testing
|
||||
|
@ -20,4 +33,8 @@ function invokeMain() {
|
|||
event.returnValue = "Are you really sure?<br>You're still connected!";
|
||||
//event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
//console.log("XF: " + globalClient.settings.static("forum_user_data"));
|
||||
//console.log("XF: " + globalClient.settings.static("forum_user_sign"));
|
||||
}
|
13
js/proto.js
13
js/proto.js
|
@ -22,14 +22,17 @@ if (!Array.prototype.last) {
|
|||
return this[this.length - 1];
|
||||
};
|
||||
}
|
||||
if (!$.spawn) {
|
||||
$.spawn = function (tagName) {
|
||||
return $(document.createElement(tagName));
|
||||
};
|
||||
if (typeof ($) !== "undefined") {
|
||||
if (!$.spawn) {
|
||||
$.spawn = function (tagName) {
|
||||
return $(document.createElement(tagName));
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!String.prototype.format) {
|
||||
String.prototype.format = function () {
|
||||
const args = arguments;
|
||||
let array = args.length == 1 && $.isArray(args[0]);
|
||||
return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) {
|
||||
if (m == "{{") {
|
||||
return "{";
|
||||
|
@ -37,7 +40,7 @@ if (!String.prototype.format) {
|
|||
if (m == "}}") {
|
||||
return "}";
|
||||
}
|
||||
return args[n];
|
||||
return array ? args[0][n] : args[n];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"proto.js","sourceRoot":"","sources":["proto.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"proto.js","sourceRoot":"","sources":["proto.ts"],"names":[],"mappings":"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"}
|
14
js/proto.ts
14
js/proto.ts
|
@ -13,8 +13,11 @@ interface JQueryStatic<TElement extends Node = HTMLElement> {
|
|||
spawn<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface String {
|
||||
format(...fmt): string;
|
||||
format(arguments: string[]): string;
|
||||
}
|
||||
|
||||
if (!Array.prototype.remove) {
|
||||
|
@ -42,19 +45,22 @@ if (!Array.prototype.last){
|
|||
};
|
||||
}
|
||||
|
||||
if(!$.spawn) {
|
||||
$.spawn = function<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]> {
|
||||
return $(document.createElement(tagName));
|
||||
if(typeof ($) !== "undefined") {
|
||||
if(!$.spawn) {
|
||||
$.spawn = function<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]> {
|
||||
return $(document.createElement(tagName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.prototype.format) {
|
||||
String.prototype.format = function() {
|
||||
const args = arguments;
|
||||
let array = args.length == 1 && $.isArray(args[0]);
|
||||
return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) {
|
||||
if (m == "{{") { return "{"; }
|
||||
if (m == "}}") { return "}"; }
|
||||
return args[n];
|
||||
return array ? args[0][n] : args[n];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,8 +25,9 @@ class Settings {
|
|||
let result = this.cacheGlobal[key];
|
||||
return result ? result : _default;
|
||||
}
|
||||
server(key) {
|
||||
return this.cacheServer[key];
|
||||
server(key, _default) {
|
||||
let result = this.cacheServer[key];
|
||||
return result ? result : _default;
|
||||
}
|
||||
changeGlobal(key, value) {
|
||||
if (this.cacheGlobal[key] == value)
|
||||
|
@ -45,7 +46,7 @@ class Settings {
|
|||
this.save();
|
||||
}
|
||||
loadServer() {
|
||||
if (this.handle.channelTree.server) {
|
||||
if (!this.handle.channelTree.server) {
|
||||
this.cacheServer = {};
|
||||
console.warn("[Settings] tried to load settings for unknown server");
|
||||
return;
|
||||
|
@ -69,7 +70,12 @@ class Settings {
|
|||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if (result.length == 0)
|
||||
return _default;
|
||||
return result.attr("value");
|
||||
return decodeURIComponent(result.attr("value"));
|
||||
}
|
||||
deleteStatic(key) {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if (result.length != 0)
|
||||
result.detach();
|
||||
}
|
||||
}
|
||||
Settings.UPDATE_DIRECT = true;
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"settings.js","sourceRoot":"","sources":["settings.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"settings.js","sourceRoot":"","sources":["settings.ts"],"names":[],"mappings":"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"}
|
|
@ -34,8 +34,9 @@ class Settings {
|
|||
return result ? result : _default;
|
||||
}
|
||||
|
||||
server?(key: string) : string {
|
||||
return this.cacheServer[key];
|
||||
server?(key: string, _default?: string) : string {
|
||||
let result = this.cacheServer[key];
|
||||
return result ? result : _default;
|
||||
}
|
||||
|
||||
changeGlobal(key: string, value?: string){
|
||||
|
@ -59,7 +60,7 @@ class Settings {
|
|||
}
|
||||
|
||||
loadServer() {
|
||||
if(this.handle.channelTree.server) {
|
||||
if(!this.handle.channelTree.server) {
|
||||
this.cacheServer = {};
|
||||
console.warn("[Settings] tried to load settings for unknown server");
|
||||
return;
|
||||
|
@ -86,6 +87,11 @@ class Settings {
|
|||
static?(key: string, _default: string = undefined) : string {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if(result.length == 0) return _default;
|
||||
return result.attr("value");
|
||||
return decodeURIComponent(result.attr("value"));
|
||||
}
|
||||
|
||||
deleteStatic(key: string) {
|
||||
let result = this._staticPropsTag.find("[key='" + key + "']");
|
||||
if(result.length != 0) result.detach();
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ class ControlBar {
|
|||
this.handle.settings.changeGlobal("mute_input", this._muteInput ? "1" : "0");
|
||||
this.updateMicrophoneRecordState();
|
||||
}
|
||||
get muteOutput() { return this._muteOutput; }
|
||||
set muteOutput(flag) {
|
||||
if (this._muteOutput == flag)
|
||||
return;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -75,6 +75,8 @@ class ControlBar {
|
|||
this.updateMicrophoneRecordState();
|
||||
}
|
||||
|
||||
get muteOutput() : boolean { return this._muteOutput; }
|
||||
|
||||
set muteOutput(flag: boolean) {
|
||||
if(this._muteOutput == flag) return;
|
||||
this._muteOutput = flag;
|
||||
|
|
|
@ -116,14 +116,14 @@ class ChannelEntry {
|
|||
return this.htmlTag.find(".clients").last(); //Here last because from the sibling tag client tags could be before
|
||||
}
|
||||
adjustSize(parent = true) {
|
||||
var size = this.originalHeight;
|
||||
var subSize = 0;
|
||||
var clientSize = 0;
|
||||
var sub = this.siblings(false);
|
||||
const size = this.originalHeight;
|
||||
let subSize = 0;
|
||||
let clientSize = 0;
|
||||
const sub = this.siblings(false);
|
||||
sub.forEach(function (e) {
|
||||
subSize += e.htmlTag.outerHeight(true);
|
||||
});
|
||||
var clients = this.clients(false);
|
||||
const clients = this.clients(false);
|
||||
clients.forEach(function (e) {
|
||||
clientSize += e.htmlTag.outerHeight(true);
|
||||
});
|
||||
|
@ -164,6 +164,7 @@ class ChannelEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_create_sub",
|
||||
name: "Create sub channel",
|
||||
disabled: true,
|
||||
callback: () => {
|
||||
//TODO here
|
||||
}
|
||||
|
@ -171,6 +172,7 @@ class ChannelEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_create",
|
||||
name: "Create channel",
|
||||
disabled: true,
|
||||
callback: () => {
|
||||
//TODO here
|
||||
}
|
||||
|
@ -291,7 +293,7 @@ class ChannelEntry {
|
|||
tag.attr("oncontextmenu", "chat_channel_contextmenu(this, ...arguments);");
|
||||
tag.attr("channelId", this.channelId);
|
||||
tag.attr("channelName", this.channelName());
|
||||
return tag.wrap("<p/>").parent().html();
|
||||
return tag.wrap("<p/>").parent();
|
||||
}
|
||||
channelType() {
|
||||
if (this.properties.channel_flag_permanent == "1")
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -143,16 +143,16 @@ class ChannelEntry {
|
|||
}
|
||||
|
||||
adjustSize(parent = true) {
|
||||
var size = this.originalHeight;
|
||||
var subSize = 0;
|
||||
var clientSize = 0;
|
||||
const size = this.originalHeight;
|
||||
let subSize = 0;
|
||||
let clientSize = 0;
|
||||
|
||||
var sub = this.siblings(false);
|
||||
const sub = this.siblings(false);
|
||||
sub.forEach(function (e) {
|
||||
subSize += e.htmlTag.outerHeight(true);
|
||||
});
|
||||
|
||||
var clients = this.clients(false);
|
||||
const clients = this.clients(false);
|
||||
clients.forEach(function (e) {
|
||||
clientSize += e.htmlTag.outerHeight(true);
|
||||
});
|
||||
|
@ -197,6 +197,7 @@ class ChannelEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_create_sub",
|
||||
name: "Create sub channel",
|
||||
disabled: true,
|
||||
callback: () => {
|
||||
//TODO here
|
||||
}
|
||||
|
@ -204,6 +205,7 @@ class ChannelEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_create",
|
||||
name: "Create channel",
|
||||
disabled: true,
|
||||
callback: () => {
|
||||
//TODO here
|
||||
}
|
||||
|
@ -313,7 +315,7 @@ class ChannelEntry {
|
|||
}
|
||||
}
|
||||
|
||||
createChatTag(braces: boolean = false) : string {
|
||||
createChatTag(braces: boolean = false) : JQuery {
|
||||
let tag = $.spawn("div");
|
||||
|
||||
tag.css("display", "table");
|
||||
|
@ -327,7 +329,7 @@ class ChannelEntry {
|
|||
tag.attr("oncontextmenu", "chat_channel_contextmenu(this, ...arguments);");
|
||||
tag.attr("channelId", this.channelId);
|
||||
tag.attr("channelName", this.channelName());
|
||||
return tag.wrap("<p/>").parent().html();
|
||||
return tag.wrap("<p/>").parent();
|
||||
}
|
||||
|
||||
channelType() : ChannelType {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
class ClientEntry {
|
||||
constructor(clientId, clientName) {
|
||||
this.properties = {
|
||||
|
@ -22,6 +23,7 @@ class ClientEntry {
|
|||
this.audioController.onSilence = function () {
|
||||
_this.speaking = false;
|
||||
};
|
||||
this.audioController.initialize();
|
||||
}
|
||||
currentChannel() { return this._channel; }
|
||||
clientNickName() { return this.properties.client_nickname; }
|
||||
|
@ -85,6 +87,16 @@ class ClientEntry {
|
|||
}, { width: 400, maxLength: 1024 }).open();
|
||||
}
|
||||
}, 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",
|
||||
|
@ -116,6 +128,24 @@ class ClientEntry {
|
|||
}
|
||||
}, { width: 400, maxLength: 255 }).open();
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-ban_client",
|
||||
name: "Ban client",
|
||||
disabled: true,
|
||||
callback: () => { }
|
||||
}, MenuEntry.HR(), {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-volume",
|
||||
name: "Change Volume",
|
||||
callback: () => {
|
||||
Modals.spawnChangeVolume(this.audioController.volume, volume => {
|
||||
globalClient.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this.audioController.volume = volume;
|
||||
if (globalClient.selectInfo.currentSelected == this)
|
||||
globalClient.selectInfo.update();
|
||||
});
|
||||
}
|
||||
}, MenuEntry.CLOSE(on_close));
|
||||
}
|
||||
get htmlTag() {
|
||||
|
@ -148,7 +178,7 @@ class ClientEntry {
|
|||
tag.attr("clientId", id);
|
||||
tag.attr("clientUid", uid);
|
||||
tag.attr("clientName", name);
|
||||
return tag.wrap("<p/>").parent().html();
|
||||
return tag.wrap("<p/>").parent();
|
||||
}
|
||||
createChatTag(braces = false) {
|
||||
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
|
||||
|
@ -156,7 +186,6 @@ class ClientEntry {
|
|||
set speaking(flag) {
|
||||
if (flag == this._speaking)
|
||||
return;
|
||||
console.log("SPeakig " + flag);
|
||||
this._speaking = flag;
|
||||
this.updateClientIcon();
|
||||
}
|
||||
|
@ -221,6 +250,10 @@ class ClientEntry {
|
|||
if (key == "client_away_message" || key == "client_away") {
|
||||
this.updateAwayMessage();
|
||||
}
|
||||
if (key == "client_unique_identifier") {
|
||||
this.audioController.volume = parseFloat(globalClient.settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
console.error("Updated volume from config " + this.audioController.volume + " - " + "volume_client_" + this.clientUid() + " - " + globalClient.settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
}
|
||||
}
|
||||
updateVariables() {
|
||||
if (this.lastVariableUpdate == 0 || new Date().getTime() - 10 * 60 * 1000 > this.lastVariableUpdate) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,5 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
|
||||
class ClientEntry {
|
||||
private _clientId: number;
|
||||
|
@ -33,6 +34,7 @@ class ClientEntry {
|
|||
this.audioController.onSilence = function () {
|
||||
_this.speaking = false;
|
||||
};
|
||||
this.audioController.initialize();
|
||||
}
|
||||
|
||||
currentChannel() { return this._channel; }
|
||||
|
@ -104,8 +106,17 @@ class ClientEntry {
|
|||
}, { width: 400, maxLength: 1024 }).open();
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
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",
|
||||
|
@ -139,6 +150,26 @@ class ClientEntry {
|
|||
}
|
||||
}, { width: 400, maxLength: 255 }).open();
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-ban_client",
|
||||
name: "Ban client",
|
||||
disabled: true,
|
||||
callback: () => {}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-volume",
|
||||
name: "Change Volume",
|
||||
callback: () => {
|
||||
Modals.spawnChangeVolume(this.audioController.volume, volume => {
|
||||
globalClient.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||
this.audioController.volume = volume;
|
||||
if(globalClient.selectInfo.currentSelected == this)
|
||||
globalClient.selectInfo.update();
|
||||
});
|
||||
}
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
);
|
||||
|
@ -166,7 +197,7 @@ class ClientEntry {
|
|||
return this._htmlTag = tag;
|
||||
}
|
||||
|
||||
static chatTag(id: number, name: string, uid: string, braces: boolean = false) {
|
||||
static chatTag(id: number, name: string, uid: string, braces: boolean = false) : JQuery {
|
||||
let tag = $.spawn("div");
|
||||
|
||||
tag.css("cursor", "pointer");
|
||||
|
@ -182,16 +213,15 @@ class ClientEntry {
|
|||
tag.attr("clientId", id);
|
||||
tag.attr("clientUid", uid);
|
||||
tag.attr("clientName", name);
|
||||
return tag.wrap("<p/>").parent().html();
|
||||
return tag.wrap("<p/>").parent();
|
||||
}
|
||||
|
||||
createChatTag(braces: boolean = false) : string {
|
||||
createChatTag(braces: boolean = false) : JQuery {
|
||||
return ClientEntry.chatTag(this.clientId(), this.clientNickName(), this.clientUid(), braces);
|
||||
}
|
||||
|
||||
set speaking(flag) {
|
||||
if(flag == this._speaking) return;
|
||||
console.log("SPeakig " + flag);
|
||||
this._speaking = flag;
|
||||
this.updateClientIcon();
|
||||
}
|
||||
|
@ -241,7 +271,7 @@ class ClientEntry {
|
|||
updateVariable(key: string, value: string) {
|
||||
this.properties[key] = value;
|
||||
|
||||
console.debug("Updating client " + this.clientId() + ". Key " + key + " Value: '" + value + "'")
|
||||
console.debug("Updating client " + this.clientId() + ". Key " + key + " Value: '" + value + "'");
|
||||
if(key == "client_nickname") {
|
||||
this.htmlTag.find(".name").text(value);
|
||||
let chat = this.chat(false);
|
||||
|
@ -253,6 +283,10 @@ class ClientEntry {
|
|||
if(key == "client_away_message" || key == "client_away") {
|
||||
this.updateAwayMessage();
|
||||
}
|
||||
if(key == "client_unique_identifier") {
|
||||
this.audioController.volume = parseFloat(globalClient.settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
console.error("Updated volume from config " + this.audioController.volume + " - " + "volume_client_" + this.clientUid() + " - " + globalClient.settings.server("volume_client_" + this.clientUid(), "1"));
|
||||
}
|
||||
}
|
||||
|
||||
updateVariables(){
|
||||
|
|
66
js/ui/modal/ModalChangeVolume.ts
Normal file
66
js/ui/modal/ModalChangeVolume.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnChangeVolume(current: number, callback: (number) => void) {
|
||||
let updateCallback: (number) => void;
|
||||
const connectModal = createModal({
|
||||
header: function() {
|
||||
let header = $.spawn("div");
|
||||
header.text("Change volume");
|
||||
return header;
|
||||
},
|
||||
body: function () {
|
||||
let tag = $("#tmpl_change_volume").tmpl();
|
||||
tag.find(".volume_slider").on("change",_ => updateCallback(tag.find(".volume_slider").val()));
|
||||
tag.find(".volume_slider").on("input",_ => updateCallback(tag.find(".volume_slider").val()));
|
||||
//connect_address
|
||||
return tag;
|
||||
},
|
||||
footer: function () {
|
||||
let tag = $.spawn("div");
|
||||
tag.css("text-align", "right");
|
||||
tag.css("margin-top", "3px");
|
||||
tag.css("margin-bottom", "6px");
|
||||
tag.addClass("modal-button-group");
|
||||
|
||||
|
||||
let buttonReset = $.spawn("button");
|
||||
buttonReset.text("Reset");
|
||||
buttonReset.on("click", function () {
|
||||
updateCallback(100);
|
||||
});
|
||||
tag.append(buttonReset);
|
||||
|
||||
|
||||
let buttonCancel = $.spawn("button");
|
||||
buttonCancel.text("Cancel");
|
||||
buttonCancel.on("click", function () {
|
||||
updateCallback(current * 100);
|
||||
connectModal.close();
|
||||
});
|
||||
tag.append(buttonCancel);
|
||||
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text("OK");
|
||||
buttonOk.on("click", function () {
|
||||
connectModal.close();
|
||||
});
|
||||
tag.append(buttonOk);
|
||||
return tag;
|
||||
},
|
||||
|
||||
width: 600
|
||||
});
|
||||
updateCallback = value => {
|
||||
connectModal.htmlTag.find(".volume_slider").val(value);
|
||||
let display = connectModal.htmlTag.find(".display_volume");
|
||||
let number = (value - 100);
|
||||
display.html((number == 0 ? "±" : number > 0 ? "+" : "") + number + " %");
|
||||
callback(value / 100);
|
||||
};
|
||||
connectModal.open();
|
||||
updateCallback(current * 100);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
var Modals;
|
||||
(function (Modals) {
|
||||
function spawnConnectModal(defaultHost = "ts.TeaSpeak.de") {
|
||||
let connectIdentity;
|
||||
const connectModal = createModal({
|
||||
header: function () {
|
||||
let header = $.spawn("div");
|
||||
|
@ -11,13 +12,17 @@ var Modals;
|
|||
body: function () {
|
||||
let tag = $("#tmpl_connect").contents().clone();
|
||||
let updateFields = function () {
|
||||
if (connectIdentity)
|
||||
tag.find(".connect_nickname").attr("placeholder", connectIdentity.name());
|
||||
else
|
||||
tag.find(".connect_nickname").attr("");
|
||||
let button = tag.parents(".modal-content").find(".connect_connect_button");
|
||||
let field_address = tag.find(".connect_address");
|
||||
let address = field_address.val().toString();
|
||||
let flag_address = !!address.match(Regex.IP_V4) || !!address.match(Regex.DOMAIN);
|
||||
let field_nickname = tag.find(".connect_nickname");
|
||||
let nickname = field_nickname.val().toString();
|
||||
let flag_nickname = nickname.length >= 3 && nickname.length <= 32;
|
||||
let flag_nickname = nickname.length == 0 || nickname.length >= 3 && nickname.length <= 32;
|
||||
if (flag_address) {
|
||||
if (field_address.hasClass("invalid_input"))
|
||||
field_address.removeClass("invalid_input");
|
||||
|
@ -34,7 +39,7 @@ var Modals;
|
|||
if (!field_nickname.hasClass("invalid_input"))
|
||||
field_nickname.addClass("invalid_input");
|
||||
}
|
||||
if (!flag_nickname || !flag_address) {
|
||||
if (!flag_nickname || !flag_address || !connectIdentity) {
|
||||
button.attr("disabled", "true");
|
||||
}
|
||||
else {
|
||||
|
@ -44,6 +49,54 @@ var Modals;
|
|||
tag.find(".connect_address").val(defaultHost);
|
||||
tag.find(".connect_address").on("keyup", () => updateFields());
|
||||
tag.find(".connect_nickname").on("keyup", () => updateFields());
|
||||
tag.find(".identity_select").on('change', function () {
|
||||
globalClient.settings.changeGlobal("connect_identity_type", this.value);
|
||||
tag.find(".error_message").hide();
|
||||
tag.find(".identity_config:not(" + ".identity_config_" + this.value + ")").hide();
|
||||
tag.find(".identity_config_" + this.value).show().trigger('shown');
|
||||
});
|
||||
tag.find(".identity_select").val(globalClient.settings.global("connect_identity_type", "forum"));
|
||||
setTimeout(() => tag.find(".identity_select").trigger('change'), 0); //For some reason could not be run instantly
|
||||
tag.find(".identity_file").change(function () {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
connectIdentity = TSIdentityHelper.loadIdentityFromFileContains(reader.result);
|
||||
console.log(connectIdentity.uid());
|
||||
if (!connectIdentity)
|
||||
tag.find(".error_message").text("Could not read identity! " + TSIdentityHelper.last_error());
|
||||
else {
|
||||
tag.find(".identity_string").val(connectIdentity.exported());
|
||||
globalClient.settings.changeGlobal("connect_identity_teamspeak_identity", connectIdentity.exported());
|
||||
}
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
updateFields();
|
||||
};
|
||||
reader.onerror = ev => {
|
||||
tag.find(".error_message").text("Could not read identity file!").show();
|
||||
updateFields();
|
||||
};
|
||||
reader.readAsText(this.files[0]);
|
||||
});
|
||||
tag.find(".identity_string").on('change', function () {
|
||||
if (this.value.length == 0) {
|
||||
tag.find(".error_message").text("Please select an identity!");
|
||||
}
|
||||
else {
|
||||
connectIdentity = TSIdentityHelper.loadIdentity(this.value);
|
||||
if (!connectIdentity)
|
||||
tag.find(".error_message").text("Could not parse identity! " + TSIdentityHelper.last_error());
|
||||
else
|
||||
globalClient.settings.changeGlobal("connect_identity_teamspeak_identity", this.value);
|
||||
}
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
tag.find(".identity_file").val("");
|
||||
updateFields();
|
||||
});
|
||||
tag.find(".identity_string").val(globalClient.settings.global("connect_identity_teamspeak_identity", ""));
|
||||
tag.find(".identity_config_teamspeak").on('shown', ev => { tag.find(".identity_string").trigger('change'); });
|
||||
if (!forumIdentity)
|
||||
tag.find(".identity_config_forum").html("You cant use your TeaSpeak forum account.<br>You're not connected!");
|
||||
tag.find(".identity_config_forum").on('shown', ev => { connectIdentity = forumIdentity; updateFields(); });
|
||||
//connect_address
|
||||
return tag;
|
||||
},
|
||||
|
@ -60,7 +113,7 @@ var Modals;
|
|||
connectModal.close();
|
||||
let field_address = tag.parents(".modal-content").find(".connect_address");
|
||||
let address = field_address.val().toString();
|
||||
globalClient.startConnection(address);
|
||||
globalClient.startConnection(address, connectIdentity, tag.parents(".modal-content").find(".connect_nickname").val().toString());
|
||||
});
|
||||
tag.append(button);
|
||||
return tag;
|
||||
|
@ -79,4 +132,28 @@ var Modals;
|
|||
IP: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
|
||||
};
|
||||
})(Modals || (Modals = {}));
|
||||
/*
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">Identity Settings</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="identity_type_forum">Forum Account</option>
|
||||
<option name="identity_type" value="identity_type_teamspeak">TeamSpeak</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_teamspeak">
|
||||
Please enter your exported TS3 Identity string bellow or select your exported Identity<br>
|
||||
<div style="width: 100%; display: flex; flex-direction: row">
|
||||
<input placeholder="Identity string" style="width: 70%; margin: 5px;" class="identity_string">
|
||||
<div style="width: 30%; margin: 5px"><input name="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_forum">
|
||||
You're using your forum account as verification
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message">
|
||||
Identity isnt valid!
|
||||
</div>
|
||||
*/
|
||||
//# sourceMappingURL=ModalConnect.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,7 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
namespace Modals {
|
||||
export function spawnConnectModal(defaultHost: string = "ts.TeaSpeak.de") {
|
||||
let connectIdentity: Identity;
|
||||
const connectModal = createModal({
|
||||
header: function() {
|
||||
let header = $.spawn("div");
|
||||
|
@ -10,8 +11,10 @@ namespace Modals {
|
|||
body: function () {
|
||||
let tag = $("#tmpl_connect").contents().clone();
|
||||
|
||||
|
||||
let updateFields = function () {
|
||||
if(connectIdentity) tag.find(".connect_nickname").attr("placeholder", connectIdentity.name());
|
||||
else tag.find(".connect_nickname").attr("");
|
||||
|
||||
let button = tag.parents(".modal-content").find(".connect_connect_button");
|
||||
|
||||
let field_address = tag.find(".connect_address");
|
||||
|
@ -20,7 +23,7 @@ namespace Modals {
|
|||
|
||||
let field_nickname = tag.find(".connect_nickname");
|
||||
let nickname = field_nickname.val().toString();
|
||||
let flag_nickname = nickname.length >= 3 && nickname.length <= 32;
|
||||
let flag_nickname = nickname.length == 0 || nickname.length >= 3 && nickname.length <= 32;
|
||||
|
||||
if(flag_address) {
|
||||
if(field_address.hasClass("invalid_input"))
|
||||
|
@ -38,7 +41,7 @@ namespace Modals {
|
|||
field_nickname.addClass("invalid_input");
|
||||
}
|
||||
|
||||
if(!flag_nickname || !flag_address) {
|
||||
if(!flag_nickname || !flag_address || !connectIdentity) {
|
||||
button.attr("disabled", "true");
|
||||
} else {
|
||||
button.removeAttr("disabled");
|
||||
|
@ -48,6 +51,57 @@ namespace Modals {
|
|||
tag.find(".connect_address").val(defaultHost);
|
||||
tag.find(".connect_address").on("keyup", () => updateFields());
|
||||
tag.find(".connect_nickname").on("keyup", () => updateFields());
|
||||
|
||||
tag.find(".identity_select").on('change', function (this: HTMLSelectElement) {
|
||||
globalClient.settings.changeGlobal("connect_identity_type", this.value);
|
||||
tag.find(".error_message").hide();
|
||||
tag.find(".identity_config:not(" + ".identity_config_" + this.value + ")").hide();
|
||||
tag.find(".identity_config_" + this.value).show().trigger('shown');
|
||||
});
|
||||
tag.find(".identity_select").val(globalClient.settings.global("connect_identity_type", "forum"));
|
||||
setTimeout(() => tag.find(".identity_select").trigger('change'), 0); //For some reason could not be run instantly
|
||||
|
||||
tag.find(".identity_file").change(function (this: HTMLInputElement) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
connectIdentity = TSIdentityHelper.loadIdentityFromFileContains(reader.result);
|
||||
|
||||
console.log(connectIdentity.uid());
|
||||
if(!connectIdentity) tag.find(".error_message").text("Could not read identity! " + TSIdentityHelper.last_error());
|
||||
else {
|
||||
tag.find(".identity_string").val((connectIdentity as TeamSpeakIdentity).exported());
|
||||
globalClient.settings.changeGlobal("connect_identity_teamspeak_identity", (connectIdentity as TeamSpeakIdentity).exported());
|
||||
}
|
||||
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
updateFields();
|
||||
};
|
||||
reader.onerror = ev => {
|
||||
tag.find(".error_message").text("Could not read identity file!").show();
|
||||
updateFields();
|
||||
};
|
||||
reader.readAsText(this.files[0]);
|
||||
});
|
||||
|
||||
tag.find(".identity_string").on('change', function (this: HTMLInputElement) {
|
||||
if(this.value.length == 0){
|
||||
tag.find(".error_message").text("Please select an identity!");
|
||||
} else {
|
||||
connectIdentity = TSIdentityHelper.loadIdentity(this.value);
|
||||
if(!connectIdentity) tag.find(".error_message").text("Could not parse identity! " + TSIdentityHelper.last_error());
|
||||
else globalClient.settings.changeGlobal("connect_identity_teamspeak_identity", this.value);
|
||||
}
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
tag.find(".identity_file").val("");
|
||||
updateFields();
|
||||
});
|
||||
tag.find(".identity_string").val(globalClient.settings.global("connect_identity_teamspeak_identity", ""));
|
||||
tag.find(".identity_config_teamspeak").on('shown', ev => { tag.find(".identity_string").trigger('change'); });
|
||||
|
||||
if(!forumIdentity)
|
||||
tag.find(".identity_config_forum").html("You cant use your TeaSpeak forum account.<br>You're not connected!");
|
||||
tag.find(".identity_config_forum").on('shown', ev => { connectIdentity = forumIdentity; updateFields(); });
|
||||
|
||||
//connect_address
|
||||
return tag;
|
||||
},
|
||||
|
@ -66,7 +120,7 @@ namespace Modals {
|
|||
|
||||
let field_address = tag.parents(".modal-content").find(".connect_address");
|
||||
let address = field_address.val().toString();
|
||||
globalClient.startConnection(address);
|
||||
globalClient.startConnection(address, connectIdentity, tag.parents(".modal-content").find(".connect_nickname").val().toString());
|
||||
});
|
||||
tag.append(button);
|
||||
return tag;
|
||||
|
@ -86,4 +140,29 @@ namespace Modals {
|
|||
IP_V6: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/,
|
||||
IP: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">Identity Settings</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="identity_type_forum">Forum Account</option>
|
||||
<option name="identity_type" value="identity_type_teamspeak">TeamSpeak</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_teamspeak">
|
||||
Please enter your exported TS3 Identity string bellow or select your exported Identity<br>
|
||||
<div style="width: 100%; display: flex; flex-direction: row">
|
||||
<input placeholder="Identity string" style="width: 70%; margin: 5px;" class="identity_string">
|
||||
<div style="width: 30%; margin: 5px"><input name="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_forum">
|
||||
You're using your forum account as verification
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message">
|
||||
Identity isnt valid!
|
||||
</div>
|
||||
*/
|
|
@ -41,10 +41,10 @@ var Modals;
|
|||
tag.find(".vad_settings .vad_type_settings").hide();
|
||||
tag.find(".vad_settings .vad_type_" + this.value).show();
|
||||
globalClient.settings.changeGlobal("vad_type", this.value);
|
||||
globalClient.voiceConnection.voiceRecorder.reinizaliszeVAD();
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
switch (this.value) {
|
||||
case "ppt":
|
||||
let keyCode = Number.parseInt(globalClient.settings.global("vad_ppt_key", 84 /* T */.toString()));
|
||||
let keyCode = parseInt(globalClient.settings.global("vad_ppt_key", 84 /* T */.toString()));
|
||||
tag.find(".vat_ppt_key").text(String.fromCharCode(keyCode));
|
||||
break;
|
||||
case "vad":
|
||||
|
@ -79,7 +79,7 @@ var Modals;
|
|||
console.log("Got key " + e.keyCode);
|
||||
modal.close();
|
||||
globalClient.settings.changeGlobal("vad_ppt_key", e.keyCode.toString());
|
||||
globalClient.voiceConnection.voiceRecorder.reinizaliszeVAD();
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
tag.find(".vat_ppt_key").text(String.fromCharCode(e.keyCode));
|
||||
});
|
||||
modal.open();
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -45,11 +45,11 @@ namespace Modals {
|
|||
tag.find(".vad_settings .vad_type_settings").hide();
|
||||
tag.find(".vad_settings .vad_type_" + this.value).show();
|
||||
globalClient.settings.changeGlobal("vad_type", this.value);
|
||||
globalClient.voiceConnection.voiceRecorder.reinizaliszeVAD();
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
|
||||
switch (this.value) {
|
||||
case "ppt":
|
||||
let keyCode: number = Number.parseInt(globalClient.settings.global("vad_ppt_key", Key.T.toString()));
|
||||
let keyCode: number = parseInt(globalClient.settings.global("vad_ppt_key", Key.T.toString()));
|
||||
tag.find(".vat_ppt_key").text(String.fromCharCode(keyCode));
|
||||
break;
|
||||
case "vad":
|
||||
|
@ -87,7 +87,7 @@ namespace Modals {
|
|||
console.log("Got key " + e.keyCode);
|
||||
modal.close();
|
||||
globalClient.settings.changeGlobal("vad_ppt_key", e.keyCode.toString());
|
||||
globalClient.voiceConnection.voiceRecorder.reinizaliszeVAD();
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
tag.find(".vat_ppt_key").text(String.fromCharCode(e.keyCode));
|
||||
});
|
||||
modal.open();
|
||||
|
|
|
@ -26,7 +26,7 @@ class ServerEntry {
|
|||
tag.addClass("server");
|
||||
tag.append("<div class=\"icon client-server_green\"></div>");
|
||||
tag.append("<a class='name'>" + this.properties.virtualserver_name + "</a>");
|
||||
var serverIcon = $("<span/>");
|
||||
const serverIcon = $("<span/>");
|
||||
//we cant spawn an icon on creation :)
|
||||
serverIcon.append("<div class='icon_property icon_empty'></div>");
|
||||
tag.append(serverIcon);
|
||||
|
@ -52,6 +52,7 @@ class ServerEntry {
|
|||
}, MenuEntry.CLOSE(on_close));
|
||||
}
|
||||
updateProperty(key, value) {
|
||||
console.trace("Updating property " + key + " => '" + value + "' for the server");
|
||||
this.properties[key] = value;
|
||||
if (key == "virtualserver_name") {
|
||||
this.htmlTag.find(".name").text(value);
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"server.js","sourceRoot":"","sources":["server.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"server.js","sourceRoot":"","sources":["server.ts"],"names":[],"mappings":"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"}
|
|
@ -36,7 +36,7 @@ class ServerEntry {
|
|||
|
||||
tag.append("<a class='name'>" + this.properties.virtualserver_name + "</a>");
|
||||
|
||||
var serverIcon = $("<span/>");
|
||||
const serverIcon = $("<span/>");
|
||||
//we cant spawn an icon on creation :)
|
||||
serverIcon.append("<div class='icon_property icon_empty'></div>");
|
||||
tag.append(serverIcon);
|
||||
|
@ -69,6 +69,7 @@ class ServerEntry {
|
|||
}
|
||||
|
||||
updateProperty(key, value) : void {
|
||||
console.trace("Updating property " + key + " => '" + value + "' for the server");
|
||||
this.properties[key] = value;
|
||||
if(key == "virtualserver_name") {
|
||||
this.htmlTag.find(".name").text(value);
|
||||
|
|
|
@ -3,16 +3,19 @@ var PlayerState;
|
|||
PlayerState[PlayerState["PREBUFFERING"] = 0] = "PREBUFFERING";
|
||||
PlayerState[PlayerState["PLAYING"] = 1] = "PLAYING";
|
||||
PlayerState[PlayerState["BUFFERING"] = 2] = "BUFFERING";
|
||||
PlayerState[PlayerState["STOPPED"] = 3] = "STOPPED";
|
||||
PlayerState[PlayerState["STOPPING"] = 3] = "STOPPING";
|
||||
PlayerState[PlayerState["STOPPED"] = 4] = "STOPPED";
|
||||
})(PlayerState || (PlayerState = {}));
|
||||
class AudioController {
|
||||
constructor() {
|
||||
this.timeIndex = 0;
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this._playingSources = [];
|
||||
this.audioCache = [];
|
||||
this.playingAudioCache = [];
|
||||
this._volume = 1;
|
||||
this._codecCache = [];
|
||||
this._timeIndex = 0;
|
||||
this.allowBuffering = true;
|
||||
this.speakerContext = AudioController.globalContext;
|
||||
this.audioCache = [];
|
||||
this.onSpeaking = function () { };
|
||||
this.onSilence = function () { };
|
||||
}
|
||||
|
@ -22,11 +25,21 @@ class AudioController {
|
|||
this._globalContext = new AudioContext();
|
||||
return this._globalContext;
|
||||
}
|
||||
static initializeAudioController() {
|
||||
//this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me
|
||||
}
|
||||
initialize() {
|
||||
AudioController._audioInstances.push(this);
|
||||
}
|
||||
close() {
|
||||
AudioController._audioInstances.remove(this);
|
||||
}
|
||||
playBuffer(buffer) {
|
||||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate!");
|
||||
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")");
|
||||
this.applayVolume(buffer);
|
||||
this.audioCache.push(buffer);
|
||||
if (this.playerState == PlayerState.STOPPED) {
|
||||
if (this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
|
||||
console.log("[Audio] Starting new playback");
|
||||
this.playerState = PlayerState.PREBUFFERING;
|
||||
//New audio
|
||||
|
@ -34,7 +47,7 @@ class AudioController {
|
|||
switch (this.playerState) {
|
||||
case PlayerState.PREBUFFERING:
|
||||
case PlayerState.BUFFERING:
|
||||
if (this.audioCache.length < 5) {
|
||||
if (this.audioCache.length < 3) {
|
||||
if (this.playerState == PlayerState.BUFFERING) {
|
||||
if (this.allowBuffering)
|
||||
break;
|
||||
|
@ -50,55 +63,80 @@ class AudioController {
|
|||
if (this.allowBuffering)
|
||||
console.log("[Audio] Buffering succeeded (Replaying now)");
|
||||
}
|
||||
this.timeIndex = 0; //Instant replay
|
||||
this.playerState = PlayerState.PLAYING;
|
||||
case PlayerState.PLAYING:
|
||||
this.playCache(this.audioCache);
|
||||
this.playQueue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
playCache(cache) {
|
||||
while (cache.length) {
|
||||
let buffer = cache.shift();
|
||||
let source = this.speakerContext.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.connect(this.speakerContext.destination);
|
||||
source.onended = () => {
|
||||
this._playingSources.remove(source);
|
||||
this.testBufferQueue();
|
||||
};
|
||||
if (this.timeIndex == 0) {
|
||||
this.timeIndex = this.speakerContext.currentTime;
|
||||
console.log("New next time!");
|
||||
}
|
||||
source.start(this.timeIndex);
|
||||
this.timeIndex += source.buffer.duration;
|
||||
this._playingSources.push(source);
|
||||
playQueue() {
|
||||
let buffer;
|
||||
while (buffer = this.audioCache.pop_front()) {
|
||||
if (this._timeIndex < this.speakerContext.currentTime)
|
||||
this._timeIndex = this.speakerContext.currentTime;
|
||||
let player = this.speakerContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
player.onended = () => this.removeNode(player);
|
||||
this.playingAudioCache.push(player);
|
||||
player.connect(this.speakerContext.destination);
|
||||
player.start(this._timeIndex);
|
||||
this._timeIndex += buffer.duration;
|
||||
}
|
||||
}
|
||||
;
|
||||
removeNode(node) {
|
||||
this.playingAudioCache.remove(node);
|
||||
this.testBufferQueue();
|
||||
}
|
||||
stopAudio(now = false) {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.playerState = PlayerState.STOPPING;
|
||||
if (now) {
|
||||
for (let e of this._playingSources)
|
||||
e.stop();
|
||||
this._playingSources = [];
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.audioCache = [];
|
||||
for (let entry of this.playingAudioCache)
|
||||
entry.stop(0);
|
||||
this.playingAudioCache = [];
|
||||
}
|
||||
this.testBufferQueue();
|
||||
}
|
||||
testBufferQueue() {
|
||||
if (this._playingSources.length == 0) {
|
||||
if (this.playerState != PlayerState.STOPPED) {
|
||||
if (this.audioCache.length == 0 && this.playingAudioCache.length == 0) {
|
||||
if (this.playerState != PlayerState.STOPPING) {
|
||||
this.playerState = PlayerState.BUFFERING;
|
||||
if (!this.allowBuffering)
|
||||
console.warn("[Audio] Detected a buffer underflow!");
|
||||
}
|
||||
else
|
||||
else {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.onSilence();
|
||||
}
|
||||
}
|
||||
}
|
||||
close() {
|
||||
get volume() { return this._volume; }
|
||||
set volume(val) {
|
||||
if (this._volume == val)
|
||||
return;
|
||||
this._volume = val;
|
||||
for (let buffer of this.audioCache)
|
||||
this.applayVolume(buffer);
|
||||
}
|
||||
applayVolume(buffer) {
|
||||
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for (let sample = 0; sample < data.length; sample++) {
|
||||
let lane = data[sample];
|
||||
lane *= this._volume;
|
||||
data[sample] = lane;
|
||||
}
|
||||
}
|
||||
}
|
||||
codecCache(codec) {
|
||||
while (this._codecCache.length <= codec)
|
||||
this._codecCache.push(new CodecClientCache());
|
||||
return this._codecCache[codec];
|
||||
}
|
||||
}
|
||||
AudioController._audioInstances = [];
|
||||
AudioController._timeIndex = 0;
|
||||
//# sourceMappingURL=AudioController.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"AudioController.js","sourceRoot":"","sources":["AudioController.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"AudioController.js","sourceRoot":"","sources":["AudioController.ts"],"names":[],"mappings":"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"}
|
|
@ -2,22 +2,84 @@ enum PlayerState {
|
|||
PREBUFFERING,
|
||||
PLAYING,
|
||||
BUFFERING,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
class AudioController {
|
||||
private static _globalContext;
|
||||
private static _globalContext: AudioContext;
|
||||
private static _audioInstances: AudioController[] = [];
|
||||
private static _globalReplayScheduler: NodeJS.Timer;
|
||||
private static _timeIndex: number = 0;
|
||||
|
||||
static get globalContext() : AudioContext {
|
||||
if(this._globalContext) return this._globalContext;
|
||||
this._globalContext = new AudioContext();
|
||||
return this._globalContext;
|
||||
}
|
||||
|
||||
static initializeAudioController() {
|
||||
//this._globalReplayScheduler = setInterval(() => { AudioController.invokeNextReplay(); }, 20); //Fix me
|
||||
}
|
||||
|
||||
/*
|
||||
private static joinTracks(tracks: AudioBuffer[]) : Promise<AudioBuffer> {
|
||||
let length = Math.max.apply(Math, tracks.map(e => e.length));
|
||||
if(length == 0 || tracks.length == 0) return new Promise<AudioBuffer>((resolve, reject) => {}); //Do nothink
|
||||
|
||||
let context = new OfflineAudioContext(2, length, 44100);
|
||||
//let context = new OfflineAudioContext(tracks[0].numberOfChannels, tracks[0].length, tracks[0].sampleRate);
|
||||
|
||||
tracks.forEach(track => {
|
||||
let player = context.createBufferSource();
|
||||
player.buffer = track;
|
||||
player.connect(context.destination);
|
||||
player.start(0);
|
||||
});
|
||||
|
||||
return context.startRendering();
|
||||
}
|
||||
|
||||
static invokeNextReplay() {
|
||||
let replay: {controller: AudioController,buffer: AudioBuffer}[] = [];
|
||||
|
||||
for(let instance of this._audioInstances)
|
||||
if(instance.playerState == PlayerState.PLAYING || instance.playerState == PlayerState.STOPPING) {
|
||||
let entry = {controller: instance, buffer: instance.audioCache.pop_front() };
|
||||
instance.flagPlaying = !!entry.buffer;
|
||||
instance.testBufferQueue();
|
||||
if(!!entry.buffer) replay.push(entry);
|
||||
} else if(instance.flagPlaying) {
|
||||
instance.flagPlaying = false;
|
||||
instance.testBufferQueue();
|
||||
}
|
||||
|
||||
|
||||
this.joinTracks(replay.map(e => e.buffer)).then(buffer => {
|
||||
if(this._timeIndex < this._globalContext.currentTime) {
|
||||
this._timeIndex = this._globalContext.currentTime;
|
||||
console.log("Resetting time index!");
|
||||
}
|
||||
//console.log(buffer.length + "|" + buffer.duration);
|
||||
//console.log(buffer);
|
||||
|
||||
let player = this._globalContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
player.connect(this._globalContext.destination);
|
||||
player.start(this._timeIndex);
|
||||
|
||||
this._timeIndex += buffer.duration;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
speakerContext: AudioContext;
|
||||
private timeIndex: number = 0;
|
||||
private playerState: PlayerState = PlayerState.STOPPED;
|
||||
private audioCache: AudioBuffer[];
|
||||
private _playingSources: AudioBufferSourceNode[] = [];
|
||||
private audioCache: AudioBuffer[] = [];
|
||||
private playingAudioCache: AudioBufferSourceNode[] = [];
|
||||
private _volume: number = 1;
|
||||
private _codecCache: CodecClientCache[] = [];
|
||||
private _timeIndex: number = 0;
|
||||
allowBuffering: boolean = true;
|
||||
|
||||
//Events
|
||||
|
@ -26,18 +88,26 @@ class AudioController {
|
|||
|
||||
constructor() {
|
||||
this.speakerContext = AudioController.globalContext;
|
||||
this.audioCache = [];
|
||||
|
||||
this.onSpeaking = function () { };
|
||||
this.onSilence = function () { };
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
AudioController._audioInstances.push(this);
|
||||
}
|
||||
|
||||
public close(){
|
||||
AudioController._audioInstances.remove(this);
|
||||
}
|
||||
|
||||
playBuffer(buffer: AudioBuffer) {
|
||||
if (buffer.sampleRate != this.speakerContext.sampleRate)
|
||||
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate!");
|
||||
this.audioCache.push(buffer);
|
||||
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")");
|
||||
|
||||
if(this.playerState == PlayerState.STOPPED) {
|
||||
this.applayVolume(buffer);
|
||||
this.audioCache.push(buffer);
|
||||
if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {
|
||||
console.log("[Audio] Starting new playback");
|
||||
this.playerState = PlayerState.PREBUFFERING;
|
||||
//New audio
|
||||
|
@ -47,7 +117,7 @@ class AudioController {
|
|||
switch (this.playerState) {
|
||||
case PlayerState.PREBUFFERING:
|
||||
case PlayerState.BUFFERING:
|
||||
if(this.audioCache.length < 5) {
|
||||
if(this.audioCache.length < 3) {
|
||||
if(this.playerState == PlayerState.BUFFERING) {
|
||||
if(this.allowBuffering) break;
|
||||
} else break;
|
||||
|
@ -59,57 +129,86 @@ class AudioController {
|
|||
if(this.allowBuffering)
|
||||
console.log("[Audio] Buffering succeeded (Replaying now)");
|
||||
}
|
||||
this.timeIndex = 0; //Instant replay
|
||||
this.playerState = PlayerState.PLAYING;
|
||||
case PlayerState.PLAYING:
|
||||
this.playCache(this.audioCache);
|
||||
this.playQueue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private playCache(cache) {
|
||||
while (cache.length) {
|
||||
let buffer = cache.shift();
|
||||
let source = this.speakerContext.createBufferSource();
|
||||
private playQueue() {
|
||||
let buffer: AudioBuffer;
|
||||
while(buffer = this.audioCache.pop_front()) {
|
||||
if(this._timeIndex < this.speakerContext.currentTime) this._timeIndex = this.speakerContext.currentTime;
|
||||
|
||||
source.buffer = buffer;
|
||||
source.connect(this.speakerContext.destination);
|
||||
source.onended = () => {
|
||||
this._playingSources.remove(source);
|
||||
this.testBufferQueue();
|
||||
};
|
||||
if (this.timeIndex == 0) {
|
||||
this.timeIndex = this.speakerContext.currentTime;
|
||||
console.log("New next time!");
|
||||
}
|
||||
source.start(this.timeIndex);
|
||||
this.timeIndex += source.buffer.duration;
|
||||
this._playingSources.push(source);
|
||||
}
|
||||
};
|
||||
let player = this.speakerContext.createBufferSource();
|
||||
player.buffer = buffer;
|
||||
|
||||
player.onended = () => this.removeNode(player);
|
||||
this.playingAudioCache.push(player);
|
||||
|
||||
player.connect(this.speakerContext.destination);
|
||||
player.start(this._timeIndex);
|
||||
this._timeIndex += buffer.duration;
|
||||
}
|
||||
}
|
||||
|
||||
private removeNode(node: AudioBufferSourceNode) {
|
||||
this.playingAudioCache.remove(node);
|
||||
this.testBufferQueue();
|
||||
}
|
||||
|
||||
stopAudio(now: boolean = false) {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.playerState = PlayerState.STOPPING;
|
||||
if(now) {
|
||||
for(let e of this._playingSources)
|
||||
e.stop();
|
||||
this._playingSources = [];
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.audioCache = [];
|
||||
|
||||
for(let entry of this.playingAudioCache)
|
||||
entry.stop(0);
|
||||
this.playingAudioCache = [];
|
||||
}
|
||||
this.testBufferQueue();
|
||||
}
|
||||
|
||||
private testBufferQueue() {
|
||||
if(this._playingSources.length == 0) {
|
||||
if(this.playerState != PlayerState.STOPPED) {
|
||||
if(this.audioCache.length == 0 && this.playingAudioCache.length == 0) {
|
||||
if(this.playerState != PlayerState.STOPPING) {
|
||||
this.playerState = PlayerState.BUFFERING;
|
||||
if(!this.allowBuffering)
|
||||
console.warn("[Audio] Detected a buffer underflow!");
|
||||
} else this.onSilence();
|
||||
} else {
|
||||
this.playerState = PlayerState.STOPPED;
|
||||
this.onSilence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(){
|
||||
get volume() : number { return this._volume; }
|
||||
|
||||
set volume(val: number) {
|
||||
if(this._volume == val) return;
|
||||
this._volume = val;
|
||||
for(let buffer of this.audioCache)
|
||||
this.applayVolume(buffer);
|
||||
}
|
||||
|
||||
private applayVolume(buffer: AudioBuffer) {
|
||||
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
||||
let data = buffer.getChannelData(channel);
|
||||
for(let sample = 0; sample < data.length; sample++) {
|
||||
let lane = data[sample];
|
||||
lane *= this._volume;
|
||||
data[sample] = lane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codecCache(codec: number) : CodecClientCache {
|
||||
while(this._codecCache.length <= codec)
|
||||
this._codecCache.push(new CodecClientCache());
|
||||
return this._codecCache[codec];
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
class AudioResampler {
|
||||
constructor(targetSampleRate = 44100) {
|
||||
this.targetSampleRate = targetSampleRate;
|
||||
if (this.targetSampleRate < 3000 || this.targetSampleRate > 384000)
|
||||
throw "The target sample rate is outside the range [3000, 384000].";
|
||||
}
|
||||
resample(buffer) {
|
||||
if (buffer.sampleRate == this.targetSampleRate)
|
||||
return new Promise(resolve => resolve(buffer));
|
||||
let context;
|
||||
context = new OfflineAudioContext(1, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
context = new OfflineAudioContext(buffer.numberOfChannels, Math.floor(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
let source = context.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.connect(context.destination);
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"AudioResampler.js","sourceRoot":"","sources":["AudioResampler.ts"],"names":[],"mappings":"AAAA;IAGI,YAAY,mBAA2B,KAAK;QACxC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC7C,CAAC;IAED,QAAQ,CAAC,MAAmB;QACxB,EAAE,CAAA,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,gBAAgB,CAAC;YAC1C,MAAM,CAAC,IAAI,OAAO,CAAc,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAEhE,IAAI,OAAO,CAAC;QACZ,OAAO,GAAG,IAAI,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAElI,IAAI,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAC1C,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAEhB,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;IACpC,CAAC;CACJ"}
|
||||
{"version":3,"file":"AudioResampler.js","sourceRoot":"","sources":["AudioResampler.ts"],"names":[],"mappings":"AAAA;IAGI,YAAY,mBAA2B,KAAK;QACxC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,EAAE,CAAA,CAAC,IAAI,CAAC,gBAAgB,GAAG,IAAI,IAAI,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC;YAAC,MAAM,6DAA6D,CAAC;IAC3I,CAAC;IAED,QAAQ,CAAC,MAAmB;QACxB,EAAE,CAAA,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,gBAAgB,CAAC;YAC1C,MAAM,CAAC,IAAI,OAAO,CAAc,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAEhE,IAAI,OAAO,CAAC;QACZ,OAAO,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAEzJ,IAAI,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAC1C,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAEhB,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;IACpC,CAAC;CACJ"}
|
|
@ -3,6 +3,7 @@ class AudioResampler {
|
|||
|
||||
constructor(targetSampleRate: number = 44100){
|
||||
this.targetSampleRate = targetSampleRate;
|
||||
if(this.targetSampleRate < 3000 || this.targetSampleRate > 384000) throw "The target sample rate is outside the range [3000, 384000].";
|
||||
}
|
||||
|
||||
resample(buffer: AudioBuffer) : Promise<AudioBuffer> {
|
||||
|
@ -10,7 +11,7 @@ class AudioResampler {
|
|||
return new Promise<AudioBuffer>(resolve => resolve(buffer));
|
||||
|
||||
let context;
|
||||
context = new OfflineAudioContext(1, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
context = new OfflineAudioContext(buffer.numberOfChannels, Math.floor(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
|
||||
|
||||
let source = context.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
|
|
|
@ -1,15 +1,61 @@
|
|||
/// <reference path="../client.ts" />
|
||||
/// <reference path="../codec/Codec.ts" />
|
||||
/// <reference path="VoiceRecorder.ts" />
|
||||
class CodecPoolEntry {
|
||||
}
|
||||
class CodecPool {
|
||||
constructor(handle, index, creator) {
|
||||
this.entries = [];
|
||||
this.maxInstances = 2;
|
||||
this.creator = creator;
|
||||
this.handle = handle;
|
||||
this.codecIndex = index;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (free == 0 && this.entries[index].owner == 0) {
|
||||
free = 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;
|
||||
}
|
||||
releaseCodec(clientId) {
|
||||
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.codecs = [
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined //opus music
|
||||
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?
|
||||
];
|
||||
this.vpacketId = 0;
|
||||
this.chunkVPacketId = 0;
|
||||
|
@ -17,16 +63,7 @@ class VoiceConnection {
|
|||
this.voiceRecorder = new VoiceRecorder(this);
|
||||
this.voiceRecorder.on_data = this.handleVoiceData.bind(this);
|
||||
this.voiceRecorder.on_end = this.handleVoiceEnded.bind(this);
|
||||
this.codecs[4] = new OpusCodec(1, OpusType.VOIP);
|
||||
this.codecs[5] = new OpusCodec(2, OpusType.AUDIO);
|
||||
for (let index = 0; index < this.codecs.length; index++) {
|
||||
if (!this.codecs[index])
|
||||
continue;
|
||||
let codec = this.codecs[index];
|
||||
console.log("Got codec " + codec.name());
|
||||
codec.initialise();
|
||||
codec.on_encoded_data = buffer => this.sendVoicePacket(buffer, index);
|
||||
}
|
||||
this.voiceRecorder.reinitialiseVAD();
|
||||
}
|
||||
sendVoicePacket(data, codec) {
|
||||
if (this.dataChannel) {
|
||||
|
@ -35,9 +72,9 @@ class VoiceConnection {
|
|||
this.vpacketId = 0;
|
||||
let packet = new Uint8Array(data.byteLength + 2 + 3);
|
||||
packet[0] = this.chunkVPacketId++ < 5 ? 1 : 0; //Flag header
|
||||
packet[1] = 0; //Flag frag mented
|
||||
packet[2] = (this.vpacketId >> 8) & 0xFF; //HIGHT(voiceID)
|
||||
packet[3] = (this.vpacketId >> 0) & 0xFF; //LOW (voiceID)
|
||||
packet[1] = 0; //Flag fragmented
|
||||
packet[2] = (this.vpacketId >> 8) & 0xFF; //HIGHT (voiceID)
|
||||
packet[3] = (this.vpacketId >> 0) & 0xFF; //LOW (voiceID)
|
||||
packet[4] = codec; //Codec
|
||||
packet.set(data, 5);
|
||||
this.dataChannel.send(packet);
|
||||
|
@ -100,6 +137,8 @@ class VoiceConnection {
|
|||
console.log("Got new data channel!");
|
||||
}
|
||||
onDataChannelMessage(message) {
|
||||
if (this.client.controlBar.muteOutput)
|
||||
return;
|
||||
let bin = new Uint8Array(message.data);
|
||||
let clientId = bin[2] << 8 | bin[3];
|
||||
let packetId = bin[0] << 8 | bin[1];
|
||||
|
@ -110,7 +149,8 @@ class VoiceConnection {
|
|||
console.error("Having voice from unknown client? (ClientID: " + clientId + ")");
|
||||
return;
|
||||
}
|
||||
if (!this.codecs[codec]) {
|
||||
let codecPool = this.codecPool[codec];
|
||||
if (!codecPool) {
|
||||
console.error("Could not playback codec " + codec);
|
||||
return;
|
||||
}
|
||||
|
@ -121,9 +161,11 @@ class VoiceConnection {
|
|||
encodedData = new Uint8Array(message.data, 5);
|
||||
if (encodedData.length == 0) {
|
||||
client.getAudioController().stopAudio();
|
||||
codecPool.releaseCodec(clientId);
|
||||
}
|
||||
else {
|
||||
this.codecs[codec].decodeSamples(encodedData).then(buffer => {
|
||||
let decoder = codecPool.ownCodec(clientId);
|
||||
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 + ")");
|
||||
|
@ -131,14 +173,23 @@ class VoiceConnection {
|
|||
}
|
||||
}
|
||||
handleVoiceData(data, head) {
|
||||
if (!this.voiceRecorder)
|
||||
return;
|
||||
if (head) {
|
||||
this.chunkVPacketId = 0;
|
||||
this.client.getClient().speaking = true;
|
||||
}
|
||||
this.codecs[4].encodeSamples(data); //TODO Use channel codec!
|
||||
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!
|
||||
//this.client.getClient().getAudioController().play(data);
|
||||
}
|
||||
handleVoiceEnded() {
|
||||
if (!this.voiceRecorder)
|
||||
return;
|
||||
console.log("Voice ended");
|
||||
this.client.getClient().speaking = false;
|
||||
this.sendVoicePacket(new Uint8Array(0), 4); //TODO Use channel codec!
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,62 @@
|
|||
/// <reference path="../codec/Codec.ts" />
|
||||
/// <reference path="VoiceRecorder.ts" />
|
||||
|
||||
class CodecPoolEntry {
|
||||
instance: BasicCodec;
|
||||
owner: number;
|
||||
|
||||
last_access: number;
|
||||
}
|
||||
|
||||
class CodecPool {
|
||||
handle: VoiceConnection;
|
||||
codecIndex: number;
|
||||
creator: () => BasicCodec;
|
||||
|
||||
entries: CodecPoolEntry[] = [];
|
||||
maxInstances: number = 2;
|
||||
|
||||
ownCodec?(clientId: number, create: boolean = true) : BasicCodec {
|
||||
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;
|
||||
} else if(free == 0 && this.entries[index].owner == 0) {
|
||||
free = 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;
|
||||
}
|
||||
|
||||
releaseCodec(clientId: number) {
|
||||
for(let index = 0; index < this.entries.length; index++) {
|
||||
if(this.entries[index].owner == clientId) this.entries[index].owner = 0;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(handle: VoiceConnection, index: number, creator: () => BasicCodec){
|
||||
this.creator = creator;
|
||||
this.handle = handle;
|
||||
this.codecIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceConnection {
|
||||
client: TSClient;
|
||||
rtcPeerConnection: RTCPeerConnection;
|
||||
|
@ -9,13 +65,14 @@ class VoiceConnection {
|
|||
|
||||
voiceRecorder: VoiceRecorder;
|
||||
|
||||
private codecs: Codec[] = [
|
||||
undefined, //Spex
|
||||
undefined, //Spex
|
||||
undefined, //Spex
|
||||
undefined, //CELT Mono
|
||||
undefined, //opus voice
|
||||
undefined //opus music
|
||||
private codecPool: CodecPool[] = [
|
||||
new CodecPool(this,0,undefined), //Spex
|
||||
new CodecPool(this,1,undefined), //Spex
|
||||
new CodecPool(this,2,undefined), //Spex
|
||||
new CodecPool(this,3,undefined), //CELT Mono
|
||||
new CodecPool(this,4,() => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 1) }), //opus voice
|
||||
new CodecPool(this,5,() => { return new CodecWrapper(CodecWorkerType.WORKER_OPUS, 2) }) //opus music
|
||||
//FIXME Why is it at index 5 currently only 1?
|
||||
];
|
||||
|
||||
private vpacketId: number = 0;
|
||||
|
@ -26,18 +83,7 @@ class VoiceConnection {
|
|||
this.voiceRecorder = new VoiceRecorder(this);
|
||||
this.voiceRecorder.on_data = this.handleVoiceData.bind(this);
|
||||
this.voiceRecorder.on_end = this.handleVoiceEnded.bind(this);
|
||||
|
||||
this.codecs[4] = new OpusCodec(1, OpusType.VOIP);
|
||||
this.codecs[5] = new OpusCodec(2, OpusType.AUDIO);
|
||||
|
||||
for(let index = 0; index < this.codecs.length; index++) {
|
||||
if(!this.codecs[index]) continue;
|
||||
|
||||
let codec = this.codecs[index];
|
||||
console.log("Got codec " + codec.name());
|
||||
codec.initialise();
|
||||
codec.on_encoded_data = buffer => this.sendVoicePacket(buffer, index);
|
||||
}
|
||||
this.voiceRecorder.reinitialiseVAD();
|
||||
}
|
||||
|
||||
sendVoicePacket(data: Uint8Array, codec: number) {
|
||||
|
@ -46,9 +92,9 @@ class VoiceConnection {
|
|||
if(this.vpacketId > 65535) this.vpacketId = 0;
|
||||
let packet = new Uint8Array(data.byteLength + 2 + 3);
|
||||
packet[0] = this.chunkVPacketId++ < 5 ? 1 : 0; //Flag header
|
||||
packet[1] = 0; //Flag frag mented
|
||||
packet[2] = (this.vpacketId >> 8) & 0xFF; //HIGHT(voiceID)
|
||||
packet[3] = (this.vpacketId >> 0) & 0xFF; //LOW (voiceID)
|
||||
packet[1] = 0; //Flag fragmented
|
||||
packet[2] = (this.vpacketId >> 8) & 0xFF; //HIGHT (voiceID)
|
||||
packet[3] = (this.vpacketId >> 0) & 0xFF; //LOW (voiceID)
|
||||
packet[4] = codec; //Codec
|
||||
packet.set(data, 5);
|
||||
this.dataChannel.send(packet);
|
||||
|
@ -119,6 +165,8 @@ class VoiceConnection {
|
|||
}
|
||||
|
||||
onDataChannelMessage(message) {
|
||||
if(this.client.controlBar.muteOutput) return;
|
||||
|
||||
let bin = new Uint8Array(message.data);
|
||||
let clientId = bin[2] << 8 | bin[3];
|
||||
let packetId = bin[0] << 8 | bin[1];
|
||||
|
@ -129,10 +177,13 @@ class VoiceConnection {
|
|||
console.error("Having voice from unknown client? (ClientID: " + clientId + ")");
|
||||
return;
|
||||
}
|
||||
if(!this.codecs[codec]) {
|
||||
|
||||
let codecPool = this.codecPool[codec];
|
||||
if(!codecPool) {
|
||||
console.error("Could not playback codec " + codec);
|
||||
return;
|
||||
}
|
||||
|
||||
let encodedData;
|
||||
if(message.data.subarray)
|
||||
encodedData = message.data.subarray(5);
|
||||
|
@ -140,8 +191,10 @@ class VoiceConnection {
|
|||
|
||||
if(encodedData.length == 0) {
|
||||
client.getAudioController().stopAudio();
|
||||
codecPool.releaseCodec(clientId);
|
||||
} else {
|
||||
this.codecs[codec].decodeSamples(encodedData).then(buffer => {
|
||||
let decoder = codecPool.ownCodec(clientId);
|
||||
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 + ")");
|
||||
|
@ -150,15 +203,24 @@ class VoiceConnection {
|
|||
}
|
||||
|
||||
private handleVoiceData(data: AudioBuffer, head: boolean) {
|
||||
if(!this.voiceRecorder) return;
|
||||
|
||||
if(head) {
|
||||
this.chunkVPacketId = 0;
|
||||
this.client.getClient().speaking = true;
|
||||
}
|
||||
this.codecs[4].encodeSamples(data); //TODO Use channel codec!
|
||||
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!
|
||||
//this.client.getClient().getAudioController().play(data);
|
||||
}
|
||||
|
||||
private handleVoiceEnded() {
|
||||
if(!this.voiceRecorder) return;
|
||||
|
||||
console.log("Voice ended");
|
||||
this.client.getClient().speaking = false;
|
||||
this.sendVoicePacket(new Uint8Array(0), 4); //TODO Use channel codec!
|
||||
|
|
|
@ -19,9 +19,9 @@ class VoiceRecorder {
|
|||
this.microphoneStream = undefined;
|
||||
this.mediaStream = undefined;
|
||||
this._chunkCount = 0;
|
||||
this._deviceId = "default";
|
||||
this.handle = handle;
|
||||
this.userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||
this._deviceId = handle.client.settings.global("microphone_id", "default");
|
||||
this.audioContext = AudioController.globalContext;
|
||||
this.processor = this.audioContext.createScriptProcessor(VoiceRecorder.BUFFER_SIZE, VoiceRecorder.CHANNELS, VoiceRecorder.CHANNELS);
|
||||
this.processor.addEventListener('audioprocess', ev => {
|
||||
|
@ -56,10 +56,10 @@ class VoiceRecorder {
|
|||
getMicrophoneStream() {
|
||||
return this.microphoneStream;
|
||||
}
|
||||
reinizaliszeVAD() {
|
||||
let type = this.handle.client.settings.global("vad_type", "ppt");
|
||||
reinitialiseVAD() {
|
||||
let type = this.handle.client.settings.global("vad_type", "vad");
|
||||
if (type == "ppt") {
|
||||
let keyCode = parseInt(globalClient.settings.global("vad_ppt_key", 84 /* T */.toString()));
|
||||
let keyCode = parseInt(this.handle.client.settings.global("vad_ppt_key", 84 /* T */.toString()));
|
||||
if (!(this.getVADHandler() instanceof PushToTalkVAD))
|
||||
this.setVADHander(new PushToTalkVAD(keyCode));
|
||||
else
|
||||
|
@ -72,7 +72,7 @@ class VoiceRecorder {
|
|||
else if (type == "vad") {
|
||||
if (!(this.getVADHandler() instanceof VoiceActivityDetectorVAD))
|
||||
this.setVADHander(new VoiceActivityDetectorVAD());
|
||||
let threshold = parseInt(globalClient.settings.global("vad_threshold", "50"));
|
||||
let threshold = parseInt(this.handle.client.settings.global("vad_threshold", "50"));
|
||||
this.getVADHandler().percentageThreshold = threshold;
|
||||
}
|
||||
else {
|
||||
|
@ -104,6 +104,7 @@ class VoiceRecorder {
|
|||
if (this._deviceId == device)
|
||||
return;
|
||||
this._deviceId = device;
|
||||
this.handle.client.settings.changeGlobal("microphone_id", device);
|
||||
if (this._recording) {
|
||||
this.stop();
|
||||
this.start(device);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -39,12 +39,14 @@ class VoiceRecorder {
|
|||
private vadHandler: VoiceActivityDetector;
|
||||
private _chunkCount: number = 0;
|
||||
|
||||
private _deviceId: string = "default";
|
||||
private _deviceId: string;
|
||||
|
||||
constructor(handle: VoiceConnection) {
|
||||
this.handle = handle;
|
||||
this.userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||
|
||||
this._deviceId = handle.client.settings.global("microphone_id", "default");
|
||||
|
||||
this.audioContext = AudioController.globalContext;
|
||||
this.processor = this.audioContext.createScriptProcessor(VoiceRecorder.BUFFER_SIZE, VoiceRecorder.CHANNELS, VoiceRecorder.CHANNELS);
|
||||
|
||||
|
@ -88,10 +90,10 @@ class VoiceRecorder {
|
|||
return this.microphoneStream;
|
||||
}
|
||||
|
||||
reinizaliszeVAD() {
|
||||
let type = this.handle.client.settings.global("vad_type", "ppt");
|
||||
reinitialiseVAD() {
|
||||
let type = this.handle.client.settings.global("vad_type", "vad");
|
||||
if(type == "ppt") {
|
||||
let keyCode: number = parseInt(globalClient.settings.global("vad_ppt_key", Key.T.toString()));
|
||||
let keyCode: number = parseInt(this.handle.client.settings.global("vad_ppt_key", Key.T.toString()));
|
||||
if(!(this.getVADHandler() instanceof PushToTalkVAD))
|
||||
this.setVADHander(new PushToTalkVAD(keyCode));
|
||||
else (this.getVADHandler() as PushToTalkVAD).key = keyCode;
|
||||
|
@ -101,7 +103,7 @@ class VoiceRecorder {
|
|||
} else if(type == "vad") {
|
||||
if(!(this.getVADHandler() instanceof VoiceActivityDetectorVAD))
|
||||
this.setVADHander(new VoiceActivityDetectorVAD());
|
||||
let threshold = parseInt(globalClient.settings.global("vad_threshold", "50"));
|
||||
let threshold = parseInt(this.handle.client.settings.global("vad_threshold", "50"));
|
||||
(this.getVADHandler() as VoiceActivityDetectorVAD).percentageThreshold = threshold;
|
||||
} else {
|
||||
console.warn("Invalid VAD handler! (" + type + ")");
|
||||
|
@ -132,6 +134,7 @@ class VoiceRecorder {
|
|||
changeDevice(device: string) {
|
||||
if(this._deviceId == device) return;
|
||||
this._deviceId = device;
|
||||
this.handle.client.settings.changeGlobal("microphone_id", device);
|
||||
if(this._recording) {
|
||||
this.stop();
|
||||
this.start(device);
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>TeaSpeak-Web client templates</title>
|
||||
|
||||
|
||||
<!-- Imports just for my IDE -->
|
||||
<link rel="stylesheet" href="css/ts/tab.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/ts/chat.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/ts/client.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/ts/icons.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/general.css" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Template for chennel create & edit-->
|
||||
|
@ -110,7 +118,32 @@
|
|||
</div>
|
||||
<div>
|
||||
<div>Nickname:</div>
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="A TeaSpeak web user">
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">Identity Settings</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="forum">Forum Account</option>
|
||||
<option name="identity_type" value="teamspeak">TeamSpeak</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_teamspeak identity_config">
|
||||
Please enter your exported TS3 Identity string bellow or select your exported Identity<br>
|
||||
<div style="width: 100%; display: flex; flex-direction: row">
|
||||
<input placeholder="Identity string" style="width: 70%; margin: 5px;" class="identity_string">
|
||||
<div style="width: 30%; margin: 5px"><input class="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_forum identity_config">
|
||||
You're using your forum account as verification
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message">
|
||||
Identity isnt valid!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -170,5 +203,12 @@
|
|||
</x-entry>
|
||||
</x-tab>
|
||||
</template>
|
||||
|
||||
<template id="tmpl_change_volume">
|
||||
<div style="display: flex; justify-content: center; vertical-align: center">
|
||||
<input type="range" min="0" max="200" value="100" class="volume_slider" style="width: 100%">
|
||||
<div class="display_volume" style="width: 60px; align-self: center; text-align: center">±0 %</div>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
</html>
|
|
@ -1,26 +0,0 @@
|
|||
<!--
|
||||
HTML index for the example globalClient.
|
||||
|
||||
Brian Ho
|
||||
brian@brkho.com
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Example Client</title>
|
||||
<script src="../vendor/jquery/jquery.min.js"></script>
|
||||
<script src="../vendor/aurora/aurora.js"></script>
|
||||
<script src="../vendor/aurora/ogg.js"></script>
|
||||
<script src="../vendor/aurora/opus.js"></script>
|
||||
<script src="../vendor/opus/opus_to_pcm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="connect()">Connect</button>
|
||||
<button onclick="ping()">Ping</button>
|
||||
<audio controls="true" id="audio" src=""></audio>
|
||||
|
||||
<script src="webrtc.js"></script>
|
||||
</body>
|
||||
</html>
|
172
test/opus.html
172
test/opus.html
|
@ -1,172 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Ogg Opus Encoder Example</title>
|
||||
<script src="../vendor/opus-recorder/recorder.js"></script>
|
||||
<style type='text/css'>
|
||||
ul { list-style: none; }
|
||||
li { margin: 1em; }
|
||||
audio { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Encoder example</h1>
|
||||
<p>Before you enable monitoring, make sure to either plug in headphones or turn the volume down.</p>
|
||||
<p>This ogg opus implementation does not support more than 2 channels.</p>
|
||||
|
||||
<h2>Options</h2>
|
||||
|
||||
<div>
|
||||
<label>monitorGain</label>
|
||||
<input id="monitorGain" type="number" value="0" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>numberOfChannels</label>
|
||||
<input id="numberOfChannels" type="number" value="1" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>encoderSampleRate</label>
|
||||
<input id="encoderSampleRate" type="number" value="48000" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>bitRate</label>
|
||||
<input id="bitRate" type="number" value="64000" />
|
||||
</div>
|
||||
|
||||
<h2>Commands</h2>
|
||||
<button id="start">start</button>
|
||||
<button id="pause" disabled>pause</button>
|
||||
<button id="resume" disabled>resume</button>
|
||||
<button id="stopButton" disabled>stop</button>
|
||||
|
||||
<h2>Recordings</h2>
|
||||
<ul id="recordingslist"></ul>
|
||||
|
||||
<h2>Log</h2>
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script>
|
||||
function screenLogger(text, data) {
|
||||
log.innerHTML += "\n" + text + " " + (data || '');
|
||||
}
|
||||
|
||||
if (!Recorder.isRecordingSupported()) {
|
||||
screenLogger("Recording features are not supported in your browser.");
|
||||
}
|
||||
|
||||
else {
|
||||
var recorder = new Recorder({
|
||||
monitorGain: parseInt(monitorGain.value, 10),
|
||||
numberOfChannels: parseInt(numberOfChannels.value, 10),
|
||||
encoderBitRate: parseInt(bitRate.value,10),
|
||||
encoderSampleRate: parseInt(encoderSampleRate.value,10),
|
||||
encoderPath: "../vendor/opus-recorder/encoderWorker.js",
|
||||
|
||||
bufferLength: 0,
|
||||
encoderFrameSize: 10,
|
||||
streamPages: true,
|
||||
maxBuffersPerPage: 1
|
||||
});
|
||||
|
||||
pause.addEventListener( "click", function(){ recorder.pause(); });
|
||||
resume.addEventListener( "click", function(){ recorder.resume(); });
|
||||
stopButton.addEventListener( "click", function(){ recorder.stop(); });
|
||||
start.addEventListener( "click", function(){
|
||||
recorder.start().catch(function(e){
|
||||
screenLogger('Error encountered: ' + e.message );
|
||||
});
|
||||
});
|
||||
|
||||
recorder.onstart = function(e){
|
||||
screenLogger('Recorder is started');
|
||||
start.disabled = resume.disabled = true;
|
||||
pause.disabled = stopButton.disabled = false;
|
||||
};
|
||||
|
||||
recorder.onstop = function(e){
|
||||
screenLogger('Recorder is stopped');
|
||||
start.disabled = false;
|
||||
pause.disabled = resume.disabled = stopButton.disabled = true;
|
||||
};
|
||||
|
||||
recorder.onpause = function(e){
|
||||
screenLogger('Recorder is paused');
|
||||
pause.disabled = start.disabled = true;
|
||||
resume.disabled = stopButton.disabled = false;
|
||||
};
|
||||
|
||||
recorder.onresume = function(e){
|
||||
screenLogger('Recorder is resuming');
|
||||
start.disabled = resume.disabled = true;
|
||||
pause.disabled = stopButton.disabled = false;
|
||||
};
|
||||
|
||||
//X
|
||||
var decoderWorker = new Worker('../vendor/opus-recorder/decoderWorker.js');
|
||||
decoderWorker.postMessage({
|
||||
command:'init',
|
||||
decoderSampleRate: parseInt(encoderSampleRate.value,10),
|
||||
outputBufferSampleRate: parseInt(encoderSampleRate.value,10)
|
||||
});
|
||||
|
||||
const audioContext = new AudioContext();
|
||||
const speakers = audioContext.destination;
|
||||
const audioBuffer = audioContext.createBuffer(1, parseInt(encoderSampleRate.value,10), parseInt(encoderSampleRate.value,10));
|
||||
decoderWorker.onmessage = function(e){
|
||||
|
||||
// null means decoder is finished
|
||||
if (e.data === null) {
|
||||
console.log("DONE!");
|
||||
}
|
||||
|
||||
// e.data contains decoded buffers as float32 values
|
||||
else {
|
||||
console.log("Data: ");
|
||||
console.log(e);
|
||||
audioBuffer.getChannelData(0).set(e.data[0]);
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(speakers);
|
||||
source.start();
|
||||
}
|
||||
};
|
||||
|
||||
recorder.ondataavailable = function( typedArray ){
|
||||
console.log("Page");
|
||||
//console.log(typedArray);
|
||||
decoderWorker.postMessage({
|
||||
command: 'decode',
|
||||
pages: typedArray
|
||||
}, [typedArray.buffer] );
|
||||
|
||||
/*
|
||||
var dataBlob = new Blob( [typedArray], { type: 'audio/ogg' } );
|
||||
var fileName = new Date().toISOString() + ".opus";
|
||||
var url = URL.createObjectURL( dataBlob );
|
||||
|
||||
var audio = document.createElement('audio');
|
||||
audio.controls = true;
|
||||
audio.src = url;
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.innerHTML = link.download;
|
||||
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(link);
|
||||
li.appendChild(audio);
|
||||
|
||||
recordingslist.appendChild(li);
|
||||
*/
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
172
test/webrtc.js
172
test/webrtc.js
|
@ -1,172 +0,0 @@
|
|||
/**
|
||||
* This a minimal fully functional example for setting up a client written in JavaScript that
|
||||
* communicates with a server via WebRTC data channels. This uses WebSockets to perform the WebRTC
|
||||
* handshake (offer/accept SDP) with the server. We only use WebSockets for the initial handshake
|
||||
* because TCP often presents too much latency in the context of real-time action games. WebRTC
|
||||
* data channels, on the other hand, allow for unreliable and unordered message sending via SCTP
|
||||
*
|
||||
* Brian Ho
|
||||
* brian@brkho.com
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
const audioContext = new AudioContext();
|
||||
const speakers = audioContext.destination;
|
||||
const audioBuffer = audioContext.createBuffer(1, 48000, 48000);
|
||||
|
||||
var mediaSource = new MediaSource();
|
||||
const audio = document.getElementById("audio");
|
||||
audio.src = window.URL.createObjectURL(mediaSource);
|
||||
|
||||
mediaSource.addEventListener('sourceopen', function() {
|
||||
var sourceBuffer = mediaSource.addSourceBuffer('audio/webm;codecs=opus');
|
||||
sourceBuffer.appendWindowStart = 0;
|
||||
sourceBuffer.mode = 'segments';
|
||||
|
||||
navigator.getUserMedia({video: false, audio: true}, function (stream) {
|
||||
var options = {
|
||||
audioBitsPerSecond: 48000, //128000,
|
||||
//videoBitsPerSecond : 2500000,
|
||||
mimeType: "audio/webm;codecs=opus" //
|
||||
};
|
||||
|
||||
var mediaRecorder = new MediaRecorder(stream, options);
|
||||
mediaRecorder.ondataavailable = function (e) {
|
||||
|
||||
|
||||
let now = new Date().getUTCMilliseconds();
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function () {//onloadend
|
||||
console.log(e);
|
||||
|
||||
console.log(fileReader.result);
|
||||
/*
|
||||
audioBuffer.getChannelData(0).set(fileReader.result);
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(speakers);
|
||||
source.start();
|
||||
|
||||
|
||||
|
||||
// single opus packet and it is a typedArray
|
||||
// decoder.decode(fileReader.result);
|
||||
|
||||
let buf = new Uint8Array(fileReader.result);
|
||||
buf.timestampOffset = 0;
|
||||
sourceBuffer.appendBuffer(buf);
|
||||
if(audio.paused) {
|
||||
audio.play(0);
|
||||
}
|
||||
let later = new Date().getUTCMilliseconds();
|
||||
console.log(later - now);
|
||||
console.log(sourceBuffer.buffered);
|
||||
|
||||
if (i === NUM_CHUNKS - 1) {
|
||||
mediaSource.endOfStream();
|
||||
} else {
|
||||
if ($("audio").paused) {
|
||||
// start playing after first chunk is appended
|
||||
$("audio").play();
|
||||
}
|
||||
//readChunk(++i);
|
||||
console.log("Read chunk?");
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
fileReader.readAsArrayBuffer(e.data);
|
||||
};
|
||||
mediaRecorder.start(20);
|
||||
}, function () {
|
||||
console.log("Cant applay audio");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Callback for when the data channel was successfully opened.
|
||||
var decoder = new Decoder.OpusToPCM({
|
||||
channels: 1,
|
||||
fallback: true
|
||||
});
|
||||
decoder.decoder.on("error", function (pcmData) {
|
||||
//do whatever you want to do with PCM data
|
||||
console.log("PCM:");
|
||||
console.log(pcmData);
|
||||
});
|
||||
decoder.on("decode", function (pcmData) {
|
||||
//do whatever you want to do with PCM data
|
||||
console.log("DDDD:");
|
||||
console.log(pcmData);
|
||||
|
||||
audioBuffer.getChannelData(0).set(pcmData);
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(speakers);
|
||||
source.start();
|
||||
|
||||
});
|
||||
console.log(decoder);
|
||||
|
||||
navigator.getUserMedia = navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||
|
||||
// TODO: Figure out what else we need and give the user feedback if he doesn't
|
||||
// support microphone input.
|
||||
if (navigator.getUserMedia) {
|
||||
captureMicrophone();
|
||||
}
|
||||
// First Step - Capture microphone and process the input
|
||||
function captureMicrophone() {
|
||||
// process input from microphone
|
||||
const processAudio = ev => processBuffer(ev.inputBuffer.getChannelData(CHANNEL));
|
||||
|
||||
// setup media stream from microphone
|
||||
const microphoneStream = stream => {
|
||||
const microphone = audioContext.createMediaStreamSource(stream);
|
||||
microphone.connect(processor);
|
||||
// #1 If we don't pass through to speakers 'audioprocess' won't be triggerd
|
||||
processor.connect(mute);
|
||||
};
|
||||
// TODO: Handle error properly (see todo above - but probably more specific)
|
||||
const userMediaError = err => console.error(err);
|
||||
|
||||
// Second step - Process buffer and output to speakers
|
||||
const processBuffer = buffer => {
|
||||
console.log(buffer);
|
||||
audioBuffer.getChannelData(CHANNEL).set(buffer);
|
||||
// We could move this out but that would affect audio quality
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(speakers);
|
||||
source.start();
|
||||
}
|
||||
|
||||
const audioContext = new AudioContext();
|
||||
const speakers = audioContext.destination;
|
||||
// We currently only operate on this channel we might need to add a couple
|
||||
// lines of code if this fact changes
|
||||
const CHANNEL = 0;
|
||||
const CHANNELS = 1;
|
||||
const BUFFER_SIZE = 4096;
|
||||
const audioBuffer = audioContext.createBuffer(CHANNELS, BUFFER_SIZE, audioContext.sampleRate);
|
||||
|
||||
const processor = audioContext.createScriptProcessor(BUFFER_SIZE, CHANNELS, CHANNELS);
|
||||
|
||||
// #2 Not needed we could directly pass through to speakers since there's no
|
||||
// data anyway but just to be sure that we don't output anything
|
||||
const mute = audioContext.createGain();
|
||||
//mute.gain.value = 1;
|
||||
mute.connect(speakers);
|
||||
|
||||
processor.addEventListener('audioprocess', processAudio);
|
||||
navigator.getUserMedia({audio: true}, microphoneStream, userMediaError);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
//###########################################################################################
|
||||
console.log(AV.Decoder.find("opus")());
|
||||
var player = AV.Player.fromURL('videoplayback.opus');
|
||||
player.play();
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
//"extends": "./tsconfig/tsconfig_debug.json"
|
||||
"extends": "./tsconfig/tsconfig_release.json"
|
||||
"extends": "./tsconfig/tsconfig_debug.json"
|
||||
//"extends": "./tsconfig/tsconfig_release.json"
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"sourceMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"../node_modules"
|
||||
"../node_modules",
|
||||
"../js/codec/workers"
|
||||
]
|
||||
}
|
|
@ -4,6 +4,11 @@
|
|||
"outFile": "../generated/js/client.js"
|
||||
},
|
||||
"exclude": [
|
||||
"../js/load.ts"
|
||||
"../js/load.ts",
|
||||
"../node_modules",
|
||||
"../js/codec/workers"
|
||||
],
|
||||
"include": [
|
||||
"../js/**/*"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue