Implemented native opus codec transfer
This commit is contained in:
parent
1dbd201267
commit
986f84f726
8 changed files with 154 additions and 63 deletions
|
@ -19,6 +19,7 @@
|
|||
|
||||
$localhost = true;
|
||||
}
|
||||
$localhost |= gethostname() == "WolverinDEV";
|
||||
if (!$localhost || $testXF) {
|
||||
//redirectOnInvalidSession();
|
||||
}
|
||||
|
@ -54,6 +55,7 @@
|
|||
}
|
||||
|
||||
spawnProperty('connect_default_host', $localhost ? "localhost" : "ts.TeaSpeak.de");
|
||||
spawnProperty('localhost_debug', $localhost ? "true" : "false");
|
||||
spawnProperty('forum_user_data', $_COOKIE[$GLOBALS["COOKIE_NAME_USER_DATA"]]);
|
||||
spawnProperty('forum_user_sign', $_COOKIE[$GLOBALS["COOKIE_NAME_USER_SIGN"]]);
|
||||
?>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
enum IdentitifyType {
|
||||
TEAFORO,
|
||||
TEAMSPEAK
|
||||
TEAMSPEAK,
|
||||
NICKNAME
|
||||
}
|
||||
|
||||
namespace TSIdentityHelper {
|
||||
|
@ -72,6 +73,33 @@ interface Identity {
|
|||
valid() : boolean;
|
||||
}
|
||||
|
||||
class NameIdentity implements Identity {
|
||||
private _name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
set_name(name: string) { this._name = name; }
|
||||
|
||||
name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
uid(): string {
|
||||
return btoa(this._name); //FIXME hash!
|
||||
}
|
||||
|
||||
type(): IdentitifyType {
|
||||
return IdentitifyType.NICKNAME;
|
||||
}
|
||||
|
||||
valid(): boolean {
|
||||
return this._name != undefined && this._name != "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TeamSpeakIdentity implements Identity {
|
||||
private handle: any;
|
||||
private _name: string;
|
||||
|
|
|
@ -293,11 +293,17 @@ class HandshakeHandler {
|
|||
data.publicKey = (this.identity as TeamSpeakIdentity).publicKey();
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
data.data = (this.identity as TeaForumIdentity).identityDataJson;
|
||||
} else if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
data["client_nickname"] = this.identity.name();
|
||||
}
|
||||
|
||||
this.connection.sendCommand("handshakebegin", data).catch(error => {
|
||||
console.log(error);
|
||||
//TODO here
|
||||
}).then(() => {
|
||||
if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
this.handshake_finished();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -307,20 +313,26 @@ class HandshakeHandler {
|
|||
proof = (this.identity as TeamSpeakIdentity).signMessage(json[0]["message"]);
|
||||
} else if(this.identity.type() == IdentitifyType.TEAFORO) {
|
||||
proof = (this.identity as TeaForumIdentity).identitySign;
|
||||
} else if(this.identity.type() == IdentitifyType.NICKNAME) {
|
||||
//FIXME handle error this should never happen!
|
||||
}
|
||||
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
|
||||
});
|
||||
this.handshake_finished();
|
||||
}).catch(error => {
|
||||
console.error("Got login error");
|
||||
console.log(error);
|
||||
}); //TODO handle error
|
||||
}
|
||||
|
||||
private handshake_finished() {
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionCommandHandler {
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Modals {
|
|||
|
||||
let field_nickname = tag.find(".connect_nickname");
|
||||
let nickname = field_nickname.val().toString();
|
||||
let flag_nickname = nickname.length == 0 || nickname.length >= 3 && nickname.length <= 32;
|
||||
let flag_nickname = (nickname.length == 0 && connectIdentity && connectIdentity.name().length > 0) || nickname.length >= 3 && nickname.length <= 32;
|
||||
|
||||
if(flag_address) {
|
||||
if(field_address.hasClass("invalid_input"))
|
||||
|
@ -61,47 +61,69 @@ namespace Modals {
|
|||
tag.find(".identity_select").val(IdentitifyType[def_connect_type ? def_connect_type : settings.global("connect_identity_type", IdentitifyType.TEAFORO)]);
|
||||
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);
|
||||
{
|
||||
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());
|
||||
settings.changeGlobal("connect_identity_teamspeak_identity", (connectIdentity as TeamSpeakIdentity).exported());
|
||||
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());
|
||||
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!");
|
||||
connectIdentity = undefined;
|
||||
} else {
|
||||
connectIdentity = TSIdentityHelper.loadIdentity(this.value);
|
||||
if(!connectIdentity) tag.find(".error_message").text("Could not parse identity! " + TSIdentityHelper.last_error());
|
||||
else settings.changeGlobal("connect_identity_teamspeak_identity", this.value);
|
||||
}
|
||||
|
||||
(!!connectIdentity ? tag.hide : tag.show).apply(tag.find(".error_message"));
|
||||
tag.find(".identity_file").val("");
|
||||
updateFields();
|
||||
};
|
||||
reader.onerror = ev => {
|
||||
tag.find(".error_message").text("Could not read identity file!").show();
|
||||
});
|
||||
tag.find(".identity_string").val(settings.global("connect_identity_teamspeak_identity", ""));
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAMSPEAK]).on('shown', ev => {
|
||||
tag.find(".identity_string").trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
if(!forumIdentity)
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAFORO]).html("You cant use your TeaSpeak forum account.<br>You're not connected!");
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAFORO]).on('shown', ev => {
|
||||
connectIdentity = forumIdentity;
|
||||
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!");
|
||||
connectIdentity = undefined;
|
||||
} else {
|
||||
connectIdentity = TSIdentityHelper.loadIdentity(this.value);
|
||||
if(!connectIdentity) tag.find(".error_message").text("Could not parse identity! " + TSIdentityHelper.last_error());
|
||||
else 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(settings.global("connect_identity_teamspeak_identity", ""));
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAMSPEAK]).on('shown', ev => { tag.find(".identity_string").trigger('change'); });
|
||||
|
||||
if(!forumIdentity)
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAFORO]).html("You cant use your TeaSpeak forum account.<br>You're not connected!");
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.TEAFORO]).on('shown', ev => { connectIdentity = forumIdentity; updateFields(); });
|
||||
{
|
||||
tag.find(".identity_config_" + IdentitifyType[IdentitifyType.NICKNAME]).on('shown', ev => {
|
||||
connectIdentity = new NameIdentity(tag.find(".connect_nickname").val() as string);
|
||||
updateFields();
|
||||
});
|
||||
tag.find(".connect_nickname").on("keyup", () => {
|
||||
if(connectIdentity instanceof NameIdentity)
|
||||
connectIdentity.set_name(tag.find(".connect_nickname").val() as string);
|
||||
});
|
||||
if(!settings.static("localhost_debug", false))
|
||||
tag.find(".identity_select option[value=" + IdentitifyType[IdentitifyType.NICKNAME] + "]").remove();
|
||||
}
|
||||
|
||||
//connect_address
|
||||
return tag;
|
||||
|
|
|
@ -101,12 +101,20 @@ class CodecPool {
|
|||
}
|
||||
}
|
||||
|
||||
enum VoiceConnectionType {
|
||||
JS_ENCODE,
|
||||
NATIVE_ENCODE
|
||||
}
|
||||
|
||||
class VoiceConnection {
|
||||
client: TSClient;
|
||||
rtcPeerConnection: RTCPeerConnection;
|
||||
dataChannel: RTCDataChannel;
|
||||
|
||||
voiceRecorder: VoiceRecorder;
|
||||
type: VoiceConnectionType = VoiceConnectionType.NATIVE_ENCODE;
|
||||
|
||||
local_audio_stream: any;
|
||||
|
||||
private codec_pool: CodecPool[] = [
|
||||
new CodecPool(this,0,"Spex A", undefined), //Spex
|
||||
|
@ -124,13 +132,23 @@ class VoiceConnection {
|
|||
constructor(client) {
|
||||
this.client = client;
|
||||
this.voiceRecorder = new VoiceRecorder(this);
|
||||
this.voiceRecorder.on_data = this.handleVoiceData.bind(this);
|
||||
if(this.type != VoiceConnectionType.NATIVE_ENCODE) {
|
||||
this.voiceRecorder.on_data = this.handleVoiceData.bind(this);
|
||||
}
|
||||
this.voiceRecorder.on_end = this.handleVoiceEnded.bind(this);
|
||||
this.voiceRecorder.reinitialiseVAD();
|
||||
|
||||
AudioController.on_initialized(() => {
|
||||
this.codec_pool[4].initialize(2);
|
||||
this.codec_pool[5].initialize(2);
|
||||
|
||||
if(this.type == VoiceConnectionType.NATIVE_ENCODE) {
|
||||
let stream = this.voiceRecorder.get_output_stream();
|
||||
stream.disconnect();
|
||||
|
||||
this.local_audio_stream = AudioController.globalContext.createMediaStreamDestination();
|
||||
stream.connect(this.local_audio_stream);
|
||||
}
|
||||
});
|
||||
|
||||
this.send_task = setInterval(this.sendNextVoicePacket.bind(this), 20);
|
||||
|
@ -180,6 +198,7 @@ class VoiceConnection {
|
|||
createSession() {
|
||||
this._ice_use_cache = true;
|
||||
|
||||
|
||||
let config: RTCConfiguration = {};
|
||||
config.iceServers = [];
|
||||
config.iceServers.push({ urls: 'stun:stun.l.google.com:19302' });
|
||||
|
@ -192,10 +211,14 @@ class VoiceConnection {
|
|||
this.dataChannel.binaryType = "arraybuffer";
|
||||
|
||||
let sdpConstraints : RTCOfferOptions = {};
|
||||
sdpConstraints.offerToReceiveAudio = 0;
|
||||
sdpConstraints.offerToReceiveAudio = this.type == VoiceConnectionType.NATIVE_ENCODE ? 1 : 0;
|
||||
sdpConstraints.offerToReceiveVideo = 0;
|
||||
|
||||
this.rtcPeerConnection.onicecandidate = this.onIceCandidate.bind(this);
|
||||
|
||||
if(this.local_audio_stream) { //May a typecheck?
|
||||
this.rtcPeerConnection.addStream(this.local_audio_stream.stream);
|
||||
}
|
||||
this.rtcPeerConnection.createOffer(this.onOfferCreated.bind(this), () => {
|
||||
console.error("Could not create ice offer!");
|
||||
}, sdpConstraints);
|
||||
|
@ -317,6 +340,10 @@ class VoiceConnection {
|
|||
.then(encoder => encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(4), data));
|
||||
}
|
||||
|
||||
audio_destination() : AudioNode {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private handleVoiceEnded() {
|
||||
if(!this.voiceRecorder) return;
|
||||
if(!this.client.connected) return;
|
||||
|
@ -324,6 +351,6 @@ class VoiceConnection {
|
|||
console.log("Voice ended");
|
||||
this.client.getClient().speaking = false;
|
||||
if(this.dataChannel)
|
||||
this.sendVoicePacket(new Uint8Array(0), 4); //TODO Use channel codec!
|
||||
this.sendVoicePacket(new Uint8Array(0), 5); //TODO Use channel codec!
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ class VoiceRecorder {
|
|||
private static readonly BUFFER_SIZE = 1024 * 4;
|
||||
|
||||
handle: VoiceConnection;
|
||||
on_data: (data: AudioBuffer, head: boolean) => void = (data) => {};
|
||||
on_data: (data: AudioBuffer, head: boolean) => void = undefined;
|
||||
on_end: () => void = () => {};
|
||||
|
||||
private _recording: boolean = false;
|
||||
|
@ -38,8 +38,8 @@ class VoiceRecorder {
|
|||
private mediaStream: MediaStream = undefined;
|
||||
|
||||
private audioContext: AudioContext;
|
||||
private processor: any;
|
||||
private mute: GainNode;
|
||||
private processor: ScriptProcessorNode;
|
||||
get_output_stream() : ScriptProcessorNode { return this.processor; }
|
||||
|
||||
private vadHandler: VoiceActivityDetector;
|
||||
private _chunkCount: number = 0;
|
||||
|
@ -58,20 +58,20 @@ class VoiceRecorder {
|
|||
this.processor = this.audioContext.createScriptProcessor(VoiceRecorder.BUFFER_SIZE, VoiceRecorder.CHANNELS, VoiceRecorder.CHANNELS);
|
||||
|
||||
this.processor.addEventListener('audioprocess', ev => {
|
||||
if(this.microphoneStream && this.vadHandler.shouldRecord(ev.inputBuffer))
|
||||
this.on_data(ev.inputBuffer, this._chunkCount++ == 0);
|
||||
else {
|
||||
if(this.microphoneStream && this.vadHandler.shouldRecord(ev.inputBuffer)) {
|
||||
if(this.on_data)
|
||||
this.on_data(ev.inputBuffer, this._chunkCount++ == 0);
|
||||
else {
|
||||
for(let channel = 0; channel < ev.inputBuffer.numberOfChannels; channel++)
|
||||
ev.outputBuffer.copyToChannel(ev.inputBuffer.getChannelData(channel), channel);
|
||||
}
|
||||
} else {
|
||||
if(this._chunkCount != 0) this.on_end();
|
||||
this._chunkCount = 0
|
||||
}
|
||||
});
|
||||
this.processor.connect(this.audioContext.destination);
|
||||
|
||||
//Not needed but make sure we have data for the preprocessor
|
||||
this.mute = this.audioContext.createGain();
|
||||
this.mute.gain.setValueAtTime(0, 0);
|
||||
this.mute.connect(this.audioContext.destination);
|
||||
|
||||
if(this.vadHandler)
|
||||
this.vadHandler.initialise();
|
||||
this.on_microphone(this.mediaStream);
|
||||
|
@ -92,10 +92,6 @@ class VoiceRecorder {
|
|||
return this.mediaStream;
|
||||
}
|
||||
|
||||
getDestinationContext() : AudioNode {
|
||||
return this.mute;
|
||||
}
|
||||
|
||||
getMicrophoneStream() : MediaStreamAudioSourceNode {
|
||||
return this.microphoneStream;
|
||||
}
|
||||
|
|
|
@ -437,6 +437,7 @@
|
|||
<select class="identity_select">
|
||||
<option name="identity_type" value="TEAFORO">Forum Account</option>
|
||||
<option name="identity_type" value="TEAMSPEAK">TeamSpeak</option>
|
||||
<option name="identity_type" value="NICKNAME">Nickname (Debug purposes only!)</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
|
@ -450,6 +451,9 @@
|
|||
<div class="identity_config_TEAFORO identity_config">
|
||||
You're using your forum account as verification
|
||||
</div>
|
||||
<div class="identity_config_NICKNAME identity_config">
|
||||
This is just for debug and uses the name as unique identifier
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message"></div>
|
||||
</div>
|
||||
|
|
2
vendor/bbcode
vendored
2
vendor/bbcode
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 67a1519a7254f2db9706f13184c0c38f331464b0
|
||||
Subproject commit 5fe4c1d9dedf52484b036575d4b068cde8858fb6
|
Loading…
Add table
Reference in a new issue