Added sound management and ppt delay
parent
2f81479519
commit
9234b95d5b
|
@ -1,4 +1,9 @@
|
|||
# Changelog:
|
||||
* **27.01.19**
|
||||
- Made sounds configurable
|
||||
- Added option to mute sounds when output is muted
|
||||
- Added push to talk delay option
|
||||
|
||||
* **26.01.19**
|
||||
- Improved TeaSpeak identities (now generates automatic and are saveable)
|
||||
- Fixed `connect_profile` parameter within URL
|
||||
|
|
|
@ -386,4 +386,11 @@ html, body {
|
|||
background: url('../img/music/playlist.svg') no-repeat;
|
||||
background-position: -11px -9px;
|
||||
background-size: 50px;
|
||||
}
|
||||
|
||||
x-content {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -1,3 +1,11 @@
|
|||
.help-tip-container {
|
||||
/* position: relative; */
|
||||
|
||||
.help-tip {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.help-tip {
|
||||
z-index: 100;
|
||||
|
||||
|
@ -21,7 +29,7 @@
|
|||
color:#fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &.show {
|
||||
p {
|
||||
display:block;
|
||||
transform-origin: 100% 0%;
|
||||
|
|
|
@ -18,6 +18,16 @@
|
|||
.group_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.sound {
|
||||
flex-shrink: 1;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-device {
|
||||
|
@ -96,6 +106,180 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.property {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.key {
|
||||
width: 120px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&.ppt-delay {
|
||||
margin-top: 5px;
|
||||
.value {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: .5em;
|
||||
transition: all .05s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
right: 1.5em;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: 'ms';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sound-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.property {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.key {
|
||||
width: 150px;
|
||||
|
||||
&.muted-sounds {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
&.master-volume {
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-left: 5px;
|
||||
width: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sound-list {
|
||||
margin-top: 5px;
|
||||
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
.column {
|
||||
&.sound-name {
|
||||
width: calc(100% - 150px);
|
||||
}
|
||||
|
||||
&.sound-activated {
|
||||
width: 150px;
|
||||
flex-grow: 0;
|
||||
|
||||
input {
|
||||
margin-left: 75px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sound-list-header {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
|
||||
.column {
|
||||
border: 1px solid lightgray;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sound-list-entries-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
overflow-y: auto;
|
||||
|
||||
min-height: 400px;
|
||||
max-height: 400px;
|
||||
|
||||
.entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.column {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #00000022;
|
||||
}
|
||||
|
||||
.button-playback:hover {
|
||||
background-color: #00000022;
|
||||
}
|
||||
}
|
||||
|
||||
&.scrollbar {
|
||||
.column {
|
||||
&.sound-name {
|
||||
width: calc(100% - 150px + 60px)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sound-list-filter {
|
||||
margin-top: 3px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -640,8 +640,16 @@
|
|||
{{tr "There are no setting entries for an <b>always</b> online voice detection."/}}
|
||||
</div>
|
||||
<div class="settings-vad-impl-entry setting-vad-ppt">
|
||||
<a>{{tr "Push to talk key:"/}}</a>
|
||||
<button class="vat_ppt_key">{{tr "Uninitialised"/}}</button>
|
||||
<div class="property ppt-key">
|
||||
<div class="key">{{tr "Push to talk key:"/}}</div>
|
||||
<div class="value"><button class="vat_ppt_key">{{tr "Uninitialised"/}}</button></div>
|
||||
</div>
|
||||
<div class="property ppt-delay">
|
||||
<div class="key">{{tr "Key release delay:" /}}</div>
|
||||
<div class="value">
|
||||
<input type="number" min="0" max="5000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-vad-impl-entry setting-vad-vad">
|
||||
<div>{{tr "Voice activity threshold (<a class='vad_vad_slider_value'>20</a>%)"/}}</div>
|
||||
|
@ -670,6 +678,68 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box sound">
|
||||
<div class="header">{{tr "Sound Settings" /}}</div>
|
||||
<div class="content">
|
||||
<div class="sound-settings">
|
||||
<div class="property">
|
||||
<div class="key">Master volume:</div>
|
||||
<div class="value master-volume">
|
||||
<input type="range" min="0" max="100" value="100">
|
||||
<a>(66%)</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key">
|
||||
Overlap same sounds:
|
||||
</div>
|
||||
<div class="value overlap-sounds">
|
||||
<input type="checkbox">
|
||||
<div class="help-tip-container"> <!-- lets be absolute -->
|
||||
<div class="help-tip tip-right tip-small">
|
||||
<p>
|
||||
{{tr "This options deferments if a sound overlaps itself when played twice.<br>" +
|
||||
"An example would be when you move multiple clients, you hear that sound n-times.<br>" +
|
||||
"If this option is disabled, you hear that sound just once."
|
||||
/}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="key muted-sounds">
|
||||
Mute sounds when output is muted:
|
||||
</div>
|
||||
<div class="value muted-sounds">
|
||||
<input type="checkbox">
|
||||
<div class="help-tip-container"> <!-- lets be absolute -->
|
||||
<div class="help-tip tip-right tip-small">
|
||||
<p>
|
||||
{{tr "Mute all system sounds, when you've muted your output.<br>If this option isn't disabled you'll still receive system sounds like 'user joined your channel'."/}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sound-list">
|
||||
<div class="sound-list-header">
|
||||
<div class="column sound-name">{{tr "Name" /}}</div>
|
||||
<div class="column sound-activated">{{tr "Activated" /}}</div>
|
||||
</div>
|
||||
<div class="sound-list-entries-container">
|
||||
<div class="sound-list-entries">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sound-list-filter">
|
||||
<a>Filter:</a>
|
||||
<input type="text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
|
@ -810,6 +880,15 @@
|
|||
</x-tab>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="tmpl_settings-sound_entry" type="text/html">
|
||||
<div class="entry">
|
||||
<div class="column sound-name"><div class="icon client-play button-playback"></div> {{>name}}</div>
|
||||
<div class="column sound-activated">
|
||||
<input type="checkbox" {{if activated}}checked{{/if}}>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script class="jsrender-template" id="settings-profile-list-entry" type="text/html">
|
||||
<div class="entry profile {{if id == 'default'}}default{{/if}}">
|
||||
<div class="name">{{>profile_name}}</div>
|
||||
|
|
|
@ -216,7 +216,7 @@ function base64ArrayBuffer(arrayBuffer) {
|
|||
return base64
|
||||
}
|
||||
|
||||
function Base64EncodeUrl(str){
|
||||
function Base64EncodeUrl(str) {
|
||||
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace profiles.identities {
|
|||
buffer[index++] = 0x01; /* length */
|
||||
buffer[index++] = 0x20;
|
||||
}
|
||||
{ /* Public kex X */
|
||||
try { /* Public kex X */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
|
@ -58,8 +58,13 @@ namespace profiles.identities {
|
|||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
} catch(error) {
|
||||
if(error instanceof DOMException)
|
||||
throw "failed to parse x coordinate (invalid base64)";
|
||||
throw error;
|
||||
}
|
||||
{ /* Public kex Y */
|
||||
|
||||
try { /* Public kex Y */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
|
@ -71,20 +76,32 @@ namespace profiles.identities {
|
|||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
} catch(error) {
|
||||
if(error instanceof DOMException)
|
||||
throw "failed to parse y coordinate (invalid base64)";
|
||||
throw error;
|
||||
}
|
||||
if(!public_key) { /* Public kex K */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
const raw = atob(Base64DecodeUrl(key_data.d, false));
|
||||
if(raw.charCodeAt(0) > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
if(!public_key) {
|
||||
try { /* Public kex K */
|
||||
buffer[index++] = 0x02; /* type */
|
||||
buffer[index++] = 0x20; /* length */
|
||||
|
||||
const raw = atob(Base64DecodeUrl(key_data.d, false));
|
||||
if(raw.charCodeAt(0) > 0x7F) {
|
||||
buffer[index - 1] += 1;
|
||||
buffer[index++] = 0;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
} catch(error) {
|
||||
if(error instanceof DOMException)
|
||||
throw "failed to parse y coordinate (invalid base64)";
|
||||
throw error;
|
||||
}
|
||||
|
||||
for(let i = 0; i < 32; i++)
|
||||
buffer[index++] = raw.charCodeAt(i);
|
||||
}
|
||||
|
||||
buffer[1] = index - 2; /* set the final sequence length */
|
||||
|
||||
return base64ArrayBuffer(buffer.buffer.slice(0, index));
|
||||
|
@ -127,7 +144,15 @@ namespace profiles.identities {
|
|||
* @param buffer base64 encoded ASN.1 string
|
||||
*/
|
||||
export function decode_tomcrypt_key(buffer: string) {
|
||||
const decoded = asn1.decode(atob(buffer));
|
||||
let decoded;
|
||||
|
||||
try {
|
||||
decoded = asn1.decode(atob(buffer));
|
||||
} catch(error) {
|
||||
if(error instanceof DOMException)
|
||||
throw "failed to parse key buffer (invalid base64)";
|
||||
throw error;
|
||||
}
|
||||
|
||||
let {x, y, k} = {
|
||||
x: decoded.children[2].content(Infinity, asn1.TagType.VisibleString),
|
||||
|
@ -373,7 +398,7 @@ namespace profiles.identities {
|
|||
static async generate_new() : Promise<TeaSpeakIdentity> {
|
||||
const key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
|
||||
const private_key = await CryptoHelper.export_ecc_key(key.privateKey, false);
|
||||
console.log("Private key: %s (%d)", private_key, atob(private_key).length);
|
||||
|
||||
const identity = new TeaSpeakIdentity(private_key, "0", undefined, false);
|
||||
await identity.initialize();
|
||||
return identity;
|
||||
|
@ -405,7 +430,7 @@ namespace profiles.identities {
|
|||
|
||||
if(!identity) throw "missing identity keyword";
|
||||
if(identity[0] == "\"" && identity[identity.length - 1] == "\"")
|
||||
identity = identity.substr(1, identity.length - 1);
|
||||
identity = identity.substr(1, identity.length - 2);
|
||||
|
||||
const result = parse_string(identity);
|
||||
result.name = name || result.name;
|
||||
|
|
|
@ -64,28 +64,131 @@ namespace sound {
|
|||
cached?: AudioBuffer;
|
||||
node?: HTMLAudioElement;
|
||||
|
||||
replaying: boolean;
|
||||
}
|
||||
|
||||
let warned = false;
|
||||
let speech_mapping: {[key: string]:SpeechFile} = {};
|
||||
|
||||
let volume_require_save = false;
|
||||
let speech_volume: {[key: string]:number} = {};
|
||||
let master_volume: number;
|
||||
|
||||
let overlap_sounds: boolean;
|
||||
let ignore_muted: boolean;
|
||||
|
||||
let master_mixed: GainNode;
|
||||
|
||||
function register_sound(key: string, file: string) {
|
||||
speech_mapping[key] = {key: key, filename: file} as SpeechFile;
|
||||
}
|
||||
|
||||
export function get_sound_volume(sound: Sound) : number {
|
||||
let result = speech_volume[sound];
|
||||
if(typeof(result) === "undefined")
|
||||
result = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function set_sound_volume(sound: Sound, volume: number) {
|
||||
volume_require_save = volume_require_save || speech_volume[sound] != volume;
|
||||
speech_volume[sound] = volume == 1 ? undefined : volume;
|
||||
}
|
||||
|
||||
export function get_master_volume() : number {
|
||||
return master_volume;
|
||||
}
|
||||
|
||||
export function set_master_volume(volume: number) {
|
||||
volume_require_save = volume_require_save || master_volume != volume;
|
||||
master_volume = volume;
|
||||
if(master_mixed.gain.setValueAtTime)
|
||||
master_mixed.gain.setValueAtTime(volume, 0);
|
||||
else
|
||||
master_mixed.gain.value = volume;
|
||||
}
|
||||
|
||||
export function overlap_activated() : boolean {
|
||||
return overlap_sounds;
|
||||
}
|
||||
|
||||
export function set_overlap_activated(flag: boolean) {
|
||||
volume_require_save = volume_require_save || overlap_sounds != flag;
|
||||
overlap_sounds = flag;
|
||||
}
|
||||
|
||||
export function ignore_output_muted() : boolean {
|
||||
return ignore_muted;
|
||||
}
|
||||
|
||||
export function set_ignore_output_muted(flag: boolean) {
|
||||
volume_require_save = volume_require_save || ignore_muted != flag;
|
||||
ignore_muted = flag;
|
||||
}
|
||||
|
||||
export function reinitialisize_audio() {
|
||||
const context = audio.player.context();
|
||||
const destination = audio.player.destination();
|
||||
|
||||
if(master_mixed)
|
||||
master_mixed.disconnect();
|
||||
|
||||
master_mixed = context.createGain();
|
||||
if(master_mixed.gain.setValueAtTime)
|
||||
master_mixed.gain.setValueAtTime(master_volume, 0);
|
||||
else
|
||||
master_mixed.gain.value = master_volume;
|
||||
master_mixed.connect(destination);
|
||||
}
|
||||
|
||||
export function save() {
|
||||
if(volume_require_save) {
|
||||
volume_require_save = false;
|
||||
|
||||
const data: any = {};
|
||||
data.version = 1;
|
||||
|
||||
for(const sound in Sound) {
|
||||
if(typeof(speech_volume[sound]) !== "undefined")
|
||||
data[sound] = speech_volume[sound];
|
||||
}
|
||||
data.master = master_volume;
|
||||
data.overlap = overlap_sounds;
|
||||
data.ignore_muted = ignore_muted;
|
||||
|
||||
settings.changeGlobal("sound_volume", JSON.stringify(data));
|
||||
console.error(data);
|
||||
}
|
||||
}
|
||||
|
||||
export function initialize() : Promise<void> {
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings){
|
||||
if (settings.dataType === 'binary'){
|
||||
console.log("Settins binary");
|
||||
settings.xhr().responseType = 'arraybuffer';
|
||||
settings.processData = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* volumes */
|
||||
{
|
||||
const data = JSON.parse(settings.static_global("sound_volume", "{}"));
|
||||
for(const sound in Sound) {
|
||||
if(typeof(data[sound]) !== "undefined")
|
||||
speech_volume[sound] = data[sound];
|
||||
}
|
||||
|
||||
console.error(data);
|
||||
master_volume = data.master || 1;
|
||||
overlap_sounds = data.overlap || true;
|
||||
ignore_muted = data.ignore_muted || true;
|
||||
}
|
||||
|
||||
register_sound("message.received", "effects/message_received.wav");
|
||||
register_sound("message.send", "effects/message_send.wav");
|
||||
|
||||
reinitialisize_audio();
|
||||
return new Promise<void>(resolve => {
|
||||
$.ajax({
|
||||
url: "audio/speech/mapping.json",
|
||||
|
@ -107,26 +210,23 @@ namespace sound {
|
|||
})
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
|
||||
var bufView = new Uint16Array(buf);
|
||||
for (var i = 0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
export interface PlaybackOptions {
|
||||
ignore_muted?: boolean;
|
||||
ignore_overlap?: boolean;
|
||||
}
|
||||
|
||||
export function play(sound: Sound, options?: {
|
||||
background_notification?: boolean
|
||||
}) {
|
||||
console.log(tr("playback sound %o"), sound);
|
||||
export function play(sound: Sound, options?: PlaybackOptions) {
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const file: SpeechFile = speech_mapping[sound];
|
||||
if(!file) {
|
||||
console.warn(tr("Missing sound %o"), sound);
|
||||
return;
|
||||
}
|
||||
if(file.not_supported) {
|
||||
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported isnt may timeouted
|
||||
if(!file.not_supported_timeout || Date.now() < file.not_supported_timeout) //Test if the not supported isn't may timeouted
|
||||
return;
|
||||
file.not_supported = false;
|
||||
file.not_supported_timeout = undefined;
|
||||
|
@ -134,15 +234,30 @@ namespace sound {
|
|||
|
||||
const path = "audio/" + file.filename;
|
||||
const context = audio.player.context();
|
||||
const volume = options && options.background_notification ? .5 : 1;
|
||||
const volume = get_sound_volume(sound);
|
||||
|
||||
console.log(tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), sound, volume, master_volume);
|
||||
if(volume == 0) return;
|
||||
if(master_volume == 0) return;
|
||||
if(!options.ignore_muted && !ignore_muted && globalClient.controlBar.muteOutput) return;
|
||||
|
||||
if(context.decodeAudioData) {
|
||||
if(file.cached) {
|
||||
if(!options.ignore_overlap && file.replaying && !overlap_sounds) {
|
||||
console.log(tr("Dropping requested playback for sound %s because it would overlap."), sound);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(tr("Using cached buffer: %o"), file.cached);
|
||||
const player = context.createBufferSource();
|
||||
player.buffer = file.cached;
|
||||
player.start(0);
|
||||
|
||||
file.replaying = true;
|
||||
player.onended = event => {
|
||||
file.replaying = false;
|
||||
};
|
||||
|
||||
if(volume != 1 && context.createGain) {
|
||||
const gain = context.createGain();
|
||||
if(gain.gain.setValueAtTime)
|
||||
|
@ -151,9 +266,10 @@ namespace sound {
|
|||
gain.gain.value = volume;
|
||||
|
||||
player.connect(gain);
|
||||
gain.connect(audio.player.destination());
|
||||
} else
|
||||
player.connect(audio.player.destination());
|
||||
gain.connect(master_mixed);
|
||||
} else {
|
||||
player.connect(master_mixed);
|
||||
}
|
||||
} else {
|
||||
const decode_data = buffer => {
|
||||
console.log(buffer);
|
||||
|
@ -203,6 +319,7 @@ namespace sound {
|
|||
console.log(tr("Replaying %s"), path);
|
||||
if(file.node) {
|
||||
file.node.currentTime = 0;
|
||||
file.node.
|
||||
file.node.play();
|
||||
} else {
|
||||
if(!warned) {
|
||||
|
|
|
@ -75,6 +75,8 @@ namespace Modals {
|
|||
ppt_settings = ppt_settings ? JSON.parse(ppt_settings as any as string) : {};
|
||||
|
||||
vad_tag.find(".vat_ppt_key").text(ppt.key_description(ppt_settings));
|
||||
vad_tag.find(".ppt-delay input").val(ppt_settings.delay === undefined ? 300 : ppt_settings.delay);
|
||||
|
||||
break;
|
||||
case "vad":
|
||||
let slider = vad_tag.find(".vad_vad_slider");
|
||||
|
@ -123,6 +125,15 @@ namespace Modals {
|
|||
ppt.register_key_listener(listener);
|
||||
modal.open();
|
||||
});
|
||||
|
||||
vad_tag.find(".ppt-delay input").on('change', event => {
|
||||
let ppt_settings: PPTKeySettings = settings.global('vad_ppt_settings', undefined);
|
||||
ppt_settings = ppt_settings ? JSON.parse(ppt_settings as any as string) : {};
|
||||
ppt_settings.delay = (<HTMLInputElement>event.target).valueAsNumber;
|
||||
settings.changeGlobal('vad_ppt_settings', ppt_settings);
|
||||
|
||||
globalClient.voiceConnection.voiceRecorder.reinitialiseVAD();
|
||||
});
|
||||
}
|
||||
|
||||
{ //Initialized voice activation detection
|
||||
|
@ -246,6 +257,89 @@ namespace Modals {
|
|||
}
|
||||
}
|
||||
|
||||
{ /* initialize sounds */
|
||||
const sound_tag = tag.find(".sound-settings");
|
||||
|
||||
{ /* master volume */
|
||||
const master_tag = sound_tag.find(".master-volume");
|
||||
master_tag.find("input").on('change input', event => {
|
||||
const value = parseInt((<HTMLInputElement>event.target).value);
|
||||
master_tag.find('a').text("(" + value + "%)");
|
||||
|
||||
sound.set_master_volume(value / 100);
|
||||
}).val((sound.get_master_volume() * 100).toString()).trigger('change');
|
||||
}
|
||||
|
||||
{
|
||||
const overlap_tag = sound_tag.find(".overlap-sounds input");
|
||||
overlap_tag.on('change', event => {
|
||||
const activated = (<HTMLInputElement>event.target).checked;
|
||||
sound.set_overlap_activated(activated);
|
||||
}).prop("checked", sound.overlap_activated());
|
||||
}
|
||||
|
||||
{
|
||||
const muted_tag = sound_tag.find(".muted-sounds input");
|
||||
muted_tag.on('change', event => {
|
||||
const activated = (<HTMLInputElement>event.target).checked;
|
||||
sound.set_ignore_output_muted(!activated);
|
||||
}).prop("checked", !sound.ignore_output_muted());
|
||||
}
|
||||
|
||||
{ /* sound elements */
|
||||
const template_tag = $("#tmpl_settings-sound_entry");
|
||||
const entry_tag = sound_tag.find(".sound-list-entries");
|
||||
|
||||
for(const _sound in Sound) {
|
||||
const sound_name = Sound[_sound];
|
||||
|
||||
console.log(sound.get_sound_volume(sound_name as Sound));
|
||||
const data = {
|
||||
name: sound_name,
|
||||
activated: sound.get_sound_volume(sound_name as Sound) > 0
|
||||
};
|
||||
|
||||
const entry = template_tag.renderTag(data);
|
||||
entry.find("input").on('change', event => {
|
||||
const activated = (<HTMLInputElement>event.target).checked;
|
||||
console.log(tr("Sound %s had changed to %o"), sound_name, activated);
|
||||
sound.set_sound_volume(sound_name as Sound, activated ? 1 : 0);
|
||||
});
|
||||
|
||||
entry.find(".button-playback").on('click', event => {
|
||||
sound.play(sound_name as Sound);
|
||||
});
|
||||
|
||||
entry_tag.append(entry);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const entry_container = sound_tag.find(".sound-list-entries-container");
|
||||
if(entry_container.hasScrollBar())
|
||||
entry_container.addClass("scrollbar");
|
||||
}, 100);
|
||||
|
||||
/* filter */
|
||||
const filter_tag = sound_tag.find(".sound-list-filter input");
|
||||
filter_tag.on('change keyup', event => {
|
||||
const filter = ((<HTMLInputElement>event.target).value || "").toLowerCase();
|
||||
if(!filter)
|
||||
entry_tag.find(".entry").show();
|
||||
else {
|
||||
entry_tag.find(".entry").each((_, _entry) => {
|
||||
const entry = $(_entry);
|
||||
if(entry.text().toLowerCase().indexOf(filter) == -1)
|
||||
entry.hide();
|
||||
else
|
||||
entry.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modal.close_listener.push(sound.save);
|
||||
}
|
||||
|
||||
//Initialise microphones
|
||||
/*
|
||||
let select_microphone = tag.find(".voice_microphone_select");
|
||||
|
@ -570,7 +664,7 @@ namespace Modals {
|
|||
const element = event.target as HTMLInputElement;
|
||||
const file_reader = new FileReader();
|
||||
file_reader.onload = function() {
|
||||
const identity_promise = profiles.identities.TeaSpeakIdentity.import_ts(file_reader.result, true);
|
||||
const identity_promise = profiles.identities.TeaSpeakIdentity.import_ts(file_reader.result as string, true);
|
||||
identity_promise.then(identity => {
|
||||
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e));
|
||||
selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);
|
||||
|
|
|
@ -145,9 +145,12 @@ class VoiceRecorder {
|
|||
if(ppt_settings.key_windows === undefined)
|
||||
ppt_settings.key_windows = false;
|
||||
|
||||
if(ppt_settings.delay === undefined)
|
||||
ppt_settings.delay = 300;
|
||||
|
||||
if(!(this.getVADHandler() instanceof PushToTalkVAD))
|
||||
this.setVADHandler(new PushToTalkVAD(ppt_settings));
|
||||
else (this.getVADHandler() as PushToTalkVAD).key = ppt_settings;
|
||||
else (this.getVADHandler() as PushToTalkVAD).settings = ppt_settings;
|
||||
} else if(type == "pt") {
|
||||
if(!(this.getVADHandler() instanceof PassThroughVAD))
|
||||
this.setVADHandler(new PassThroughVAD());
|
||||
|
@ -324,28 +327,28 @@ class VoiceActivityDetectorVAD extends VoiceActivityDetector {
|
|||
}
|
||||
}
|
||||
|
||||
interface PPTKeySettings extends ppt.KeyDescriptor{
|
||||
interface PPTKeySettings extends ppt.KeyDescriptor {
|
||||
version?: number;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
class PushToTalkVAD extends VoiceActivityDetector {
|
||||
private _key: ppt.KeyDescriptor;
|
||||
private _settings: PPTKeySettings;
|
||||
private _key_hook: ppt.KeyHook;
|
||||
private _timeout: NodeJS.Timer;
|
||||
private _delay = /* 300 */ 0; //TODO configurable
|
||||
|
||||
private _pushed: boolean = false;
|
||||
|
||||
constructor(key: ppt.KeyDescriptor) {
|
||||
constructor(settings: PPTKeySettings) {
|
||||
super();
|
||||
this._key = key;
|
||||
this._settings = settings;
|
||||
this._key_hook = {
|
||||
callback_release: () => {
|
||||
if(this._timeout)
|
||||
clearTimeout(this._timeout);
|
||||
|
||||
if(this._delay > 0)
|
||||
this._timeout = setTimeout(() => this._pushed = false, this._delay);
|
||||
if(this._settings.delay > 0)
|
||||
this._timeout = setTimeout(() => this._pushed = false, this._settings.delay);
|
||||
else
|
||||
this._pushed = false;
|
||||
},
|
||||
|
@ -358,9 +361,17 @@ class PushToTalkVAD extends VoiceActivityDetector {
|
|||
|
||||
cancel: false
|
||||
} as ppt.KeyHook;
|
||||
Object.assign(this._key_hook, this._key);
|
||||
|
||||
this.initialize_hook();
|
||||
}
|
||||
|
||||
private initialize_hook() {
|
||||
this._key_hook.key_code = this._settings.key_code;
|
||||
this._key_hook.key_alt = this._settings.key_alt;
|
||||
this._key_hook.key_ctrl = this._settings.key_ctrl;
|
||||
this._key_hook.key_shift = this._settings.key_shift;
|
||||
this._key_hook.key_windows = this._settings.key_windows;
|
||||
}
|
||||
|
||||
initialise() {
|
||||
ppt.register_key_hook(this._key_hook);
|
||||
|
@ -376,11 +387,13 @@ class PushToTalkVAD extends VoiceActivityDetector {
|
|||
this._pushed = flag;
|
||||
}
|
||||
|
||||
set key(key: ppt.KeyDescriptor) {
|
||||
set settings(settings: PPTKeySettings) {
|
||||
ppt.unregister_key_hook(this._key_hook);
|
||||
Object.assign(this._key, key);
|
||||
Object.assign(this._key_hook, key);
|
||||
|
||||
this._settings = settings;
|
||||
this.initialize_hook();
|
||||
this._pushed = false;
|
||||
|
||||
ppt.register_key_hook(this._key_hook);
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8304246b4f651b9de141e4d3252b4f78e5c55391
|
||||
Subproject commit 7fe6a479984b77160a9135f9d2182fb79ca56023
|
Loading…
Reference in New Issue