494 lines
18 KiB
JavaScript
494 lines
18 KiB
JavaScript
// 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;
|
|
|
|
context.JK.SessionModel = function(app, server, client) {
|
|
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);
|
|
function id() {
|
|
return currentSession ? currentSession.id : null;
|
|
}
|
|
|
|
function participants() {
|
|
if (currentSession) {
|
|
return currentSession.participants;
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
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.recorded_tracks
|
|
}
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Join the session specified by the provided id.
|
|
*/
|
|
function joinSession(sessionId) {
|
|
currentSessionId = sessionId;
|
|
logger.debug("SessionModel.joinSession(" + sessionId + ")");
|
|
var deferred = joinSessionRest(sessionId);
|
|
|
|
deferred
|
|
.done(function(){
|
|
logger.debug("calling jamClient.JoinSession");
|
|
|
|
if(!alreadyInSession()) {
|
|
// on temporary disconnect scenarios, a user may already be in a session when they enter this path
|
|
// so we avoid double counting
|
|
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
|
|
}
|
|
|
|
recordingModel.reset();
|
|
client.JoinSession({ sessionID: sessionId });
|
|
refreshCurrentSession();
|
|
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
|
|
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
|
|
|
|
|
|
})
|
|
.fail(function() {
|
|
currentSessionId = null;
|
|
});
|
|
|
|
return deferred;
|
|
}
|
|
|
|
function performLeaveSession(deferred) {
|
|
|
|
logger.debug("SessionModel.leaveCurrentSession()");
|
|
// TODO - sessionChanged will be called with currentSession = null
|
|
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
|
|
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
|
|
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
|
client.LeaveSession({ sessionID: currentSessionId });
|
|
deferred = leaveSessionRest(currentSessionId);
|
|
deferred.done(function() {
|
|
sessionChanged();
|
|
});
|
|
|
|
// 'unregister' for callbacks
|
|
context.jamClient.SessionRegisterCallback("");
|
|
context.jamClient.SessionSetAlertCallback("");
|
|
updateCurrentSession(null);
|
|
currentSessionId = 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;
|
|
}
|
|
|
|
/**
|
|
* Refresh the current session, and participants.
|
|
*/
|
|
function refreshCurrentSession() {
|
|
// XXX use backend instead: https://jamkazam.atlassian.net/browse/VRFS-854
|
|
logger.debug("SessionModel.refreshCurrentSession()");
|
|
refreshCurrentSessionRest(sessionChanged);
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
logger.debug("SessionModel.sessionChanged()");
|
|
for (var subscriberId in subscribers) {
|
|
subscribers[subscriberId](currentSession);
|
|
}
|
|
}
|
|
|
|
// you should only update currentSession with this function
|
|
function updateCurrentSession(sessionData) {
|
|
if(sessionData != null) {
|
|
currentOrLastSession = sessionData;
|
|
}
|
|
currentSession = sessionData;
|
|
}
|
|
|
|
/**
|
|
* Reload the session data from the REST server, calling
|
|
* the provided callback when complete.
|
|
*/
|
|
function refreshCurrentSessionRest(callback) {
|
|
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
|
|
pendingSessionRefresh = true;
|
|
}
|
|
else {
|
|
requestingSessionRefresh = true;
|
|
$.ajax({
|
|
type: "GET",
|
|
url: url,
|
|
async: false,
|
|
success: function(response) {
|
|
sendClientParticipantChanges(currentSession, response);
|
|
updateCurrentSession(response);
|
|
if(callback != null) {
|
|
callback();
|
|
}
|
|
},
|
|
error: ajaxError,
|
|
complete: function() {
|
|
requestingSessionRefresh = false;
|
|
if(pendingSessionRefresh) {
|
|
// and when the request is done, if we have a pending, fire t off again
|
|
pendingSessionRefresh = false;
|
|
refreshCurrentSessionRest(null);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 : ""
|
|
};
|
|
}
|
|
|
|
function sendClientParticipantChanges(oldSession, newSession) {
|
|
var joins = [], leaves = []; // 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.push(this.client_id);
|
|
});
|
|
}
|
|
if (newSession && newSession.participants) {
|
|
newParticipants = newSession.participants;
|
|
$.each(newParticipants, function() {
|
|
newParticipantIds.push(this.client_id);
|
|
});
|
|
}
|
|
|
|
$.each(newParticipantIds, function(i,v) {
|
|
if ($.inArray(v, oldParticipantIds) === -1) {
|
|
// new participant id that's not in old participant ids: Join
|
|
joins.push(_toJamClientParticipant(newParticipants[i]));
|
|
}
|
|
});
|
|
$.each(oldParticipantIds, function(i,v) {
|
|
if ($.inArray(v, newParticipantIds) === -1) {
|
|
// old participant id that's not in new participant ids: Leave
|
|
leaves.push(_toJamClientParticipant(oldParticipants[i]));
|
|
}
|
|
});
|
|
|
|
$.each(joins, function(i,v) {
|
|
if (v.client_id != clientId) {
|
|
client.ParticipantJoined(newSession, v);
|
|
}
|
|
});
|
|
$.each(leaves, function(i,v) {
|
|
if (v.client_id != clientId) {
|
|
client.ParticipantLeft(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;
|
|
}
|
|
|
|
function addTrack(sessionId, data) {
|
|
logger.debug("updating tracks on the server %o", data);
|
|
var url = "/api/sessions/" + sessionId + "/tracks";
|
|
$.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: 'application/json',
|
|
url: url,
|
|
async: false,
|
|
data: JSON.stringify(data),
|
|
processData:false,
|
|
success: function(response) {
|
|
// save to the backend
|
|
logger.debug("successfully updated tracks on the server");
|
|
//refreshCurrentSession();
|
|
},
|
|
error: ajaxError
|
|
});
|
|
}
|
|
|
|
function updateTrack(sessionId, trackId, data) {
|
|
var url = "/api/sessions/" + sessionId + "/tracks/" + trackId;
|
|
$.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: 'application/json',
|
|
url: url,
|
|
async: false,
|
|
data: JSON.stringify(data),
|
|
processData:false,
|
|
success: function(response) {
|
|
logger.debug("Successfully updated track info (" + JSON.stringify(data) + ")");
|
|
},
|
|
error: ajaxError
|
|
});
|
|
}
|
|
|
|
function deleteTrack(sessionId, trackId) {
|
|
|
|
if (trackId) {
|
|
|
|
client.TrackSetCount(1);
|
|
client.TrackSaveAssignments();
|
|
|
|
/**
|
|
$.ajax({
|
|
type: "DELETE",
|
|
url: "/api/sessions/" + sessionId + "/tracks/" + trackId,
|
|
async: false,
|
|
success: function(response) {
|
|
// TODO: if in recording, more cleanup to do???
|
|
|
|
// update backend client (for now, only the second track can be removed)
|
|
client.TrackSetCount(1);
|
|
client.TrackSaveAssignments();
|
|
|
|
// refresh Session screen
|
|
refreshCurrentSession();
|
|
},
|
|
error: function(jqXHR, textStatus, errorThrown) {
|
|
logger.error("Error deleting track " + trackId);
|
|
}
|
|
});
|
|
*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make the server calls to join the current user to
|
|
* the session provided.
|
|
*/
|
|
function joinSessionRest(sessionId) {
|
|
var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
|
|
var data = {
|
|
client_id: clientId,
|
|
ip_address: server.publicIP,
|
|
as_musician: true,
|
|
tracks: tracks
|
|
};
|
|
var url = "/api/sessions/" + sessionId + "/participants";
|
|
return $.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: 'application/json',
|
|
url: url,
|
|
data: JSON.stringify(data),
|
|
processData:false
|
|
});
|
|
}
|
|
|
|
function leaveSessionRest(sessionId) {
|
|
var url = "/api/participants/" + clientId;
|
|
return $.ajax({
|
|
type: "DELETE",
|
|
url: url,
|
|
async: true
|
|
});
|
|
}
|
|
|
|
function reconnect() {
|
|
context.JK.CurrentSessionModel.leaveCurrentSession()
|
|
.always(function() {
|
|
window.location.reload();
|
|
});
|
|
}
|
|
|
|
function registerReconnect(content) {
|
|
$('a.disconnected-reconnect', content).click(function() {
|
|
|
|
var template = $('#template-reconnecting').html();
|
|
var templateHtml = context.JK.fillTemplate(template, null);
|
|
var content = context.JK.Banner.show({
|
|
html : template
|
|
});
|
|
|
|
rest.serverHealthCheck()
|
|
.done(function() {
|
|
reconnect();
|
|
})
|
|
.fail(function(xhr, textStatus, errorThrown) {
|
|
|
|
if(xhr && xhr.status >= 100) {
|
|
// we could connect to the server, and it's alive
|
|
reconnect();
|
|
}
|
|
else {
|
|
var template = $('#template-could-not-reconnect').html();
|
|
var templateHtml = context.JK.fillTemplate(template, null);
|
|
var content = context.JK.Banner.show({
|
|
html : template
|
|
});
|
|
|
|
registerReconnect(content);
|
|
}
|
|
});
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function onWebsocketDisconnected(in_error) {
|
|
|
|
if(app.clientUpdating) {
|
|
// we don't want to do a 'cover the whole screen' dialog
|
|
// because the client update is already showing.
|
|
return;
|
|
}
|
|
|
|
// kill the streaming of the session immediately
|
|
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
|
client.LeaveSession({ sessionID: currentSessionId });
|
|
|
|
if(in_error) {
|
|
var template = $('#template-disconnected').html();
|
|
var templateHtml = context.JK.fillTemplate(template, null);
|
|
var content = context.JK.Banner.show({
|
|
html : template
|
|
}) ;
|
|
registerReconnect(content);
|
|
}
|
|
}
|
|
|
|
function ajaxError(jqXHR, textStatus, errorMessage) {
|
|
logger.error("Unexpected ajax error: " + textStatus);
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Public interface
|
|
this.id = id;
|
|
this.recordedTracks = recordedTracks;
|
|
this.participants = participants;
|
|
this.joinSession = joinSession;
|
|
this.leaveCurrentSession = leaveCurrentSession;
|
|
this.refreshCurrentSession = refreshCurrentSession;
|
|
this.subscribe = subscribe;
|
|
this.participantForClientId = participantForClientId;
|
|
this.isPlayingRecording = isPlayingRecording;
|
|
this.addTrack = addTrack;
|
|
this.updateTrack = updateTrack;
|
|
this.deleteTrack = deleteTrack;
|
|
this.onWebsocketDisconnected = onWebsocketDisconnected;
|
|
this.recordingModel = recordingModel;
|
|
this.findUserBy = findUserBy;
|
|
this.getCurrentSession = function() {
|
|
return currentSession;
|
|
};
|
|
this.getCurrentOrLastSession = function() {
|
|
return currentOrLastSession;
|
|
};
|
|
};
|
|
|
|
})(window,jQuery); |