// The session model contains information about the music // sessions that the current client has joined. (function(context,$) { "use strict"; context.JK = context.JK || {}; var logger = context.JK.logger; // screen can be null context.JK.SessionModel = function(app, server, client, sessionScreen) { var ALERT_TYPES = context.JK.ALERT_TYPES; var EVENTS = context.JK.EVENTS; var MIX_MODES = context.JK.MIX_MODES; var gearUtils = context.JK.GearUtilsInstance; var userTracks = null; // comes from the backend var clientId = client.clientID; var currentSessionId = null; // Set on join, prior to setting currentSession. var currentSession = null; var currentOrLastSession = null; var subscribers = {}; var users = {}; // User info for session participants var rest = context.JK.Rest(); var requestingSessionRefresh = false; var pendingSessionRefresh = false; var recordingModel = new context.JK.RecordingModel(app, this, rest, context.jamClient); var currentTrackChanges = 0; var backendMixerAlertThrottleTimer = null; // we track all the clientIDs of all the participants ever seen by this session, so that we can reliably convert a clientId from the backend into a username/avatar var participantsEverSeen = {}; var currentParticipants = {}; var $self = $(this); var sessionPageEnterDeferred = null; var sessionPageEnterTimeout = null; var startTime = null; var joinDeferred = null; var previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []}; var openBackingTrack = null; var shownAudioMediaMixerHelp = false; var controlsLockedForJamTrackRecording = false; var mixerMode = MIX_MODES.PERSONAL; server.registerOnSocketClosed(onWebsocketDisconnected); function id() { return currentSessionId; } function start(sessionId) { currentSessionId = sessionId; startTime = new Date().getTime(); } function setUserTracks(_userTracks) { userTracks = _userTracks; } function inSession() { return !!currentSessionId; } function participants() { if (currentSession) { return currentSession.participants; } else { return []; } } // if any participant has the metronome open, then we say this session has the metronome open function isMetronomeOpen() { var metronomeOpen = false; context._.each(participants(), function(participant) { if(participant.metronome_open) { metronomeOpen = true; return false; } }) return metronomeOpen; } function isPlayingRecording() { // this is the server's state; there is no guarantee that the local tracks // requested from the backend will have corresponding track information return !!(currentSession && currentSession.claimed_recording); } function recordedTracks() { if(currentSession && currentSession.claimed_recording) { return currentSession.claimed_recording.recording.recorded_tracks } else { return null; } } function recordedBackingTracks() { if(currentSession && currentSession.claimed_recording) { return currentSession.claimed_recording.recording.recorded_backing_tracks } else { return null; } } function backingTracks() { var backingTracks = [] // this may be wrong if we loosen the idea that only one person can have a backing track open. // but for now, the 1st person we find with a backing track open is all there is to find... context._.each(participants(), function(participant) { if(participant.backing_tracks.length > 0) { backingTracks = participant.backing_tracks; return false; // break } }) return backingTracks; } function jamTracks() { if(currentSession && currentSession.jam_track) { return currentSession.jam_track.tracks.filter(function(track) { return track.track_type == 'Track'}) } else { return null; } } function recordedJamTracks() { if(currentSession && currentSession.claimed_recording) { return currentSession.claimed_recording.recording.recorded_jam_track_tracks } else { return null; } } function jamTrackName() { if (currentSession && currentSession.jam_track) { return currentSession.jam_track.name; } else { return null; } } function recordedJamTrackName() { if(currentSession && currentSession.claimed_recording && currentSession.claimed_recording.recording.jam_track) { return currentSession.claimed_recording.recording.jam_track.name; } else { return null; } } // did I open up the current JamTrack? function selfOpenedJamTracks() { logger.debug("currentSession", currentSession) return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId) } function backingTrack() { if(currentSession) { // TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668 return { path: currentSession.backing_track_path } } else { return null; } } function creatorId() { if(!currentSession) { throw "creator is not known" } return currentSession.user_id; } function alreadyInSession() { var inSession = false; $.each(participants(), function(index, participant) { if(participant.user.id == context.JK.currentUserId) { inSession = true; return false; } }); return inSession; } function lockControlsforJamTrackRecording() { controlsLockedForJamTrackRecording = true; } function unlockControlsforJamTrackRecording() { controlsLockedForJamTrackRecording = false; } function areControlsLockedForJamTrackRecording() { return controlsLockedForJamTrackRecording; } function onMixerModeChanged(newMixerMode) { mixerMode = newMixerMode; var mode = newMixerMode == MIX_MODES.MASTER ? "master" : "personal"; logger.debug("onMixerModeChanged:" + mode); $(document).triggerHandler(EVENTS.MIXER_MODE_CHANGED, {mode:mode}); } async function waitForSessionPageEnterDone() { sessionPageEnterDeferred = $.Deferred(); // see if we already have tracks; if so, we need to run with these var inputTracks = await context.JK.TrackHelpers.getUserTracks(context.jamClient); logger.debug("isNoInputProfile", gearUtils.isNoInputProfile()) if(inputTracks.length > 0 || gearUtils.isNoInputProfile() ) { logger.debug("on page enter, tracks are already available") sessionPageEnterDeferred.resolve(inputTracks); var deferred = sessionPageEnterDeferred; sessionPageEnterDeferred = null; return deferred; } sessionPageEnterTimeout = setTimeout(function() { if(sessionPageEnterTimeout) { if(sessionPageEnterDeferred) { sessionPageEnterDeferred.reject('timeout'); sessionPageEnterDeferred = null; } sessionPageEnterTimeout = null; } }, 5000); return sessionPageEnterDeferred; } /** * Join the session specified by the provided id. */ function joinSession(sessionId) { logger.debug("SessionModel.joinSession(" + sessionId + ")"); joinDeferred = joinSessionRest(sessionId); joinDeferred .done(function(response){ if(!inSession()) { // the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out logger.debug("user left before fully joined to session. telling server again that they have left") leaveSessionRest(response.id); return; } logger.debug("calling jamClient.JoinSession"); // on temporary disconnect scenarios, a user may already be in a session when they enter this path // so we avoid double counting if(!alreadyInSession()) { if(response.music_session.participant_count == 1) { context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create); } else { context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join); } } recordingModel.reset(); client.JoinSession({ sessionID: sessionId }); refreshCurrentSession(true); server.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges); server.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges); server.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges); server.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges); $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: sessionId, lesson_session: session.lesson_session}}); }) .fail(function() { updateCurrentSession(null); }); return joinDeferred; } async function performLeaveSession(deferred) { logger.debug("SessionModel.leaveCurrentSession()"); // TODO - sessionChanged will be called with currentSession = null\ server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges); //server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession); // leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long // time, for that entire duration you'll still be sending voice data to the other users. // this may be bad if someone decides to badmouth others in the left-session during this time //console.trace(); logger.debug("performLeaveSession: calling jamClient.LeaveSession for clientId=" + clientId); await client.LeaveSession({ sessionID: currentSessionId }); leaveSessionRest(currentSessionId) .done(function() { sessionChanged(); deferred.resolve(arguments[0], arguments[1], arguments[2]); }) .fail(function() { deferred.reject(arguments[0], arguments[1], arguments[2]); }); // 'unregister' for callbacks await context.jamClient.SessionRegisterCallback(""); //context.jamClient.SessionSetAlertCallback(""); await context.jamClient.SessionSetConnectionStatusRefreshRate(0); updateCurrentSession(null); } /** * Leave the current session, if there is one. * callback: called in all conditions; either after an attempt is made to tell the server that we are leaving, * or immediately if there is no session */ function leaveCurrentSession() { var deferred = new $.Deferred(); recordingModel.stopRecordingIfNeeded() .always(function(){ performLeaveSession(deferred); }); return deferred; } function trackChanges(header, payload) { if(currentTrackChanges < payload.track_changes_counter) { // we don't have the latest info. try and go get it logger.debug("track_changes_counter = stale. refreshing...") refreshCurrentSession(); } else { if(header.type != 'HEARTBEAT_ACK') { // don't log if HEARTBEAT_ACK, or you will see this log all the time logger.info("track_changes_counter = fresh. skipping refresh...", header, payload) } } } /** * Refresh the current session, and participants. */ function refreshCurrentSession(force) { // XXX use backend instead: https://jamkazam.atlassian.net/browse/VRFS-854 //logger.debug("SessionModel.refreshCurrentSession(" + currentSessionId +")"); if(force) { logger.debug("refreshCurrentSession(force=true)") } else { } refreshCurrentSessionRest(sessionChanged, force); } /** * Subscribe for sessionChanged events. Provide a subscriberId * and a callback to be invoked on session changes. */ function subscribe(subscriberId, sessionChangedCallback) { logger.debug("SessionModel.subscribe(" + subscriberId + ", [callback])"); subscribers[subscriberId] = sessionChangedCallback; } /** * Notify subscribers that the current session has changed. */ function sessionChanged() { for (var subscriberId in subscribers) { subscribers[subscriberId](currentSession); } } // universal place to clean up, reset items // fullyJoined means the user stayed in the screen until they had a successful rest.joinSession // you might not get a fully joined if you join/leave really quickly before the REST API completes function sessionEnded(fullyJoined) { //cleanup server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, trackChanges); server.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, trackChanges); if(sessionPageEnterDeferred != null) { sessionPageEnterDeferred.reject('session_over'); sessionPageEnterDeferred = null; } userTracks = null; startTime = null; joinDeferred = null; mixerMode = MIX_MODES.PERSONAL; if(fullyJoined) { $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: currentSessionId}}); } currentSessionId = null; currentParticipants = {} previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []} openBackingTrack = null shownAudioMediaMixerHelp = false controlsLockedForJamTrackRecording = false; } // you should only update currentSession with this function function updateCurrentSession(sessionData) { if(sessionData != null) { currentOrLastSession = sessionData; } var beforeUpdate = currentSession; currentSession = sessionData; // the 'beforeUpdate != null' makes sure we only do a clean up one time internally if(sessionData == null) { sessionEnded(beforeUpdate != null); } } function updateSession(response) { updateSessionInfo(response, null, true); } function updateSessionInfo(response, callback, force) { if(force === true || currentTrackChanges < response.track_changes_counter) { logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter) currentTrackChanges = response.track_changes_counter; sendClientParticipantChanges(currentSession, response); updateCurrentSession(response); if(callback != null) { callback(); } } else { logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter); } } /** * Reload the session data from the REST server, calling * the provided callback when complete. */ function refreshCurrentSessionRest(callback, force) { if(!inSession()) { logger.debug("refreshCurrentSession skipped: ") return; } var url = "/api/sessions/" + currentSessionId; if(requestingSessionRefresh) { // if someone asks for a refresh while one is going on, we ask for another to queue up logger.debug("queueing refresh"); pendingSessionRefresh = true; } else { requestingSessionRefresh = true; $.ajax({ type: "GET", url: url, success: function(response) { updateSessionInfo(response, callback, force); }, error: function(jqXHR) { if(jqXHR.status != 404) { app.notifyServerError(jqXHR, "Unable to refresh session data") } else { logger.debug("refreshCurrentSessionRest: could not refresh data for session because it's gone") } }, complete: function() { requestingSessionRefresh = false; if(pendingSessionRefresh) { // and when the request is done, if we have a pending, fire it off again pendingSessionRefresh = false; refreshCurrentSessionRest(sessionChanged, force); } } }); } } /** * Seems silly. We should just have the bridge take sessionId, clientId */ function _toJamClientParticipant(participant) { return { userID : "", clientID : participant.client_id, tcpPort : 0, udpPort : 0, localIPAddress : participant.ip_address, // ? globalIPAddress : participant.ip_address, // ? latency : 0, natType : "" }; } async function ParticipantJoined(newSession, participant) { await client.ParticipantJoined(newSession, _toJamClientParticipant(participant)); currentParticipants[participant.client_id] = {server:participant, client: {audio_established:null}} } async function ParticipantLeft(newSession, participant) { await client.ParticipantLeft(newSession, _toJamClientParticipant(participant)); delete currentParticipants[participant.client_id] } function sendClientParticipantChanges(oldSession, newSession) { var joins = [], leaves = [], leaveJoins = []; // Will hold JamClientParticipants var oldParticipants = []; // will be set to session.participants if session var oldParticipantIds = {}; var newParticipants = []; var newParticipantIds = {}; if (oldSession && oldSession.participants) { oldParticipants = oldSession.participants; $.each(oldParticipants, function() { oldParticipantIds[this.client_id] = this; }); } if (newSession && newSession.participants) { newParticipants = newSession.participants; $.each(newParticipants, function() { newParticipantIds[this.client_id] = this; }); } $.each(newParticipantIds, function(client_id, participant) { // grow the 'all participants seen' list if(!(client_id in participantsEverSeen)) { participantsEverSeen[client_id] = participant; } if(client_id in oldParticipantIds) { // if the participant is here now, and here before, there is still a chance we missed a // very fast leave/join. So check if joined_session_at is different if(oldParticipantIds[client_id].joined_session_at != participant.joined_session_at) { leaveJoins.push(participant) } } else { // new participant id that's not in old participant ids: Join joins.push(participant); } }); $.each(oldParticipantIds, function(client_id, participant) { if (!(client_id in newParticipantIds)) { // old participant id that's not in new participant ids: Leave leaves.push(participant); } }); $.each(joins, function(i,v) { if (v.client_id != clientId) { logger.debug("jamClient.ParticipantJoined", v.client_id) ParticipantJoined(newSession, v) } }); $.each(leaves, function(i,v) { if (v.client_id != clientId) { logger.debug("jamClient.ParticipantLeft", v.client_id) ParticipantLeft(newSession, v) } }); $.each(leaveJoins, function(i,v) { if (v.client_id != clientId) { logger.debug("participant had a rapid leave/join") logger.debug("jamClient.ParticipantLeft", v.client_id) ParticipantLeft(newSession, v) logger.debug("jamClient.ParticipantJoined", v.client_id) ParticipantJoined(newSession, v) } }); } function participantForClientId(clientId) { var foundParticipant = null; $.each(currentSession.participants, function(index, participant) { if (participant.client_id === clientId) { foundParticipant = participant; return false; } }); return foundParticipant; } async function deleteTrack(sessionId, trackId) { if (trackId) { await client.TrackSetCount(1); await client.TrackSaveAssignments(); } } /** * Make the server calls to join the current user to * the session provided. */ function joinSessionRest(sessionId) { var data = { client_id: clientId, ip_address: server.publicIP, as_musician: true, tracks: userTracks, session_id: sessionId, audio_latency: context.jamClient.FTUEGetExpectedLatency().latency }; return rest.joinSession(data); } function leaveSessionRest(sessionId) { var url = "/api/participants/" + clientId; return $.ajax({ type: "DELETE", url: url }); } function setMixerMode(newMixerMode) { if(mixerMode != newMixerMode) { onMixerModeChanged(newMixerMode); } } function isMasterMixMode() { return mixerMode == MIX_MODES.MASTER; } function isPersonalMixMode() { return mixerMode == MIX_MODES.PERSONAL; } function getMixMode() { return mixerMode; } function syncTracks(allTracks) { // double check that we are in session, since a bunch could have happened since then if(!inSession()) { logger.debug("dropping queued up sync tracks because no longer in session"); return null; } if(allTracks === undefined) { allTracks = context.JK.TrackHelpers.getTrackInfo(context.jamClient); } var inputTracks = allTracks.userTracks; var backingTracks = allTracks.backingTracks; var metronomeTracks = allTracks.metronomeTracks; // create a trackSync request based on backend data var syncTrackRequest = {}; syncTrackRequest.client_id = app.clientId; syncTrackRequest.tracks = inputTracks; syncTrackRequest.backing_tracks = backingTracks; syncTrackRequest.metronome_open = metronomeTracks.length > 0; syncTrackRequest.id = id(); return rest.putTrackSyncChange(syncTrackRequest) .done(function() { }) .fail(function(jqXHR) { if(jqXHR.status != 404) { app.notify({ "title": "Can't Sync Local Tracks", "text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.", "icon_url": "/assets/content/icon_alert_big.png" }); } else { logger.debug("Unable to sync local tracks because session is gone.") } }) } async function onWebsocketDisconnected(in_error) { // kill the streaming of the session immediately if(currentSessionId) { logger.debug("onWebsocketDisconnect: calling jamClient.LeaveSession for clientId=" + clientId); await client.LeaveSession({ sessionID: currentSessionId }); } } // returns a deferred object function findUserBy(finder) { if(finder.clientId) { var foundParticipant = null; $.each(participants(), function(index, participant) { if(participant.client_id == finder.clientId) { foundParticipant = participant; return false; } }); if(foundParticipant) { return $.Deferred().resolve(foundParticipant.user).promise(); } } // TODO: find it via some REST API if not found? return $.Deferred().reject().promise(); } function onDeadUserRemove(type, text) { if(!inSession()) return; var clientId = text; var participant = participantsEverSeen[clientId]; if(participant) { app.notify({ "title": ALERT_TYPES[type].title, "text": participant.user.name + " is no longer sending audio.", "icon_url": context.JK.resolveAvatarUrl(participant.user.photo_url) }); var $track = $('div.track[client-id="' + clientId + '"]'); $('.disabled-track-overlay', $track).show(); } } function onWindowBackgrounded(type, text) { app.user() .done(function(userProfile) { if(userProfile.show_whats_next && window.location.pathname.indexOf(gon.client_path) == 0 && !app.layout.isDialogShowing('getting-started')) { app.layout.showDialog('getting-started'); } }) if(!inSession()) return; // the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen if(sessionScreen) { sessionScreen.setPromptLeave(false); context.location = '/client#/home' } } function onBroadcastSuccess(type, text) { logger.debug("SESSION_LIVEBROADCAST_ACTIVE alert. reason:" + text); if(currentSession && currentSession.mount) { rest.createSourceChange({ mount_id: currentSession.mount.id, source_direction: true, success: true, reason: text, client_id: app.clientId }) } else { logger.debug("unable to report source change because no mount seen on session") } } function onBroadcastFailure(type, text) { logger.debug("SESSION_LIVEBROADCAST_FAIL alert. reason:" + text); if(currentSession && currentSession.mount) { rest.createSourceChange({ mount_id: currentSession.mount.id, source_direction: true, success: false, reason: text, client_id: app.clientId }) } else { logger.debug("unable to report source change because no mount seen on session") } } function onBroadcastStopped(type, text) { logger.debug("SESSION_LIVEBROADCAST_STOPPED alert. reason:" + text); if(currentSession && currentSession.mount) { rest.createSourceChange({ mount_id: currentSession.mount.id, source_direction: false, success: true, reason: text, client_id: app.clientId }) } else { logger.debug("unable to report source change because no mount seen on session") } } function onPlaybackStateChange(type, text) { // if text is play_start or play_stop, tell the play_controls if(sessionScreen) { sessionScreen.onPlaybackStateChange(text); } } async function onBackendMixerChanged(type, text) { logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text); if(inSession() && text == "RebuildAudioIoControl") { // the backend will send these events rapid-fire back to back. // the server can still perform correctly, but it is nicer to wait 100 ms to let them all fall through if(backendMixerAlertThrottleTimer) {clearTimeout(backendMixerAlertThrottleTimer);} backendMixerAlertThrottleTimer = setTimeout(async function() { if(sessionPageEnterDeferred) { // this means we are still waiting for the BACKEND_MIXER_CHANGE that indicates we have user tracks built-out/ready // we will get at least one BACKEND_MIXER_CHANGE that corresponds to the backend doing a 'audio pause', which won't matter much // so we need to check that we actaully have userTracks before considering ourselves done var inputTracks = await context.JK.TrackHelpers.getUserTracks(context.jamClient); if(inputTracks.length > 0) { logger.debug("obtained tracks at start of session") sessionPageEnterDeferred.resolve(inputTracks); sessionPageEnterDeferred = null; } return; } // wait until we are fully in session before trying to sync tracks to server if(joinDeferred) { joinDeferred.done(function() { syncTracks(); }) } }, 100); } else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) { var allTracks = await context.JK.TrackHelpers.getTrackInfo(context.jamClient); var backingTracks = allTracks.backingTracks; var previousBackingTracks = previousAllTracks.backingTracks; var metronomeTracks = allTracks.metronomeTracks; var previousMetronomeTracks = previousAllTracks.metronomeTracks; // the way we know if backing tracks changes, or recordings are opened, is via this event. // but we want to report to the user when backing tracks change; so we need to detect change on our own if(!(previousBackingTracks.length == 0 && backingTracks.length == 0) && previousBackingTracks != backingTracks) { logger.debug("backing tracks changed", previousBackingTracks, backingTracks) syncTracks(allTracks); } else if(!(previousMetronomeTracks.length == 0 && metronomeTracks.length == 0) && previousMetronomeTracks != metronomeTracks) { logger.debug("metronome state changed ", previousMetronomeTracks, metronomeTracks) syncTracks(allTracks); } else { refreshCurrentSession(true); } previousAllTracks = allTracks; } else if(inSession() && (text == 'Global Peer Input Mixer Mode')) { setMixerMode(MIX_MODES.MASTER); } else if(inSession() && (text == 'Local Peer Stream Mixer Mode')) { setMixerMode(MIX_MODES.PERSONAL); } } // Public interface this.id = id; this.start = start; this.backingTrack = backingTrack; this.backingTracks = backingTracks; this.recordedBackingTracks = recordedBackingTracks; this.recordedJamTracks = recordedJamTracks; this.setUserTracks = setUserTracks; this.recordedTracks = recordedTracks; this.jamTrackName = jamTrackName; this.recordedJamTrackName = recordedJamTrackName; this.jamTracks = jamTracks; this.participants = participants; this.joinSession = joinSession; this.leaveCurrentSession = leaveCurrentSession; this.refreshCurrentSession = refreshCurrentSession; this.updateSession = updateSession; this.subscribe = subscribe; this.participantForClientId = participantForClientId; this.isPlayingRecording = isPlayingRecording; this.deleteTrack = deleteTrack; this.onWebsocketDisconnected = onWebsocketDisconnected; this.recordingModel = recordingModel; this.findUserBy = findUserBy; this.inSession = inSession; this.setMixerMode = setMixerMode; this.isMasterMixMode = isMasterMixMode; this.isPersonalMixMode = isPersonalMixMode; this.getMixMode = getMixMode; this.selfOpenedJamTracks = selfOpenedJamTracks; this.isMetronomeOpen = isMetronomeOpen; this.areControlsLockedForJamTrackRecording = areControlsLockedForJamTrackRecording; this.lockControlsforJamTrackRecording = lockControlsforJamTrackRecording; this.unlockControlsforJamTrackRecording = unlockControlsforJamTrackRecording; // ALERT HANDLERS this.onBackendMixerChanged = onBackendMixerChanged; this.onDeadUserRemove = onDeadUserRemove; this.onWindowBackgrounded = onWindowBackgrounded; this.waitForSessionPageEnterDone = waitForSessionPageEnterDone; this.onBroadcastStopped = onBroadcastStopped; this.onBroadcastSuccess = onBroadcastSuccess; this.onBroadcastFailure = onBroadcastFailure; this.onPlaybackStateChange = onPlaybackStateChange; this.getCurrentSession = function() { return currentSession; }; this.getCurrentOrLastSession = function() { return currentOrLastSession; }; this.getParticipant = function(clientId) { return participantsEverSeen[clientId] }; this.setBackingTrack = function(backingTrack) { openBackingTrack = backingTrack; }; this.getBackingTrack = function() { return openBackingTrack; }; this.hasShownAudioMediaMixerHelp = function() { return shownAudioMediaMixerHelp; } this.markShownAudioMediaMixerHelp = function() { shownAudioMediaMixerHelp = true; } // call to report if the current user was able to establish audio with the specified clientID this.setAudioEstablished = function(clientId, audioEstablished) { var participant = currentParticipants[clientId] if(participant) { if(participant.client.audio_established === null) { participant.client.audio_established = audioEstablished; context.stats.write('client.audio_established.' + (audioEstablished ? 'success': 'failure'), {user_id: context.JK.currentUserId, name: context.JK.currentUserName, remote_user_id: participant.server.user.id, remote_name: participant.server.user.name, value: 1}) } else if(participant.client.audio_established === false && audioEstablished === true) { // this means the frontend had declared this audio failed, but later says it works. // this could mean our threshold of time to wait before audio is considered failed is too low, and needs tweaking context.stats.write('client.audio_established.delayed', {user_id: context.JK.currentUserId, name: context.JK.currentUserName, remote_user_id: participant.server.user.id, remote_name: participant.server.user.name, value: 1}) } } } this.ensureEnded = function() { updateCurrentSession(null); } }; })(window,jQuery);