From 1dce9842470fcfdd3b6ad1a2798c710fd4dd0de5 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 22 Sep 2015 15:25:48 -0500 Subject: [PATCH] * VRFS-3519 - 2-player sessions working with custom mix --- .../MediaControls.js.jsx.coffee | 2 +- .../PopupMediaControls.js.jsx.coffee | 451 +++++++++--------- .../helpers/MixerHelper.js.coffee | 9 +- websocket-gateway/bin/websocket_gateway | 2 + .../lib/jam_websockets/router.rb | 33 +- .../lib/jam_websockets/server.rb | 40 +- 6 files changed, 310 insertions(+), 227 deletions(-) diff --git a/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee index fed32bea6..e4a1d9d42 100644 --- a/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee @@ -72,7 +72,7 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged')) monitorControls: (controls, mediaSummary) -> if mediaSummary.mediaOpen || mediaSummary.jamTrack? - if mediaSummary.jamTrack? + if mediaSummary.jamTrackOpen? controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK) else if mediaSummary.backingTrackOpen controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE) diff --git a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee index a15428170..a475ffecd 100644 --- a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee @@ -49,6 +49,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged')) metronome: mixers.metronome recordingName: mixers.recordingName() jamTrackName: mixers.jamTrackName() + jamTrackMixdown: session.jamTrackMixdown() @setState(media: state, downloadingJamTrack: session.downloadingJamTrack) @@ -76,7 +77,8 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged')) creatingMixdown: false, createMixdownErrors: null, editingMixdownId: null, - downloadingJamTrack: @props.downloadingJamTrack + downloadingJamTrack: @props.downloadingJamTrack, + jamTrackMixdown: {} } close: () -> @@ -95,234 +97,255 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged')) mediaName = @state.media.recordedTracks[0].recordingName closeLinkText = 'close recording' header = `

{mediaType}: {mediaName}

` - else if @state.jamTrackState.jamTrack? - jamTrack = @state.jamTrackState.jamTrack - mediaType = "JamTrack" - mediaName = jamTrack.name - closeLinkText = 'CLOSE JAMTRACK' + else if @state.media.mediaSummary.jamTrackOpen || @state.jamTrackState.jamTrack? + if @state.media.mediaSummary.isOpener || @state.jamTrackState.jamTrack? + # if you opened the JamTrack, then you get all the good info + jamTrack = @state.jamTrackState.jamTrack + mediaType = "JamTrack" + mediaName = jamTrack.name + closeLinkText = 'CLOSE JAMTRACK' + + selectedMixdown = jamTrack.activeMixdown - selectedMixdown = jamTrack.activeMixdown + if selectedMixdown? + jamTrackTypeHeader = 'Custom Mix' + disabled = true + if selectedMixdown.client_state? + switch selectedMixdown.client_state + when 'cant_open' + customMixName = `
{selectedMixdown.name}
` + when 'keying_timeout' + customMixName = `
{selectedMixdown.name}
` + when 'download_fail' + customMixName = `
{selectedMixdown.name}
` + when 'keying' + customMixName = `
Loading selected mix...
` + when 'downloading' + customMixName = `
Loading selected mix...
` + when 'ready' + customMixName = `
{selectedMixdown.name}
` + disabled = false + else + customMixName = `
Creating mixdown...
` - if selectedMixdown? - jamTrackTypeHeader = 'Custom Mix' - - disabled = true - if selectedMixdown.client_state? - switch selectedMixdown.client_state - when 'cant_open' - customMixName = `
{selectedMixdown.name}
` - when 'keying_timeout' - customMixName = `
{selectedMixdown.name}
` - when 'download_fail' - customMixName = `
{selectedMixdown.name}
` - when 'keying' - customMixName = `
Loading selected mix...
` - when 'downloading' - customMixName = `
Loading selected mix...
` - when 'ready' - customMixName = `
{selectedMixdown.name}
` - disabled = false else - customMixName = `
Creating mixdown...
` + if SessionStore.downloadingJamTrack + downloader = `` - else - if SessionStore.downloadingJamTrack - downloader = `` + jamTrackTypeHeader = `Full JamTrack {downloader}` - jamTrackTypeHeader = `Full JamTrack {downloader}` + header = ` +
+

{mediaType}: {mediaName}

+

{jamTrackTypeHeader}

+ {customMixName} +
` - header = ` -
-

{mediaType}: {mediaName}

-

{jamTrackTypeHeader}

