1085 lines
40 KiB
CoffeeScript
1085 lines
40 KiB
CoffeeScript
$ = jQuery
|
|
context = window
|
|
logger = context.JK.logger
|
|
rest = context.JK.Rest()
|
|
EVENTS = context.JK.EVENTS
|
|
MIX_MODES = context.JK.MIX_MODES
|
|
|
|
JamTrackActions = @JamTrackActions
|
|
SessionActions = @SessionActions
|
|
RecordingActions = @RecordingActions
|
|
NotificationActions = @NotificationActions
|
|
VideoActions = @VideoActions
|
|
ConfigureTracksActions = @ConfigureTracksActions
|
|
|
|
@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
|
|
joinDeferred: null
|
|
recordingModel: null
|
|
currentTrackChanges: 0
|
|
isRecording: false
|
|
previousAllTracks: {userTracks: [], backingTracks: [], metronomeTracks: []}
|
|
webcamViewer: null
|
|
openBackingTrack: null
|
|
helper: null
|
|
downloadingJamTrack: false
|
|
|
|
init: ->
|
|
# Register with the app store to get @app
|
|
this.listenTo(context.AppStore, this.onAppInit)
|
|
this.listenTo(context.RecordingStore, this.onRecordingChanged)
|
|
this.listenTo(context.VideoStore, this.onVideoChanged)
|
|
|
|
|
|
onAppInit: (@app) ->
|
|
@gearUtils = context.JK.GearUtilsInstance
|
|
@sessionUtils = context.JK.SessionUtils
|
|
@recordingModel = new context.JK.RecordingModel(@app, rest, context.jamClient);
|
|
RecordingActions.initModel(@recordingModel)
|
|
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
|
|
|
|
onMixdownActive: (mixdown) ->
|
|
if @currentSession?.jam_track?
|
|
@currentSession.jam_track.mixdown = mixdown
|
|
@issueChange()
|
|
|
|
|
|
onVideoChanged: (@videoState) ->
|
|
|
|
issueChange: () ->
|
|
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
|
|
this.trigger(@helper)
|
|
|
|
onWindowBackgrounded: () ->
|
|
@app.user()
|
|
.done((userProfile) =>
|
|
if userProfile.show_whats_next &&
|
|
window.location.pathname.indexOf(gon.client_path) == 0 &&
|
|
!@app.layout.isDialogShowing('getting-started')
|
|
@app.layout.showDialog('getting-started')
|
|
)
|
|
|
|
return unless @inSession()
|
|
|
|
# the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen
|
|
logger.debug("leaving session because window was closed")
|
|
SessionActions.leaveSession({location: '/client#/home'})
|
|
|
|
onBroadcastFailure: (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")
|
|
|
|
onBroadcastSuccess: (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")
|
|
|
|
onBroadcastStopped: (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")
|
|
|
|
onShowNativeMetronomeGui: () ->
|
|
context.jamClient.SessionShowMetronomeGui()
|
|
|
|
onOpenMetronome: () ->
|
|
unstable = @unstableNTPClocks()
|
|
if @participants().length > 1 && unstable.length > 0
|
|
names = unstable.join(", ")
|
|
logger.debug("Unstable clocks: ", names, unstable)
|
|
context.JK.Banner.showAlert("Couldn't open metronome", context._.template($('#template-help-metronome-unstable').html(), {names: names}, { variable: 'data' }));
|
|
else
|
|
data =
|
|
value: 1
|
|
session_size: @participants().length
|
|
user_id: context.JK.currentUserId
|
|
user_name: context.JK.currentUserName
|
|
|
|
context.stats.write('web.metronome.open', data)
|
|
rest.openMetronome({id: @currentSessionId})
|
|
.done((response) =>
|
|
MixerActions.openMetronome()
|
|
@updateSessionInfo(response, true)
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notify({
|
|
"title": "Couldn't open metronome",
|
|
"text": "Couldn't inform the server to open metronome. msg=" + jqXHR.responseText,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
onMetronomeCricketChange: (isCricket) ->
|
|
context.jamClient.setMetronomeCricketTestState(isCricket);
|
|
|
|
unstableNTPClocks: () ->
|
|
unstable = []
|
|
# This should be handled in the below loop, actually:
|
|
myState = context.jamClient.getMyNetworkState()
|
|
map = null
|
|
for participant in @participants()
|
|
isSelf = participant.client_id == @app.clientId
|
|
|
|
if isSelf
|
|
isStable = myState.ntp_stable
|
|
else
|
|
map = context.jamClient.getPeerState(participant.client_id)
|
|
isStable = map.ntp_stable
|
|
|
|
if !isStable
|
|
name = participant.user.name
|
|
|
|
if isSelf
|
|
name += " (this computer)"
|
|
|
|
unstable.push(name)
|
|
unstable
|
|
|
|
|
|
|
|
onDownloadingJamTrack: (downloading) ->
|
|
@downloadingJamTrack = downloading
|
|
|
|
@issueChange()
|
|
|
|
onToggleSessionVideo: () ->
|
|
|
|
if @videoState?.videoEnabled
|
|
logger.debug("toggle session video")
|
|
VideoActions.toggleVideo()
|
|
else
|
|
context.JK.Banner.showAlert({
|
|
title: "Video Is Disabled",
|
|
html: "To re-enable video, you must go your video settings in your account settings and enable video.",
|
|
})
|
|
|
|
|
|
onAudioResync: () ->
|
|
logger.debug("audio resyncing")
|
|
response = context.jamClient.SessionAudioResync()
|
|
if response?
|
|
@app.notify({
|
|
"title": "Error",
|
|
"text": response,
|
|
"icon_url": "/assets/content/icon_alert_big.png"})
|
|
|
|
onSyncWithServer: () ->
|
|
@refreshCurrentSession(true)
|
|
|
|
onWatchedInputs: (inputTracks) ->
|
|
|
|
logger.debug("obtained tracks at start of session")
|
|
@sessionPageEnterDeferred.resolve(inputTracks);
|
|
@sessionPageEnterDeferred = null
|
|
|
|
|
|
# codeInitiated means the user did not initiate this
|
|
onCloseMedia: (codeInitiated) ->
|
|
|
|
logger.debug("SessionStore: onCloseMedia")
|
|
if @helper.recordedTracks()
|
|
@closeRecording()
|
|
else if @helper.jamTracks() || @downloadingJamTrack
|
|
@closeJamTrack()
|
|
else if @helper.backingTrack() && @helper.backingTrack().path
|
|
@closeBackingTrack()
|
|
else if @helper.isMetronomeOpen()
|
|
@closeMetronomeTrack()
|
|
else
|
|
logger.error("don't know how to close open media") unless codeInitiated
|
|
|
|
closeJamTrack: () ->
|
|
logger.debug("closing jam track");
|
|
|
|
if @isRecording
|
|
logger.debug("can't close jamtrack while recording")
|
|
@app.notify({title: 'Can Not Close JamTrack', text: 'A JamTrack can not be closed while recording.'})
|
|
return
|
|
|
|
unless @selfOpenedJamTracks()
|
|
logger.debug("can't close jamtrack if not the opener")
|
|
@app.notify({title: 'Can Not Close JamTrack', text: 'Only the person who opened the JamTrack can close it.'})
|
|
return
|
|
|
|
rest.closeJamTrack({id: @currentSessionId})
|
|
.done(() =>
|
|
@downloadingJamTrack = false
|
|
@refreshCurrentSession(true)
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notify({
|
|
"title": "Couldn't Close JamTrack",
|
|
"text": "Couldn't inform the server to close JamTrack. msg=" + jqXHR.responseText,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
context.jamClient.JamTrackStopPlay()
|
|
JamTrackActions.close()
|
|
|
|
|
|
onOpenBackingTrack: (result) ->
|
|
unless @inSession()
|
|
logger.debug("ignoring backing track selected callback (not in session)")
|
|
return
|
|
|
|
if result.success
|
|
logger.debug("backing track selected: " + result.file);
|
|
|
|
rest.openBackingTrack({id: @currentSessionId, backing_track_path: result.file})
|
|
.done(() =>
|
|
|
|
openResult = context.jamClient.SessionOpenBackingTrackFile(result.file, false);
|
|
|
|
if openResult
|
|
# storing session state in memory, not in response of Session server response. bad.
|
|
@openBackingTrack = result.file
|
|
else
|
|
@app.notify({
|
|
"title": "Couldn't Open Backing Track",
|
|
"text": "Is the file a valid audio file?",
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
});
|
|
@closeBackingTrack()
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notifyServerError(jqXHR, "Unable to Open Backing Track For Playback");
|
|
)
|
|
|
|
closeRecording: () ->
|
|
logger.debug("closing recording");
|
|
|
|
rest.stopPlayClaimedRecording({id: @currentSessionId, claimed_recording_id: @currentSession.claimed_recording.id})
|
|
.done((response) =>
|
|
#sessionModel.refreshCurrentSession(true);
|
|
# update session info
|
|
@onUpdateSession(response)
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notify({
|
|
"title": "Couldn't Stop Recording Playback",
|
|
"text": "Couldn't inform the server to stop playback. msg=" + jqXHR.responseText,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
context.jamClient.CloseRecording()
|
|
|
|
closeMetronomeTrack:() ->
|
|
logger.debug("SessionStore: closeMetronomeTrack")
|
|
rest.closeMetronome({id: @currentSessionId})
|
|
.done(() =>
|
|
context.jamClient.SessionCloseMetronome()
|
|
@refreshCurrentSession(true)
|
|
)
|
|
.fail((jqXHR) =>
|
|
@app.notify({
|
|
"title": "Couldn't Close MetronomeTrack",
|
|
"text": "Couldn't inform the server to close MetronomeTrack. msg=" + jqXHR.responseText,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
closeBackingTrack: () ->
|
|
if @isRecording
|
|
logger.debug("can't close backing track while recording")
|
|
return
|
|
|
|
rest.closeBackingTrack({id: @currentSessionId})
|
|
.done(() =>
|
|
)
|
|
.fail(() =>
|
|
@app.notify({
|
|
"title": "Couldn't Close Backing Track",
|
|
"text": "Couldn't inform the server to close Backing Track. msg=" + jqXHR.responseText,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
});
|
|
)
|
|
|
|
# '' closes all open backing tracks
|
|
context.jamClient.SessionStopPlay();
|
|
context.jamClient.SessionCloseBackingTrackFile('');
|
|
|
|
|
|
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(()=>
|
|
MixerActions.syncTracks()
|
|
)
|
|
, 100)
|
|
else if text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl'
|
|
|
|
backingTracks = trackInfo.backingTracks
|
|
previousBackingTracks = @previousAllTracks.backingTracks
|
|
metronomeTracks = trackInfo.metronomeTracks
|
|
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)
|
|
MixerActions.syncTracks()
|
|
else if !(previousMetronomeTracks.length == 0 && metronomeTracks.length == 0) && previousMetronomeTracks != metronomeTracks
|
|
#logger.debug("metronome state changed ", previousMetronomeTracks, metronomeTracks)
|
|
MixerActions.syncTracks()
|
|
else
|
|
@refreshCurrentSession(true)
|
|
|
|
@previousAllTracks = trackInfo
|
|
|
|
else if text == 'Global Peer Input Mixer Mode'
|
|
MixerActions.mixerModeChanged(MIX_MODES.MASTER)
|
|
|
|
else if text == 'Local Peer Stream Mixer Mode'
|
|
MixerActions.mixerModeChanged(MIX_MODES.PERSONAL)
|
|
|
|
onRecordingChanged: (details) ->
|
|
logger.debug("SessionStore.onRecordingChanged: " + details.cause)
|
|
@isRecording = details.isRecording
|
|
|
|
switch details.cause
|
|
when 'started'
|
|
|
|
if details.reason
|
|
reason = details.reason;
|
|
detail = details.detail;
|
|
title = "Could Not Start Recording";
|
|
|
|
switch reason
|
|
when 'client-no-response'
|
|
@notifyWithUserInfo(title, 'did not respond to the start signal.', detail)
|
|
when 'empty-recording-id'
|
|
@app.notifyAlert(title, "No recording ID specified.")
|
|
when 'missing-client'
|
|
@notifyWithUserInfo(title, 'could not be signalled to start recording.', detail)
|
|
when 'already-recording'
|
|
@app.notifyAlert(title, 'Already recording. If this appears incorrect, try restarting JamKazam.')
|
|
when 'recording-engine-unspecified'
|
|
@notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail)
|
|
when 'recording-engine-create-directory'
|
|
@notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail)
|
|
when 'recording-engine-create-file'
|
|
@notifyWithUserInfo(title, 'had a problem creating a recording file.', detail)
|
|
when 'recording-engine-sample-rate'
|
|
@notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail)
|
|
when 'rest'
|
|
jqXHR = detail[0];
|
|
@app.notifyServerError(jqXHR);
|
|
else
|
|
@notifyWithUserInfo(title, 'Error Reason: ' + reason)
|
|
else
|
|
@displayWhoCreatedRecording(details.clientId)
|
|
|
|
when 'stopped'
|
|
if @selfOpenedJamTracks()
|
|
timeline = context.jamClient.GetJamTrackTimeline();
|
|
|
|
rest.addRecordingTimeline(details.recordingId, timeline)
|
|
.fail(()=>
|
|
@app.notify({
|
|
title: "Unable to Add JamTrack Volume Data",
|
|
text: "The volume of the JamTrack will not be correct in the recorded mix."
|
|
}, null, true)
|
|
)
|
|
|
|
if details.reason
|
|
logger.warn("Recording Discarded: ", details)
|
|
reason = details.reason
|
|
detail = details.detail
|
|
title = "Recording Discarded"
|
|
|
|
switch reason
|
|
when 'client-no-response'
|
|
@notifyWithUserInfo(title, 'did not respond to the stop signal.', detail)
|
|
when 'missing-client'
|
|
@notifyWithUserInfo(title, 'could not be signalled to stop recording.', detail)
|
|
when 'empty-recording-id'
|
|
@app.notifyAlert(title, "No recording ID specified.")
|
|
when 'wrong-recording-id'
|
|
@app.notifyAlert(title, "Wrong recording ID specified.")
|
|
when 'not-recording'
|
|
@app.notifyAlert(title, "Not currently recording.")
|
|
when 'already-stopping'
|
|
@app.notifyAlert(title, "Already stopping the current recording.")
|
|
when 'start-before-stop'
|
|
@notifyWithUserInfo(title, 'asked that we start a new recording; cancelling the current one.', detail)
|
|
else
|
|
@app.notifyAlert(title, "Error reason: " + reason)
|
|
else
|
|
@promptUserToSave(details.recordingId, timeline);
|
|
|
|
when 'abortedRecording'
|
|
reason = details.reason
|
|
detail = details.detail
|
|
|
|
title = "Recording Cancelled"
|
|
|
|
switch reason
|
|
when 'client-no-response'
|
|
@notifyWithUserInfo(title, 'did not respond to the start signal.', detail)
|
|
when 'missing-client'
|
|
@notifyWithUserInfo(title, 'could not be signalled to start recording.', detail)
|
|
when 'populate-recording-info'
|
|
@notifyWithUserInfo(title, 'could not synchronize with the server.', detail)
|
|
when 'recording-engine-unspecified'
|
|
@notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail)
|
|
when 'recording-engine-create-directory'
|
|
@notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail)
|
|
when 'recording-engine-create-file'
|
|
@notifyWithUserInfo(title, 'had a problem creating a recording file.', detail)
|
|
when 'recording-engine-sample-rate'
|
|
@notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail)
|
|
else
|
|
@app.notifyAlert(title, "Error reason: " + reason)
|
|
|
|
@issueChange()
|
|
|
|
notifyWithUserInfo: (title , text, clientId) ->
|
|
@findUserBy({clientId: clientId})
|
|
.done((user)=>
|
|
@app.notify({
|
|
"title": title,
|
|
"text": user.name + " " + text,
|
|
"icon_url": context.JK.resolveAvatarUrl(user.photo_url)
|
|
});
|
|
)
|
|
.fail(()=>
|
|
@app.notify({
|
|
"title": title,
|
|
"text": 'Someone ' + text,
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
findUserBy: (finder) ->
|
|
if finder.clientId
|
|
foundParticipant = null
|
|
for participant in @participants()
|
|
if participant.client_id == finder.clientId
|
|
foundParticipant = participant
|
|
break
|
|
|
|
if foundParticipant
|
|
return $.Deferred().resolve(foundParticipant.user).promise();
|
|
|
|
# TODO: find it via some REST API if not found?
|
|
return $.Deferred().reject().promise();
|
|
|
|
displayWhoCreatedRecording: (clientId) ->
|
|
if @app.clientId != clientId # don't show to creator
|
|
@findUserBy({clientId: clientId})
|
|
.done((user) =>
|
|
@app.notify({
|
|
"title": "Recording Started",
|
|
"text": user.name + " started a recording",
|
|
"icon_url": context.JK.resolveAvatarUrl(user.photo_url)
|
|
})
|
|
)
|
|
.fail(() =>
|
|
@app.notify({
|
|
"title": "Recording Started",
|
|
"text": "Oops! Can't determine who started this recording",
|
|
"icon_url": "/assets/content/icon_alert_big.png"
|
|
})
|
|
)
|
|
|
|
promptUserToSave: (recordingId, timeline) ->
|
|
rest.getRecording( {id: recordingId} )
|
|
.done((recording) =>
|
|
if timeline
|
|
recording.timeline = timeline.global
|
|
|
|
context.JK.recordingFinishedDialog.setRecording(recording)
|
|
@app.layout.showDialog('recordingFinished').one(EVENTS.DIALOG_CLOSED, (e, data) =>
|
|
if data.result && data.result.keep
|
|
context.JK.prodBubble($('#recording-manager-viewer'), 'file-manager-poke', {}, {positions:['top', 'left', 'right', 'bottom'], offsetParent: $('#session-screen').parent()})
|
|
)
|
|
)
|
|
.fail(@app.ajaxError)
|
|
|
|
onJoinSession: (sessionId) ->
|
|
|
|
# poke ShareDialog
|
|
shareDialog = new JK.ShareDialog(@app, sessionId, "session");
|
|
shareDialog.initialize(context.JK.FacebookHelperInstance);
|
|
|
|
# initialize webcamViewer
|
|
VideoActions.stopVideo();
|
|
|
|
# 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(true)
|
|
|
|
# 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(@currentSessionId)
|
|
return
|
|
|
|
@updateSessionInfo(response, true)
|
|
@issueChange()
|
|
|
|
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 @participants().length == 1
|
|
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create);
|
|
else
|
|
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
|
|
|
|
@recordingModel.reset(@currentSessionId);
|
|
|
|
context.jamClient.JoinSession({sessionID: @currentSessionId});
|
|
|
|
#@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()
|
|
|
|
ConfigureTracksActions.reset(false)
|
|
)
|
|
.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: @currentSessionId, jam_track_id: jamTrack.id})
|
|
.done((response) =>
|
|
logger.debug("jamtrack opened")
|
|
# now actually load the jamtrack
|
|
context.SessionActions.updateSession.trigger(response);
|
|
# context.JK.CurrentSessionModel.updateSession(response);
|
|
# loadJamTrack(jamTrack);
|
|
JamTrackActions.open(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) =>
|
|
try
|
|
@updateSessionInfo(response, force)
|
|
catch e
|
|
logger.error("unable to updateSessionInfo in session refresh", e)
|
|
#setTimeout(() =>
|
|
# @updateSessionInfo(response, force)
|
|
#, 5000)
|
|
)
|
|
.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)
|
|
)
|
|
|
|
onUpdateSession: (session) ->
|
|
@updateSessionInfo(session, true)
|
|
|
|
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 of @participantsEverSeen)
|
|
@participantsEverSeen[client_id] = participant;
|
|
|
|
|
|
if client_id of 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 of 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
|
|
|
|
#logger.debug("session changed")
|
|
|
|
@issueChange()
|
|
|
|
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 if behavior.hash
|
|
window.location.hash = behavior.hash
|
|
else
|
|
logger.warn("no location specified in leaveSession action", behavior)
|
|
window.location = '/client#/home'
|
|
|
|
#VideoActions.stopVideo()
|
|
|
|
@leaveSession()
|
|
|
|
@sessionUtils.SessionPageLeave()
|
|
|
|
leaveSession: () ->
|
|
|
|
if !@joinDeferred? || @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()
|
|
|
|
@issueChange()
|
|
|
|
selfOpenedJamTracks: () ->
|
|
@currentSession && (@currentSession.jam_track_initiator_id == context.JK.currentUserId)
|
|
|
|
sessionEnded: (onJoin) ->
|
|
# 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 @joinDeferred?.state() == 'resolved'
|
|
$(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}})
|
|
|
|
@currentTrackChanges = 0
|
|
@currentSession = null
|
|
@joinDeferred = null
|
|
@isRecording = false
|
|
@currentSessionId = null
|
|
@currentParticipants = {}
|
|
@previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []}
|
|
@openBackingTrack = null
|
|
@shownAudioMediaMixerHelp = false
|
|
@controlsLockedForJamTrackRecording = false
|
|
@openBackingTrack = null
|
|
@downloadingJamTrack = false
|
|
@sessionUtils.setAutoOpenJamTrack(null) unless onJoin
|
|
|
|
JamTrackActions.close()
|
|
NotificationActions.sessionEnded()
|
|
|
|
$(context.AppStore).triggerHandler('SessionEnded')
|
|
|
|
id: () ->
|
|
@currentSessionId
|
|
|
|
getCurrentOrLastSession: () ->
|
|
@currentOrLastSession
|
|
|
|
}
|
|
) |