* wip
This commit is contained in:
parent
3c53eab168
commit
c3cd742c17
|
|
@ -79,6 +79,10 @@ module ValidationMessages
|
|||
# notification
|
||||
DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender'
|
||||
|
||||
# mods
|
||||
MODS_NO_SHOW_MUST_BE_HASH = 'no_show must be a hash'
|
||||
MODS_UNKNOWN_KEY = 'unknown mod'
|
||||
|
||||
# takes either a string/string hash, or a string/array-of-strings|symbols hash,
|
||||
# and creates a ActiveRecord.errors style object
|
||||
def createValidationStyleObject(validation_errors)
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ module JamRuby
|
|||
validate :validate_avatar_info
|
||||
validate :email_case_insensitive_uniqueness
|
||||
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
|
||||
validate :validate_mods
|
||||
|
||||
scope :musicians, where(:musician => true)
|
||||
scope :fans, where(:musician => false)
|
||||
|
|
@ -185,6 +186,17 @@ module JamRuby
|
|||
errors.add(:musician_instruments, ValidationMessages::INSTRUMENT_LIMIT_EXCEEDED) if !administratively_created && musician && musician_instruments.length > 5
|
||||
end
|
||||
|
||||
# let's work to stop junk from getting into the mods array; this is essentially the schema
|
||||
def validate_mods
|
||||
mods_json.each do |key, value|
|
||||
if key == "no_show"
|
||||
errors.add(:mods, ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH) unless value.is_a?(Hash)
|
||||
else
|
||||
errors.add(:mods, ValidationMessages::MODS_UNKNOWN_KEY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_current_password
|
||||
# checks if the user put in their current password (used when changing your email, for instance)
|
||||
errors.add(:current_password, ValidationMessages::NOT_YOUR_PASSWORD) if should_confirm_existing_password? && !valid_password?(self.current_password)
|
||||
|
|
@ -355,7 +367,20 @@ module JamRuby
|
|||
|
||||
# mods comes back as text; so give ourselves a parsed version
|
||||
def mods_json
|
||||
@mods_json ||= mods ? JSON.parse(mods, symbolize_names: true) : {}
|
||||
@mods_json ||= mods ? JSON.parse(mods) : {}
|
||||
end
|
||||
|
||||
# new_modes should be a regular hash with non-symbolized keys (vs symbolized keys)
|
||||
def mod_merge(new_mods)
|
||||
self.mods = (mods_json.merge(new_mods) do |key, old_val, new_val|
|
||||
if key == "no_show"
|
||||
# we take the values from previous no_shows, and merge it with the new no_shows
|
||||
old_val.merge(new_val)
|
||||
else
|
||||
raise "unknown in mode_merge key: #{key}"
|
||||
end
|
||||
end).to_json
|
||||
@mods_json = nil # invalidate this since we've updated self.mods
|
||||
end
|
||||
|
||||
def heartbeat_interval_client
|
||||
|
|
|
|||
|
|
@ -77,14 +77,9 @@ describe User do
|
|||
it { should be_valid }
|
||||
end
|
||||
|
||||
describe "when mods is empty" do
|
||||
before { @user.mods = 'nil' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
|
||||
describe "when mods is json object" do
|
||||
before { @user.mods = '{"key":"value"}' }
|
||||
before { @user.mods = '{"no_show":{"value": true}}' }
|
||||
it { should be_valid }
|
||||
end
|
||||
|
||||
|
|
@ -453,25 +448,9 @@ describe User do
|
|||
|
||||
describe "mods" do
|
||||
it "should allow update of JSON" do
|
||||
@user.mods = {some_field: 5}.to_json
|
||||
@user.mods = {no_show: {something:1}}.to_json
|
||||
@user.save!
|
||||
end
|
||||
|
||||
it "should return heartbeart interval" do
|
||||
@user.heartbeat_interval_client.should be_nil
|
||||
@user.mods = {heartbeat_interval_client: 5}.to_json
|
||||
@user.save!
|
||||
@user = User.find(@user.id) # necessary because mods_json is cached in the model
|
||||
@user.heartbeat_interval_client.should == 5
|
||||
end
|
||||
|
||||
it "should return connection_expire_time" do
|
||||
@user.connection_expire_time_client.should be_nil
|
||||
@user.mods = {connection_expire_time_client: 5}.to_json
|
||||
@user.save!
|
||||
@user = User.find(@user.id) # necessary because mods_json is cached in the model
|
||||
@user.connection_expire_time_client.should == 5
|
||||
end
|
||||
end
|
||||
|
||||
describe "audio latency" do
|
||||
|
|
@ -587,6 +566,52 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe "mods_merge" do
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
|
||||
it "allow empty merge" do
|
||||
user.mod_merge({})
|
||||
user.valid?.should be_true
|
||||
user.mods.should == {}.to_json
|
||||
end
|
||||
|
||||
it "allow no_show set" do
|
||||
user.mod_merge({"no_show" => {"some_screen" => true}})
|
||||
user.valid?.should be_true
|
||||
user.mods.should == {no_show:{some_screen:true}}.to_json
|
||||
end
|
||||
|
||||
it "allow no_show aggregation" do
|
||||
user.mod_merge({"no_show" => {"some_screen1" => true}})
|
||||
user.save!
|
||||
user.reload
|
||||
user.mod_merge({"no_show" => {"some_screen2" => true}})
|
||||
user.valid?.should be_true
|
||||
user.mods.should == {"no_show" => {"some_screen1" => true, "some_screen2" => true}}.to_json
|
||||
end
|
||||
|
||||
it "allow no_show override" do
|
||||
user.mod_merge({"no_show" => {"some_screen1" => true}})
|
||||
user.save!
|
||||
user.reload
|
||||
user.mod_merge({"no_show" => {"some_screen1" => false}})
|
||||
user.valid?.should be_true
|
||||
user.mods.should == {no_show:{some_screen1:false}}.to_json
|
||||
end
|
||||
|
||||
it "does not allow random root keys" do
|
||||
user.mod_merge({random_root_key:true})
|
||||
user.valid?.should be_false
|
||||
user.errors[:mods].should == [ValidationMessages::MODS_UNKNOWN_KEY]
|
||||
end
|
||||
|
||||
it "does not allow non-hash no_show" do
|
||||
user.mod_merge({no_show:true})
|
||||
user.valid?.should be_false
|
||||
user.errors[:mods].should == [ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH]
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
describe "update avatar" do
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@
|
|||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.Banner = (function () {
|
||||
var modUtils = context.JK.ModUtils;
|
||||
var self = this;
|
||||
var logger = context.JK.logger;
|
||||
var $banner = null;
|
||||
var $closeBtn = null;
|
||||
var $yesBtn = null;
|
||||
var $noBtn = null;
|
||||
var $noShow = null;
|
||||
var $noShowCheckbox = null;
|
||||
var $buttons = null;
|
||||
|
||||
// you can also do
|
||||
// * showAlert('title', 'text')
|
||||
|
|
@ -77,7 +81,7 @@
|
|||
|
||||
|
||||
|
||||
if((options.type == "alert" && !options.buttons) || options.close) {
|
||||
if((options.type == "alert" && !options.buttons) || options.close || options.no_show) {
|
||||
|
||||
var closeButtonText = 'CLOSE';
|
||||
if(options.close !== null && typeof options.close == 'object') {
|
||||
|
|
@ -87,13 +91,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
if(options.no_show) {
|
||||
$buttons.addClass('center')
|
||||
$noShowCheckbox.data('no_show', options.no_show)
|
||||
$noShow.show()
|
||||
}
|
||||
$closeBtn.show().text(closeButtonText).unbind('click').click(function() {
|
||||
if ($noShowCheckbox.is(':visible') && $noShowCheckbox.is(':checked')) {
|
||||
modUtils.updateNoShow($noShowCheckbox.data('no_show'))
|
||||
}
|
||||
hide();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
$closeBtn.hide();
|
||||
$noShow.hide();
|
||||
}
|
||||
|
||||
if(options.type == "yes_no") {
|
||||
|
|
@ -118,7 +131,6 @@
|
|||
}
|
||||
|
||||
if(options.buttons) {
|
||||
var $buttons = $banner.find('.buttons')
|
||||
context._.each(options.buttons, function(button, i) {
|
||||
if(!button.name) throw "button.name must be specified";
|
||||
if(!button.click) throw "button.click must be specified";
|
||||
|
|
@ -148,6 +160,9 @@
|
|||
$banner.find('.user-btn').remove();
|
||||
$('#banner_overlay .dialog-inner').html("");
|
||||
$('#banner_overlay').hide();
|
||||
$buttons.removeClass('center')
|
||||
$noShowCheckbox.data('no_show', null).iCheck('uncheck').attr('checked', false)
|
||||
$buttons.children().hide();
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
|
@ -157,6 +172,11 @@
|
|||
$closeBtn = $banner.find('.close-btn');
|
||||
$yesBtn = $banner.find('.yes-btn');
|
||||
$noBtn = $banner.find('.no-btn');
|
||||
$noShowCheckbox = $banner.find('.no-more-show-checkbox')
|
||||
$noShow = $banner.find('.no-more-show')
|
||||
$buttons = $banner.find('.buttons')
|
||||
|
||||
context.JK.checkbox($noShowCheckbox);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -427,7 +427,8 @@
|
|||
stereo: true,
|
||||
volume_left: -40,
|
||||
volume_right:-40,
|
||||
instrument_id:50 // see globals.js
|
||||
instrument_id:50, // see globals.js
|
||||
mode: false
|
||||
});
|
||||
}
|
||||
return response;
|
||||
|
|
@ -489,6 +490,7 @@
|
|||
function SessionGetDeviceLatency() { return 10.0; }
|
||||
function SessionPageEnter() {logger.debug("FakeJamClient: SessionPageEnter"); return {}}
|
||||
function SessionPageLeave() {logger.debug("FakeJamClient: SessionPageLeave")}
|
||||
function SetMixerMode(mode) {}
|
||||
function SessionGetMasterLocalMix() {
|
||||
logger.debug('SessionGetMasterLocalMix. Returning: ' + _mix);
|
||||
return _mix;
|
||||
|
|
@ -929,6 +931,7 @@
|
|||
this.SessionGetTracksPlayDurationMs = SessionGetTracksPlayDurationMs;
|
||||
this.SessionPageEnter = SessionPageEnter;
|
||||
this.SessionPageLeave = SessionPageLeave;
|
||||
this.SetMixerMode = SetMixerMode;
|
||||
|
||||
this.SetVURefreshRate = SetVURefreshRate;
|
||||
this.SessionGetMasterLocalMix = SessionGetMasterLocalMix;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@
|
|||
FILE_MANAGER_CMD_START : 'file_manager_cmd_start',
|
||||
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'
|
||||
FILE_MANAGER_CMD_ASAP_UPDATE : 'file_manager_cmd_asap_update',
|
||||
MIXER_MODE_CHANGED : 'mixer_mode_changed'
|
||||
};
|
||||
|
||||
context.JK.ALERT_NAMES = {
|
||||
|
|
@ -284,4 +285,14 @@
|
|||
showASIO: false
|
||||
}
|
||||
}
|
||||
|
||||
context.JK.MIX_MODES = {
|
||||
MASTER: true,
|
||||
PERSONAL: false
|
||||
}
|
||||
|
||||
/** NAMED_MESSAGES means messages that we show to the user (dialogs/banners/whatever), that we have formally named */
|
||||
context.JK.NAMED_MESSAGES = {
|
||||
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
|
||||
}
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# Common utility functions.
|
||||
#
|
||||
|
||||
$ = jQuery
|
||||
context = window
|
||||
context.JK ||= {};
|
||||
|
||||
class ModUtils
|
||||
constructor: () ->
|
||||
@logger = context.JK.logger
|
||||
|
||||
init: () =>
|
||||
|
||||
# creates a new show structure suitable for applying to a user update
|
||||
noShow: (noShowName) =>
|
||||
noShowValue = {}
|
||||
noShowValue[noShowName] = true
|
||||
{no_show: noShowValue}
|
||||
|
||||
updateNoShow: (noShowName) =>
|
||||
context.JK.app.updateUserModel({mods: this.noShow(noShowName)})
|
||||
|
||||
# returns a deferred, so use .done
|
||||
shouldShow: (noShowName) =>
|
||||
deferred = new $.Deferred();
|
||||
context.JK.app.user()
|
||||
.done((user) => (
|
||||
noShows = user.mods?.no_show
|
||||
shouldShowForName = if noShows? then !noShows[noShowName] else true
|
||||
deferred.resolve(shouldShowForName)
|
||||
))
|
||||
return deferred;
|
||||
# global instance
|
||||
context.JK.ModUtils = new ModUtils()
|
||||
|
|
@ -5,8 +5,11 @@
|
|||
context.JK = context.JK || {};
|
||||
context.JK.SessionScreen = function(app) {
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var MIX_MODES = context.JK.MIX_MODES;
|
||||
var NAMED_MESSAGES = context.JK.NAMED_MESSAGES;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
var sessionUtils = context.JK.SessionUtils;
|
||||
var modUtils = context.JK.ModUtils;
|
||||
var logger = context.JK.logger;
|
||||
var self = this;
|
||||
var sessionModel = null;
|
||||
|
|
@ -39,8 +42,9 @@
|
|||
var $recordingManagerViewer = null;
|
||||
var $screen = null;
|
||||
var $mixModeDropdown = null;
|
||||
var rest = context.JK.Rest();
|
||||
var $templateMixerModeChange = null;
|
||||
|
||||
var rest = context.JK.Rest();
|
||||
var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there.
|
||||
|
||||
var defaultParticipant = {
|
||||
|
|
@ -491,16 +495,35 @@
|
|||
var mixerIds = context.jamClient.SessionGetIDs();
|
||||
var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)});
|
||||
mixers = holder.mixers;
|
||||
|
||||
console.log("mixers", mixers)
|
||||
// grab the first mixer, and check the mode
|
||||
|
||||
var newMixerMode;;
|
||||
if(mixers.length > 0) {
|
||||
newMixerMode = mixers[0]["mode"]
|
||||
if(newMixerMode === undefined) {
|
||||
logger.error("mixer mode not present. defaulting to personal")
|
||||
newMixerMode = MIX_MODES.PERSONAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error("no mixers present. defaulting mixer mode to personal")
|
||||
newMixerMode = MIX_MODES.PERSONAL;
|
||||
}
|
||||
|
||||
sessionModel.setMixerMode(newMixerMode)
|
||||
|
||||
// Always add a hard-coded simplified 'mixer' for the L2M mix
|
||||
|
||||
/**
|
||||
var l2m_mixer = {
|
||||
id: '__L2M__',
|
||||
range_low: -80,
|
||||
range_high: 20,
|
||||
volume_left: context.jamClient.SessionGetMasterLocalMix()
|
||||
};
|
||||
mixers.push(l2m_mixer);*/
|
||||
/**
|
||||
var l2m_mixer = {
|
||||
id: '__L2M__',
|
||||
range_low: -80,
|
||||
range_high: 20,
|
||||
volume_left: context.jamClient.SessionGetMasterLocalMix()
|
||||
};
|
||||
mixers.push(l2m_mixer);*/
|
||||
}
|
||||
|
||||
function _mixersForGroupId(groupId) {
|
||||
|
|
@ -561,13 +584,15 @@
|
|||
var gainPercent = 0;
|
||||
var mixerIds = [];
|
||||
$.each(mixers, function(index, mixer) {
|
||||
if (mixer.group_id === ChannelGroupIds.MasterGroup) {
|
||||
if (sessionModel.isMasterMixMode() && mixer.group_id === ChannelGroupIds.MasterGroup) {
|
||||
mixerIds.push(mixer.id);
|
||||
gainPercent = percentFromMixerValue(
|
||||
mixer.range_low, mixer.range_high, mixer.volume_left);
|
||||
}
|
||||
if (mixer.group_id === ChannelGroupIds.MonitorGroup) {
|
||||
else if (!sessionModel.isMasterMixMode() && mixer.group_id === ChannelGroupIds.MonitorGroup) {
|
||||
mixerIds.push(mixer.id);
|
||||
gainPercent = percentFromMixerValue(
|
||||
mixer.range_low, mixer.range_high, mixer.volume_left);
|
||||
}
|
||||
});
|
||||
var faderId = mixerIds.join(',');
|
||||
|
|
@ -811,13 +836,28 @@
|
|||
// With multiple tracks, there will be more than one mixer for a
|
||||
// particular client, in a particular group, and I'll need to further
|
||||
// identify by track id or something similar.
|
||||
var mixer = _mixerForClientId(
|
||||
|
||||
var mixer = null;
|
||||
if(sessionModel.isMasterMixMode()) {
|
||||
mixer = _mixerForClientId(
|
||||
participant.client_id,
|
||||
[
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.PeerAudioInputMusicGroup
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.PeerAudioInputMusicGroup
|
||||
],
|
||||
usedMixers);
|
||||
}
|
||||
else {
|
||||
// don't pass in used mixers; we need to associate multiple tracks with the same mixer
|
||||
mixer = _mixerForClientId(
|
||||
participant.client_id,
|
||||
[
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.UserMusicInputGroup
|
||||
],
|
||||
{});
|
||||
}
|
||||
|
||||
if (mixer) {
|
||||
usedMixers[mixer.id] = true;
|
||||
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
|
||||
|
|
@ -832,6 +872,7 @@
|
|||
trackData.muteClass = muteClass;
|
||||
trackData.mixerId = mixer.id;
|
||||
trackData.noaudio = false;
|
||||
trackData.group_id = mixer.group_id;
|
||||
context.jamClient.SessionSetUserName(participant.client_id,name);
|
||||
|
||||
} else { // No mixer to match, yet
|
||||
|
|
@ -859,20 +900,20 @@
|
|||
addNewGearDialog = new context.JK.AddNewGearDialog(app, self);
|
||||
}
|
||||
|
||||
function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent) {
|
||||
function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent, groupId) {
|
||||
var vuOpts = $.extend({}, trackVuOpts);
|
||||
var faderOpts = $.extend({}, trackFaderOpts);
|
||||
faderOpts.faderId = mixerId;
|
||||
var vuLeftSelector = trackSelector + " .track-vu-left";
|
||||
var vuRightSelector = trackSelector + " .track-vu-right";
|
||||
var faderSelector = trackSelector + " .track-gain";
|
||||
var $fader = $(faderSelector).attr('mixer-id', mixerId);
|
||||
var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId)
|
||||
var $track = $(trackSelector);
|
||||
// Set mixer-id attributes and render VU/Fader
|
||||
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
|
||||
$track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul');
|
||||
$track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul').data('groupId', groupId)
|
||||
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
|
||||
$track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur');
|
||||
$track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur').data('groupId', groupId)
|
||||
context.JK.FaderHelpers.renderFader($fader, faderOpts);
|
||||
// Set gain position
|
||||
context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent);
|
||||
|
|
@ -890,13 +931,26 @@
|
|||
var keysToDelete = [];
|
||||
for (var key in lookingForMixers) {
|
||||
var clientId = lookingForMixers[key];
|
||||
var mixer = _mixerForClientId(
|
||||
var mixer = null;
|
||||
if(sessionModel.isMasterMixMode()) {
|
||||
mixer = _mixerForClientId(
|
||||
clientId,
|
||||
[
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.PeerAudioInputMusicGroup
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.PeerAudioInputMusicGroup
|
||||
],
|
||||
usedMixers);
|
||||
}
|
||||
else {
|
||||
// don't pass in used mixers; we need to associate multiple tracks with the same mixer
|
||||
mixer = _mixerForClientId(
|
||||
clientId,
|
||||
[
|
||||
ChannelGroupIds.AudioInputMusicGroup,
|
||||
ChannelGroupIds.UserMusicInputGroup
|
||||
],
|
||||
{});
|
||||
}
|
||||
if (mixer) {
|
||||
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
|
||||
logger.debug("found mixer=" + mixer.id + ", participant=" + participant)
|
||||
|
|
@ -906,7 +960,7 @@
|
|||
mixer.range_low, mixer.range_high, mixer.volume_left);
|
||||
var trackSelector = 'div.track[track-id="' + key + '"]';
|
||||
|
||||
connectTrackToMixer(trackSelector, key, mixer.id, gainPercent);
|
||||
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);
|
||||
// hide overlay for all tracks associated with this client id (if one mixer is present, then all tracks are valid)
|
||||
|
|
@ -986,7 +1040,7 @@
|
|||
// Render VU meters and gain fader
|
||||
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
|
||||
var gainPercent = trackData.gainPercent || 0;
|
||||
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent);
|
||||
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, trackData.group_id);
|
||||
|
||||
var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]');
|
||||
if (!allowDelete) {
|
||||
|
|
@ -1018,7 +1072,7 @@
|
|||
// Render VU meters and gain fader
|
||||
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
|
||||
var gainPercent = trackData.gainPercent || 0;
|
||||
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent);
|
||||
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, null);
|
||||
|
||||
tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId);
|
||||
}
|
||||
|
|
@ -1031,11 +1085,17 @@
|
|||
function faderChanged(e, data) {
|
||||
var $target = $(this);
|
||||
var faderId = $target.attr('mixer-id');
|
||||
var groupId = $target.data('groupId');
|
||||
var mixerIds = faderId.split(',');
|
||||
$.each(mixerIds, function(i,v) {
|
||||
var broadcast = !(data.dragging); // If fader is still dragging, don't broadcast
|
||||
fillTrackVolumeObject(v, broadcast);
|
||||
setMixerVolume(v, data.percentage);
|
||||
|
||||
if(groupId == ChannelGroupIds.UserMusicInputGroup) {
|
||||
// there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
|
||||
context.JK.FaderHelpers.setFaderValue(v, data.percentage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1440,6 +1500,28 @@
|
|||
sessionId);
|
||||
inviteMusiciansUtil.loadFriends();
|
||||
$(friendInput).show();
|
||||
}
|
||||
|
||||
function onMixerModeChanged(e, data)
|
||||
{
|
||||
$mixModeDropdown.easyDropDown('select', data.mode, true);
|
||||
|
||||
setTimeout(renderSession, 1);
|
||||
}
|
||||
|
||||
function onUserChangeMixMode(e) {
|
||||
var mode = $mixModeDropdown.val() == "master" ? MIX_MODES.MASTER : MIX_MODES.PERSONAL;
|
||||
|
||||
context.jamClient.SetMixerMode(mode)
|
||||
|
||||
modUtils.shouldShow(NAMED_MESSAGES.MASTER_VS_PERSONAL_MIX).done(function(shouldShow) {
|
||||
if(shouldShow) {
|
||||
var modeChangeHtml = $($templateMixerModeChange.html());
|
||||
context.JK.Banner.show({title: 'Master vs. Personal Mix', text: modeChangeHtml, no_show: NAMED_MESSAGES.MASTER_VS_PERSONAL_MIX});
|
||||
}
|
||||
})
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function events() {
|
||||
|
|
@ -1463,6 +1545,8 @@
|
|||
.on('play', onPlay)
|
||||
.on('change-position', onChangePlayPosition);
|
||||
$(friendInput).focus(function() { $(this).val(''); })
|
||||
$(document).on(EVENTS.MIXER_MODE_CHANGED, onMixerModeChanged)
|
||||
$mixModeDropdown.change(onUserChangeMixMode)
|
||||
}
|
||||
|
||||
this.initialize = function(localRecordingsDialogInstance, recordingFinishedDialogInstance, friendSelectorDialog) {
|
||||
|
|
@ -1473,7 +1557,6 @@
|
|||
context.jamClient.SetVURefreshRate(150);
|
||||
context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback");
|
||||
playbackControls = new context.JK.PlaybackControls($('.session-recordings .recording-controls'));
|
||||
events();
|
||||
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
|
|
@ -1487,6 +1570,9 @@
|
|||
$recordingManagerViewer = $('#recording-manager-viewer');
|
||||
$screen = $('#session-screen');
|
||||
$mixModeDropdown = $screen.find('select.monitor-mode')
|
||||
$templateMixerModeChange = $('#template-mixer-mode-change');
|
||||
events();
|
||||
|
||||
|
||||
// make sure no previous plays are still going on by accident
|
||||
context.jamClient.SessionStopPlay();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
context.JK.SessionModel = function(app, server, client, sessionScreen) {
|
||||
var ALERT_TYPES = context.JK.ALERT_TYPES;
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var MIX_MODES = context.JK.MIX_MODES;
|
||||
|
||||
var userTracks = null; // comes from the backend
|
||||
var clientId = client.clientID;
|
||||
|
|
@ -33,6 +34,8 @@
|
|||
var startTime = null;
|
||||
var joinDeferred = null;
|
||||
|
||||
var mixerMode = MIX_MODES.GLOBAL;
|
||||
|
||||
server.registerOnSocketClosed(onWebsocketDisconnected);
|
||||
|
||||
function id() {
|
||||
|
|
@ -93,6 +96,13 @@
|
|||
return inSession;
|
||||
}
|
||||
|
||||
function onMixerModeChanged(newMixerMode)
|
||||
{
|
||||
mixerMode = newMixerMode;
|
||||
var mode = newMixerMode == MIX_MODES.MASTER ? "master" : "personal";
|
||||
logger.debug("onMixerModeChanged:" + mode);
|
||||
$(document).triggerHandler(EVENTS.MIXER_MODE_CHANGED, {mode:mode});
|
||||
}
|
||||
|
||||
function waitForSessionPageEnterDone() {
|
||||
sessionPageEnterDeferred = $.Deferred();
|
||||
|
|
@ -277,6 +287,7 @@
|
|||
userTracks = null;
|
||||
startTime = null;
|
||||
joinDeferred = null;
|
||||
mixerMode = MIX_MODES.PERSONAL;
|
||||
if(fullyJoined) {
|
||||
$(document).trigger(EVENTS.SESSION_ENDED, {session: {id: currentSessionId}});
|
||||
}
|
||||
|
|
@ -485,6 +496,16 @@
|
|||
});
|
||||
}
|
||||
|
||||
function setMixerMode(newMixerMode) {
|
||||
if(mixerMode != newMixerMode) {
|
||||
onMixerModeChanged(newMixerMode);
|
||||
}
|
||||
}
|
||||
|
||||
function isMasterMixMode() {
|
||||
return mixerMode == MIX_MODES.MASTER;
|
||||
}
|
||||
|
||||
function onWebsocketDisconnected(in_error) {
|
||||
// kill the streaming of the session immediately
|
||||
if(currentSessionId) {
|
||||
|
|
@ -616,6 +637,12 @@
|
|||
else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) {
|
||||
refreshCurrentSession(true);
|
||||
}
|
||||
else if(inSession() && (text == 'Global Peer Input Mixer Mode')) {
|
||||
setMixerMode(MIX_MODES.MASTER);
|
||||
}
|
||||
else if(inSession() && (text == 'Local Peer Stream Mixer Mode')) {
|
||||
setMixerMode(MIX_MODES.PERSONAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Public interface
|
||||
|
|
@ -635,6 +662,8 @@
|
|||
this.recordingModel = recordingModel;
|
||||
this.findUserBy = findUserBy;
|
||||
this.inSession = inSession;
|
||||
this.setMixerMode = setMixerMode;
|
||||
this.isMasterMixMode = isMasterMixMode;
|
||||
|
||||
// ALERT HANDLERS
|
||||
this.onBackendMixerChanged = onBackendMixerChanged;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
padding:11px 0px 11px 0px;
|
||||
background-color:#4c4c4c;
|
||||
min-height:20px;
|
||||
overflow-x:hidden;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
height:240px;
|
||||
}
|
||||
|
||||
.dialog-inner {
|
||||
padding-bottom:0;
|
||||
margin-bottom:25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight:bold;
|
||||
font-size:x-large;
|
||||
|
|
@ -21,12 +26,40 @@
|
|||
|
||||
.buttons {
|
||||
margin:0 20px 20px 0;
|
||||
position:relative;
|
||||
|
||||
&.center {
|
||||
text-align:center;
|
||||
float:none;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
.no-more-show {
|
||||
display:none;
|
||||
position:absolute;
|
||||
margin-left:50px;
|
||||
left:50%;
|
||||
top:-4px;
|
||||
color:rgb(170, 170, 170);
|
||||
|
||||
span {
|
||||
font-size:15px;
|
||||
}
|
||||
|
||||
.icheckbox_minimal {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
top:3px;
|
||||
margin-right:3px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style:disc;
|
||||
margin-left:20px;
|
||||
|
|
@ -40,6 +73,10 @@
|
|||
margin: 15px 12px 15px 36px;
|
||||
}
|
||||
|
||||
.definition {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.end-content {
|
||||
height: 0;
|
||||
line-height: 0;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class ApiUsersController < ApiController
|
|||
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
|
||||
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
|
||||
@user.biography = params[:biography] if params.has_key?(:biography)
|
||||
@user.mod_merge(params[:mods]) if params[:mods]
|
||||
|
||||
# allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user
|
||||
if params.has_key?(:notification_seen_at)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ if @user == current_user
|
|||
geoiplocation.info if geoiplocation
|
||||
end
|
||||
|
||||
node :mods do |user|
|
||||
user.mods_json
|
||||
end
|
||||
|
||||
elsif current_user
|
||||
node :is_friend do |uu|
|
||||
current_user.friends?(@user)
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@
|
|||
<!-- Mix: Me versus Others -->
|
||||
|
||||
<div class="block monitor-mode-holder">
|
||||
<div class="label">MONITOR MODE:</div>
|
||||
<div class="label">MIX:</div>
|
||||
<select class="monitor-mode easydropdown">
|
||||
<option value="personal" class="label">Personal</option>
|
||||
<option value="global">Global</option>
|
||||
<option value="master">Master</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
#banner_overlay.overlay
|
||||
#banner.dialog-overlay-sm{ 'data-type' => '' }
|
||||
.content-head
|
||||
= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon")
|
||||
%h1
|
||||
|
||||
.dialog-inner
|
||||
|
||||
%br.end-content{ clear: 'all'}
|
||||
|
||||
.right.buttons
|
||||
%a.button-orange.close-btn CLOSE
|
||||
%a.button-orange.yes-btn YES
|
||||
%a.button-grey.no-btn CANCEL
|
||||
|
||||
%script{type: 'text/template', id: 'template-app-in-read-only-volume'}
|
||||
.template-app-in-read-only-volume
|
||||
%p The JamKazam application is running in a read-only volume. This stops the automatic update feature from working, and may cause other issues because it is not a supported configuration.
|
||||
%p So let's fix it. Don't worry--it's easy to do--please read on.
|
||||
%p First, here's almost certainly what happened to cause this problem: after JamKazam.dmg was downloaded, it was then double-clicked and a window opened showing the contents of the dmg. The JamKazam application icon was double-clicked inside that opened window. Unfortunately, that isn't OK.
|
||||
%p Instead, do this to move JamKazam to a good location, and run it from there:
|
||||
%ol
|
||||
%li.download-dmg
|
||||
Download the latest mac installer from the
|
||||
%a{href:"/downloads", rel: 'external'}Downloads
|
||||
page.
|
||||
%br
|
||||
%em (the download will have a filename ending in .dmg)
|
||||
%li Double-click the downloaded dmg file to open it.
|
||||
%li In the resulting screen, drag the JamKazam icon to the Applications folder. It will show a progress bar as it copies.
|
||||
%li Double-click the Applications folder to go into the folder.
|
||||
%li If you are still running the JamKazam application at this point, you will need to stop it before executing the next step.
|
||||
%li Find the JamKazam application in the Applications folder, and double-click the icon to launch it!
|
||||
|
||||
%script{type: 'text/template', id: 'template-shutdown-prompt'}
|
||||
.shutdown-prompt
|
||||
We strongly recommend that you leave the JamKazam application running in the background.
|
||||
This is a very lightweight app that will not disrupt your use of your computer and other applications, and leaving this app running will deliver the following benefits to you:
|
||||
%ul
|
||||
%li
|
||||
%span.definition Scoring Service
|
||||
= '- If you leave the app running, there is a service that can check your Internet latency to other JamKazam users. This is critical data that will guide you on which musicians and which sessions will offer a good online play experience.'
|
||||
%li
|
||||
%span.definition Recordings
|
||||
= '- If you leave the app running, any recordings that you and others have made during sessions can be mastered - i.e. uploaded, mixed on our servers, and downloaded back to your computer - so that you have high quality versions of your recordings available.'
|
||||
|
||||
Please consider leaving this lightweight app running in the background for your own benefit, thanks!
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#banner_overlay.overlay
|
||||
#banner.dialog-overlay-sm data-type=''
|
||||
.content-head
|
||||
= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon")
|
||||
h1
|
||||
|
||||
.dialog-inner
|
||||
|
||||
br.end-content clear='all'
|
||||
|
||||
.right.buttons
|
||||
a.button-orange.close-btn CLOSE
|
||||
.no-more-show
|
||||
input.no-more-show-checkbox type="checkbox"
|
||||
span Don't show this again
|
||||
a.button-orange.yes-btn YES
|
||||
a.button-grey.no-btn CANCEL
|
||||
|
||||
script type='text/template' id='template-app-in-read-only-volume'
|
||||
.template-app-in-read-only-volume
|
||||
p The JamKazam application is running in a read-only volume. This stops the automatic update feature from working, and may cause other issues because it is not a supported configuration.
|
||||
p So let's fix it. Don't worry--it's easy to do--please read on.
|
||||
p First, here's almost certainly what happened to cause this problem: after JamKazam.dmg was downloaded, it was then double-clicked and a window opened showing the contents of the dmg. The JamKazam application icon was double-clicked inside that opened window. Unfortunately, that isn't OK.
|
||||
p Instead, do this to move JamKazam to a good location, and run it from there:
|
||||
ol
|
||||
li.download-dmg
|
||||
| Download the latest mac installer from the
|
||||
a href="/downloads" rel='external' Downloads
|
||||
| page.
|
||||
br
|
||||
em
|
||||
| (the download will have a filename ending in .dmg)
|
||||
li Double-click the downloaded dmg file to open it.
|
||||
li In the resulting screen, drag the JamKazam icon to the Applications folder. It will show a progress bar as it copies.
|
||||
li Double-click the Applications folder to go into the folder.
|
||||
li If you are still running the JamKazam application at this point, you will need to stop it before executing the next step.
|
||||
li Find the JamKazam application in the Applications folder, and double-click the icon to launch it!
|
||||
|
||||
script type='text/template' id='template-shutdown-prompt'
|
||||
.shutdown-prompt
|
||||
| We strongly recommend that you leave the JamKazam application running in the background.
|
||||
| This is a very lightweight app that will not disrupt your use of your computer and other applications, and leaving this app running will deliver the following benefits to you:
|
||||
ul
|
||||
li
|
||||
span.definition Scoring Service
|
||||
= '- If you leave the app running, there is a service that can check your Internet latency to other JamKazam users. This is critical data that will guide you on which musicians and which sessions will offer a good online play experience.'
|
||||
li
|
||||
span.definition Recordings
|
||||
= '- If you leave the app running, any recordings that you and others have made during sessions can be mastered - i.e. uploaded, mixed on our servers, and downloaded back to your computer - so that you have high quality versions of your recordings available.'
|
||||
|
||||
| Please consider leaving this lightweight app running in the background for your own benefit, thanks!
|
||||
|
||||
script type='text/template' id='template-mixer-mode-change'
|
||||
.mixer-mode-change
|
||||
| JamKazam gives you control over both a personal and a master mix in each session:
|
||||
ul
|
||||
li
|
||||
span.definition Master Mix
|
||||
div The master mix controls the audio mix that will be used for any recordings you make while in sessions, and also the audio mix that will be broadcast to fans listening to your live session performances. With master mix selected, when you adjust the faders on the session screen up or down, it changes the master mix for all musicians and tracks in the session globally.
|
||||
li
|
||||
span.definition Personal Mix
|
||||
div The personal mix controls the audio mix that you individually hear while playing in the session, and you can customize this mix to hear more or less of the music stream from each other musician playing in the session. This does not affect the master mix used for recordings or broadcasts. With personal mix selected, when you adjust the faders on the session screen up or down, it changes the personal mix only for you locally.
|
||||
br
|
||||
div
|
||||
| For more detailed information on this topic, read our knowledge base article on
|
||||
a rel="external" href="https://jamkazam.desk.com/customer/portal/articles/1757233-using-personal-vs-master-mix-controls" Configuring Master and Personal Mixes in a Session
|
||||
| .
|
||||
br
|
||||
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
describe ApiUsersController do
|
||||
render_views
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let (:user) { FactoryGirl.create(:user) }
|
||||
let (:conn) { FactoryGirl.create(:connection, user: user, last_jam_audio_latency: 5) }
|
||||
|
||||
|
||||
|
|
@ -11,6 +11,29 @@ describe ApiUsersController do
|
|||
controller.current_user = user
|
||||
end
|
||||
|
||||
describe "update mod" do
|
||||
it "empty mod" do
|
||||
post :update, id:user.id, mods: {}, :format=>'json'
|
||||
response.should be_success
|
||||
user.mods_json.should == {}
|
||||
end
|
||||
|
||||
it "no_show mod" do
|
||||
user_id = user.id
|
||||
mods = {"no_show" => {"something1" => true}}
|
||||
post :update, id:user.id, mods: mods, :format=>'json'
|
||||
response.should be_success
|
||||
|
||||
# verify that the user object has the mods data
|
||||
user_again = User.find(user_id)
|
||||
user_again.mods_json.should == mods
|
||||
|
||||
# verify that the response shows the mods structure
|
||||
json = JSON.parse(response.body)
|
||||
json["mods"].should == mods
|
||||
end
|
||||
end
|
||||
|
||||
describe "audio_latency" do
|
||||
it "updates both connection and user" do
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue