Added sound management and ppt delay

canary
WolverinDEV 2019-01-27 13:11:40 +01:00
parent 2f81479519
commit 9234b95d5b
11 changed files with 582 additions and 50 deletions

View File

@ -1,4 +1,9 @@
# Changelog: # 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** * **26.01.19**
- Improved TeaSpeak identities (now generates automatic and are saveable) - Improved TeaSpeak identities (now generates automatic and are saveable)
- Fixed `connect_profile` parameter within URL - Fixed `connect_profile` parameter within URL

View File

@ -387,3 +387,10 @@ html, body {
background-position: -11px -9px; background-position: -11px -9px;
background-size: 50px; background-size: 50px;
} }
x-content {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
}

View File

@ -1,3 +1,11 @@
.help-tip-container {
/* position: relative; */
.help-tip {
position: absolute;
}
}
.help-tip { .help-tip {
z-index: 100; z-index: 100;
@ -21,7 +29,7 @@
color:#fff; color:#fff;
} }
&:hover { &:hover, &.show {
p { p {
display:block; display:block;
transform-origin: 100% 0%; transform-origin: 100% 0%;

View File

@ -18,6 +18,16 @@
.group_box { .group_box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-shrink: 0;
&.sound {
flex-shrink: 1;
.content {
display: flex;
flex-direction: column;
}
}
} }
.settings-device { .settings-device {
@ -96,6 +106,180 @@
display: none; 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;
}
}
} }
} }

View File

@ -640,8 +640,16 @@
{{tr "There are no setting entries for an <b>always</b> online voice detection."/}} {{tr "There are no setting entries for an <b>always</b> online voice detection."/}}
</div> </div>
<div class="settings-vad-impl-entry setting-vad-ppt"> <div class="settings-vad-impl-entry setting-vad-ppt">
<a>{{tr "Push to talk key:"/}}</a> <div class="property ppt-key">
<button class="vat_ppt_key">{{tr "Uninitialised"/}}</button> <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>
<div class="settings-vad-impl-entry setting-vad-vad"> <div class="settings-vad-impl-entry setting-vad-vad">
<div>{{tr "Voice activity threshold (<a class='vad_vad_slider_value'>20</a>%)"/}}</div> <div>{{tr "Voice activity threshold (<a class='vad_vad_slider_value'>20</a>%)"/}}</div>
@ -670,6 +678,68 @@
</div> </div>
</div> </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> </div>
</x-content> </x-content>
</x-entry> </x-entry>
@ -810,6 +880,15 @@
</x-tab> </x-tab>
</script> </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"> <script class="jsrender-template" id="settings-profile-list-entry" type="text/html">
<div class="entry profile {{if id == 'default'}}default{{/if}}"> <div class="entry profile {{if id == 'default'}}default{{/if}}">
<div class="name">{{>profile_name}}</div> <div class="name">{{>profile_name}}</div>

View File

@ -216,7 +216,7 @@ function base64ArrayBuffer(arrayBuffer) {
return base64 return base64
} }
function Base64EncodeUrl(str){ function Base64EncodeUrl(str) {
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''); return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
} }

View File

