jam-cloud/web/app/assets/javascripts/voiceChatHelper.js

407 lines
14 KiB
JavaScript

(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.VoiceChatHelper = function (app) {
var logger = context.JK.logger;
var ALERT_NAMES = context.JK.ALERT_NAMES;
var ASSIGNMENT = context.JK.ASSIGNMENT;
var VOICE_CHAT = context.JK.VOICE_CHAT;
var MAX_TRACKS = context.JK.MAX_TRACKS;
var MAX_OUTPUTS = context.JK.MAX_OUTPUTS;
var gearUtils = context.JK.GearUtils;
var $parent = null;
var $reuseAudioInputRadio = null;
var $useChatInputRadio = null;
var $chatInputs = null;
var $templateChatInput = null;
var $selectedChatInput = null;// should only be used if isChatEnabled = true
var $voiceChatVuLeft = null;
var $voiceChatVuRight = null;
var $voiceChatFader = null;
var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist
var uniqueCallbackName = null;
// needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x)
var ignoreICheckEvent = false;
var lastSavedTime = new Date();
var vuOptions = null;
var faderHeight = null;
var startingState = null;
var resettedOnInvalidDevice = false; // this is set to true when we clear chat, and set to false when the user interacts in anyway
function defaultReuse() {
suppressChange(function(){
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
$useChatInputRadio.removeAttr('checked');
})
}
function isChatEnabled() {
return $useChatInputRadio.is(':checked');
}
function onInvalidAudioDevice(e, data) {
logger.debug("voice_chat_helper: onInvalidAudioDevice")
if(resettedOnInvalidDevice) {
// we've already tried to clear the audio device, and the user hasn't interacted, but still we are getting this event
// we can't keep taking action, so stop
logger.error("voice_chat_helper: onInvalidAudioDevice: ignoring event because we have already tried to handle it");
return;
}
resettedOnInvalidDevice = true;
$selectedChatInput = null;
// you can't do this in the event callback; it hangs the app indefinitely, and somehow 'sticks' the mic input into bad state until reboot
setTimeout(async function() {
await context.jamClient.FTUEClearChatInput();
await context.jamClient.TrackSetChatEnable(true);
var result = await context.jamClient.TrackSaveAssignments();
if(!result || result.length === 0) {
context.JK.Banner.showAlert('It appears the selected chat input is not functioning. Please try another chat input.');
}
else {
context.JK.alertSupportedNeeded("Unable to unwind invalid chat input selection.")
}
}, 1);
}
function beforeShow() {
userInteracted();
renderNoVolume();
context.JK.onBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper', onInvalidAudioDevice);
registerVuCallbacks();
}
async function beforeHide() {
context.JK.offBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper', onInvalidAudioDevice);
await jamClient.FTUERegisterVUCallbacks('', '', '');
}
function userInteracted() {
resettedOnInvalidDevice = false;
}
async function reset(forceDisabledChat) {
$selectedChatInput = null;
if(!forceDisabledChat && await context.jamClient.TrackGetChatEnable()) {
enableChat(false);
}
else {
disableChat(false);
}
$chatInputs.empty();
var chatInputs = await gearUtils.getChatInputs();
context._.each(chatInputs, function(chatInput) {
if(chatInput.assignment > 0) {
return;
}
var chatChannelName = chatInput.name;
var chatChannelId = chatInput.id;
var isCurrentlyChat = chatInput.assignment == ASSIGNMENT.CHAT;
var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' }));
var $chatInput = $chat.find('input');
if(isCurrentlyChat) {
$selectedChatInput = $chatInput;
$selectedChatInput.attr('checked', 'checked');
}
//$chat.hide(); // we'll show it once it's styled with iCheck
$chatInputs.append($chat);
});
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
context.JK.checkbox($radioButtons).on('ifChecked', async function(e) {
userInteracted();
var $input = $(e.currentTarget);
$selectedChatInput = $input; // for use in handleNext
if(saveImmediate) {
var channelId = $input.attr('data-channel-id');
lastSavedTime = new Date();
await context.jamClient.TrackSetChatInput(channelId);
lastSavedTime = new Date();
//context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT);
var result = await context.jamClient.TrackSaveAssignments();
if(!result || result.length === 0) {
// success
}
else {
await context.jamClient.FTUEClearChatInput();
await context.jamClient.TrackSetChatEnable(true);
var result = context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
context.JK.Banner.showAlert('Unable to save chat selection. ' + result);
}
else {
context.JK.alertSupportedNeeded("Unable to unwind invalid chat selection.")
}
return false;
}
}
});
if(!isChatEnabled()) {
disableChatButtonsUI();
}
renderVolumes();
startingState = getCurrentState();
}
function disableChatButtonsUI() {
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
$radioButtons.iCheck('disable')
$chatInputs.addClass('disabled');
$radioButtons.iCheck('uncheck');
$selectedChatInput = null;
}
function enableChatButtonsUI() {
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
$radioButtons.iCheck('enable')
$chatInputs.removeClass('disabled');
}
function suppressChange(proc) {
ignoreICheckEvent = true;
try {
proc();
}
finally {
ignoreICheckEvent = false;
}
}
async function disableChat(applyToBackend) {
if(saveImmediate && applyToBackend) {
logger.debug("voiceChatHelper: disabling chat to backend");
var state = getCurrentState();
//context.jamClient.TrackSetChatEnable(false);
//if(state.chat_channel) {
// context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
//}
await context.jamClient.FTUEClearChatInput();
var result = await context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
renderNoVolume();
// success
suppressChange(function() {
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
$useChatInputRadio.removeAttr('checked');
})
}
else {
context.JK.Banner.showAlert('Unable to disable chat. ' + result);
return false;
}
}
else {
logger.debug("voiceChatHelper: disabling chat UI only");
suppressChange(function() {
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
$useChatInputRadio.removeAttr('checked');
})
}
disableChatButtonsUI();
}
async function enableChat(applyToBackend) {
if(saveImmediate && applyToBackend) {
logger.debug("voiceChatHelper: enabling chat to backend");
await context.jamClient.TrackSetChatEnable(true);
var result = await context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
// success
suppressChange(function() {
$useChatInputRadio.iCheck('check').attr('checked', 'checked');
$reuseAudioInputRadio.removeAttr('checked');
})
}
else {
context.JK.Banner.showAlert('Unable to enable chat. ' + result);
return false;
}
}
else {
logger.debug("voiceChatHelper: enabling chat UI only");
suppressChange(function() {
$useChatInputRadio.iCheck('check').attr('checked', 'checked');
$reuseAudioInputRadio.removeAttr('checked');
})
}
enableChatButtonsUI();
renderVolumes();
}
function handleChatEnabledToggle() {
context.JK.checkbox($reuseAudioInputRadio);
context.JK.checkbox($useChatInputRadio);
// plugin sets to relative on the element; have to do this as an override
$reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute');
$useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute');
$reuseAudioInputRadio.on('ifChecked', function() {
if(!ignoreICheckEvent) {
userInteracted();
disableChat(true);
}
});
$useChatInputRadio.on('ifChecked', function() {
if(!ignoreICheckEvent) {
userInteracted();
enableChat(true)
}
});
}
// gets the state of the UI
function getCurrentState() {
var state = {
enabled:null,
chat_channel:null
};
state.enabled = $useChatInputRadio.is(':checked');
state.chat_channel = $selectedChatInput && $selectedChatInput.attr('data-channel-id');
logger.debug("desired chat state: enabled=" + state.enabled + ", chat_channel=" + state.chat_channel)
return state;
}
function cancel() {
logger.debug("canceling voice chat state");
return trySave(startingState);
}
async function trySave(state) {
if(!state) {
state = getCurrentState();
}
if(state.enabled && state.chat_channel) {
logger.debug("enabling chat. chat_channel=" + state.chat_channel);
await context.jamClient.TrackSetChatEnable(true);
await context.jamClient.FTUESetChatInput(state.chat_channel);
//context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.CHAT);
}
else {
logger.debug("disabling chat.");
await context.jamClient.FTUEClearChatInput();
//context.jamClient.TrackSetChatEnable(false);
//if(state.chat_channel) {
//context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
//}
}
var result = await context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
// success
if(!state.enabled) {
renderNoVolume();
}
return true;
}
else {
context.JK.Banner.showAlert('Unable to save chat assignments. ' + result);
return false;
}
}
function initializeVUMeters() {
context.JK.VuHelpers.renderVU($voiceChatVuLeft, vuOptions);
context.JK.VuHelpers.renderVU($voiceChatVuRight, vuOptions);
context.JK.FaderHelpers.renderFader($voiceChatFader, {faderId: '', faderType: "vertical", height: faderHeight});
$voiceChatFader.on('fader_change', faderChange);
}
// renders volumes based on what the backend says
async function renderVolumes() {
var $fader = $voiceChatFader.find('[data-control="fader"]');
var db = await context.jamClient.FTUEGetChatInputVolume();
var faderPct = db + 80;
context.JK.FaderHelpers.setHandlePosition($fader, faderPct);
}
function renderNoVolume() {
var $fader = $voiceChatFader.find('[data-control="fader"]');
context.JK.FaderHelpers.setHandlePosition($fader, 50);
context.JK.VuHelpers.updateVU($voiceChatVuLeft, 0);
context.JK.VuHelpers.updateVU($voiceChatVuRight, 0);
}
async function faderChange(e, data) {
// TODO - using hardcoded range of -80 to 20 for output levels.
var mixerLevel = data.percentage - 80; // Convert our [0-100] to [-80 - +20] range
await context.jamClient.FTUESetChatInputVolume(mixerLevel);
}
async function registerVuCallbacks() {
logger.debug("voice-chat-helper: registering vu callbacks");
await jamClient.FTUERegisterVUCallbacks(
"JK.voiceChatHelperAudioOutputVUCallback",
"JK.voiceChatHelperAudioInputVUCallback",
"JK." + uniqueCallbackName
);
await jamClient.SetVURefreshRate(200);
}
function initialize(_$step, caller, _saveImmediate, _vuOptions, _faderHeight) {
$parent = _$step;
saveImmediate = _saveImmediate;
vuOptions = _vuOptions;
faderHeight = _faderHeight;
$reuseAudioInputRadio = $parent.find('.reuse-audio-input input');
$useChatInputRadio = $parent.find('.use-chat-input input');
$chatInputs = $parent.find('.chat-inputs');
$templateChatInput = $('#template-chat-input');
$voiceChatVuLeft = $parent.find('.voice-chat-vu-left');
$voiceChatVuRight = $parent.find('.voice-chat-vu-right');
$voiceChatFader = $parent.find('.chat-fader')
handleChatEnabledToggle();
initializeVUMeters();
renderVolumes();
uniqueCallbackName = 'voiceChatHelperChatInputVUCallback' + caller;
context.JK[uniqueCallbackName] = function(dbValue, leftClip, rightClip) {
context.JK.ftueVUCallback(dbValue, $voiceChatVuLeft);
context.JK.ftueVUCallback(dbValue, $voiceChatVuRight);
}
}
context.JK.voiceChatHelperAudioInputVUCallback = function (dbValue) {};
context.JK.voiceChatHelperAudioOutputVUCallback = function (dbValue) {};
this.reset = reset;
this.trySave = trySave;
this.cancel = cancel;
this.initialize = initialize;
this.beforeShow = beforeShow;
this.beforeHide = beforeHide;
return this;
};
})(window, jQuery);