diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 0e6e05e99..05b1f62f4 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -40,7 +40,8 @@ FILE_MANAGER_CMD_STOP : 'file_manager_cmd_stop', FILE_MANAGER_CMD_PROGRESS : 'file_manager_cmd_progress', FILE_MANAGER_CMD_ASAP_UPDATE : 'file_manager_cmd_asap_update', - MIXER_MODE_CHANGED : 'mixer_mode_changed' + MIXER_MODE_CHANGED : 'mixer_mode_changed', + MUTE_SELECTED: 'mute_selected' }; context.JK.ALERT_NAMES = { diff --git a/web/app/assets/javascripts/jquery.muteSelector.js b/web/app/assets/javascripts/jquery.muteSelector.js new file mode 100644 index 000000000..8020b8283 --- /dev/null +++ b/web/app/assets/javascripts/jquery.muteSelector.js @@ -0,0 +1,71 @@ +(function(context, $) { + + "use strict"; + + context.JK = context.JK || {}; + + + // creates an iconic/graphical instrument selector. useful when there is minimal real-estate + + $.fn.muteSelector = function(options) { + + return this.each(function(index) { + + function close() { + $parent.btOff(); + $parent.focus(); + } + + var $parent = $(this); + + function onMuteOptionSelected() { + var $li = $(this); + var muteOption = $li.attr('data-mute-option'); + + close(); + $parent.triggerHandler(context.JK.EVENTS.MUTE_SELECTED, {muteOption: muteOption}); + return false; + }; + + // if the user goes into the bubble, remove + function waitForBubbleHover($bubble) { + $bubble.hoverIntent({ + over: function() { + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + }, + out: function() { + $parent.btOff(); + }}); + } + + var timeout = null; + + context.JK.hoverBubble($parent, $('#template-mute-select').html(), { + trigger:'none', + cssClass: 'mute-selector-popup', + spikeGirth:0, + spikeLength:0, + width:220, + closeWhenOthersOpen: true, + offsetParent: $parent.offsetParent(), + positions:['bottom'], + preShow: function() { + }, + postShow:function(container) { + $(container).find('li').click(onMuteOptionSelected) + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + waitForBubbleHover($(container)) + timeout = setTimeout(function() {$parent.btOff()}, 3000) + } + }); + }); + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 8c6e81a00..54efbf840 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -496,7 +496,8 @@ var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)}); mixers = holder.mixers; - console.log("mixers", mixers) + //console.log("mixers", mixers) + // grab the first mixer, and check the mode var newMixerMode;; @@ -814,6 +815,30 @@ } } + + function trackMuteSelected(e, data) { + var muteOption = data.muteOption; // muteOption is going to be either 'master' or 'personal'. We mute the correct one, based on track info + + var $muteControl = $(this); + + // mixer is the mixer object returned from the backend corresponding to the mixer in this particular mode + // oppositeMixer is the mixer correspond to the opposite mode. + // Note that oppositeMixer is not ever set for ChannelGroupIds.AudioInputMusicGroup or ChannelGroupIds.MediaTrackGroup + + var mixer = $muteControl.data('mixer') + var oppositeMixer = $muteControl.data('opposite-mixer') + + if(mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || mixer.group_id == ChannelGroupIds.MediaTrackGroup) { + context.jamClient.SessionSetControlState(mixer.id, sessionModel.isMasterMixMode()); + context.jamClient.SessionSetControlState(mixer.id, !sessionModel.isMasterMixMode()); + } + else if(mixer.group_id == ChannelGroupIds.UserMusicInputGroup || mixer.group_id == ChannelGroupIds.PeerAudioInputMusicGroup) { + context.jamClient.SessionSetControlState(mixer.id, sessionModel.isMasterMixMode()); + context.jamClient.SessionSetControlState(oppositeMixer.id, !sessionModel.isMasterMixMode()); + } + + _toggleVisualMuteControl($control, true); + } function _renderTracks() { myTracks = []; @@ -911,7 +936,7 @@ } var allowDelete = myTrack && index > 0; - _addTrack(allowDelete, trackData); + _addTrack(allowDelete, trackData, mixer); // Show settings icons only for my tracks if (myTrack) { @@ -991,12 +1016,15 @@ connectTrackToMixer(trackSelector, key, mixer.id, gainPercent, mixer.group_id); var $track = $('div.track[client-id="' + clientId + '"]'); - $track.find('.track-icon-mute').attr('mixer-id', mixer.id).attr('opposite-mixer-id', oppositeMixer.id) + var $trackIconMute = $track.find('.track-icon-mute') + $trackIconMute.attr('mixer-id', mixer.id).attr('opposite-mixer-id', oppositeMixer.id).data('mixer', mixer).data('opposite-mixer', oppositeMixer) + $trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected) + // 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 yellow green').addClass('grey'); // Set mute state - _toggleVisualMuteControl($track.find('.track-icon-mute'), mixer, oppositeMixer); + _toggleVisualMuteControl($trackIconMute, mixer, oppositeMixer); } else { // if 1 second has gone by and still no mixer, then we gray the participant's tracks @@ -1051,7 +1079,7 @@ } } - function _addTrack(allowDelete, trackData) { + function _addTrack(allowDelete, trackData, mixer) { var parentSelector = '#session-mytracks-container'; var $destination = $(parentSelector); @@ -1063,6 +1091,10 @@ var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); var audioOverlay = $('.disabled-track-overlay', newTrack); + var $trackIconMute = newTrack.find('.track-icon-mute') + $trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected) + $trackIconMute.data('mixer', mixer) + audioOverlay.hide(); // always start with overlay hidden, and only show if no audio persists $destination.append(newTrack); @@ -1234,14 +1266,40 @@ context.jamClient.SessionSetControlState(mixerId); } + function showMuteDropdowns($control) { + $control.btOn(); + } + function toggleMute(evt) { var $control = $(evt.currentTarget); var muting = ($control.hasClass('enabled')); var mixerIds = $control.attr('mixer-id').split(','); - $.each(mixerIds, function(i,v) { + + // track icons have a special mute behavior + if($control.is('.track-icon-mute')) { + $.each(mixerIds, function(i,v) { + if(muting) { + // show insta-dropdown providing two options for mute + showMuteDropdowns($control); + } + else { + _toggleAudioMute(v, muting); + } + }); + + if(!muting) { + _toggleVisualMuteControl($control, muting); + } + + } + else { + $.each(mixerIds, function(i,v) { _toggleAudioMute(v, muting); - }); - _toggleVisualMuteControl($control, muting); + }); + _toggleVisualMuteControl($control, muting); + } + + } function fillTrackVolumeObject(mixerId, broadcast) { diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 75d262cbe..446a19fbd 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -45,6 +45,7 @@ // single include to pull in all dialogs *= require dialogs/dialog *= require ./iconInstrumentSelect + *= require ./muteSelect *= require ./terms *= require ./createSession *= require ./feed diff --git a/web/app/assets/stylesheets/client/muteSelect.css.scss b/web/app/assets/stylesheets/client/muteSelect.css.scss new file mode 100644 index 000000000..bd73ce571 --- /dev/null +++ b/web/app/assets/stylesheets/client/muteSelect.css.scss @@ -0,0 +1,22 @@ +@import "client/common"; + +.mute-selector-popup { + .bt-content { + height:40px; + width:210px; + background-color:#333; + overflow:auto; + border:1px solid #ED3618; + text-align:center; + font-family: Raleway, Arial, Helvetica, sans-serif; + ul { + @include vertical-align-column; + height:100%; + } + li { + font-size:14px; + margin-left:0px; + list-style-type: none; + } + } +} \ No newline at end of file diff --git a/web/app/views/clients/_muteSelect.html.slim b/web/app/views/clients/_muteSelect.html.slim new file mode 100644 index 000000000..c9e66f328 --- /dev/null +++ b/web/app/views/clients/_muteSelect.html.slim @@ -0,0 +1,7 @@ +script type='text/template' id='template-mute-select' + ul + li data-mute-option="master" + a href='#' Mute For Everyone in Master Mix + + li data-mute-option="personal" + a href='#' Mute Only in My Personal Mix diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 82abf884c..178de0c52 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -18,6 +18,7 @@ <%= render "ftue" %> <%= render "jamServer" %> <%= render "iconInstrumentSelect" %> +<%= render "muteSelect" %> <%= render "clients/wizard/buttons" %> <%= render "clients/wizard/gear/gear_wizard" %> <%= render "clients/wizard/loopback/loopback_wizard" %> diff --git a/web/lib/tasks/start.rake b/web/lib/tasks/start.rake index 4f8111ffe..87cbce1a0 100644 --- a/web/lib/tasks/start.rake +++ b/web/lib/tasks/start.rake @@ -4,7 +4,7 @@ task :all_jobs do Rake::Task['environment'].invoke - ENV['QUEUE'] = '*' + ENV['QUEUE'] = ENV['QUEUE'] || '*' Rake::Task['resque:work'].invoke end