- {customMixName} -
` + myMixes = null + if @state.showMyMixes + myMixdowns = [] - myMixes = null - if @state.showMyMixes - myMixdowns = [] + boundPlayClick = this.jamTrackPlay.bind(this, jamTrack); - boundPlayClick = this.jamTrackPlay.bind(this, jamTrack); - - active = jamTrack.last_mixdown_id == null - - myMixdowns.push ` -
-
- Full JamTrack -
-
- -
-
` - - for mixdown in jamTrack.mixdowns - boundPlayClick = this.mixdownPlay.bind(this, mixdown); - boundEditClick = this.mixdownEdit.bind(this, mixdown); - boundSaveClick = this.mixdownSave.bind(this, mixdown); - boundDeleteClick = this.mixdownDelete.bind(this, mixdown); - boundErrorClick = this.mixdownError.bind(this, mixdown); - boundEditKeydown = this.onEditKeydown.bind(this, mixdown); - - mixdown_package = mixdown.myPackage - - active = mixdown.id == jamTrack.last_mixdown_id - - editing = mixdown.id == @state.editingMixdownId - - # if there is a package, check it's state; otherwise let the user enqueue it - if mixdown_package - switch mixdown_package.signing_state - when 'QUIET_TIMEOUT' - action = `` - when 'QUIET' - action = `` - when 'QUEUED' - action = `` - when 'QUEUED_TIMEOUT' - action = `` - when 'SIGNING' - action = `` - when 'SIGNING_TIMEOUT' - action = `` - when 'SIGNED' - action = `` - when 'ERROR' - action = `` - else - action = `` - - if editing - mixdownName = `` - editIcon = `` - else - mixdownName = mixdown.name - editIcon = `` + active = jamTrack.last_mixdown_id == null myMixdowns.push ` -
-
- {mixdownName} -
-
- {action} +
+
+ Full JamTrack +
+
+ +
+
` - {editIcon} + for mixdown in jamTrack.mixdowns + boundPlayClick = this.mixdownPlay.bind(this, mixdown); + boundEditClick = this.mixdownEdit.bind(this, mixdown); + boundSaveClick = this.mixdownSave.bind(this, mixdown); + boundDeleteClick = this.mixdownDelete.bind(this, mixdown); + boundErrorClick = this.mixdownError.bind(this, mixdown); + boundEditKeydown = this.onEditKeydown.bind(this, mixdown); - -
+ mixdown_package = mixdown.myPackage + + active = mixdown.id == jamTrack.last_mixdown_id + + editing = mixdown.id == @state.editingMixdownId + + # if there is a package, check it's state; otherwise let the user enqueue it + if mixdown_package + switch mixdown_package.signing_state + when 'QUIET_TIMEOUT' + action = `` + when 'QUIET' + action = `` + when 'QUEUED' + action = `` + when 'QUEUED_TIMEOUT' + action = `` + when 'SIGNING' + action = `` + when 'SIGNING_TIMEOUT' + action = `` + when 'SIGNED' + action = `` + when 'ERROR' + action = `` + else + action = `` + + if editing + mixdownName = `` + editIcon = `` + else + mixdownName = mixdown.name + editIcon = `` + + myMixdowns.push ` +
+
+ {mixdownName} +
+
+ {action} + + {editIcon} + + +
+
` + + myMixes = `
{myMixdowns}
` + + mixControls = null + if @state.showCustomMixes + + nameClassData = {field: true} + if @state.createMixdownErrors? + + errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'}) + + createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown}) + mixControls = ` +
+

Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.

+
+ + +
+
+ + +
+
+ + +
+
+ CREATE MIX + {errorHtml} +
+
+ +
` + + if @state.showMyMixes + showMyMixesText = `hide my mixes
` + else + showMyMixesText = `show my mixes
` + + if @state.showCustomMixes + showMixControlsText = `hide mix controls
` + else + showMixControlsText = `show mix controls
` + + + extraControls = ` +
+

My Mixes {showMyMixesText}

+ + {myMixes} + +

Create Custom Mix {showMixControlsText}

+ + {mixControls} + +
` + else + + mediaType = "JamTrack" + mediaName = @state.media.jamTrackName + closeLinkText = 'CLOSE JAMTRACK' + + # implies we have a mixdown + if @state.media.jamTrackMixdown.id? + jamTrackTypeHeader = 'Custom Mix' + else + jamTrackTypeHeader = 'Full JamTrack' + + header = ` +
+

{mediaType}: {mediaName}

+

{jamTrackTypeHeader}

+ {customMixName}
` - myMixes = `
{myMixdowns}
` - mixControls = null - if @state.showCustomMixes - - nameClassData = {field: true} - if @state.createMixdownErrors? - - errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'}) - - createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown}) - mixControls = ` -
-

Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.

-
- - -
-
- - -
-
- - -
-
- CREATE MIX - {errorHtml} -
-
- -
` - - if @state.showMyMixes - showMyMixesText = `hide my mixes
` - else - showMyMixesText = `show my mixes
` - - if @state.showCustomMixes - showMixControlsText = `hide mix controls
` - else - showMixControlsText = `show mix controls
` - - - extraControls = ` -
-

