(function(context,$) { "use strict"; context.JK = context.JK || {}; context.JK.SessionScreen = function(app) { var logger = context.JK.logger; var sessionModel = null; var sessionId; var tracks = {}; var myTracks = []; var mixers = []; var configureTrackDialog; var addTrackDialog; var addNewGearDialog; var screenActive = false; var currentMixerRangeMin = null; var currentMixerRangeMax = null; var lookingForMixersCount = 0; var lookingForMixersTimer = null; var lookingForMixers = {}; var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there. var defaultParticipant = { tracks: [{ instrument_id: "unknown" }], user: { first_name: 'Unknown', last_name: 'User', photo_url: null } }; // Be sure to copy/extend these instead of modifying in place var trackVuOpts = { vuType: "vertical", lightCount: 13, lightWidth: 3, lightHeight: 17 }; // Must add faderId key to this var trackFaderOpts = { faderType: "vertical", height: 83 }; // Recreate ChannelGroupIDs ENUM from C++ var ChannelGroupIds = { "MasterGroup": 0, "MonitorGroup": 1, "AudioInputMusicGroup": 2, "AudioInputChatGroup": 3, "MediaTrackGroup": 4, "StreamOutMusicGroup": 5, "StreamOutChatGroup": 6, "UserMusicInputGroup": 7, "UserChatInputGroup": 8, "PeerAudioInputMusicGroup": 9, "PeerMediaTrackGroup": 10 }; // recreate eThresholdType enum from MixerDialog.h var alert_type = { 0: {"title": "", "message": ""}, // NO_EVENT, 1: {"title": "", "message": ""}, // BACKEND_ERROR: generic error - eg P2P message error 2: {"title": "", "message": ""}, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated 3: {"title": "", "message": ""}, // PACKET_JTR, 4: { "title": "Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here." }, // PACKET_LOSS 5: {"title": "", "message": ""}, // PACKET_LATE, 6: {"title": "", "message": ""}, // JTR_QUEUE_DEPTH, 7: {"title": "", "message": ""}, // NETWORK_JTR, 8: { "title": "Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click here." }, // NETWORK_PING, 9: {"title": "", "message": ""}, // BITRATE_THROTTLE_WARN, 10: { "title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low,and this may impact your audio quality. For troubleshooting tips, click here." }, // BANDWIDTH_LOW //IO related events 11: { "title": "Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // INPUT_IO_RATE 12: {"title": "", "message": ""}, // INPUT_IO_JTR, 13: { "title": "Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // OUTPUT_IO_RATE 14: {"title": "", "message": ""}, // OUTPUT_IO_JTR, // CPU load related 15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, click here." }, // CPU_LOAD 16: {"title": "", "message": ""}, // DECODE_VIOLATIONS, 17: {"title": "", "message": ""} // LAST_THRESHOLD }; function beforeShow(data) { sessionId = data.id; $('#session-mytracks-container').empty(); } function alertCallback(type, text) { if (type === 2) { // BACKEND_MIXER_CHANGE sessionModel.refreshCurrentSession(); } else { context.setTimeout(function() { app.notify({ "title": alert_type[type].title, "text": text, "icon_url": "/assets/content/icon_alert_big.png" }); }, 1); } } function afterShow(data) { // indicate that the screen is active, so that // body-scoped drag handlers can go active screenActive = true; // Subscribe for callbacks on audio events context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback"); context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback"); context.jamClient.SessionSetAlertCallback("JK.AlertCallback"); // If you load this page directly, the loading of the current user // is happening in parallel. We can't join the session until the // current user has been completely loaded. Poll for the current user // before proceeding with session joining. function checkForCurrentUser() { if (context.JK.userMe) { afterCurrentUserLoaded(); } else { context.setTimeout(checkForCurrentUser, 100); } } checkForCurrentUser(); } function afterCurrentUserLoaded() { logger.debug("afterCurrentUserLoaded"); // It seems the SessionModel should be a singleton. // a client can only be in one session at a time, // and other parts of the code want to know at any certain times // about the current session, if any (for example, reconnect logic) context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel( context.JK.app, context.JK.JamServer, context.jamClient ); sessionModel.subscribe('sessionScreen', sessionChanged); logger.debug("sessionId=" + sessionId); sessionModel.joinSession(sessionId) .fail(function(xhr, textStatus, errorMessage) { if(xhr.status == 404) { // we tried to join the session, but it's already gone. kick user back to join session screen context.window.location = "#/findSession"; app.notify( { title: "Unable to Join Session", text: "The session you attempted to join is over." }, { no_cancel: true }); }else { app.ajaxError(xhr, textStatus, errorMessage); } }); } function beforeHide(data) { // track that the screen is inactive, to disable body-level handlers screenActive = false; sessionModel.leaveCurrentSession() .fail(app.ajaxError); } function sessionChanged() { logger.debug("sessionChanged()"); // TODO - in the specific case of a user changing their tracks using the configureTrack dialog, // this event appears to fire before the underlying mixers have updated. I have no event to // know definitively when the underlying mixers are up to date, so for now, we just delay slightly. // This obviously has the possibility of introducing time-based bugs. context.setTimeout(renderSession, RENDER_SESSION_DELAY); } /** * the mixers object is a list. In order to find one by key, * you must iterate. Convenience method to locate a particular * mixer by id. */ function getMixer(mixerId) { var foundMixer = null; $.each(mixers, function(index, mixer) { if (mixer.id === mixerId) { foundMixer = mixer; } }); return foundMixer; } function renderSession() { $('#session-mytracks-container').empty(); $('.session-track').remove(); // Remove previous tracks var $voiceChat = $('#voice-chat'); $voiceChat.hide(); _updateMixers(); _renderTracks(); _wireTopVolume(); _wireTopMix(); _addVoiceChat(); _initDialogs(); if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } } function _initDialogs() { logger.debug("Calling _initDialogs"); configureTrackDialog.initialize(); addTrackDialog.initialize(); addNewGearDialog.initialize(); } // Get the latest list of underlying audio mixer channels function _updateMixers() { var mixerIds = context.jamClient.SessionGetIDs(); var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)}); mixers = holder.mixers; // Always add a hard-coded simplified 'mixer' for the L2M mix var l2m_mixer = { id: '__L2M__', range_low: -80, range_high: 20, volume_left: context.jamClient.SessionGetMasterLocalMix() }; mixers.push(l2m_mixer); } // TODO FIXME - This needs to support multiple tracks for an individual // client id and group. function _mixerForClientId(clientId, groupIds, usedMixers) { var foundMixer = null; $.each(mixers, function(index, mixer) { if (mixer.client_id === clientId) { for (var i=0; i 20) { lookingForMixersCount = 0; lookingForMixers = {}; context.clearTimeout(lookingForMixersTimer); lookingForMixersTimer = null; } } // Given a mixerID and a value between 0.0-1.0, // light up the proper VU lights. function _updateVU(mixerId, value) { // Special-case for mono tracks. If mono, and it's a _vul id, // update both sides, otherwise do nothing. // If it's a stereo track, just do the normal thing. var selector; var pureMixerId = mixerId.replace("_vul", ""); pureMixerId = pureMixerId.replace("_vur", ""); var mixer = getMixer(pureMixerId); if (mixer) { if (!(mixer.stereo)) { // mono track if (mixerId.substr(-4) === "_vul") { // Do the left selector = '#tracks [mixer-id="' + pureMixerId + '_vul"]'; context.JK.VuHelpers.updateVU(selector, value); // Do the right selector = '#tracks [mixer-id="' + pureMixerId + '_vur"]'; context.JK.VuHelpers.updateVU(selector, value); } // otherwise, it's a mono track, _vur event - ignore. } else { // stereo track selector = '#tracks [mixer-id="' + mixerId + '"]'; context.JK.VuHelpers.updateVU(selector, value); } } } function _addTrack(index, trackData) { var parentSelector = '#session-mytracks-container'; var $destination = $(parentSelector); if (trackData.clientId !== app.clientId) { parentSelector = '#session-livetracks-container'; $destination = $(parentSelector); $('.session-livetracks .when-empty').hide(); } var template = $('#template-session-track').html(); var newTrack = context.JK.fillTemplate(template, trackData); $destination.append(newTrack); // Render VU meters and gain fader var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; var gainPercent = trackData.gainPercent || 0; connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent); var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]'); if (index === 0) { $closeButton.hide(); } else { $closeButton.click(deleteTrack); } var $trackSettings = $('div[mixer-id="' + trackData.mixerId + '"].track-icon-settings'); $trackSettings.click(function() { // call this to initialize Voice Chat tab configureTrackDialog.showVoiceChatPanel(true); configureTrackDialog.showMusicAudioPanel(true); }); tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId); } /** * Will be called when fader changes. The fader id (provided at subscribe time), * the new value (0-100) and whether the fader is still being dragged are passed. */ function faderChanged(faderId, newValue, dragging) { var mixerIds = faderId.split(','); $.each(mixerIds, function(i,v) { var broadcast = !(dragging); // If fader is still dragging, don't broadcast fillTrackVolumeObject(v, broadcast); setMixerVolume(v, newValue); }); } function handleVolumeChangeCallback(mixerId, isLeft, value) { // Visually update mixer // There is no need to actually set the back-end mixer value as the // back-end will already have updated the audio mixer directly prior to sending // me this event. I simply need to visually show the new fader position. // TODO: Use mixer's range var faderValue = percentFromMixerValue(-80, 20, value); context.JK.FaderHelpers.setFaderValue(mixerId, faderValue); } function handleBridgeCallback() { var eventName = null; var mixerId = null; var value = null; var tuples = arguments.length / 3; for (var i=0; i