@ -46,7 +46,7 @@ namespace profiles.identities {
buffer[index++] = 0x01; /* length */ buffer[index++] = 0x01; /* length */
buffer[index++] = 0x20; buffer[index++] = 0x20;
} }
{ /* Public kex X */ try { /* Public kex X */
buffer[index++] = 0x02; /* type */ buffer[index++] = 0x02; /* type */
buffer[index++] = 0x20; /* length */ buffer[index++] = 0x20; /* length */
@ -58,8 +58,13 @@ namespace profiles.identities {
for(let i = 0; i < 32; i++) for(let i = 0; i < 32; i++)
buffer[index++] = raw.charCodeAt(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++] = 0x02; /* type */
buffer[index++] = 0x20; /* length */ buffer[index++] = 0x20; /* length */
@ -71,8 +76,14 @@ namespace profiles.identities {
for(let i = 0; i < 32; i++) for(let i = 0; i < 32; i++)
buffer[index++] = raw.charCodeAt(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 */
if(!public_key) {
try { /* Public kex K */
buffer[index++] = 0x02; /* type */ buffer[index++] = 0x02; /* type */
buffer[index++] = 0x20; /* length */ buffer[index++] = 0x20; /* length */
@ -84,7 +95,13 @@ namespace profiles.identities {
for(let i = 0; i < 32; i++) for(let i = 0; i < 32; i++)
buffer[index++] = raw.charCodeAt(i); buffer[index++] = raw.charCodeAt(i);
} catch(error) {
if(error instanceof DOMException)
throw "failed to parse y coordinate (invalid base64)";
throw error;
} }
}
buffer[1] = index - 2; /* set the final sequence length */ buffer[1] = index - 2; /* set the final sequence length */
return base64ArrayBuffer(buffer.buffer.slice(0, index)); return base64ArrayBuffer(buffer.buffer.slice(0, index));
@ -127,7 +144,15 @@ namespace profiles.identities {
* @param buffer base64 encoded ASN.1 string * @param buffer base64 encoded ASN.1 string
*/ */
export function decode_tomcrypt_key(buffer: 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} = { let {x, y, k} = {
x: decoded.children[2].content(Infinity, asn1.TagType.VisibleString), x: decoded.children[2].content(Infinity, asn1.TagType.VisibleString),
@ -373,7 +398,7 @@ namespace profiles.identities {
static async generate_new() : Promise<TeaSpeakIdentity> { static async generate_new() : Promise<TeaSpeakIdentity> {
const key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]); const key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
const private_key = await CryptoHelper.export_ecc_key(key.privateKey, false); 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); const identity = new TeaSpeakIdentity(private_key, "0", undefined, false);
await identity.initialize(); await identity.initialize();
return identity; return identity;
@ -405,7 +430,7 @@ namespace profiles.identities {
if(!identity) throw "missing identity keyword"; if(!identity) throw "missing identity keyword";
if(identity[0] == "\"" && identity[identity.length - 1] == "\"") 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); const result = parse_string(identity);
result.name = name || result.name; result.name = name || result.name;

View File

@ -64,28 +64,131 @@ namespace sound {
cached?: AudioBuffer; cached?: AudioBuffer;
node?: HTMLAudioElement; node?: HTMLAudioElement;
replaying: boolean;
} }
let warned = false; let warned = false;
let speech_mapping: {[key: string]:SpeechFile} = {}; 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) { function register_sound(key: string, file: string) {
speech_mapping[key] = {key: key, filename: file} as SpeechFile; 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> { export function initialize() : Promise<void> {
$.ajaxSetup({ $.ajaxSetup({
beforeSend: function(jqXHR,settings){ beforeSend: function(jqXHR,settings){
if (settings.dataType === 'binary'){ if (settings.dataType === 'binary'){
console.log("Settins binary");
settings.xhr().responseType = 'arraybuffer'; settings.xhr().responseType = 'arraybuffer';
settings.processData = false; 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.received", "effects/message_received.wav");
register_sound("message.send", "effects/message_send.wav"); register_sound("message.send", "effects/message_send.wav");
reinitialisize_audio();
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
$.ajax({ $.ajax({
url: "audio/speech/mapping.json", url: "audio/speech/mapping.json",
@ -107,26 +210,23 @@ namespace sound {
}) })
} }
function str2ab(str) { export interface PlaybackOptions {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char ignore_muted?: boolean;
var bufView = new Uint16Array(buf); ignore_overlap?: boolean;
for (var i = 0, strLen=str.length; i<strLen; i++) { }
bufView[i] = str.charCodeAt(i);
} export function play(sound: Sound, options?: PlaybackOptions) {
return buf; if(!options) {
options = {};
} }
export function play(sound: Sound, options?: {
background_notification?: boolean
}) {
console.log(tr("playback sound %o"), sound);
const file: SpeechFile = speech_mapping[sound]; const file: SpeechFile = speech_mapping[sound];
if(!file) { if(!file) {
console.warn(tr("Missing sound %o"), sound); console.warn(tr("Missing sound %o"), sound);
return; return;
} }
if(file.not_supported) { 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; return;
file.not_supported = false; file.not_supported = false;
file.not_supported_timeout = undefined; file.not_supported_timeout = undefined;
@ -134,15 +234,30 @@ namespace sound {
const path = "audio/" + file.filename; const path = "audio/" + file.filename;
const context = audio.player.context(); 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(context.decodeAudioData) {
if(file.cached) { 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); console.log(tr("Using cached buffer: %o"), file.cached);
const player = context.createBufferSource(); const player = context.createBufferSource();
player.buffer = file.cached; player.buffer = file.cached;
player.start(0); player.start(0);
file.replaying = true;
player.onended = event => {
file.replaying = false;
};
if(volume != 1 && context.createGain) { if(volume != 1 && context.createGain) {
const gain = context.createGain(); const gain = context.createGain();
if(gain.gain.setValueAtTime) if(gain.gain.setValueAtTime)
@ -151,9 +266,10 @@ namespace sound {
gain.gain.value = volume; gain.gain.value = volume;
player.connect(gain); player.connect(gain);
gain.connect(audio.player.destination()); gain.connect(master_mixed);
} else } else {
player.connect(audio.player.destination()); player.connect(master_mixed);
}
} else { } else {
const decode_data = buffer => { const decode_data = buffer => {
console.log(buffer); console.log(buffer);
@ -203,6 +319,7 @@ namespace sound {
console.log(tr("Replaying %s"), path); console.log(tr("Replaying %s"), path);
if(file.node) { if(file.node) {
file.node.currentTime = 0; file.node.currentTime = 0;
file.node.
file.node.play(); file.node.play();
} else { } else {
if(!warned) { if(!warned) {

View File

@ -75,6 +75,8 @@ namespace Modals {
ppt_settings = ppt_settings ? JSON.parse(ppt_settings as any as string) : {}; 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(".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; break;
case "vad": case "vad":
let slider = vad_tag.find(".vad_vad_slider"); let slider = vad_tag.find(".vad_vad_slider");
@ -123,6 +125,15 @@ namespace Modals {
ppt.register_key_listener(listener); ppt.register_key_listener(listener);
modal.open(); 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 { //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 //Initialise microphones
/* /*
let select_microphone = tag.find(".voice_microphone_select"); let select_microphone = tag.find(".voice_microphone_select");
@ -570,7 +664,7 @@ namespace Modals {
const element = event.target as HTMLInputElement; const element = event.target as HTMLInputElement;
const file_reader = new FileReader(); const file_reader = new FileReader();
file_reader.onload = function() { 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_promise.then(identity => {
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e)); (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); selected_profile.set_identity(IdentitifyType.TEAMSPEAK, identity as any);

View File

@ -145,9 +145,12 @@ class VoiceRecorder {
if(ppt_settings.key_windows === undefined) if(ppt_settings.key_windows === undefined)
ppt_settings.key_windows = false; ppt_settings.key_windows = false;
if(ppt_settings.delay === undefined)
ppt_settings.delay = 300;
if(!(this.getVADHandler() instanceof PushToTalkVAD)) if(!(this.getVADHandler() instanceof PushToTalkVAD))
this.setVADHandler(new PushToTalkVAD(ppt_settings)); 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") { } else if(type == "pt") {
if(!(this.getVADHandler() instanceof PassThroughVAD)) if(!(this.getVADHandler() instanceof PassThroughVAD))
this.setVADHandler(new 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; version?: number;
delay: number;
} }
class PushToTalkVAD extends VoiceActivityDetector { class PushToTalkVAD extends VoiceActivityDetector {
private _key: ppt.KeyDescriptor; private _settings: PPTKeySettings;
private _key_hook: ppt.KeyHook; private _key_hook: ppt.KeyHook;
private _timeout: NodeJS.Timer; private _timeout: NodeJS.Timer;
private _delay = /* 300 */ 0; //TODO configurable
private _pushed: boolean = false; private _pushed: boolean = false;
constructor(key: ppt.KeyDescriptor) { constructor(settings: PPTKeySettings) {
super(); super();
this._key = key; this._settings = settings;
this._key_hook = { this._key_hook = {
callback_release: () => { callback_release: () => {
if(this._timeout) if(this._timeout)
clearTimeout(this._timeout); clearTimeout(this._timeout);
if(this._delay > 0) if(this._settings.delay > 0)
this._timeout = setTimeout(() => this._pushed = false, this._delay); this._timeout = setTimeout(() => this._pushed = false, this._settings.delay);
else else
this._pushed = false; this._pushed = false;
}, },
@ -358,9 +361,17 @@ class PushToTalkVAD extends VoiceActivityDetector {
cancel: false cancel: false
} as ppt.KeyHook; } 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() { initialise() {
ppt.register_key_hook(this._key_hook); ppt.register_key_hook(this._key_hook);
@ -376,11 +387,13 @@ class PushToTalkVAD extends VoiceActivityDetector {
this._pushed = flag; this._pushed = flag;
} }
set key(key: ppt.KeyDescriptor) { set settings(settings: PPTKeySettings) {
ppt.unregister_key_hook(this._key_hook); 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; this._pushed = false;
ppt.register_key_hook(this._key_hook); ppt.register_key_hook(this._key_hook);
} }

2
vendor/bbcode vendored

@ -1 +1 @@
Subproject commit 8304246b4f651b9de141e4d3252b4f78e5c55391 Subproject commit 7fe6a479984b77160a9135f9d2182fb79ca56023