// 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(server, client, currentUser) { var clientId = client.clientID; var currentSessionId = null; // Set on join, prior to setting currentSession. var currentSession = null; var subscribers = {}; var users = {}; // User info for session participants function id() { return currentSession.id; } function participants() { if (currentSession) { return currentSession.participants; } else { return []; } } /** * Join the session specified by the provided id. */ function joinSession(sessionId) { currentSessionId = sessionId; logger.debug("SessionModel.joinSession(" + sessionId + ")"); joinSessionRest(sessionId, function() { refreshCurrentSession(); }); server.registerMessageCallback( context.JK.MessageType.USER_JOINED_MUSIC_SESSION, refreshCurrentSession); server.registerMessageCallback( context.JK.MessageType.USER_LEFT_MUSIC_SESSION, refreshCurrentSession); } /** * Leave the current session */ function leaveCurrentSession() { logger.debug("SessionModel.leaveCurrentSession()"); // TODO - sessionChanged will be called with currentSession = null server.unregisterMessageCallback( context.JK.MessageType.USER_JOINED_MUSIC_SESSION, refreshCurrentSession); server.unregisterMessageCallback( context.JK.MessageType.USER_LEFT_MUSIC_SESSION, refreshCurrentSession); leaveSessionRest(currentSessionId, sessionChanged); currentSession = null; currentSessionId = null; } /** * Refresh the current session, and participants. */ function refreshCurrentSession() { logger.debug("SessionModel.refreshCurrentSession()"); refreshCurrentSessionRest(function() { refreshCurrentSessionParticipantsRest(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](); } } /** * Reload the session data from the REST server, calling * the provided callback when complete. */ function refreshCurrentSessionRest(callback) { var url = "/api/sessions/" + currentSessionId; $.ajax({ type: "GET", url: url, success: function(response) { sendClientParticipantChanges(currentSession, response); currentSession = response; callback(); }, error: ajaxError }); } /** * 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); } }); } /** * Ensure that we have user info for all current participants. */ function refreshCurrentSessionParticipantsRest(callback) { var callCount = 0; $.each(participants(), function(index, value) { if (!(this.user.id in users)) { var userInfoUrl = "/api/users/" + this.user.id; callCount += 1; $.ajax({ type: "GET", url: userInfoUrl, success: function(user) { callCount -= 1; users[user.id] = user; }, error: function(jqXHR, textStatus, errorThrown) { callCount -= 1; logger.error('Error getting user info from ' + userInfoUrl); } }); } }); if (!(callback)) { return; } context.JK.joinCalls( function() { return callCount === 0; }, callback, 10); } function participantForClientId(clientId) { var foundParticipant = null; $.each(currentSession.participants, function(index, participant) { if (participant.client_id === clientId) { foundParticipant = participant; return false; } }); return foundParticipant; } /** * Make the server calls to join the current user to * the session provided. */ function joinSessionRest(sessionId, callback) { var tracks = getUserTracks(); var data = { client_id: clientId, ip_address: server.publicIP, as_musician: true, tracks: tracks }; var url = "/api/sessions/" + sessionId + "/participants"; $.ajax({ type: "POST", dataType: "json", contentType: 'application/json', url: url, data: JSON.stringify(data), processData:false, success: function(response) { logger.debug("calling jamClient.JoinSession"); client.JoinSession({ sessionID: sessionId }); callback(); }, error: ajaxError }); } function leaveSessionRest(sessionId, callback) { var url = "/api/participants/" + clientId; $.ajax({ type: "DELETE", url: url, success: function (response) { logger.debug("calling jamClient.LeaveSession"); client.LeaveSession({ sessionID: sessionId }); callback(); }, error: ajaxError }); } function ajaxError(jqXHR, textStatus, errorMessage) { logger.error("Unexpected ajax error: " + textStatus); } function getUserTracks() { // FIXME. Setting tracks for join session. Only looking at profile // for now. Needs to check jamClient instruments and if set, use those // as first preference. Also need to support jamClient possibly having // multiple tracks. // TODO: Defaulting to electric guitar... var track = { instrument_id: "electric guitar", sound: "stereo" }; if (currentUser.instruments && currentUser.instruments.length) { track = { instrument_id: currentUser.instruments[0].instrument_id, sound: "stereo" }; } return [track]; } // Public interface this.id = id; this.participants = participants; this.joinSession = joinSession; this.leaveCurrentSession = leaveCurrentSession; this.refreshCurrentSession = refreshCurrentSession; this.subscribe = subscribe; this.participantForClientId = participantForClientId; }; })(window,jQuery);