My Mixes {showMyMixesText}

- - {myMixes} - -

Create Custom Mix {showMixControlsText}

- - {mixControls} - -
` else if @state.media.mediaSummary.backingTrackOpen mediaType = "Audio File" @@ -444,14 +467,14 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged')) time = enqueued.queue_time if time == 0 - alert("It will take approximately 1 minute to create your custom mix.") + alert("Your custom mix will take about 1 minute to be created.") else guess = Math.ceil(time / 60.0) if guess == 1 msg = '1 minute' else msg = "#{guess} minutes" - alert("Your custom mix will take approximately #{msg} to be created.") + alert("Your custom mix will take about #{msg} to be created.") createMix: (e) -> diff --git a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee index c1fe26514..ddd8153a8 100644 --- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee @@ -69,6 +69,7 @@ MIX_MODES = context.JK.MIX_MODES; backingTracks = @session.backingTracks() recordedJamTracks = @session.recordedJamTracks() jamTracks = @session.jamTracks() + jamTrackMixdown = @session.jamTrackMixdown() ### with mixer info, we use these to decide what kind of tracks are open in the backend @@ -92,6 +93,7 @@ MIX_MODES = context.JK.MIX_MODES; @metronomeTrackMixers = [] @adhocTrackMixers = [] + groupByType = (mixers, isLocalMixer) => for mixer in mixers mediaType = mixer.media_type @@ -106,7 +108,10 @@ MIX_MODES = context.JK.MIX_MODES; isJamTrack = false; - if jamTracks + if mixer.id == jamTrackMixdown.id + isJamTrack = true; + + if !isJamTrack && jamTracks # check if the ID matches that of an open jam track for jamTrack in jamTracks if mixer.id == jamTrack.id @@ -186,6 +191,8 @@ MIX_MODES = context.JK.MIX_MODES; backingTrackOpen: @backingTracks.length > 0 metronomeOpen: @session.isMetronomeOpen() + + # figure out if any media is open mediaOpenSummary = false for mediaType, mediaOpen of @mediaSummary diff --git a/websocket-gateway/bin/websocket_gateway b/websocket-gateway/bin/websocket_gateway index 456592701..0045cd96a 100755 --- a/websocket-gateway/bin/websocket_gateway +++ b/websocket-gateway/bin/websocket_gateway @@ -22,6 +22,8 @@ if jam_instance == 0 exit 1 end +#Resque.redis = "localhost:6380" + # now bring in the Jam code require 'jam_websockets' diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index 4d79c3647..223685460 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -147,6 +147,7 @@ module JamWebsockets # subscribe for any messages to users @user_topic.subscribe(:ack => false) do |headers, msg| + time_it('user_topic') { begin routing_key = headers.routing_key user_id = routing_key["user.".length..-1] @@ -175,6 +176,7 @@ module JamWebsockets @log.error "unhandled error in messaging to client" @log.error e end + } end MQRouter.user_exchange = @users_exchange @@ -189,6 +191,7 @@ module JamWebsockets # subscribe for any p2p messages to a client @client_topic.subscribe(:ack => false) do |headers, msg| + time_it('p2p_topic') { begin routing_key = headers.routing_key client_id = routing_key["client.".length..-1] @@ -240,6 +243,7 @@ module JamWebsockets @log.error "unhandled error in messaging to client" @log.error e end + } end MQRouter.client_exchange = @clients_exchange @@ -252,6 +256,7 @@ module JamWebsockets # subscribe for any p2p messages to a client @subscription_topic.subscribe(:ack => false) do |headers, msg| + time_it('subscribe_topic') { begin routing_key = headers.routing_key type_and_id = routing_key["subscription.".length..-1] @@ -276,6 +281,7 @@ module JamWebsockets @log.error "unhandled error in messaging to client for mount" @log.error e end + } end MQRouter.subscription_exchange = @subscriptions_exchange @@ -397,8 +403,10 @@ module JamWebsockets } client.onclose { - @log.debug "connection closed. marking stale: #{client.context}" - cleanup_client(client) + time_it('ws_close') { + @log.debug "connection closed. marking stale: #{client.context}" + cleanup_client(client) + } } client.onerror { |error| @@ -478,6 +486,7 @@ module JamWebsockets end def route(client_msg, client) + time_it('route') { message_type = @message_factory.get_message_type(client_msg) if message_type.nil? Diagnostic.unknown_message_type(client.user_id, client_msg) @@ -516,7 +525,7 @@ module JamWebsockets else raise SessionError, "client_msg.route_to is unknown type: #{client_msg.route_to}" end - + } end def handle_server_directed(client_msg, client) @@ -529,11 +538,11 @@ module JamWebsockets elsif client_msg.type == ClientMessage::Type::HEARTBEAT sane_logging { handle_heartbeat(client_msg.heartbeat, client_msg.message_id, client) } elsif client_msg.type == ClientMessage::Type::SUBSCRIBE_BULK - sane_logging { handle_bulk_subscribe(client_msg.subscribe_bulk, client) } + time_it('subscribe_bulk') { sane_logging { handle_bulk_subscribe(client_msg.subscribe_bulk, client) } } elsif client_msg.type == ClientMessage::Type::SUBSCRIBE - sane_logging { handle_subscribe(client_msg.subscribe, client) } + time_it('subscribe') { sane_logging { handle_subscribe(client_msg.subscribe, client) } } elsif client_msg.type == ClientMessage::Type::UNSUBSCRIBE - sane_logging { handle_unsubscribe(client_msg.unsubscribe, client) } + time_it('unsubscribe') { sane_logging { handle_unsubscribe(client_msg.unsubscribe, client) } } else raise SessionError, "unknown message type '#{client_msg.type}' for #{client_msg.route_to}-directed message" end @@ -1198,6 +1207,7 @@ module JamWebsockets # removes all resources associated with a client def cleanup_client(client) + time_it('cleanup_client') { client.close # unregister any subscriptions @@ -1225,6 +1235,7 @@ module JamWebsockets end end end + } end def stats_logged_in @@ -1245,6 +1256,16 @@ module JamWebsockets private + def time_it(cat, &blk) + start = Time.now + + blk.call + + time = Time.now - start + + @log.warn("LONG TIME: #{cat}: #{time}") if time > 1 + end + def sane_logging(&blk) # used around repeated transactions that cause too much ActiveRecord::Base logging begin diff --git a/websocket-gateway/lib/jam_websockets/server.rb b/websocket-gateway/lib/jam_websockets/server.rb index fd45f7c42..42cc42038 100644 --- a/websocket-gateway/lib/jam_websockets/server.rb +++ b/websocket-gateway/lib/jam_websockets/server.rb @@ -10,6 +10,8 @@ module JamWebsockets @count=0 @router = Router.new @ar_base_logger = ::Logging::Repository.instance[ActiveRecord::Base] + + @last_conn_check = nil end def run(options={}) @@ -26,6 +28,7 @@ module JamWebsockets rabbitmq_port = options[:rabbitmq_port].to_i allow_dynamic_registration = options[:allow_dynamic_registration].nil? ? true : options[:allow_dynamic_registration] + Stats::init(options) calling_thread = options[:calling_thread] @@ -61,6 +64,18 @@ module JamWebsockets EventMachine::stop_event_loop end + + def check_for_em_drift(timer) + # if our timer check is a full second off, say what's up + if Time.now - @last_conn_check > timer + 1 + @log.error("significant drift! Should be 2 seconds. Instead was: #{Time.now - @last_conn_check}") + end + + @last_conn_check = Time.now + end + + + def start_websocket_listener(listen_ip, port, trust_port, trust_check, emwebsocket_debug) EventMachine::WebSocket.run(:host => listen_ip, :port => port, :debug => emwebsocket_debug) do |ws| #@log.info "new client #{ws}" @@ -84,8 +99,11 @@ module JamWebsockets # one cleanup on startup @router.periodical_check_connections - EventMachine::PeriodicTimer.new(2) do - safety_net { sane_logging { @router.periodical_check_connections } } + @last_conn_check = Time.now + timer = 2 + EventMachine::PeriodicTimer.new(timer) do + check_for_em_drift(timer) + time_it('conn_expire') { safety_net { sane_logging { @router.periodical_check_connections } } } end end @@ -94,7 +112,7 @@ module JamWebsockets @router.periodical_check_clients EventMachine::PeriodicTimer.new(30) do - safety_net { sane_logging { @router.periodical_check_clients } } + time_it('client_expire') { safety_net { sane_logging { @router.periodical_check_clients } } } end end @@ -103,13 +121,13 @@ module JamWebsockets @router.periodical_flag_connections EventMachine::PeriodicTimer.new(2) do - safety_net { sane_logging { @router.periodical_flag_connections } } + time_it('conn_flagger') { safety_net { sane_logging { @router.periodical_flag_connections } } } end end def start_stats_dump EventMachine::PeriodicTimer.new(60) do - safety_net { @router.periodical_stats_dump } + time_it('stats_dump') { safety_net { @router.periodical_stats_dump } } end end @@ -126,6 +144,18 @@ module JamWebsockets @log.error("unhandled exception in EM Timer #{e}") end end + + def time_it(cat, &blk) + start = Time.now + + blk.call + + time = Time.now - start + + @log.warn("LONG TIME #{cat}: #{time}") if time > 1 + end + + def sane_logging(&blk) # used around repeated transactions that cause too much ActiveRecord::Base logging # example is handling heartbeats