563 lines
21 KiB
CoffeeScript
563 lines
21 KiB
CoffeeScript
$ = jQuery
|
|
context = window
|
|
logger = context.JK.logger
|
|
rest = context.JK.Rest()
|
|
EVENTS = context.JK.EVENTS;
|
|
|
|
|
|
SessionActions = @SessionActions
|
|
|
|
@SessionStore = Reflux.createStore(
|
|
{
|
|
listenables: SessionActions
|
|
|
|
userTracks: null # comes from the backend
|
|
currentSessionId: null
|
|
currentSession: null
|
|
currentOrLastSession: null
|
|
startTime: null
|
|
currentParticipants: {}
|
|
participantsEverSeen: {}
|
|
users: {} # // User info for session participants
|
|
requestingSessionRefresh: false
|
|
pendingSessionRefresh: false
|
|
sessionPageEnterTimeout: null
|
|
sessionPageEnterDeferred: null
|
|
gearUtils: null
|
|
sessionUtils: null
|
|
joinDeffered: null
|
|
recordingModel: null
|
|
currentTrackChanges: 0
|
|
|
|
|
|
init: ->
|
|
# Register with the app store to get @app
|
|
this.listenTo(context.AppStore, this.onAppInit)
|
|
|
|
onAppInit: (@app) ->
|
|
@gearUtils = context.JK.GearUtilsInstance
|
|
@sessionUtils = context.JK.SessionUtils
|
|
@recordingModel = new context.JK.RecordingModel(@app, this, rest, context.jamClient);
|
|
|
|
onWatchedInputs: (inputTracks) ->
|
|
|
|
logger.debug("obtained tracks at start of session")
|
|
@sessionPageEnterDeferred.resolve(inputTracks);
|
|
@sessionPageEnterDeferred = null
|
|
|
|
onMixersChanged: (type, text, trackInfo) ->
|
|
|
|
return unless @inSession()
|
|
|
|
if text == 'RebuildAudioIoControl'
|
|
if @backendMixerAlertThrottleTimer
|
|
clearTimeout(@backendMixerAlertThrottleTimer)
|
|
|
|
@backendMixerAlertThrottleTimer = setTimeout(
|
|
() =>
|
|
@backendMixerAlertThrottleTimer = null
|
|
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
|
|
if trackInfo.userTracks.length > 0
|
|
logger.debug("obtained tracks at start of session")
|
|
@sessionPageEnterDeferred.resolve(trackInfo.userTracks);
|
|
@sessionPageEnterDeferred = null
|
|
|
|
return
|
|
|
|
|
|
// wait until we are fully in session before trying to sync tracks to server
|
|
if @joinDeferred
|
|
@joinDeferred
|
|
.done(()=>
|
|
@syncTracks()
|
|
|
|
, 100)
|
|
else if @session.inSession() && text == 'RebuildMediaControl'
|
|
SessionActions.mediaTracksChanged.trigger(@mixers.getTrackInfo())
|
|
else if @session.inSession() && text == 'RebuildRemoteUserControl'
|
|
SessionActions.otherTracksChanged.trigger(@mixers.getTrackInfo())
|
|
|
|
|
|
|
|
onJoinSession: (sessionId) ->
|
|
|
|
# initialize webcamViewer
|
|
if gon.global.video_available && gon.global.video_available != "none"
|
|
@webcamViewer.beforeShow()
|
|
|
|
# double-check that we are connected to the server via websocket
|
|
|
|
return unless @ensureConnected()
|
|
|
|
# just make double sure a previous session state is cleared out
|
|
@sessionEnded()
|
|
|
|
# update the session data to be empty
|
|
@updateCurrentSession(null)
|
|
|
|
# start setting data for this new session
|
|
@currentSessionId = sessionId
|
|
@startTime = new Date().getTime()
|
|
|
|
# let's find out the public/private nature of this session,
|
|
# so that we can decide whether we need to validate the audio profile more aggressively
|
|
rest.getSessionHistory(@currentSessionId)
|
|
.done((musicSession)=>
|
|
musicianAccessOnJoin = musicSession.musician_access
|
|
|
|
shouldVerifyNetwork = musicSession.musician_access;
|
|
|
|
@gearUtils.guardAgainstInvalidConfiguration(@app, shouldVerifyNetwork).fail(() =>
|
|
SessionActions.leaveSession.trigger({location: '/client#/home'})
|
|
).done(() =>
|
|
result = @sessionUtils.SessionPageEnter();
|
|
|
|
@gearUtils.guardAgainstActiveProfileMissing(@app, result)
|
|
.fail((data) =>
|
|
leaveBehavior = {}
|
|
|
|
if data && data.reason == 'handled'
|
|
if data.nav == 'BACK'
|
|
leaveBehavior.location = -1
|
|
else
|
|
leaveBehavior.location = data.nav
|
|
else
|
|
leaveBehavior.location = '/client#/home';
|
|
|
|
SessionActions.leaveSession.trigger(leaveBehavior)
|
|
).done(() =>
|
|
@waitForSessionPageEnterDone()
|
|
.done((userTracks) =>
|
|
@userTracks = userTracks
|
|
|
|
@ensureAppropriateProfile(musicianAccessOnJoin)
|
|
.done(() =>
|
|
logger.debug("user has passed all session guards")
|
|
@joinSession()
|
|
)
|
|
.fail((result) =>
|
|
unless result.controlled_location
|
|
SessionActions.leaveSession.trigger({location: "/client#/home"})
|
|
)
|
|
).fail((data) =>
|
|
if data == "timeout"
|
|
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
|
|
else if data == 'session_over'
|
|
# do nothing; session ended before we got the user track info. just bail
|
|
logger.debug("session is over; bailing")
|
|
else
|
|
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
|
|
|
|
SessionActions.leaveSession.trigger({location: '/client#/home'})
|
|
)
|
|
)
|
|
)
|
|
)
|
|
.fail(() =>
|
|
logger.error("unable to fetch session history")
|
|
)
|
|
|
|
waitForSessionPageEnterDone: () ->
|
|
@sessionPageEnterDeferred = $.Deferred()
|
|
|
|
# see if we already have tracks; if so, we need to run with these
|
|
inputTracks = 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)
|
|
deferred = @sessionPageEnterDeferred
|
|
@sessionPageEnterDeferred = null
|
|
return deferred
|
|
|
|
@sessionPageEnterTimeout = setTimeout(()=>
|
|
if @sessionPageEnterTimeout
|
|
if @sessionPageEnterDeferred
|
|
@sessionPageEnterDeferred.reject('timeout')
|
|
@sessionPageEnterDeferred = null
|
|
@sessionPageEnterTimeout = null
|
|
, 5000)
|
|
|
|
@sessionPageEnterDeferred
|
|
|
|
ensureAppropriateProfile: (musicianAccess) ->
|
|
deferred = new $.Deferred();
|
|
if musicianAccess
|
|
deferred = context.JK.guardAgainstSinglePlayerProfile(@app)
|
|
else
|
|
deferred.resolve();
|
|
deferred
|
|
|
|
joinSession: () ->
|
|
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
|
|
#context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
|
|
context.jamClient.SessionSetConnectionStatusRefreshRate(1000);
|
|
#context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen)
|
|
|
|
# subscribe to events from the recording model
|
|
@recordingRegistration()
|
|
|
|
# tell the server we want to join
|
|
|
|
@joinDeferred = rest.joinSession({
|
|
client_id: @app.clientId,
|
|
ip_address: context.JK.JamServer.publicIP,
|
|
as_musician: true,
|
|
tracks: @userTracks,
|
|
session_id: @currentSessionId,
|
|
audio_latency: context.jamClient.FTUEGetExpectedLatency().latency
|
|
})
|
|
.done((response) =>
|
|
unless @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
|
|
unless @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(response.id);
|
|
|
|
context.jamClient.JoinSession({sessionID: response.id});
|
|
|
|
@refreshCurrentSession(true);
|
|
|
|
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, @trackChanges);
|
|
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, @trackChanges);
|
|
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges);
|
|
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges);
|
|
|
|
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document
|
|
|
|
@handleAutoOpenJamTrack()
|
|
)
|
|
.fail((xhr) =>
|
|
@updateCurrentSession(null)
|
|
|
|
if xhr.status == 404
|
|
# we tried to join the session, but it's already gone. kick user back to join session screen
|
|
leaveBehavior =
|
|
location: "/client#/findSession"
|
|
notify:
|
|
title: "Unable to Join Session",
|
|
text: " The session you attempted to join is over."
|
|
SessionActions.leaveSession.trigger(leaveBehavior)
|
|
else if xhr.status == 422
|
|
response = JSON.parse(xhr.responseText);
|
|
if response["errors"] && response["errors"]["tracks"] && response["errors"]["tracks"][0] == "Please select at least one track"
|
|
@app.notifyAlert("No Inputs Configured", $('<span>You will need to reconfigure your audio device.</span>'))
|
|
|
|
else if response["errors"] && response["errors"]["music_session"] && response["errors"]["music_session"][0] == ["is currently recording"]
|
|
|
|
leaveBehavior =
|
|
location: "/client#/findSession"
|
|
notify:
|
|
title: "Unable to Join Session"
|
|
text: "The session is currently recording."
|
|
SessionActions.leaveSession.trigger(leaveBehavior)
|
|
else
|
|
@app.notifyServerError(xhr, 'Unable to Join Session');
|
|
else
|
|
@app.notifyServerError(xhr, 'Unable to Join Session');
|
|
)
|
|
|
|
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)
|
|
|
|
handleAutoOpenJamTrack: () ->
|
|
jamTrack = @sessionUtils.grabAutoOpenJamTrack();
|
|
if jamTrack
|
|
# give the session to settle just a little (call a timeout of 1 second)
|
|
setTimeout(()=>
|
|
# tell the server we are about to open a jamtrack
|
|
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
|
|
.done((response) =>
|
|
logger.debug("jamtrack opened")
|
|
# now actually load the jamtrack
|
|
# TODO
|
|
# context.JK.CurrentSessionModel.updateSession(response);
|
|
# loadJamTrack(jamTrack);
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notifyServerError(jqXHR, "Unable to Open JamTrack For Playback")
|
|
)
|
|
, 1000)
|
|
|
|
inSession: () ->
|
|
!!@currentSessionId
|
|
|
|
alreadyInSession: () ->
|
|
inSession = false
|
|
for participant in @participants()
|
|
if participant.user.id == context.JK.currentUserId
|
|
inSession = true
|
|
break
|
|
|
|
participants: () ->
|
|
if @currentSession
|
|
@currentSession.participants;
|
|
else
|
|
[]
|
|
|
|
refreshCurrentSession: (force) ->
|
|
logger.debug("refreshCurrentSession(force=true)") if force
|
|
|
|
@refreshCurrentSessionRest(force)
|
|
|
|
refreshCurrentSessionRest: (force) ->
|
|
unless @inSession()
|
|
logger.debug("refreshCurrentSession skipped: ")
|
|
return
|
|
|
|
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
|
|
rest.getSession(@currentSessionId)
|
|
.done((response) =>
|
|
@updateSessionInfo(response, force)
|
|
)
|
|
.fail((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")
|
|
)
|
|
.always(() =>
|
|
@requestingSessionRefresh = false
|
|
if @pendingSessionRefresh
|
|
# and when the request is done, if we have a pending, fire it off again
|
|
pendingSessionRefresh = false
|
|
@refreshCurrentSessionRest(force)
|
|
)
|
|
|
|
updateSessionInfo: (session, force) ->
|
|
if force == true || @currentTrackChanges < session.track_changes_counter
|
|
logger.debug("updating current track changes from %o to %o", @currentTrackChanges, session.track_changes_counter)
|
|
@currentTrackChanges = session.track_changes_counter;
|
|
@sendClientParticipantChanges(@currentSession, session);
|
|
@updateCurrentSession(session);
|
|
#if(callback != null) {
|
|
# callback();
|
|
#}
|
|
else
|
|
logger.info("ignoring refresh because we already have current: " + @currentTrackChanges + ", seen: " + session.track_changes_counter);
|
|
|
|
|
|
leaveSessionRest: () ->
|
|
rest.deleteParticipant(@app.clientId);
|
|
|
|
sendClientParticipantChanges: (oldSession, newSession) ->
|
|
joins = []
|
|
leaves = []
|
|
leaveJoins = []; # Will hold JamClientParticipants
|
|
|
|
oldParticipants = []; # will be set to session.participants if session
|
|
oldParticipantIds = {};
|
|
newParticipants = [];
|
|
newParticipantIds = {};
|
|
|
|
if oldSession && oldSession.participants
|
|
for oldParticipant in oldSession.participants
|
|
oldParticipantIds[oldParticipant.client_id] = oldParticipant
|
|
|
|
if newSession && newSession.participants
|
|
for newParticipant in newSession.participants
|
|
newParticipantIds[newParticipant.client_id] = newParticipant
|
|
|
|
for client_id, participant of newParticipantIds
|
|
# grow the 'all participants seen' list
|
|
unless (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);
|
|
|
|
for client_id, participant of oldParticipantIds
|
|
unless (client_id in newParticipantIds)
|
|
# old participant id that's not in new participant ids: Leave
|
|
leaves.push(participant);
|
|
|
|
for i, v of joins
|
|
if v.client_id != @app.clientId
|
|
@participantJoined(newSession, v)
|
|
|
|
for i,v of leaves
|
|
if v.client_id != @app.clientId
|
|
@participantLeft(newSession, v)
|
|
|
|
for i,v of leaveJoins
|
|
if v.client_id != @app.clientId
|
|
logger.debug("participant had a rapid leave/join")
|
|
@participantLeft(newSession, v)
|
|
@participantJoined(newSession, v)
|
|
|
|
participantJoined: (newSession, participant) ->
|
|
logger.debug("jamClient.ParticipantJoined", participant.client_id)
|
|
context.jamClient.ParticipantJoined(newSession, @toJamClientParticipant(participant));
|
|
@currentParticipants[participant.client_id] = {server: participant, client: {audio_established: null}}
|
|
|
|
participantLeft: (newSession, participant) ->
|
|
logger.debug("jamClient.ParticipantLeft", participant.client_id)
|
|
context.jamClient.ParticipantLeft(newSession, @toJamClientParticipant(participant));
|
|
delete @currentParticipants[participant.client_id]
|
|
|
|
toJamClientParticipant: (participant) ->
|
|
{
|
|
userID: "",
|
|
clientID: participant.client_id,
|
|
tcpPort: 0,
|
|
udpPort: 0,
|
|
localIPAddress: participant.ip_address, # ?
|
|
globalIPAddress: participant.ip_address, # ?
|
|
latency: 0,
|
|
natType: ""
|
|
}
|
|
|
|
recordingRegistration: () ->
|
|
logger.debug("recording registration not hooked up yet")
|
|
|
|
updateCurrentSession: (sessionData) ->
|
|
if sessionData != null
|
|
@currentOrLastSession = sessionData
|
|
|
|
@currentSession = sessionData
|
|
|
|
console.log("SESSION CHANGED", sessionData)
|
|
|
|
this.trigger(new context.SessionHelper(@app, @currentSession))
|
|
|
|
ensureConnected: () ->
|
|
unless context.JK.JamServer.connected
|
|
leaveBehavior =
|
|
location: '/client#/home'
|
|
notify:
|
|
title: "Not Connected"
|
|
text: 'To create or join a session, you must be connected to the server.'
|
|
|
|
SessionActions.leaveSession.trigger(leaveBehavior)
|
|
|
|
context.JK.JamServer.connected
|
|
|
|
# called by anyone wanting to leave the session with a certain behavior
|
|
onLeaveSession: (behavior) ->
|
|
logger.debug("attempting to leave session", behavior)
|
|
if behavior.notify
|
|
@app.layout.notify(behavior.notify)
|
|
|
|
SessionActions.allowLeaveSession.trigger()
|
|
|
|
if behavior.location
|
|
if jQuery.isNumeric(behavior.location)
|
|
window.history.go(behavior.location)
|
|
else
|
|
window.location = behavior.location
|
|
else
|
|
logger.warn("no location specified in leaveSession action", behavior)
|
|
window.location = '/client#/home'
|
|
|
|
if gon.global.video_available && gon.global.video_available != "none"
|
|
@webcamViewer.setVideoOff()
|
|
|
|
@leaveSession()
|
|
|
|
@sessionUtils.SessionPageLeave()
|
|
|
|
leaveSession: () ->
|
|
|
|
if @joinDeferred?.state() == 'resolved'
|
|
deferred = new $.Deferred()
|
|
|
|
@recordingModel.stopRecordingIfNeeded()
|
|
.always(()=>
|
|
@performLeaveSession(deferred)
|
|
)
|
|
|
|
performLeaveSession: (deferred) ->
|
|
|
|
logger.debug("SessionModel.leaveCurrentSession()")
|
|
# TODO - sessionChanged will be called with currentSession = null\
|
|
|
|
# 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
|
|
logger.debug("performLeaveSession: calling jamClient.LeaveSession for clientId=" + @app.clientId)
|
|
context.jamClient.LeaveSession({ sessionID: @currentSessionId })
|
|
@leaveSessionRest(@currentSessionId)
|
|
.done(=>
|
|
deferred.resolve(arguments[0], arguments[1], arguments[2]))
|
|
.fail(=>
|
|
deferred.reject(arguments[0], arguments[1], arguments[2]);
|
|
)
|
|
|
|
# 'unregister' for callbacks
|
|
context.jamClient.SessionRegisterCallback("");
|
|
#context.jamClient.SessionSetAlertCallback("");
|
|
context.jamClient.SessionSetConnectionStatusRefreshRate(0);
|
|
|
|
@sessionEnded()
|
|
|
|
this.trigger(new context.SessionHelper(@app, @currentSession))
|
|
|
|
sessionEnded: () ->
|
|
# cleanup
|
|
|
|
context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, @trackChanges);
|
|
context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, @trackChanges);
|
|
context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges);
|
|
context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges);
|
|
|
|
if @sessionPageEnterDeferred?
|
|
@sessionPageEnterDeferred.reject('session_over')
|
|
@sessionPageEnterDeferred = null
|
|
|
|
if @backendMixerAlertThrottleTimer
|
|
clearTimeout(@backendMixerAlertThrottleTimer)
|
|
@backendMixerAlertThrottleTimer = null
|
|
|
|
@userTracks = null;
|
|
@startTime = null;
|
|
|
|
if @joinDeffered?.state() == 'resolved'
|
|
$(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}})
|
|
|
|
@currentTrackChanges = 0
|
|
@currentSession = null
|
|
@joinDeferred = null
|
|
@currentSessionId = null
|
|
@currentParticipants = {}
|
|
@previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []}
|
|
@openBackingTrack = null
|
|
@shownAudioMediaMixerHelp = false
|
|
@controlsLockedForJamTrackRecording = false;
|
|
}
|
|
) |