diff --git a/db/manifest b/db/manifest index f82a62034..fba0033a1 100755 --- a/db/manifest +++ b/db/manifest @@ -125,3 +125,4 @@ scores_mod_connections2.sql track_download_counts.sql scores_mod_users2.sql user_bio.sql +track_changes_counter.sql diff --git a/db/up/track_changes_counter.sql b/db/up/track_changes_counter.sql new file mode 100644 index 000000000..25ec1088e --- /dev/null +++ b/db/up/track_changes_counter.sql @@ -0,0 +1,2 @@ +ALTER TABLE music_sessions ADD COLUMN track_changes_counter INTEGER DEFAULT 0; +ALTER TABLE connections ADD COLUMN joined_session_at timestamp without time zone DEFAULT NULL; \ No newline at end of file diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 4d06a0319..79972f05f 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -35,6 +35,7 @@ message ClientMessage { SESSION_JOIN = 190; SESSION_DEPART = 195; MUSICIAN_SESSION_JOIN = 196; + TRACKS_CHANGED = 197; // recording notifications MUSICIAN_RECORDING_SAVED = 200; @@ -109,6 +110,7 @@ message ClientMessage { optional SessionJoin session_join = 190; optional SessionDepart session_depart = 195; optional MusicianSessionJoin musician_session_join = 196; + optional TracksChanged tracks_changed = 197; // recording notifications optional MusicianRecordingSaved musician_recording_saved = 200; @@ -286,6 +288,7 @@ message SessionJoin { optional string session_id = 1; optional string photo_url = 2; optional string msg = 3; + optional int32 track_changes_counter = 4; } message SessionDepart { @@ -293,6 +296,12 @@ message SessionDepart { optional string photo_url = 2; optional string msg = 3; optional string recording_id = 4; + optional int32 track_changes_counter = 5; +} + +message TracksChanged { + optional string session_id = 1; + optional int32 track_changes_counter = 2; } message MusicianSessionJoin { @@ -447,6 +456,7 @@ message Heartbeat { // target: client // sent from server to client in response to a Heartbeat message HeartbeatAck { + optional int32 track_changes_counter = 1; } // route_to: client diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index 91368427c..2f14cfdd1 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -53,6 +53,7 @@ module JamRuby music_session_id_expression = 'NULL' unless reconnect_music_session_id.nil? music_session_id_expression = "(CASE WHEN music_session_id='#{reconnect_music_session_id}' THEN music_session_id ELSE NULL END)" + joined_session_at_expression = "(CASE WHEN music_session_id='#{reconnect_music_session_id}' THEN NOW() ELSE NULL END)" end if ip_address @@ -97,7 +98,7 @@ module JamRuby end sql =< track_changes_counter, + ) Jampb::ClientMessage.new( :type => ClientMessage::Type::HEARTBEAT_ACK, @@ -366,11 +368,12 @@ module JamRuby ) end - def session_join(session_id, photo_url, msg) + def session_join(session_id, photo_url, msg, track_changes_counter) join = Jampb::SessionJoin.new( :session_id => session_id, :photo_url => photo_url, - :msg => msg + :msg => msg, + :track_changes_counter => track_changes_counter ) Jampb::ClientMessage.new( @@ -380,12 +383,13 @@ module JamRuby ) end - def session_depart(session_id, photo_url, msg, recording_id = nil) + def session_depart(session_id, photo_url, msg, recording_id, track_changes_counter) left = Jampb::SessionDepart.new( :session_id => session_id, :photo_url => photo_url, :msg => msg, - :recording_id => recording_id + :recording_id => recording_id, + :track_changes_counter => track_changes_counter ) Jampb::ClientMessage.new( @@ -395,6 +399,21 @@ module JamRuby ) end + + def tracks_changed(session_id, track_changes_counter) + tracks_changed = Jampb::TracksChanged.new( + :session_id => session_id, + :track_changes_counter => track_changes_counter + ) + + Jampb::ClientMessage.new( + :type => ClientMessage::Type::TRACKS_CHANGED, + :route_to => CLIENT_TARGET, + :tracks_changed => tracks_changed + ) + end + + def musician_session_join(receiver_id, session_id, photo_url, msg, notification_id, created_at) musician_session_join = Jampb::MusicianSessionJoin.new( :session_id => session_id, diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 26bd1d240..a83055c4f 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -61,7 +61,7 @@ module JamRuby end def joining_session? - return joining_session + joining_session end def can_join_music_session diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index c32e2650e..a296fb5e5 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -260,6 +260,11 @@ module JamRuby description end + def tick_track_changes + self.track_changes_counter += 1 + self.save!(:validate => false) + end + private def require_at_least_one_genre diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index 978ee08f9..4d10e0bac 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -427,6 +427,7 @@ module JamRuby join_request.id, music_session.id, music_session.creator.photo_url, + notification_msg, notification.id, notification.created_at.to_s ) @@ -441,13 +442,14 @@ module JamRuby msg = @@message_factory.session_join( music_session.id, user.photo_url, - notification_msg + notification_msg, + music_session.track_changes_counter ) @@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => connection.client_id}) end - def send_session_depart(music_session, client_id, user, recordingId = nil) + def send_session_depart(music_session, client_id, user, recordingId) notification_msg = format_msg(NotificationTypes::SESSION_DEPART, user) @@ -455,12 +457,21 @@ module JamRuby music_session.id, user.photo_url, notification_msg, - recordingId + recordingId, + music_session.track_changes_counter ) @@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id}) end + def send_tracks_changed(music_session) + msg = @@message_factory.tracks_changed( + music_session.id, music_session.track_changes_counter + ) + + @@mq_router.server_publish_to_session(music_session, msg) + end + def send_musician_session_join(music_session, connection, user) if music_session.musician_access || music_session.fan_access diff --git a/ruby/lib/jam_ruby/models/track.rb b/ruby/lib/jam_ruby/models/track.rb index da2cc8cc3..554807ea3 100644 --- a/ruby/lib/jam_ruby/models/track.rb +++ b/ruby/lib/jam_ruby/models/track.rb @@ -77,7 +77,7 @@ module JamRuby connection_tracks.each do |connection_track| tracks.each do |track| - if track[:id] == connection_track.id || track[:client_track_id] == connection_track.client_track_id; + if track[:id] == connection_track.id || track[:client_track_id] == connection_track.client_track_id to_delete.delete(connection_track) to_add.delete(track) # don't update connection_id or client_id; it's unknown what would happen if these changed mid-session @@ -86,13 +86,16 @@ module JamRuby connection_track.client_track_id = track[:client_track_id] instruments << track[:instrument_id] + + result.push(connection_track) + if connection_track.save - result.push(connection_track) next else result = connection_track raise ActiveRecord::Rollback end + end end end diff --git a/ruby/spec/jam_ruby/models/get_work_spec.rb b/ruby/spec/jam_ruby/models/get_work_spec.rb index 015d1e6a3..ae1a4d5f7 100644 --- a/ruby/spec/jam_ruby/models/get_work_spec.rb +++ b/ruby/spec/jam_ruby/models/get_work_spec.rb @@ -8,13 +8,11 @@ describe GetWork do it "get_work_1" do x = GetWork.get_work(1) - puts x.inspect x.should be_nil end it "get_work_list_1" do x = GetWork.get_work_list(1) - puts x.inspect x.should eql([]) end end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/track_spec.rb b/ruby/spec/jam_ruby/models/track_spec.rb index b4281b1a5..781456720 100644 --- a/ruby/spec/jam_ruby/models/track_spec.rb +++ b/ruby/spec/jam_ruby/models/track_spec.rb @@ -74,12 +74,21 @@ describe Track do it "updates a single track using .id to correlate" do track.id.should_not be_nil connection.tracks.length.should == 1 + begin + ActiveRecord::Base.record_timestamps = false + track.updated_at = 1.days.ago + track.save! + ensure + # very important to turn it back; it'll break all tests otherwise + ActiveRecord::Base.record_timestamps = true + end tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}]) tracks.length.should == 1 found = tracks[0] found.id.should == track.id found.sound.should == 'mono' found.client_track_id.should == 'client_guid_new' + found.updated_at.should_not == track.updated_at end it "updates a single track using .client_track_id to correlate" do @@ -92,5 +101,23 @@ describe Track do found.sound.should == 'mono' found.client_track_id.should == track.client_track_id end + + it "does not touch updated_at when nothing changes" do + track.id.should_not be_nil + connection.tracks.length.should == 1 + begin + ActiveRecord::Base.record_timestamps = false + track.updated_at = 1.days.ago + track.save! + ensure + # very important to turn it back; it'll break all tests otherwise + ActiveRecord::Base.record_timestamps = true + end + tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id}]) + tracks.length.should == 1 + found = tracks[0] + found.id.should == track.id + found.updated_at.should == track.updated_at + end end end \ No newline at end of file diff --git a/web/app/assets/javascripts/AAB_message_factory.js b/web/app/assets/javascripts/AAB_message_factory.js index a104e8e9b..48aad944a 100644 --- a/web/app/assets/javascripts/AAB_message_factory.js +++ b/web/app/assets/javascripts/AAB_message_factory.js @@ -33,6 +33,7 @@ JOIN_REQUEST_REJECTED : "JOIN_REQUEST_REJECTED", SESSION_JOIN : "SESSION_JOIN", SESSION_DEPART : "SESSION_DEPART", + TRACKS_CHANGED : "TRACKS_CHANGED", MUSICIAN_SESSION_JOIN : "MUSICIAN_SESSION_JOIN", BAND_SESSION_JOIN : "BAND_SESSION_JOIN", diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index 21d78399b..f5b4c2bbe 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -104,7 +104,7 @@ payload = message[messageType], callbacks = server.dispatchTable[message.type]; - if(message.type != context.JK.MessageType.HEARTBEAT_ACK) { + if(message.type != context.JK.MessageType.HEARTBEAT_ACK && message.type != context.JK.MessageType.PEER_MESSAGE) { logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload)); } @@ -134,7 +134,7 @@ var jsMessage = JSON.stringify(message); - if(message.type != context.JK.MessageType.HEARTBEAT) { + if(message.type != context.JK.MessageType.HEARTBEAT && message.type != context.JK.MessageType.PEER_MESSAGE) { logger.log("server.send(" + jsMessage + ")"); } if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) { @@ -163,9 +163,11 @@ * @param message the actual message */ server.sendP2PMessage = function(receiver_id, message) { - logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message); + //logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message); + console.time('sendP2PMessage'); var outgoing_msg = msg_factory.client_p2p_message(server.clientID, receiver_id, message); server.send(outgoing_msg); + console.timeEnd('sendP2PMessage'); }; context.JK.JamServer = server; diff --git a/web/app/assets/javascripts/addTrack.js b/web/app/assets/javascripts/addTrack.js index e40732a31..ba6ccd459 100644 --- a/web/app/assets/javascripts/addTrack.js +++ b/web/app/assets/javascripts/addTrack.js @@ -231,11 +231,7 @@ // TODO: repeated in configureTrack.js function _init() { // load instrument array for populating listboxes, using client_id in instrument_map as ID - context.JK.listInstruments(app, function(instruments) { - $.each(instruments, function(index, val) { - instrument_array.push({"id": context.JK.server_to_client_instrument_map[val.description].client_id, "description": val.description}); - }); - }); + instrument_array = context.JK.listInstruments(); } this.initialize = function() { diff --git a/web/app/assets/javascripts/configureTrack.js b/web/app/assets/javascripts/configureTrack.js index 6d7ea0f3a..765cda6c0 100644 --- a/web/app/assets/javascripts/configureTrack.js +++ b/web/app/assets/javascripts/configureTrack.js @@ -779,20 +779,8 @@ } function _init() { - var dialogBindings = { - 'beforeShow' : function() { - return context.JK.verifyNotRecordingForTrackChange(app); - } - }; - app.bindDialog('configure-audio', dialogBindings); - - // load instrument array for populating listboxes, using client_id in instrument_map as ID - context.JK.listInstruments(app, function(instruments) { - $.each(instruments, function(index, val) { - instrument_array.push({"id": context.JK.server_to_client_instrument_map[val.description].client_id, "description": val.description}); - }); - }); + instrument_array = context.JK.listInstruments(); originalVoiceChat = context.jamClient.TrackGetChatEnable() ? VOICE_CHAT.CHAT : VOICE_CHAT.NO_CHAT; @@ -818,11 +806,25 @@ } } - this.initialize = function() { - events(); - _init(); - myTrackCount = myTracks.length; - }; + function initialize () { + var dialogBindings = { + 'beforeShow' : function() { + return context.JK.verifyNotRecordingForTrackChange(app); + } + }; + app.bindDialog('configure-audio', dialogBindings); + + events(); + } + + // should be called before opening + function refresh() { + _init(); + myTrackCount = myTracks.length; + } + + this.initialize = initialize; + this.refresh = refresh; this.showMusicAudioPanel = showMusicAudioPanel; this.showVoiceChatPanel = showVoiceChatPanel; diff --git a/web/app/assets/javascripts/findSession.js b/web/app/assets/javascripts/findSession.js index 9c96cbe80..f602b09e4 100644 --- a/web/app/assets/javascripts/findSession.js +++ b/web/app/assets/javascripts/findSession.js @@ -83,7 +83,7 @@ var OTHER = 'div#sessions-other'; // INVITATION - logger.debug("sessionCounts[CATEGORY.INVITATION.index]=" + sessionCounts[CATEGORY.INVITATION.index]); + //logger.debug("sessionCounts[CATEGORY.INVITATION.index]=" + sessionCounts[CATEGORY.INVITATION.index]); if (sessionCounts[CATEGORY.INVITATION.index] === 0) { priorVisible = false; $(INVITATION).hide(); @@ -98,7 +98,7 @@ $(FRIEND).removeClass('mt35'); } - logger.debug("sessionCounts[CATEGORY.FRIEND.index]=" + sessionCounts[CATEGORY.FRIEND.index]); + //logger.debug("sessionCounts[CATEGORY.FRIEND.index]=" + sessionCounts[CATEGORY.FRIEND.index]); if (sessionCounts[CATEGORY.FRIEND.index] === 0) { priorVisible = false; $(FRIEND).hide(); @@ -113,7 +113,7 @@ $(OTHER).removeClass('mt35'); } - logger.debug("sessionCounts[CATEGORY.OTHER.index]=" + sessionCounts[CATEGORY.OTHER.index]); + //logger.debug("sessionCounts[CATEGORY.OTHER.index]=" + sessionCounts[CATEGORY.OTHER.index]); if (sessionCounts[CATEGORY.OTHER.index] === 0) { $(OTHER).hide(); } diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index 2d184efb6..908df7d3d 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -110,6 +110,9 @@ function heartbeatAck(header, payload) { lastHeartbeatAckTime = new Date(); + + context.JK.CurrentSessionModel.trackChanges(header, payload); + } /** diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 5faaf5bb0..fba74b681 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -30,6 +30,7 @@ var claimedRecording = null; var playbackControls = null; var promptLeave = false; + var backendMixerAlertThrottleTimer = null; var rest = context.JK.Rest(); @@ -80,26 +81,26 @@ 0: {"title": "", "message": ""}, // NO_EVENT, 1: {"title": "", "message": ""}, // BACKEND_ERROR: generic error - eg P2P message error 2: {"title": "", "message": ""}, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated - 3: {"title": "Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // PACKET_JTR, - 4: {"title": "Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here." }, // PACKET_LOSS - 5: {"title": "Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // PACKET_LATE, - 6: {"title": "Packet Queue Depth", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // JTR_QUEUE_DEPTH, - 7: {"title": "Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // NETWORK_JTR, - 8: {"title": "Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click here." }, // NETWORK_PING, + 3: {"title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // PACKET_JTR, + 4: {"title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here." }, // PACKET_LOSS + 5: {"title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // PACKET_LATE, + 6: {"title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // JTR_QUEUE_DEPTH, + 7: {"title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click here."}, // NETWORK_JTR, + 8: {"title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click here." }, // NETWORK_PING, 9: {"title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, click here."}, // BITRATE_THROTTLE_WARN, 10:{"title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, click here." }, // BANDWIDTH_LOW // IO related events - 11:{"title": "Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // INPUT_IO_RATE - 12:{"title": "Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here."}, // INPUT_IO_JTR, - 13:{"title": "Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // OUTPUT_IO_RATE - 14:{"title": "Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here."}, // OUTPUT_IO_JTR, + 11:{"title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // INPUT_IO_RATE + 12:{"title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here."}, // INPUT_IO_JTR, + 13:{"title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." }, // OUTPUT_IO_RATE + 14:{"title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here."}, // OUTPUT_IO_JTR, // CPU load related 15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, click here." }, // CPU_LOAD 16: {"title": "Decode Violations", "message": ""}, // DECODE_VIOLATIONS, 17: {"title": "", "message": ""}, // LAST_THRESHOLD - 18: {"title": "Wifi Use Alert", "message": ""}, // WIFI_NETWORK_ALERT, //user or peer is using wifi + 18: {"title": "Wifi Alert", "message": ""}, // WIFI_NETWORK_ALERT, //user or peer is using wifi 19: {"title": "No Audio Configuration", "message": "You cannot join the session because you do not have a valid audio configuration."}, // NO_VALID_AUDIO_CONFIG, 20: {"title": "Audio Device Not Present", "message": ""}, // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected 21: {"title": "", "message": ""}, // RECORD_PLAYBACK_STATE, // record/playback events have occurred @@ -123,7 +124,8 @@ //indicates problem with user computer stack or network itself (wifi, antivirus etc) 36: {"title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, click here."}, // LOCAL_NETWORK_LATENCY_HIGH, 37: {"title": "", "message": ""}, // RECORDING_CLOSE, //update and remove tracks from front-end - 38: {"title": "", "message": ""} // LAST_ALERT + 38: {"title": "No Audio Sent", "message": ""}, // PEER_REPORTS_NO_AUDIO_RECV, //update and remove tracks from front-end + 39: {"title": "", "message": ""} // LAST_ALERT }; @@ -137,10 +139,33 @@ shareDialog.initialize(context.JK.FacebookHelperInstance); } + function alertCallback(type, text) { + + function timeCallback() { + var start = new Date(); + setTimeout(function() { + var timed = new Date().getTime() - start.getTime(); + if(timed > 250) { + logger.warn("SLOW AlERT_CALLBACK. type: %o text: %o time: %o", type, text, timed); + } + }, 1); + } + + timeCallback(); + + console.log("alert callback", type, text); + if (type === 2) { // BACKEND_MIXER_CHANGE - logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text); + logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text); + if(sessionModel.id() && 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(function() { // this is a local change to our tracks. we need to tell the server about our updated track information var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); @@ -151,29 +176,17 @@ syncTrackRequest.id = sessionModel.id(); rest.putTrackSyncChange(syncTrackRequest) - .done(function() { - sessionModel.refreshCurrentSession(); - }) - .fail(function() { - 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 { - // this is wrong; we shouldn't be using the server to decide what tracks are shown - // however, we have to without a refactor, and if we wait a second, then it should be enough time - // for the client that initialed this to have made the change - // https://jamkazam.atlassian.net/browse/VRFS-854 - setTimeout(function() { - sessionModel.refreshCurrentSession(); // XXX: race condition possible here; other client may not have updated server yet - }, 3000); - var start = new Date(); - setTimeout(function() { - console.log("REBUILDMIXERCHANGE DONE: ", new Date().getTime() - start.getTime()); - }, 1) + .done(function() { + }) + .fail(function() { + 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" + }); + }) + }, 100); + } } else if (type === 19) { // NO_VALID_AUDIO_CONFIG @@ -196,6 +209,19 @@ } }); } + else if (type === 26) { + var clientId = text; + var participant = sessionModel.getParticipant(clientId); + if(participant) { + app.notify({ + "title": alert_type[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(); + } + } else if (type === 27) { // WINDOW_CLOSE_BACKGROUND_MODE // the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen promptLeave = false; @@ -557,6 +583,17 @@ return foundMixers; } + function _userMusicInputMixerForClientId(clientId) { + var found = null; + $.each(mixers, function(index, mixer) { + if (mixer.group_id === ChannelGroupIds.UserMusicInputGroup && mixer.client_id == clientId) { + found = mixer; + return false; + } + }); + return found; + } + // TODO FIXME - This needs to support multiple tracks for an individual // client id and group. function _mixerForClientId(clientId, groupIds, usedMixers) { @@ -639,17 +676,16 @@ context.JK.FaderHelpers.renderFader(faderId, faderOpts); context.JK.FaderHelpers.subscribe(faderId, l2mChanged); - // initialize to middle (50%, 0dB) per Peter's request - context.JK.FaderHelpers.setFaderValue(faderId, 50); - context.jamClient.SessionSetMasterLocalMix(0); + var value = context.jamClient.SessionGetMasterLocalMix(); + context.JK.FaderHelpers.setFaderValue(faderId, percentFromMixerValue(-80, 20, value)); } /** * This has a specialized jamClient call, so custom handler. */ function l2mChanged(faderId, newValue, dragging) { - var dbValue = context.JK.FaderHelpers.convertLinearToDb(newValue); - context.jamClient.SessionSetMasterLocalMix(dbValue); + //var dbValue = context.JK.FaderHelpers.convertLinearToDb(newValue); + context.jamClient.SessionSetMasterLocalMix(newValue - 80); } function _addVoiceChat() { @@ -688,8 +724,6 @@ var recordedTracks = sessionModel.recordedTracks(); - console.log("recorded tracks=%o local_media=%o", recordedTracks, localMediaMixers); - if(recordedTracks && localMediaMixers.length == 0) { // if we are the creator, then rather than raise an error, tell the server the recording is over. // this shoudl only happen if we get temporarily disconnected by forced reload, which isn't a very normal scenario @@ -700,7 +734,6 @@ } if(recordedTracks) { - $('.session-recording-name').text(sessionModel.getCurrentSession().claimed_recording.name); var noCorrespondingTracks = false; @@ -775,7 +808,7 @@ trackData.muteClass = muteClass; trackData.mixerId = mixer.id; - _addMediaTrack(index, trackData); + _addMediaTrack(trackData); }); if(!noCorrespondingTracks && recordedTracks.length > 0) { @@ -850,15 +883,20 @@ trackData.gainPercent = gainPercent; trackData.muteClass = muteClass; trackData.mixerId = mixer.id; + trackData.noaudio = false; context.jamClient.SessionSetUserName(participant.client_id,name); + } else { // No mixer to match, yet lookingForMixers[track.id] = participant.client_id; + trackData.noaudio = true; if (!(lookingForMixersTimer)) { + console.log("waiting for mixer to show up for track: " + track.id) lookingForMixersTimer = context.setInterval(lookForMixers, 500); } } - _addTrack(index, trackData); + var allowDelete = myTrack && index > 0; + _addTrack(allowDelete, trackData); // Show settings icons only for my tracks if (myTrack) { @@ -890,6 +928,9 @@ // Set gain position context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent); context.JK.FaderHelpers.subscribe(mixerId, faderChanged); + // associate the UserMusicInput mixer with tracks for this user + var musicInputMixer = _userMusicInputMixerForClientId(clientId); + if(musicInputMixer) $track.find('.track-connection').attr('mixer-id', musicInputMixer.id + '_connection'); } // Function called on an interval when participants change. Mixers seem to @@ -917,11 +958,25 @@ mixer.range_low, mixer.range_high, mixer.volume_left); var trackSelector = 'div.track[track-id="' + key + '"]'; connectTrackToMixer(trackSelector, key, mixer.id, gainPercent); - var $track = $('div.track[client-id="' + key + '"]'); + + + var $track = $('div.track[client-id="' + clientId + '"]'); $track.find('.track-icon-mute').attr('mixer-id', mixer.id); + + // hide overlay for all tracks associated with this client id (if one mixer is present, then all tracks are valid) + $('.disabled-track-overlay', $track).hide(); + $('.track-connection', $track).removeClass('red').addClass('grey'); // Set mute state _toggleVisualMuteControl($track.find('.track-icon-mute'), mixer.mute); } + else { + // if 1 second has gone by and still no mixer, then we gray the participant's tracks + if(lookingForMixersCount == 2) { + var $track = $('div.track[client-id="' + clientId + '"]'); + $('.disabled-track-overlay', $track).show(); + $('.track-connection', $track).removeClass('green').addClass('red'); + } + } } for (var i=0; i [ :add_like ] - before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop] + before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync] skip_before_filter :api_signed_in_user, only: [:perf_upload] respond_to :json @@ -143,10 +143,10 @@ class ApiMusicSessionsController < ApiController end def track_sync - @tracks = Track.sync(params[:client_id], params[:tracks]) + @tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks]) unless @tracks.kind_of? Array - # we have to do this because api_session_detail_url will fail with a bad @music_session + # we have to do this because api_session_detail_url will fail with a bad @tracks response.status = :unprocessable_entity respond_with @tracks else diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 95cac8103..977e90c25 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -1,6 +1,6 @@ object @music_session -attributes :id, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id +attributes :id, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter node :genres do |item| item.genres.map(&:description) @@ -20,14 +20,14 @@ end child(:connections => :participants) { collection @music_sessions, :object_root => false - attributes :ip_address, :client_id + attributes :ip_address, :client_id, :joined_session_at node :user do |connection| { :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state } end child(:tracks => :tracks) { - attributes :id, :connection_id, :instrument_id, :sound, :client_track_id + attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :updated_at } } diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 881a7fd20..a43de781f 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -166,7 +166,8 @@
-
CONNECTION
+
CONNECTION
+
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index b127e8944..3fa43842a 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -227,6 +227,35 @@ window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory); window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory)); } + else if(false) { // set to true to time long running bridge calls + var originalJamClient = window.jamClient; + var interceptedJamClient = {}; + $.each(Object.keys(originalJamClient), function(i, key) { + if(key.indexOf('(') > -1) { + // this is a method. time it + var jsKey = key.substring(0, key.indexOf('(')) + console.log("replacing " + jsKey) + interceptedJamClient[jsKey] = function() { + var original = originalJamClient[key] + var start = new Date(); + var returnVal = original.apply(originalJamClient, arguments) + var time = new Date().getTime() - start.getTime(); + if(time > 0) { + console.error(time + "ms jamClient." + jsKey); + } + + return returnVal; + } + } + else { + // we need to intercept properties... but how? + } + }); + + + window.jamClient = interceptedJamClient; + + } // Let's get things rolling... if (JK.currentUserId) { diff --git a/web/lib/music_session_manager.rb b/web/lib/music_session_manager.rb index e13fb417e..7de689c0a 100644 --- a/web/lib/music_session_manager.rb +++ b/web/lib/music_session_manager.rb @@ -105,15 +105,16 @@ MusicSessionManager < BaseManager music_session = MusicSession.find(music_session_id) - connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks) + music_session.with_lock do # VRFS-1297 + music_session.tick_track_changes + connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks) - if connection.errors.any? - # rollback the transaction to make sure nothing is disturbed in the database - raise ActiveRecord::Rollback - else - # send out notification to queue to the rest of the session - # TODO: also this isn't necessarily a user leaving; it's a client leaving' + if connection.errors.any? + # rollback the transaction to make sure nothing is disturbed in the database + raise ActiveRecord::Rollback + end end + end unless connection.errors.any? @@ -142,11 +143,25 @@ MusicSessionManager < BaseManager end recordingId = nil - ConnectionManager.new.leave_music_session(user, connection, music_session) do - recording = music_session.stop_recording # stop any ongoing recording, if there is one - recordingId = recording.id unless recording.nil? + + music_session.with_lock do # VRFS-1297 + ConnectionManager.new.leave_music_session(user, connection, music_session) do + music_session.tick_track_changes + recording = music_session.stop_recording # stop any ongoing recording, if there is one + recordingId = recording.id unless recording.nil? + end end Notification.send_session_depart(music_session, connection.client_id, user, recordingId) end + + def sync_tracks(music_session, client_id, new_tracks) + tracks = nil + music_session.with_lock do # VRFS-1297 + tracks = Track.sync(client_id, new_tracks) + music_session.tick_track_changes + end + Notification.send_tracks_changed(music_session) + tracks + end end diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index d41eb05e2..9536609a1 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -355,7 +355,10 @@ module JamWebsockets user = User.find_by_id(user_id) unless user_id.nil? recording = music_session.stop_recording unless music_session.nil? # stop any ongoing recording, if there is one recordingId = recording.id unless recording.nil? - } + music_session.with_lock do # VRFS-1297 + music_session.tick_track_changes + end if music_session + } end Notification.send_session_depart(music_session, cid, user, recordingId) unless music_session.nil? || user.nil? @@ -444,7 +447,7 @@ module JamWebsockets password = login.password if login.value_for_tag(2) token = login.token if login.value_for_tag(3) client_id = login.client_id if login.value_for_tag(4) - reconnect_music_session_id = login.client_id if login.value_for_tag(5) + reconnect_music_session_id = login.reconnect_music_session_id if login.value_for_tag(5) @log.info("*** handle_login: token=#{token}; client_id=#{client_id}") reconnected = false @@ -493,6 +496,9 @@ module JamWebsockets unless context.nil? || music_session_upon_reentry.nil? || music_session_upon_reentry.destroyed? recording = music_session_upon_reentry.stop_recording recordingId = recording.id unless recording.nil? + music_session_upon_reentry.with_lock do # VRFS-1297 + music_session_upon_reentry.tick_track_changes + end send_depart = true end else @@ -550,19 +556,24 @@ module JamWebsockets raise SessionError, 'context state is gone. please reconnect.' else connection = Connection.find_by_user_id_and_client_id(context.user.id, context.client.client_id) + track_changes_counter = nil if connection.nil? @log.warn "*** WARNING: unable to find connection due to heartbeat from client: #{context}; calling cleanup_client" cleanup_client(client) raise SessionError, 'connection state is gone. please reconnect.' else - connection.touch + Connection.transaction do + music_session = MusicSession.select(:track_changes_counter).find_by_id(connection.music_session_id) if connection.music_session_id + track_changes_counter = music_session.track_changes_counter if music_session + connection.touch + end ConnectionManager.active_record_transaction do |connection_manager| connection_manager.reconnect(connection, connection.music_session_id, nil) end if connection.stale? end - heartbeat_ack = @message_factory.heartbeat_ack() + heartbeat_ack = @message_factory.heartbeat_ack(track_changes_counter) send_to_client(client, heartbeat_ack)