diff --git a/db/manifest b/db/manifest index c75998c34..1100ddac2 100755 --- a/db/manifest +++ b/db/manifest @@ -314,4 +314,5 @@ giftcard.sql add_description_to_crash_dumps.sql acappella.sql purchasable_gift_cards.sql -versionable_jamtracks.sql \ No newline at end of file +versionable_jamtracks.sql +session_controller.sql \ No newline at end of file diff --git a/db/up/session_controller.sql b/db/up/session_controller.sql new file mode 100644 index 000000000..d146481b6 --- /dev/null +++ b/db/up/session_controller.sql @@ -0,0 +1 @@ +ALTER TABLE music_sessions ADD COLUMN session_controller_id VARCHAR(64) REFERENCES users(id); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index 352048dc0..748e50eb9 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -28,13 +28,13 @@ module JamRuby ##### TODO: refactored to notification.rb but left here for backwards compatibility w/ connection_manager_spec.rb def gather_friends(connection, user_id) - friend_ids = [] - connection.exec("SELECT f1.friend_id as friend_id FROM friendships f1 WHERE f1.user_id = $1 AND f1.friend_id IN (SELECT f2.user_id FROM friendships f2 WHERE f2.friend_id = $1)", [user_id]) do |friend_results| - friend_results.each do |friend_result| - friend_ids.push(friend_result['friend_id']) - end + friend_ids = [] + connection.exec("SELECT f1.friend_id as friend_id FROM friendships f1 WHERE f1.user_id = $1 AND f1.friend_id IN (SELECT f2.user_id FROM friendships f2 WHERE f2.friend_id = $1)", [user_id]) do |friend_results| + friend_results.each do |friend_result| + friend_ids.push(friend_result['friend_id']) end - return friend_ids + end + return friend_ids end # this simulates music_session destroy callbacks with activerecord @@ -42,7 +42,7 @@ module JamRuby music_session = ActiveMusicSession.find_by_id(music_session_id) music_session.before_destroy if music_session end - + # reclaim the existing connection, if ip_address is not nil then perhaps a new address as well def reconnect(conn, channel_id, reconnect_music_session_id, ip_address, connection_stale_time, connection_expire_time, udp_reachable, gateway) music_session_id = nil @@ -65,11 +65,19 @@ module JamRuby isp = JamIsp.lookup(addr) #puts("============= JamIsp.lookup returns #{isp.inspect} for #{addr} =============") - if isp.nil? then ispid = 0 else ispid = isp.coid end + if isp.nil? then + ispid = 0 + else + ispid = isp.coid + end block = GeoIpBlocks.lookup(addr) #puts("============= GeoIpBlocks.lookup returns #{block.inspect} for #{addr} =============") - if block.nil? then locid = 0 else locid = block.locid end + if block.nil? then + locid = 0 + else + locid = block.locid + end location = GeoIpLocations.find_by_locid(locid) if location.nil? || isp.nil? || block.nil? @@ -183,11 +191,19 @@ SQL isp = JamIsp.lookup(addr) #puts("============= JamIsp.lookup returns #{isp.inspect} for #{addr} =============") - if isp.nil? then ispid = 0 else ispid = isp.coid end + if isp.nil? then + ispid = 0 + else + ispid = isp.coid + end block = GeoIpBlocks.lookup(addr) #puts("============= GeoIpBlocks.lookup returns #{block.inspect} for #{addr} =============") - if block.nil? then locid = 0 else locid = block.locid end + if block.nil? then + locid = 0 + else + locid = block.locid + end location = GeoIpLocations.find_by_locid(locid) if location.nil? || isp.nil? || block.nil? @@ -199,11 +215,11 @@ SQL lock_connections(conn) conn.exec("INSERT INTO connections (user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, aasm_state, stale_time, expire_time, udp_reachable, gateway) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", - [user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time, udp_reachable, gateway]).clear + [user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time, udp_reachable, gateway]).clear # we just created a new connection-if this is the first time the user has shown up, we need to send out a message to his friends conn.exec("SELECT count(user_id) FROM connections WHERE user_id = $1", [user_id]) do |result| - count = result.getvalue(0, 0) .to_i + count = result.getvalue(0, 0).to_i # we're passing all this stuff so that the user record might be updated as well... blk.call(conn, count) unless blk.nil? end @@ -291,7 +307,7 @@ SQL # destroy the music_session if it's empty num_participants = nil - conn.exec("SELECT count(*) FROM connections WHERE music_session_id = $1", + conn.exec("SELECT count(*) FROM connections WHERE music_session_id = $1", [previous_music_session_id]) do |result| num_participants = result.getvalue(0, 0).to_i end @@ -324,11 +340,65 @@ SQL conn.exec("UPDATE active_music_sessions set jam_track_id = NULL, jam_track_initiator_id = NULL where jam_track_initiator_id = $1 and id = $2", [user_id, previous_music_session_id]) + + update_session_controller(previous_music_session_id) end end end + def update_session_controller(music_session_id) + active_music_session = ActiveMusicSession.find(music_session_id) + + if active_music_session + music_session = active_music_session.music_session + if music_session.session_controller_id && !active_music_session.users.exists?(music_session.session_controller) + # find next in line, because the current 'session controller' is not part of the session + next_in_line(music_session, active_music_session) + end + end + end + + # determine who should be session controller after someone leaves + def next_in_line(music_session, active_music_session) + session_users = active_music_session.users + + # check friends 1st + session_friends = music_session.creator.friends && session_users + if session_friends.length > 0 + music_session.session_controller = session_friends[0] + if music_session.save + active_music_session.tick_track_changes + Notification.send_tracks_changed(active_music_session) + return + end + end + + # check invited 2nd + invited = music_session.invited_musicians && session_users + if invited.length > 0 + music_session.session_controller = invited[0] + if music_session.save + active_music_session.tick_track_changes + Notification.send_tracks_changed(active_music_session) + return + end + end + + # go by who joined earliest + earliest = active_music_sessions.connections.order(:joined_session_at).first + + if earliest + music_session.session_controller = earliest + if music_session.save + active_music_session.tick_track_changes + Notification.send_tracks_changed(active_music_session) + return + end + end + music_session.creator + end + def join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency, video_sources=nil) connection = nil @@ -349,7 +419,10 @@ SQL if connection.errors.any? raise ActiveRecord::Rollback + else + update_session_controller(music_session.id) end + end connection @@ -383,6 +456,8 @@ SQL if result.cmd_tuples == 1 @log.debug("disassociated music_session with connection for client_id=#{client_id}, user_id=#{user_id}") + update_session_controller(music_session.id) + JamRuby::MusicSessionUserHistory.removed_music_session(user_id, music_session_id) session_checks(conn, previous_music_session_id, user_id) blk.call() unless blk.nil? diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index 1f56efc08..d2998a72b 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -36,6 +36,8 @@ module JamRuby belongs_to :active_music_session, :class_name => 'JamRuby::ActiveMusicSession', foreign_key: :music_session_id + belongs_to :session_controller, :class_name => 'JamRuby::User', :foreign_key => :session_controller_id, :inverse_of => :controlled_sessions + has_many :music_session_user_histories, :class_name => "JamRuby::MusicSessionUserHistory", :foreign_key => "music_session_id", :dependent => :delete_all has_many :comments, :class_name => "JamRuby::MusicSessionComment", :foreign_key => "music_session_id" has_many :session_info_comments, :class_name => "JamRuby::SessionInfoComment", :foreign_key => "music_session_id" @@ -116,6 +118,7 @@ module JamRuby new_session.open_rsvps = self.open_rsvps new_session.is_unstructured_rsvp = self.is_unstructured_rsvp new_session.legal_terms = true + new_session.session_controller = self.session_controller # copy rsvp_slots, rsvp_requests, and rsvp_requests_rsvp_slots RsvpSlot.find_each(:conditions => "music_session_id = '#{self.id}'") do |slot| @@ -255,6 +258,30 @@ module JamRuby end end + def set_session_controller(current_user, user) + + # only allow update of session controller by the creator or the currently marked user + + should_tick = false + + if current_user != creator && current_user != self.session_controller + return should_tick + end + + if active_music_session + if user + if active_music_session.users.exists?(user) + self.session_controller = user + should_tick = save + end + else + self.session_controller = nil + should_tick = save + end + end + should_tick + end + def self.index(current_user, user_id, band_id = nil, genre = nil) hide_private = false if current_user.id != user_id @@ -343,6 +370,7 @@ module JamRuby ms.legal_terms = true ms.open_rsvps = options[:open_rsvps] if options[:open_rsvps] ms.creator = user + ms.session_controller = user ms.create_type = options[:create_type] ms.is_unstructured_rsvp = options[:isUnstructuredRsvp] if options[:isUnstructuredRsvp] ms.scheduled_start = parse_scheduled_start(options[:start], options[:timezone]) if options[:start] && options[:timezone] diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 907cd9776..842d4beef 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -44,6 +44,8 @@ module JamRuby belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id' + has_many :controlled_sessions, :class_name=> "JamRuby::MusicSession", inverse_of: :session_controller, foreign_key: :session_controller_id + # authorizations (for facebook, etc -- omniauth) has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization" diff --git a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js index 960d44cfd..92e577ecf 100644 --- a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js +++ b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js @@ -66,6 +66,20 @@ $('#session-settings-fan-access').val('listen-chat-band'); } + var $controllerSelect = $('#session-settings-master-mix-controller') + + $controllerSelect.empty() + var sessionUsers = context.SessionStore.helper.users() + + $controllerSelect.append('') + $.each(sessionUsers, function(userId, user) { + var selected = currentSession.session_controller_id == userId ? 'selected="selected"' : '' + $controllerSelect.append('') + }) + + var canEditController = currentSession.session_controller_id == context.JK.currentUserId || context.JK.currentUserId == currentSession.user_id + $controllerSelect.easyDropDown(canEditController ? 'enable' : 'disable') + /** // notation files in the account screen. ugh. $selectedFilenames.empty(); @@ -84,10 +98,12 @@ context.JK.dropdown($('#session-settings-language')); context.JK.dropdown($('#session-settings-musician-access')); context.JK.dropdown($('#session-settings-fan-access')); + context.JK.dropdown($('#session-settings-master-mix-controller')); var easyDropDownState = canPlayWithOthers.canPlay ? 'enable' : 'disable' $('#session-settings-musician-access').easyDropDown(easyDropDownState) $('#session-settings-fan-access').easyDropDown(easyDropDownState) + } function addNotation(notation) { @@ -121,6 +137,7 @@ data.name = $('#session-settings-name').val(); data.description = $('#session-settings-description').val(); data.language = $('#session-settings-language').val(); + data.session_controller = $('#session-settings-master-mix-controller').val() // musician access var musicianAccess = $('#session-settings-musician-access').val(); @@ -148,7 +165,13 @@ data.fan_chat = true; } - rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved); + rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved) + .done(function(response) { + context.SessionActions.updateSession.trigger(response); + }) + .fail(function() { + app.notify({title: "Can't Update", text: "Unable to update session settings."}) + }) return false; } diff --git a/web/app/assets/javascripts/faderHelpers.js b/web/app/assets/javascripts/faderHelpers.js index 6d573a2c8..7cbab2f28 100644 --- a/web/app/assets/javascripts/faderHelpers.js +++ b/web/app/assets/javascripts/faderHelpers.js @@ -23,7 +23,14 @@ var $fader = $(this); var floaterConvert = $fader.data('floaterConverter') var sessionModel = window.JK.CurrentSessionModel || null; - + + /** + if(!$fader.data('has-session-control')) { + var sessionControllerName = $fader.data('session-controller-name'); + window.JK.prodBubble($fader, 'not-session-controller', {sessionControllerName:sessionControllerName}, {positions:['left', 'right'], offsetParent: $fader.closest('.top-parent'), duration:12000}) + return false; + }*/ + var mediaControlsDisabled = $fader.data('media-controls-disabled'); if(mediaControlsDisabled) { var mediaTrackOpener = $fader.data('media-track-opener'); @@ -173,7 +180,14 @@ var mediaControlsDisabled = $draggingFaderHandle.data('media-controls-disabled'); var mediaTrackOpener = $draggingFaderHandle.data('media-track-opener'); var sessionModel = window.JK.CurrentSessionModel || null; - + + /** + if(!$draggingFaderHandle.data('has-session-control')) { + var sessionControllerName = $draggingFaderHandle.data('session-controller-name'); + window.JK.prodBubble($draggingFaderHandle, 'not-session-controller', {sessionControllerName:sessionControllerName}, {positions:['left', 'right'], offsetParent: $draggingFaderHandle.closest('.top-parent'), duration:12000}) + return false; + }*/ + if(mediaControlsDisabled) { return false; } @@ -267,16 +281,33 @@ throw ("renderFader: userOptions is required"); } var renderDefaults = { - faderType: "vertical" + faderType: "vertical", + sessionController: null }; var options = $.extend({}, renderDefaults, userOptions); + var sessionCanControl = true + var sessionControllerName = null + if(userOptions.sessionController) { + if(!userOptions.sessionController.can_control) { + sessionCanControl = false + sessionControllerName = userOptions.sessionController.session_controller.name + } + } + selector.find('div[data-control="fader"]') .data('media-controls-disabled', selector.data('media-controls-disabled')) .data('media-track-opener', selector.data('media-track-opener')) .data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers')) .data('floaterConverter', floaterConverter) .data('snap', userOptions.snap) + .data('has-session-control', sessionCanControl) + .data('session-controller-name', sessionControllerName) + + + if(userOptions.sessionController) { + + } selector.find('div[data-control="fader-handle"]').draggable({ drag: onFaderDrag, @@ -289,6 +320,8 @@ .data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers')) .data('floaterConverter', floaterConverter) .data('snap', userOptions.snap) + .data('has-session-control', sessionCanControl) + .data('session-controller-name', sessionControllerName) // Embed any custom styles, applied to the .fader below selector if ("style" in options) { diff --git a/web/app/assets/javascripts/react-components/SessionMixerBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMixerBtn.js.jsx.coffee index c19e4d24f..056e1202d 100644 --- a/web/app/assets/javascripts/react-components/SessionMixerBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMixerBtn.js.jsx.coffee @@ -4,7 +4,17 @@ context = window openDialog: (e) -> e.preventDefault() - context.JK.app.layout.showDialog('session-master-mix-dialog') + + sessionController = context.SessionStore.helper.sessionController() + + # does this user have access to control the master mixer? + + if sessionController.can_control + context.JK.app.layout.showDialog('session-master-mix-dialog') + else + sessionControllerName = sessionController.session_controller?.name + $node = $(this.getDOMNode()) + window.JK.prodBubble($node, 'not-session-controller', {sessionControllerName:sessionControllerName}, {positions:['bottom'], offsetParent: $node.closest('.top-parent'), duration:12000}) render: () -> ` diff --git a/web/app/assets/javascripts/react-components/SessionTrackGain.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackGain.js.jsx.coffee index c4d1c6123..000d176c7 100644 --- a/web/app/assets/javascripts/react-components/SessionTrackGain.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionTrackGain.js.jsx.coffee @@ -2,6 +2,7 @@ context = window logger = context.JK.logger ChannelGroupIds = context.JK.ChannelGroupIds CategoryGroupIds = context.JK.CategoryGroupIds +MIX_MODES = context.JK.MIX_MODES @SessionTrackGain = React.createClass({ @@ -20,6 +21,8 @@ CategoryGroupIds = context.JK.CategoryGroupIds groupId = $target.data('groupId') mixers = @state.mixers.mixer + + # if this is a media track, jam track , or media category, affect volume of both mixer and opposing mixer if @state.mixers.mixer.group_id == ChannelGroupIds.MediaTrackGroup || @state.mixers.mixer.group_id == ChannelGroupIds.JamTrackGroup || ((@state.mixers.mixer.group_id == ChannelGroupIds.MonitorCatGroup || @state.mixers.mixer.group_id == ChannelGroupIds.MasterCatGroup) && @state.mixers.mixer.name == CategoryGroupIds.MediaTrack) MixerActions.faderChanged(data, [@state.mixers.mixer, @state.mixers.oppositeMixer], @props.gainType) @@ -47,13 +50,18 @@ CategoryGroupIds = context.JK.CategoryGroupIds if !$root.is('.track-gain') logger.error("unknown root node") - context.JK.FaderHelpers.renderFader2($root, {faderType: 'vertical'}); - # Initialize gain position mixer = @state.mixers?.mixer if mixer && $.isArray(mixer) mixer = mixer[0] + sessionController = null + if mixer.mode == MIX_MODES.MASTER + # sessionController is only relevant for master mode + sessionController = this.props.sessionController + + context.JK.FaderHelpers.renderFader2($root, {faderType: 'vertical', sessionController:sessionController}); + MixerActions.initGain(mixer) # watch for fader change events diff --git a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee index fe0eba212..8d41f2017 100644 --- a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee @@ -24,10 +24,13 @@ ptrCount = 0 newMixers = mixers.refreshMixer(@state.mixers) newMixers = {} unless newMixers? - this.setState({mixers: newMixers}) + sessionController = sessionMixers.session.sessionController() + logger.debug("session controller", sessionController) + + this.setState({mixers: newMixers, sessionController: sessionController}) getInitialState: () -> - {mixers: this.props.mixers, ptr: "STVH#{ptrCount++}" } + {mixers: this.props.mixers, ptr: "STVH#{ptrCount++}", sessionController: window.SessionStore.helper.sessionController()} handleMute: (e) -> e.preventDefault() @@ -85,7 +88,7 @@ ptrCount = 0
Volume
{volume_left}dB
- +
diff --git a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee index 460aa98e8..50a129972 100644 --- a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee +++ b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee @@ -18,6 +18,14 @@ context = window else [] + users: () -> + found = {} + + for participant in @participants() + found[participant.user.id] = participant.user + + found + otherParticipants: () -> others = [] for participant in @participants() @@ -26,6 +34,26 @@ context = window others.push(participant) unless myTrack others + sessionController: () -> + info = {} + + # XXX testing: + info["can_control"] = false + info["session_controller"] = @participants()[0].user + + if @session + if @session.session_controller_id == null + info['session_controller'] = null + info['can_control'] = true + else + for participant in @participants() + if participant.user.id == @session.session_controller_id + info['session_controller'] = participant.user + info['can_control'] = participant.user.id == context.JK.currentUserId + break + + info + # if any participant has the metronome open, then we say this session has the metronome open isMetronomeOpen: () -> @session? && @session.metronome_active diff --git a/web/app/assets/stylesheets/client/help.css.scss b/web/app/assets/stylesheets/client/help.css.scss index 7082d7d27..2e7ca51d6 100644 --- a/web/app/assets/stylesheets/client/help.css.scss +++ b/web/app/assets/stylesheets/client/help.css.scss @@ -60,6 +60,11 @@ body.jam, body.web, .dialog{ } } + .larger-text { + margin:10px; + font-size:12px; + line-height:125%; + } diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index c7433ea5a..9fcb0bf7b 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -171,7 +171,7 @@ class ApiMusicSessionsController < ApiController end def update - @music_session = MusicSessionManager.new.update( + @music_session = MusicSessionManager.new.update(current_user, @music_session.music_session, params[:name], params[:description], @@ -180,14 +180,16 @@ class ApiMusicSessionsController < ApiController params[:musician_access], params[:approval_required], params[:fan_chat], - params[:fan_access]) + params[:fan_access], + params[:session_controller]) if @music_session.errors.any? # we have to do this because api_session_detail_url will fail with a bad @music_session response.status = :unprocessable_entity respond_with @music_session else - respond_with @music_session, responder: ApiResponder, :location => api_session_detail_url(@music_session) + @music_session = @music_session.active_music_session + respond_with_model(@music_session) end end @@ -573,7 +575,6 @@ class ApiMusicSessionsController < ApiController end end - def jam_track_open unless @music_session.users.exists?(current_user) raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR diff --git a/web/app/views/api_music_sessions/session_controller.rabl b/web/app/views/api_music_sessions/session_controller.rabl new file mode 100644 index 000000000..e34b6943d --- /dev/null +++ b/web/app/views/api_music_sessions/session_controller.rabl @@ -0,0 +1,3 @@ +object @music_session + +extends "api_music_sessions/show" \ No newline at end of file diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 39a71810c..61067a716 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -37,12 +37,17 @@ else end end - node :share_url do |music_session| + node do |music_session| + session_props = {} unless music_session.music_session.share_token.nil? - share_token_url(music_session.music_session.share_token.token) + session_props[:share_url] = share_token_url(music_session.music_session.share_token.token) end + + session_props[:session_controller_id] = music_session.music_session.session_controller_id + session_props end + child(:connections => :participants) { collection @music_sessions, :object_root => false attributes :ip_address, :client_id, :joined_session_at, :audio_latency, :id, :metronome_open diff --git a/web/app/views/api_music_sessions/update.rabl b/web/app/views/api_music_sessions/update.rabl new file mode 100644 index 000000000..e34b6943d --- /dev/null +++ b/web/app/views/api_music_sessions/update.rabl @@ -0,0 +1,3 @@ +object @music_session + +extends "api_music_sessions/show" \ No newline at end of file diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index ba3dc8ceb..d5bef7834 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -222,6 +222,16 @@ script type="text/template" id="template-help-media-controls-disabled" | Only the person who opened the recording can control the volume levels. | {% } %} + +script type="text/template" id="template-help-not-session-controller" + .not-session-controller.larger-text + | This feature controls the master mix for the session, which is used to set the mix levels for recordings and session broadcasts, + |  so changes to the master mix affect all musicians in the session. + |  Only {{data.sessionControllerName}} has control of the master mix for this session. + br + br + | If you want to change the personal mix – i.e. the mix/levels that you personally hear – you can use the volume sliders on each track on the session screen to do this, and it won’t affect what other musicians in the session hear, so you can do this safely. + script type="text/template" id="template-help-jamtrack-controls-disabled" | During a recording, volume and mute controls for JamTracks are disabled. So, get the session volume levels right before starting the recording. diff --git a/web/app/views/clients/_sessionSettings.html.haml b/web/app/views/clients/_sessionSettings.html.haml index abd85514e..7aeaf5dd0 100644 --- a/web/app/views/clients/_sessionSettings.html.haml +++ b/web/app/views/clients/_sessionSettings.html.haml @@ -35,6 +35,11 @@ %option{:value => "#{language[:id]}"} = language[:label] + .clearall.left.w20.ib.mb10 + Mix Controller: + .right.w75.ib.mb10 + %select{:name => "master_mix_controller", :id => "session-settings-master-mix-controller"} + .clearall.left.w20.ib.mb10 Musician Access: .right.w75.ib.mb10 diff --git a/web/config/routes.rb b/web/config/routes.rb index cb96586fe..48755c292 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -227,6 +227,7 @@ SampleApp::Application.routes.draw do match '/sessions/:id/backing_tracks/close' => 'api_music_sessions#backing_track_close', :via => :post match '/sessions/:id/metronome/open' => 'api_music_sessions#metronome_open', :via => :post match '/sessions/:id/metronome/close' => 'api_music_sessions#metronome_close', :via => :post + match '/sessions/:id/session_controller' => 'api_music_sessions#session_controller', :via => :post # music session tracks match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post diff --git a/web/lib/music_session_manager.rb b/web/lib/music_session_manager.rb index 792fe1200..81b52db49 100644 --- a/web/lib/music_session_manager.rb +++ b/web/lib/music_session_manager.rb @@ -67,7 +67,7 @@ class MusicSessionManager < BaseManager end # Update the session. If a field is left out (meaning, it's set to nil), it's not updated. - def update(music_session, name, description, genre, language, musician_access, approval_required, fan_chat, fan_access) + def update(current_user, music_session, name, description, genre, language, musician_access, approval_required, fan_chat, fan_access, session_controller_id) music_session.name = name unless name.nil? music_session.description = description unless description.nil? @@ -77,7 +77,15 @@ class MusicSessionManager < BaseManager music_session.approval_required = approval_required unless approval_required.nil? music_session.fan_chat = fan_chat unless fan_chat.nil? music_session.fan_access = fan_access unless fan_access.nil? + session_controller = User.find(session_controller_id) if session_controller_id.present? + should_tick = music_session.set_session_controller(current_user, session_controller) music_session.save + + if should_tick && music_session.active_music_session + music_session.active_music_session.tick_track_changes + Notification.send_tracks_changed(music_session.active_music_session) + end + music_session end