* VRFS-1491 - finishing up dealing with refactor of monitor vs master tracks

This commit is contained in:
Seth Call 2014-11-21 17:16:00 -06:00
parent 76adc44a50
commit eedc5ae970
18 changed files with 501 additions and 301 deletions

View File

@ -229,3 +229,4 @@ deletable_recordings.sql
jam_tracks.sql jam_tracks.sql
shopping_carts.sql shopping_carts.sql
recurly.sql recurly.sql
add_track_resource_id.sql

View File

@ -0,0 +1 @@
ALTER TABLE tracks ADD COLUMN client_resource_id VARCHAR(100);

View File

@ -188,6 +188,7 @@ module JamRuby
t.connection = self t.connection = self
t.sound = track["sound"] t.sound = track["sound"]
t.client_track_id = track["client_track_id"] t.client_track_id = track["client_track_id"]
t.client_resource_id = track["client_resource_id"]
t.save # todo what if it fails? t.save # todo what if it fails?
self.tracks << t self.tracks << t
end end

View File

@ -16,6 +16,8 @@ module JamRuby
validates :sound, :inclusion => {:in => SOUND} validates :sound, :inclusion => {:in => SOUND}
validates :connection, presence: true validates :connection, presence: true
validates :client_track_id, presence: true
#validates :client_resource_id, presence: true
def user def user
self.connection.user self.connection.user
@ -89,6 +91,7 @@ module JamRuby
connection_track.instrument_id = track[:instrument_id] connection_track.instrument_id = track[:instrument_id]
connection_track.sound = track[:sound] connection_track.sound = track[:sound]
connection_track.client_track_id = track[:client_track_id] connection_track.client_track_id = track[:client_track_id]
connection_track.client_resource_id = track[:client_resource_id]
result.push(connection_track) result.push(connection_track)
@ -114,6 +117,7 @@ module JamRuby
connection_track.instrument_id = track[:instrument_id] connection_track.instrument_id = track[:instrument_id]
connection_track.sound = track[:sound] connection_track.sound = track[:sound]
connection_track.client_track_id = track[:client_track_id] connection_track.client_track_id = track[:client_track_id]
connection_track.client_resource_id = track[:client_resource_id]
if connection_track.save if connection_track.save
result.push(connection_track) result.push(connection_track)
else else
@ -131,7 +135,7 @@ module JamRuby
result result
end end
def self.save(id, connection_id, instrument_id, sound, client_track_id) def self.save(id, connection_id, instrument_id, sound, client_track_id, client_resource_id)
if id.nil? if id.nil?
track = Track.new track = Track.new
track.connection_id = connection_id track.connection_id = connection_id
@ -151,6 +155,10 @@ module JamRuby
track.client_track_id = client_track_id track.client_track_id = client_track_id
end end
unless client_resource_id.nil?
track.client_resource_id = resource_id
end
track.updated_at = Time.now track.updated_at = Time.now
track.save track.save
return track return track

View File

@ -229,6 +229,7 @@ FactoryGirl.define do
factory :track, :class => JamRuby::Track do factory :track, :class => JamRuby::Track do
sound "mono" sound "mono"
sequence(:client_track_id) { |n| "client_track_id#{n}"} sequence(:client_track_id) { |n| "client_track_id#{n}"}
sequence(:client_resource_id) { |n| "resource_id#{n}"}
end end
factory :video_source, :class => JamRuby::VideoSource do factory :video_source, :class => JamRuby::VideoSource do

View File

@ -99,7 +99,7 @@ describe Track do
track.id.should_not be_nil track.id.should_not be_nil
connection.tracks.length.should == 1 connection.tracks.length.should == 1
set_updated_at(track, 1.days.ago) set_updated_at(track, 1.days.ago)
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id}]) tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}])
tracks.length.should == 1 tracks.length.should == 1
found = tracks[0] found = tracks[0]
expect(found.id).to eq track.id expect(found.id).to eq track.id

View File

@ -16,9 +16,18 @@
var logger = g.JK.logger; var logger = g.JK.logger;
function faderClick(e) { function faderClick(e) {
e.stopPropagation(); e.stopPropagation();
var $fader = $(this); var $fader = $(this);
var recordingDisabled = $fader.data('recording-disabled');
if(recordingDisabled) {
var recordingOpener = $fader.data('recording-opener');
window.JK.prodBubble($fader, 'recording-controls-disabled', {recordingOpener:recordingOpener}, {positions:['top'], offsetParent: $fader.closest('.screen')})
return false;
}
draggingOrientation = $fader.attr('orientation'); draggingOrientation = $fader.attr('orientation');
var offset = $fader.offset(); var offset = $fader.offset();
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left} var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
@ -117,6 +126,14 @@
$draggingFaderHandle = $(this); $draggingFaderHandle = $(this);
$draggingFader = $draggingFaderHandle.closest('div[control="fader"]'); $draggingFader = $draggingFaderHandle.closest('div[control="fader"]');
draggingOrientation = $draggingFader.attr('orientation'); draggingOrientation = $draggingFader.attr('orientation');
var recordingDisabled = $draggingFaderHandle.data('recording-disabled');
var recordingOpener = $draggingFaderHandle.data('recording-opener');
if(recordingDisabled) {
return false;
}
return true;
} }
function onFaderDragStop(e, ui) { function onFaderDragStop(e, ui) {
@ -162,13 +179,15 @@
selector.html(g._.template(templateSource, options)); selector.html(g._.template(templateSource, options));
selector.find('div[control="fader"]').data('recording-disabled', selector.data('recording-disabled')).data('recording-opener', selector.data('recording-opener'))
selector.find('div[control="fader-handle"]').draggable({ selector.find('div[control="fader-handle"]').draggable({
drag: onFaderDrag, drag: onFaderDrag,
start: onFaderDragStart, start: onFaderDragStart,
stop: onFaderDragStop, stop: onFaderDragStop,
containment: "parent", containment: "parent",
axis: options.faderType === 'horizontal' ? 'x' : 'y' axis: options.faderType === 'horizontal' ? 'x' : 'y'
}) }).data('recording-disabled', selector.data('recording-disabled')).data('recording-opener', selector.data('recording-opener'))
// Embed any custom styles, applied to the .fader below selector // Embed any custom styles, applied to the .fader below selector
if ("style" in options) { if ("style" in options) {

View File

@ -392,6 +392,10 @@
logger.debug("Fake JamClient: SessionAudioResync()"); logger.debug("Fake JamClient: SessionAudioResync()");
} }
function SessionGetAllControlState(isMasterOrPersonal) {
var mixerIds = SessionGetIDs()
return SessionGetControlState(mixerIds, isMasterOrPersonal);
}
function SessionGetControlState(mixerIds, isMasterOrPersonal) { function SessionGetControlState(mixerIds, isMasterOrPersonal) {
dbg("SessionGetControlState"); dbg("SessionGetControlState");
var groups = [0, 1, 2, 3, 7, 9]; var groups = [0, 1, 2, 3, 7, 9];
@ -416,9 +420,9 @@
response.push({ response.push({
client_id: clientIds[i], client_id: clientIds[i],
group_id: groups[i], group_id: groups[i],
id: mixerIds[i], id: mixerIds[i] + (isMasterOrPersonal ? 'm' : 'p'),
master: true, master: isMasterOrPersonal,
monitor: true, monitor: !isMasterOrPersonal,
mute: false, mute: false,
name: names[i], name: names[i],
range_high: 20, range_high: 20,
@ -428,7 +432,8 @@
volume_left: -40, volume_left: -40,
volume_right:-40, volume_right:-40,
instrument_id:50, // see globals.js instrument_id:50, // see globals.js
mode: false mode: isMasterOrPersonal,
rid: mixerIds[i]
}); });
} }
return response; return response;
@ -504,7 +509,7 @@
} }
function doCallbacks() { function doCallbacks() {
var names = ["left_vu", "right_vu"]; var names = ["vu"];
//var ids = ["FW AP Multi_2_10200", "FW AP Multi_0_10000"]; //var ids = ["FW AP Multi_2_10200", "FW AP Multi_0_10000"];
var ids= ["i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1", var ids= ["i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1",
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~2"]; "i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~2"];
@ -518,7 +523,7 @@
} }
} }
var js = eventCallbackName + '(' + args.join(',') + ')'; var js = eventCallbackName + '(' + args.join(',') + ')';
eval(js); //eval(js);
vuValue += vuChange; vuValue += vuChange;
if (vuValue > 10 || vuValue < -70) { vuChange = vuChange * -1; } if (vuValue > 10 || vuValue < -70) { vuChange = vuChange * -1; }
@ -912,6 +917,8 @@
// Session // Session
this.SessionAddTrack = SessionAddTrack; this.SessionAddTrack = SessionAddTrack;
this.SessionGetControlState = SessionGetControlState; this.SessionGetControlState = SessionGetControlState;
this.SessionGetAllControlState = SessionGetAllControlState;
this.SessionSetUserName = SessionSetUserName;
this.SessionGetIDs = SessionGetIDs; this.SessionGetIDs = SessionGetIDs;
this.RegisterRecordingManagerCallbacks = RegisterRecordingManagerCallbacks; this.RegisterRecordingManagerCallbacks = RegisterRecordingManagerCallbacks;
this.RegisterRecordingCallbacks = RegisterRecordingCallbacks; this.RegisterRecordingCallbacks = RegisterRecordingCallbacks;

View File

@ -18,6 +18,9 @@
var myTracks = []; var myTracks = [];
var masterMixers = []; var masterMixers = [];
var personalMixers = []; var personalMixers = [];
var allMixers = {};
var mixersByResourceId = {};
var mixersByTrackId = {};
var configureTrackDialog; var configureTrackDialog;
var addNewGearDialog; var addNewGearDialog;
var localRecordingsDialog = null; var localRecordingsDialog = null;
@ -29,7 +32,7 @@
var currentMixerRangeMax = null; var currentMixerRangeMax = null;
var lookingForMixersCount = 0; var lookingForMixersCount = 0;
var lookingForMixersTimer = null; var lookingForMixersTimer = null;
var lookingForMixers = {}; var lookingForMixers = [];
var $recordingTimer = null; var $recordingTimer = null;
var recordingTimerInterval = null; var recordingTimerInterval = null;
var startTimeDate = null; var startTimeDate = null;
@ -454,15 +457,44 @@
* you must iterate. Convenience method to locate a particular * you must iterate. Convenience method to locate a particular
* mixer by id. * mixer by id.
*/ */
function getMixer(mixerId, mixMode) { function getMixer(mixerId) {
var foundMixer = null; return allMixers[mixerId];
var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers; }
$.each(mixers, function(index, mixer) {
if (mixer.id === mixerId) { function getMixerByResourceId(resourceId, mode) {
foundMixer = mixer; var mixerPair = mixersByResourceId[resourceId];
}
}); if(!mixerPair) {return null;}
return foundMixer;
if(mode === undefined) {
return mixerPair;
}
else {
if(mode == MIX_MODES.MASTER) {
return mixerPair.master;
}
else {
return mixerPair.personal;
}
}
}
function getMixerByTrackId(trackId, mode) {
var mixerPair = mixersByTrackId[trackId];
if(!mixerPair) {return null;}
if(mode === undefined) {
return mixerPair;
}
else {
if(mode == MIX_MODES.MASTER) {
return mixerPair.master;
}
else {
return mixerPair.personal;
}
}
} }
function renderSession() { function renderSession() {
@ -492,19 +524,58 @@
addNewGearDialog.initialize(); addNewGearDialog.initialize();
} }
// Get the latest list of underlying audio mixer channels // Get the latest list of underlying audio mixer channels, and populates:
// * mixersByResourceId - a hash of resourceId / { master: mixer, personal: mixer } personal: can be null in case of PeerAudioInputMusicGroup
// * mixersByTrackId - a hash of track id / {master: mixer, personal: mixer}.
// * allMixers - a hash of mixer.id / mixer
// * masterMixers - array of master mode mixers
// * personalMixers - array of personal mode mixers
function _updateMixers() { function _updateMixers() {
masterMixers = context.jamClient.SessionGetAllControlState(true); masterMixers = context.jamClient.SessionGetAllControlState(true);
//var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(masterMixerIds, true)});
//masterMixers = masterMixerIds.mixers;
personalMixers = context.jamClient.SessionGetAllControlState(false); personalMixers = context.jamClient.SessionGetAllControlState(false);
//holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(personalMixerIds, false)});
//personalMixers = personalMixerIds.mixers;
console.log("masterMixers", masterMixers) //logger.debug("masterMixers", masterMixers)
console.log("personalMixers", personalMixers) //logger.debug("personalMixers", personalMixers)
mixersByResourceId = {}
mixersByTrackId = {}
allMixers = {}
var i;
for(i = 0; i < masterMixers.length; i++) {
var masterMixer = masterMixers[i];
allMixers[masterMixer.id] = masterMixer; // populate allMixers by mixer.id
// populate mixer pair
var mixerPair = {}
mixersByResourceId[masterMixer.rid] = mixerPair
mixersByTrackId[masterMixer.id] = mixerPair
mixerPair.master = masterMixer;
}
for(i = 0; i < personalMixers.length; i++) {
var personalMixer = personalMixers[i];
if(personalMixer.group_id == ChannelGroupIds.MediaTrackGroup) {
continue;
}
allMixers[personalMixer.id] = personalMixer
// populate other side of mixer pair
var mixerPair = mixersByResourceId[personalMixer.rid]
if(!mixerPair) {
if(personalMixer.group_id != ChannelGroupIds.MonitorGroup) {
logger.warn("there is no master version of ", personalMixer)
}
mixerPair = {}
mixersByResourceId[personalMixer.rid] = mixerPair
}
mixersByTrackId[personalMixer.id] = mixerPair;
mixerPair.personal = personalMixer;
}
// Always add a hard-coded simplified 'mixer' for the L2M mix // Always add a hard-coded simplified 'mixer' for the L2M mix
/** /**
@ -565,7 +636,7 @@
//logger.debug("clientId", clientId, "groupIds", groupIds, "mixers", mixers) //logger.debug("clientId", clientId, "groupIds", groupIds, "mixers", mixers)
var foundMixers = {}; var foundMixers = {};
var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers; var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers;
console.log("_groupedMixersForClientId", mixers) // console.log("_groupedMixersForClientId", mixers)
$.each(mixers, function(index, mixer) { $.each(mixers, function(index, mixer) {
if (mixer.client_id === clientId) { if (mixer.client_id === clientId) {
for (var i=0; i<groupIds.length; i++) { for (var i=0; i<groupIds.length; i++) {
@ -694,16 +765,20 @@
$voiceChatGain.on('fader_change', faderChanged); $voiceChatGain.on('fader_change', faderChanged);
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent); context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent);
//if (mixer.mute) { //if (mixer.mute) {
_toggleVisualMuteControl($voiceChatMute, mixer, null); _toggleVisualMuteControl($voiceChatMute, mixer.mute);
//} //}
} }
}); });
} }
function _renderLocalMediaTracks() { function _renderLocalMediaTracks() {
var localMediaMixers = _mixersForGroupId(ChannelGroupIds.MediaTrackGroup, sessionModel.getMixMode());
// is this the person who opened the recording?
var isOpener = true;
var localMediaMixers = _mixersForGroupId(ChannelGroupIds.MediaTrackGroup, MIX_MODES.MASTER);
if(localMediaMixers.length == 0) { if(localMediaMixers.length == 0) {
localMediaMixers = _mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, sessionModel.getMixMode()); isOpener = false; // if we have PeerMediaTracks, then we didn't open the recording
localMediaMixers = _mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER);
} }
var recordedTracks = sessionModel.recordedTracks(); var recordedTracks = sessionModel.recordedTracks();
@ -791,7 +866,13 @@
trackData.gainPercent = gainPercent; trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass; trackData.muteClass = muteClass;
trackData.mixerId = mixer.id; trackData.mixerId = mixer.id;
trackData.vuMixerId = mixer.id;
trackData.muteMixerId = mixer.id;
if(sessionModel.isPersonalMixMode() || !isOpener) {
trackData.recordingDisabled = true;
trackData.recordingOpener = isOpener;
}
_addMediaTrack(trackData); _addMediaTrack(trackData);
}); });
@ -817,16 +898,108 @@
var mixer = $muteControl.data('mixer') var mixer = $muteControl.data('mixer')
var oppositeMixer = $muteControl.data('opposite-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()); logger.debug("muting tracks. current mixer id=" + mixer.id + ", opposite mixer id=" + oppositeMixer.id)
context.jamClient.SessionSetControlState(mixer.id, !sessionModel.isMasterMixMode());
var mixerPair = {}
if(sessionModel.isMasterMixMode()) {
mixerPair.master = mixer;
mixerPair.personal = oppositeMixer;
} }
else if(mixer.group_id == ChannelGroupIds.UserMusicInputGroup || mixer.group_id == ChannelGroupIds.PeerAudioInputMusicGroup) { else {
context.jamClient.SessionSetControlState(mixer.id, sessionModel.isMasterMixMode()); mixerPair.master = oppositeMixer;
context.jamClient.SessionSetControlState(oppositeMixer.id, !sessionModel.isMasterMixMode()); mixerPair.personal = mixer;
}
if(muteOption == 'master') {
_toggleAudioMute(mixerPair.master.id, true, mixerPair.master.mode);
_toggleAudioMute(mixerPair.personal.id, true, mixerPair.personal.mode);
}
else {
_toggleAudioMute(mixerPair.personal.id, true, mixerPair.personal.mode);
_toggleAudioMute(mixerPair.master.id, false, mixerPair.master.mode);
} }
_toggleVisualMuteControl($control, true); _toggleVisualMuteControl($muteControl, true);
}
// find backend mixer based on track data, and target client_id
function findMixerForTrack(client_id, track, myTrack) {
var mixer = null; // what is the best mixer for this track/client ID?
var oppositeMixer = null; // what is the corresponding mixer in the opposite mode?
var vuMixer = null;
var muteMixer = null;
var mixMode = sessionModel.getMixMode();
if(myTrack) {
// when it's your track, look it up by the backend resource ID
mixer = getMixerByTrackId(track.client_track_id, mixMode)
vuMixer = mixer;
muteMixer = mixer;
// sanity checks
if(mixer && (mixer.group_id != ChannelGroupIds.AudioInputMusicGroup)) { logger.error("found local mixer that was not of groupID: AudioInputMusicGroup", mixer) }
if(mixer) {
// find the matching AudioInputMusicGroup for the opposite mode
oppositeMixer = getMixerByTrackId(track.client_track_id, !mixMode)
if(mixMode == MIX_MODES.PERSONAL) {
muteMixer = oppositeMixer; // make the master mixer the mute mixer
}
// sanity checks
if(!oppositeMixer) {logger.error("unable to find opposite mixer for local mixer", mixer)}
else if(oppositeMixer.group_id != ChannelGroupIds.AudioInputMusicGroup) { logger.error("found local mixer in opposite mode that was not of groupID: AudioInputMusicGroup", mixer, oppositeMixer)}
}
else {
logger.debug("local track is not present: ", track)
}
}
else {
if(mixMode === MIX_MODES.MASTER) {
// when it's a remote track and in master mode, we should find the PeerAudioInputMusicGroup
mixer = getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if(mixer && (mixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup)) { logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer) }
vuMixer = mixer;
muteMixer = mixer;
if(mixer) {
// we should be able to find a UserMusicInputGroup for this clientId in personal mode
var oppositeMixers = _groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL);
if (oppositeMixers[ChannelGroupIds.UserMusicInputGroup]) { oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]; }
if(!oppositeMixer) {logger.error("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer ) }
}
}
else {
// when it's a remote track and in personal mode, we want the 'Peer Stream', which is UserMusicInputGroup
// this spans N tracks for the remote user
var mixers = _groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL);
if (mixers[ChannelGroupIds.UserMusicInputGroup]) { mixer = mixers[ChannelGroupIds.UserMusicInputGroup][0]; }
vuMixer = mixer;
muteMixer = mixer;
if(mixer) {
// now grab the PeerAudioInputMusicGroup in master mode to satisfy the 'opposite' mixer
oppositeMixer = getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if(!oppositeMixer) {logger.debug("unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", track.client_track_id, mixersByTrackId)}
else if(oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup) { logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer) }
vuMixer = oppositeMixer; // for personal mode, use the PeerAudioInputMusicGroup's VUs
}
}
}
return {
mixer: mixer,
oppositeMixer: oppositeMixer,
vuMixer: vuMixer,
muteMixer: muteMixer
}
} }
function _renderTracks() { function _renderTracks() {
myTracks = []; myTracks = [];
@ -834,6 +1007,8 @@
// Participants are here now, but the mixers don't update right away. // Participants are here now, but the mixers don't update right away.
// Draw tracks from participants, then setup timers to look for the // Draw tracks from participants, then setup timers to look for the
// mixers that go with those participants, if they're missing. // mixers that go with those participants, if they're missing.
lookingForMixers = [] // clear this back out as we are restarting from scratch
lookingForMixersCount = 0; lookingForMixersCount = 0;
$.each(sessionModel.participants(), function(index, participant) { $.each(sessionModel.participants(), function(index, participant) {
@ -842,19 +1017,19 @@
name = participant.user.first_name + ' ' + participant.user.last_name; name = participant.user.first_name + ' ' + participant.user.last_name;
} }
var usedMixers = {}; // Once we use a mixer, we add it here to allow us to find 'second' tracks var myTrack = app.clientId == participant.client_id;
// loop through all tracks for each participant // loop through all tracks for each participant
$.each(participant.tracks, function(index, track) { $.each(participant.tracks, function(index, track) {
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id); var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url); var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
var myTrack = false;
// Default trackData to participant + no Mixer state. // Default trackData to participant + no Mixer state.
var trackData = { var trackData = {
trackId: track.id, trackId: track.id,
connection_id: track.connection_id, connection_id: track.connection_id,
client_track_id: track.client_track_id,
client_resource_id: track.client_resource_id,
clientId: participant.client_id, clientId: participant.client_id,
name: name, name: name,
instrumentIcon: instrumentIcon, instrumentIcon: instrumentIcon,
@ -867,53 +1042,15 @@
preMasteredClass: "" preMasteredClass: ""
}; };
// This is the likely cause of multi-track problems. var mixerData = findMixerForTrack(participant.client_id, track, myTrack)
// This should really become _mixersForClientId and return a list. var mixer = mixerData.mixer;
// With multiple tracks, there will be more than one mixer for a var vuMixer = mixerData.vuMixer;
// particular client, in a particular group, and I'll need to further var muteMixer = mixerData.muteMixer;
// identify by track id or something similar. var oppositeMixer = mixerData.oppositeMixer;
var currentMixers = _groupedMixersForClientId(
participant.client_id,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, sessionModel.getMixMode());
var oppositeMixers = _groupedMixersForClientId( if (mixer && oppositeMixer) {
participant.client_id,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, !sessionModel.getMixMode());
console.log("currentMixers", currentMixers)
console.log("oppositeMixers", oppositeMixers)
var mixer = null;
var oppositeMixer = null;
if(currentMixers) {
if(currentMixers[ChannelGroupIds.AudioInputMusicGroup]) {
mixer = currentMixers[ChannelGroupIds.AudioInputMusicGroup][0]
oppositeMixer = oppositeMixers[ChannelGroupIds.AudioInputMusicGroup][0]
}
else if(sessionModel.isMasterMixMode() && currentMixers[ChannelGroupIds.PeerAudioInputMusicGroup]) {
mixer = currentMixers[ChannelGroupIds.PeerAudioInputMusicGroup][0]
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
}
else if(!sessionModel.isMasterMixMode() && currentMixers[ChannelGroupIds.UserMusicInputGroup]) {
mixer = currentMixers[ChannelGroupIds.UserMusicInputGroup][0]
oppositeMixer = oppositeMixers[ChannelGroupIds.PeerAudioInputMusicGroup][0]
}
}
if (mixer) {
usedMixers[mixer.id] = true;
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup); myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
var gainPercent = percentFromMixerValue( var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left); mixer.range_low, mixer.range_high, mixer.volume_left);
@ -925,13 +1062,15 @@
trackData.gainPercent = gainPercent; trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass; trackData.muteClass = muteClass;
trackData.mixerId = mixer.id; trackData.mixerId = mixer.id;
trackData.vuMixerId = vuMixer.id;
trackData.oppositeMixer = oppositeMixer;
trackData.muteMixerId = muteMixer.id;
trackData.noaudio = false; trackData.noaudio = false;
trackData.group_id = mixer.group_id; trackData.group_id = mixer.group_id;
trackData.oppositeMixer = oppositeMixer;
context.jamClient.SessionSetUserName(participant.client_id,name); context.jamClient.SessionSetUserName(participant.client_id,name);
} else { // No mixer to match, yet } else { // No mixer to match, yet
lookingForMixers[track.id] = participant.client_id; lookingForMixers.push({track: track, clientId: participant.client_id})
trackData.noaudio = true; trackData.noaudio = true;
if (!(lookingForMixersTimer)) { if (!(lookingForMixersTimer)) {
logger.debug("waiting for mixer to show up for track: " + track.id) logger.debug("waiting for mixer to show up for track: " + track.id)
@ -940,14 +1079,12 @@
} }
var allowDelete = myTrack && index > 0; var allowDelete = myTrack && index > 0;
_addTrack(allowDelete, trackData, mixer); _addTrack(allowDelete, trackData, mixer, oppositeMixer);
// Show settings icons only for my tracks // Show settings icons only for my tracks
if (myTrack) { if (myTrack) {
myTracks.push(trackData); myTracks.push(trackData);
} }
// TODO: UNCOMMENT THIS WHEN TESTING LOCALLY IN BROWSER
//myTracks.push(trackData);
}); });
}); });
@ -955,7 +1092,7 @@
addNewGearDialog = new context.JK.AddNewGearDialog(app, self); addNewGearDialog = new context.JK.AddNewGearDialog(app, self);
} }
function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent, groupId) { function connectTrackToMixer(trackSelector, track, mixerId, gainPercent, groupId) {
var vuOpts = $.extend({}, trackVuOpts); var vuOpts = $.extend({}, trackVuOpts);
var faderOpts = $.extend({}, trackFaderOpts); var faderOpts = $.extend({}, trackFaderOpts);
faderOpts.faderId = mixerId; faderOpts.faderId = mixerId;
@ -963,16 +1100,21 @@
var vuRightSelector = trackSelector + " .track-vu-right"; var vuRightSelector = trackSelector + " .track-vu-right";
var faderSelector = trackSelector + " .track-gain"; var faderSelector = trackSelector + " .track-gain";
var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId) var $fader = $(faderSelector).attr('mixer-id', mixerId).data('groupId', groupId)
if(track.recordingDisabled) {
$fader.data('recording-disabled', true).data('recording-opener', track.recordingOpener) // this we be applied later to the fader handle $element
}
var $track = $(trackSelector); var $track = $(trackSelector);
// Set mixer-id attributes and render VU/Fader // Set mixer-id attributes and render VU/Fader
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts); context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
$track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul').data('groupId', groupId) $track.find('.track-vu-left').attr('mixer-id', track.vuMixerId + '_vul').data('groupId', groupId)
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts); context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
$track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur').data('groupId', groupId) $track.find('.track-vu-right').attr('mixer-id', track.vuMixerId + '_vur').data('groupId', groupId)
context.JK.FaderHelpers.renderFader($fader, faderOpts); context.JK.FaderHelpers.renderFader($fader, faderOpts);
// Set gain position // Set gain position
context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent); context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent);
$fader.on('fader_change', faderChanged); $fader.on('fader_change', faderChanged);
return $track;
} }
// Function called on an interval when participants change. Mixers seem to // Function called on an interval when participants change. Mixers seem to
@ -984,83 +1126,61 @@
_updateMixers(); _updateMixers();
var usedMixers = {}; var usedMixers = {};
var keysToDelete = []; var keysToDelete = [];
for (var key in lookingForMixers) { context._.each(lookingForMixers, function(data) {
var clientId = lookingForMixers[key]; var clientId = data.clientId;
var currentMixers = _groupedMixersForClientId( var track = data.track;
clientId,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, sessionModel.getMixMode());
var oppositeMixers = _groupedMixersForClientId( var myTrack = app.clientId == clientId;
clientId,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, !sessionModel.getMixMode());
var mixer = null; var mixerData = findMixerForTrack(clientId, track, myTrack)
var oppositeMixer = null;
if(currentMixers) { var mixer = mixerData.mixer;
if(currentMixers[ChannelGroupIds.AudioInputMusicGroup]) { var oppositeMixer = mixerData.oppositeMixer;
mixer = currentMixers[ChannelGroupIds.AudioInputMusicGroup][0] var vuMixer = mixerData.vuMixer;
oppositeMixer = oppositeMixers[ChannelGroupIds.AudioInputMusicGroup][0] var muteMixer = mixerData.muteMixer;
}
else if(sessionModel.isMasterMixMode() && currentMixers[ChannelGroupIds.PeerAudioInputMusicGroup]) { if (mixer && oppositeMixer) {
mixer = currentMixers[ChannelGroupIds.PeerAudioInputMusicGroup][0] var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0] logger.debug("found mixer=" + mixer.id + ", participant=" + participant)
} usedMixers[mixer.id] = true;
else if(!sessionModel.isMasterMixMode() && currentMixers[ChannelGroupIds.UserMusicInputGroup]) { keysToDelete.push(data);
mixer = currentMixers[ChannelGroupIds.UserMusicInputGroup][0] var gainPercent = percentFromMixerValue(
oppositeMixer = oppositeMixers[ChannelGroupIds.PeerAudioInputMusicGroup][0] mixer.range_low, mixer.range_high, mixer.volume_left);
} var trackSelector = 'div.track[track-id="' + track.id + '"]';
connectTrackToMixer(trackSelector, track, mixer.id, gainPercent, mixer.group_id);
var $track = $('div.track[client-id="' + clientId + '"]');
var $trackIconMute = $track.find('.track-icon-mute')
$trackIconMute.attr('mixer-id', muteMixer.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($trackIconMute, mixer.mute || oppositeMixer.mute);
}
else {
// if 1 second has gone by and still no mixer, then we gray the participant's tracks
if(lookingForMixersCount == 2) {
var $track = $('div.track[client-id="' + clientId + '"]');
$('.disabled-track-overlay', $track).show();
$('.track-connection', $track).removeClass('red yellow green').addClass('red');
} }
if (mixer) { var participant = (sessionModel.getParticipant(clientId) || { user: {name: 'unknown'}}).user.name;
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name; logger.debug("still looking for mixer for participant=" + participant + ", clientId=" + clientId)
logger.debug("found mixer=" + mixer.id + ", participant=" + participant) }
usedMixers[mixer.id] = true; })
keysToDelete.push(key);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var trackSelector = 'div.track[track-id="' + key + '"]';
connectTrackToMixer(trackSelector, key, mixer.id, gainPercent, mixer.group_id);
var $track = $('div.track[client-id="' + clientId + '"]');
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($trackIconMute, mixer, oppositeMixer);
}
else {
// if 1 second has gone by and still no mixer, then we gray the participant's tracks
if(lookingForMixersCount == 2) {
var $track = $('div.track[client-id="' + clientId + '"]');
$('.disabled-track-overlay', $track).show();
$('.track-connection', $track).removeClass('red yellow green').addClass('red');
}
var participant = (sessionModel.getParticipant(clientId) || { user: {name: 'unknown'}}).user.name;
logger.debug("still looking for mixer for participant=" + participant + ", clientId=" + clientId)
}
}
for (var i=0; i<keysToDelete.length; i++) { for (var i=0; i<keysToDelete.length; i++) {
delete lookingForMixers[keysToDelete[i]]; var index = lookingForMixers.indexOf(keysToDelete[i]);
lookingForMixers.splice(index, 1);
} }
if (context.JK.dlen(lookingForMixers) === 0 || if (lookingForMixers.length === 0 ||
lookingForMixersCount > 20) { lookingForMixersCount > 20) {
lookingForMixersCount = 0; lookingForMixersCount = 0;
lookingForMixers = {}; lookingForMixers = []
context.clearTimeout(lookingForMixersTimer); context.clearTimeout(lookingForMixersTimer);
lookingForMixersTimer = null; lookingForMixersTimer = null;
} }
@ -1068,7 +1188,7 @@
// Given a mixerID and a value between 0.0-1.0, // Given a mixerID and a value between 0.0-1.0,
// light up the proper VU lights. // light up the proper VU lights.
function _updateVU(mixerId, value) { function _updateVU(mixerId, value, isClipping) {
// Special-case for mono tracks. If mono, and it's a _vul id, // Special-case for mono tracks. If mono, and it's a _vul id,
// update both sides, otherwise do nothing. // update both sides, otherwise do nothing.
@ -1076,7 +1196,7 @@
var selector; var selector;
var pureMixerId = mixerId.replace("_vul", ""); var pureMixerId = mixerId.replace("_vul", "");
pureMixerId = pureMixerId.replace("_vur", ""); pureMixerId = pureMixerId.replace("_vur", "");
var mixer = getMixer(pureMixerId, sessionModel.getMixMode()); var mixer = getMixer(pureMixerId);
if (mixer) { if (mixer) {
if (!(mixer.stereo)) { // mono track if (!(mixer.stereo)) { // mono track
if (mixerId.substr(-4) === "_vul") { if (mixerId.substr(-4) === "_vul") {
@ -1094,7 +1214,7 @@
} }
} }
function _addTrack(allowDelete, trackData, mixer) { function _addTrack(allowDelete, trackData, mixer, oppositeMixer) {
var parentSelector = '#session-mytracks-container'; var parentSelector = '#session-mytracks-container';
var $destination = $(parentSelector); var $destination = $(parentSelector);
@ -1108,7 +1228,7 @@
var audioOverlay = $('.disabled-track-overlay', newTrack); var audioOverlay = $('.disabled-track-overlay', newTrack);
var $trackIconMute = newTrack.find('.track-icon-mute') var $trackIconMute = newTrack.find('.track-icon-mute')
$trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected) $trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected)
$trackIconMute.data('mixer', mixer) $trackIconMute.data('mixer', mixer).data('opposite-mixer', oppositeMixer)
audioOverlay.hide(); // always start with overlay hidden, and only show if no audio persists audioOverlay.hide(); // always start with overlay hidden, and only show if no audio persists
$destination.append(newTrack); $destination.append(newTrack);
@ -1116,7 +1236,7 @@
// Render VU meters and gain fader // Render VU meters and gain fader
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
var gainPercent = trackData.gainPercent || 0; var gainPercent = trackData.gainPercent || 0;
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, trackData.group_id); connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id);
var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]'); var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]');
if (!allowDelete) { if (!allowDelete) {
@ -1148,8 +1268,11 @@
// Render VU meters and gain fader // Render VU meters and gain fader
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]'; var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
var gainPercent = trackData.gainPercent || 0; var gainPercent = trackData.gainPercent || 0;
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent, null); var $track = connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, null);
var $trackIconMute = $track.find('.track-icon-mute')
if(trackData.recordingDisabled) {
$trackIconMute.data('recording-disabled', true).data('recording-opener', trackData.recordingOpener)
}
tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId); tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId);
} }
@ -1187,62 +1310,69 @@
//_toggleVisualMuteControl($muteControl, isMuted); //_toggleVisualMuteControl($muteControl, isMuted);
} }
function handleBridgeCallback() { function handleBridgeCallback(vuData) {
var j;
var eventName = null; var eventName = null;
var mixerId = null; var mixerId = null;
var value = null; var value = null;
var tuples = arguments.length / 3; var vuInfo = null;
for (var i=0; i<tuples; i++) { for (j = 0; j < vuData.length; j++) {
eventName = arguments[3*i]; vuInfo = vuData[j];
mixerId = arguments[(3*i)+1]; var eventName = vuInfo[0];
value = arguments[(3*i)+2]; var vuVal = 0.0;
var vuVal = 0.0; if(eventName === "vu") {
if (eventName === 'left_vu' || eventName === 'right_vu') { var mixerId = vuInfo[1];
// TODO - no guarantee range will be -80 to 20. Get from the var leftValue = vuInfo[2];
// GetControlState for this mixer which returns min/max var leftClipping = vuInfo[3];
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0 var rightValue = vuInfo[4];
vuVal = (value + 80) / 100; var rightClipping = vuInfo[5];
if (eventName === 'left_vu') { // TODO - no guarantee range will be -80 to 20. Get from the
mixerId = mixerId + "_vul"; // GetControlState for this mixer which returns min/max
} else { // value is a DB value from -80 to 20. Convert to float from 0.0-1.0
mixerId = mixerId + "_vur"; _updateVU(mixerId + "_vul", (leftValue + 80) / 100, leftClipping);
} _updateVU(mixerId + "_vur", (rightValue + 80) / 100, rightClipping);
_updateVU(mixerId, vuVal); }
} else if (eventName === 'connection_status') { else if(eventName === 'connection_status') {
// Connection Quality Change var mixerId = vuInfo[1];
var connectionClass = 'green'; var value = vuInfo[2];
if (value < 7) {
connectionClass = 'yellow';
}
if (value < 4) {
connectionClass = 'red';
}
var clientId = _clientIdForUserInputMixer(mixerId, sessionModel.getMixMode());
if(clientId) { // Connection Quality Change
var $connection = $('.session-track[client-id="' + clientId + '"] .track-connection'); var connectionClass = 'green';
if($connection.length == 0) { if (value < 7) {
logger.debug("connection status: looking for clientId: " + clientId + ", mixer: " + mixerId) connectionClass = 'yellow';
}
else {
$connection.removeClass('red yellow green grey');
$connection.addClass(connectionClass);
}
}
} else if (eventName === 'add' || eventName === 'remove') {
// TODO - _renderSession. Note I get streams of these in
// sequence, so have Nat fix, or buffer/spam protect
// Note - this is already handled from websocket events.
// However, there may be use of these two events to avoid
// the polling-style check for when a mixer has been added
// to match a participant track.
} else {
// Examples of other events
// Add media file track: "add", "The_Abyss_4T", 0
logger.debug('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
} }
if (value < 4) {
connectionClass = 'red';
}
var mixerPair = getMixerByTrackId(mixerId);
var clientId = mixerPair ? mixerPair.master.client_id : null;
if(clientId) {
var $connection = $('.session-track[client-id="' + clientId + '"] .track-connection');
if($connection.length == 0) {
logger.debug("connection status: looking for clientId: " + clientId + ", mixer: " + mixerId)
}
else {
$connection.removeClass('red yellow green grey');
$connection.addClass(connectionClass);
}
}
}
else if(eventName === 'add' || eventName === 'remove') {
// TODO - _renderSession. Note I get streams of these in
// sequence, so have Nat fix, or buffer/spam protect
// Note - this is already handled from websocket events.
// However, there may be use of these two events to avoid
// the polling-style check for when a mixer has been added
// to match a participant track.
}
else {
logger.debug('non-vu event: ' + JSON.stringify(vuInfo));
}
} }
} }
function deleteSession(evt) { function deleteSession(evt) {
var sessionId = $(evt.currentTarget).attr("action-id"); var sessionId = $(evt.currentTarget).attr("action-id");
@ -1265,8 +1395,8 @@
sessionModel.deleteTrack(sessionId, trackId); sessionModel.deleteTrack(sessionId, trackId);
} }
function _toggleVisualMuteControl($control, currentMixer, oppositeMixer) { function _toggleVisualMuteControl($control, mute) {
if (currentMixer.mute) { if (mute) {
$control.removeClass('enabled'); $control.removeClass('enabled');
$control.addClass('muted'); $control.addClass('muted');
} else { } else {
@ -1275,10 +1405,14 @@
} }
} }
function _toggleAudioMute(mixerId, muting) { function _toggleAudioMute(mixerId, muting, mode) {
fillTrackVolumeObject(mixerId); fillTrackVolumeObject(mixerId);
context.trackVolumeObject.mute = muting; context.trackVolumeObject.mute = muting;
context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixerMode());
if(mode === undefined) {
mode = sessionModel.getMixMode();
}
context.jamClient.SessionSetControlState(mixerId, mode);
} }
function showMuteDropdowns($control) { function showMuteDropdowns($control) {
@ -1292,20 +1426,28 @@
// track icons have a special mute behavior // track icons have a special mute behavior
if($control.is('.track-icon-mute')) { 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) { var recordingDisabled = $control.data('recording-disabled');
_toggleVisualMuteControl($control, muting); if(recordingDisabled) {
var recordingOpener = $control.data('recording-opener');
context.JK.prodBubble($control, 'recording-controls-disabled', {recordingOpener:recordingOpener}, {positions:['bottom'], offsetParent: $control.closest('.screen')})
return false;
} }
$.each(mixerIds, function(i,v) {
var mixerId = v;
// behavior: if this is the user's track in personal mode, then we mute the track globally
// otherwise, for any other track (user+master mode, or remote track in any mode)
// we just mute the type of track for that mode
var mixer = $control.data('mixer');
var oppositeMixer = $control.data('opposite-mixer')
_toggleAudioMute(mixerId, muting, getMixer(mixerId).mode)
// look for all controls matching this mixer id (important when it's personal mode + UserMusicInputGroup)
var $controls = $screen.find('.track-icon-mute[mixer-id=' + mixerId +']');
_toggleVisualMuteControl($controls, muting);
});
} }
else { else {
$.each(mixerIds, function(i,v) { $.each(mixerIds, function(i,v) {
@ -1319,29 +1461,23 @@
function fillTrackVolumeObject(mixerId, broadcast) { function fillTrackVolumeObject(mixerId, broadcast) {
_updateMixers(); _updateMixers();
var mixer = null;
var _broadcast = true; var _broadcast = true;
if (broadcast !== undefined) { if (broadcast !== undefined) {
_broadcast = broadcast; _broadcast = broadcast;
} }
for (var i=0; i<mixers.length; i++) { var mixer = getMixer(mixerId);
mixer = mixers[i]; context.trackVolumeObject.clientID = mixer.client_id;
if (mixer.id === mixerId) { context.trackVolumeObject.broadcast = _broadcast;
context.trackVolumeObject.clientID = mixer.client_id; context.trackVolumeObject.master = mixer.master;
context.trackVolumeObject.broadcast = _broadcast; context.trackVolumeObject.monitor = mixer.monitor;
context.trackVolumeObject.master = mixer.master; context.trackVolumeObject.mute = mixer.mute;
context.trackVolumeObject.monitor = mixer.monitor; context.trackVolumeObject.name = mixer.name;
context.trackVolumeObject.mute = mixer.mute; context.trackVolumeObject.record = mixer.record;
context.trackVolumeObject.name = mixer.name; context.trackVolumeObject.volL = mixer.volume_left;
context.trackVolumeObject.record = mixer.record; context.trackVolumeObject.volR = mixer.volume_right;
context.trackVolumeObject.volL = mixer.volume_left; // trackVolumeObject doesn't have a place for range min/max
context.trackVolumeObject.volR = mixer.volume_right; currentMixerRangeMin = mixer.range_low;
// trackVolumeObject doesn't have a place for range min/max currentMixerRangeMax = mixer.range_high;
currentMixerRangeMin = mixer.range_low;
currentMixerRangeMax = mixer.range_high;
break;
}
}
} }
// Given a mixer's min/max and current value, return it as // Given a mixer's min/max and current value, return it as
@ -1394,7 +1530,7 @@
context.jamClient.SessionSetMasterLocalMix(dbValue); context.jamClient.SessionSetMasterLocalMix(dbValue);
// context.jamClient.SessionSetMasterLocalMix(sliderValue); // context.jamClient.SessionSetMasterLocalMix(sliderValue);
} else { } else {
context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixerMode()); context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixMode());
} }
} }

View File

@ -34,7 +34,7 @@
var startTime = null; var startTime = null;
var joinDeferred = null; var joinDeferred = null;
var mixerMode = MIX_MODES.GLOBAL; var mixerMode = MIX_MODES.PERSONAL;
server.registerOnSocketClosed(onWebsocketDisconnected); server.registerOnSocketClosed(onWebsocketDisconnected);
@ -110,7 +110,7 @@
// see if we already have tracks; if so, we need to run with these // see if we already have tracks; if so, we need to run with these
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
if(inputTracks.length > 0) { if(inputTracks.length > 0) {
logger.debug("on page enter, audio is already running") logger.debug("on page enter, tracks are already available")
sessionPageEnterDeferred.resolve(inputTracks); sessionPageEnterDeferred.resolve(inputTracks);
var deferred = sessionPageEnterDeferred; var deferred = sessionPageEnterDeferred;
sessionPageEnterDeferred = null; sessionPageEnterDeferred = null;
@ -506,6 +506,10 @@
return mixerMode == MIX_MODES.MASTER; return mixerMode == MIX_MODES.MASTER;
} }
function isPersonalMixMode() {
return mixerMode == MIX_MODES.PERSONAL;
}
function getMixMode() { function getMixMode() {
return mixerMode; return mixerMode;
} }
@ -668,6 +672,7 @@
this.inSession = inSession; this.inSession = inSession;
this.setMixerMode = setMixerMode; this.setMixerMode = setMixerMode;
this.isMasterMixMode = isMasterMixMode; this.isMasterMixMode = isMasterMixMode;
this.isPersonalMixMode = isPersonalMixMode;
this.getMixMode = getMixMode; this.getMixMode = getMixMode;
// ALERT HANDLERS // ALERT HANDLERS

View File

@ -42,12 +42,12 @@
localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2); localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2);
console.log("getUserTracks", localMusicTracks)
var trackObjects = []; var trackObjects = [];
for (i=0; i < localMusicTracks.length; i++) { for (i=0; i < localMusicTracks.length; i++) {
var track = {}; var track = {};
track.client_track_id = localMusicTracks[i].id; track.client_track_id = localMusicTracks[i].id;
track.client_resource_id = localMusicTracks[i].rid;
if(localMusicTracks[i].instrument_id === 0) { if(localMusicTracks[i].instrument_id === 0) {
track.instrument_id = context.JK.server_to_client_instrument_map["Other"].server_id; track.instrument_id = context.JK.server_to_client_instrument_map["Other"].server_id;

View File

@ -20,6 +20,8 @@
*/ */
renderVU: function(selector, userOptions) { renderVU: function(selector, userOptions) {
selector = $(selector); selector = $(selector);
/** /**
* The default options for rendering a VU * The default options for rendering a VU
*/ */
@ -36,9 +38,13 @@
templateSelector = "#template-vu-h"; templateSelector = "#template-vu-h";
} }
var templateSource = $(templateSelector).html(); var templateSource = $(templateSelector).html();
selector.empty();
selector.html(context._.template(templateSource, options, {variable: 'data'})); selector.each(function() {
var $track = $(this);
$track.empty();
$track.html(context._.template(templateSource, options, {variable: 'data'}));
})
}, },
/** /**
@ -48,41 +54,45 @@
updateVU: function ($selector, value) { updateVU: function ($selector, value) {
// There are 13 VU lights. Figure out how many to // There are 13 VU lights. Figure out how many to
// light based on the incoming value. // light based on the incoming value.
var countSelector = 'tr'; $selector.each(function() {
var horizontal = ($selector.find('table.horizontal').length); var $track = $(this)
if (horizontal) { var countSelector = 'tr';
countSelector = 'td'; var horizontal = ($track.find('table.horizontal').length);
} if (horizontal) {
countSelector = 'td';
var lightCount = $selector.find(countSelector).length;
var i = 0;
var state = 'on';
var lights = Math.round(value * lightCount);
var redSwitch = Math.round(lightCount * 0.6666667);
var $light = null;
var colorClass = 'vu-green-';
var lightSelectorPrefix = $selector.find('td.vu');
var thisLightSelector = null;
// Remove all light classes from all lights
var allLightsSelector = $selector.find('td.vulight');
$(allLightsSelector).removeClass('vu-green-off vu-green-on vu-red-off vu-red-on');
// Set the lights
for (i = 0; i < lightCount; i++) {
colorClass = 'vu-green-';
state = 'on';
if (i >= redSwitch) {
colorClass = 'vu-red-';
}
if (i >= lights) {
state = 'off';
} }
var lightIndex = horizontal ? i : lightCount - i - 1; var lightCount = $track.find(countSelector).length;
allLightsSelector.eq(lightIndex).addClass(colorClass + state); var i = 0;
} var state = 'on';
var lights = Math.round(value * lightCount);
var redSwitch = Math.round(lightCount * 0.6666667);
var $light = null;
var colorClass = 'vu-green-';
var lightSelectorPrefix = $track.find('td.vu');
var thisLightSelector = null;
// Remove all light classes from all lights
var allLightsSelector = $track.find('td.vulight');
$(allLightsSelector).removeClass('vu-green-off vu-green-on vu-red-off vu-red-on');
// Set the lights
for (i = 0; i < lightCount; i++) {
colorClass = 'vu-green-';
state = 'on';
if (i >= redSwitch) {
colorClass = 'vu-red-';
}
if (i >= lights) {
state = 'off';
}
var lightIndex = horizontal ? i : lightCount - i - 1;
allLightsSelector.eq(lightIndex).addClass(colorClass + state);
}
})
} }
}; };

View File

@ -373,7 +373,8 @@ class ApiMusicSessionsController < ApiController
params[:connection_id], params[:connection_id],
params[:instrument_id], params[:instrument_id],
params[:sound], params[:sound],
params[:client_track_id]) params[:client_track_id],
params[:client_resource_id])
respond_with @track, responder: ApiResponder, :status => 201, :location => api_session_track_detail_url(@track.connection.music_session, @track) respond_with @track, responder: ApiResponder, :status => 201, :location => api_session_track_detail_url(@track.connection.music_session, @track)
end end
@ -384,7 +385,8 @@ class ApiMusicSessionsController < ApiController
nil, nil,
params[:instrument_id], params[:instrument_id],
params[:sound], params[:sound],
params[:client_track_id]) params[:client_track_id],
params[:client_resource_id])
respond_with @track, responder: ApiResponder, :status => 200 respond_with @track, responder: ApiResponder, :status => 200

View File

@ -45,14 +45,14 @@ else
child(:connections => :participants) { child(:connections => :participants) {
collection @music_sessions, :object_root => false collection @music_sessions, :object_root => false
attributes :ip_address, :client_id, :joined_session_at, :audio_latency attributes :ip_address, :client_id, :joined_session_at, :audio_latency, :id
node :user do |connection| node :user do |connection|
{ :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state } { :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state }
end end
child(:tracks => :tracks) { child(:tracks => :tracks) {
attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :updated_at attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :client_resource_id, :updated_at
} }
} }

View File

@ -173,7 +173,7 @@ else
end end
child(:tracks => :tracks) { child(:tracks => :tracks) {
attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :updated_at attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :client_resource_id, :updated_at
} }
} }

View File

@ -163,3 +163,11 @@
The files associated with this recording will be deleted as soon as your client has uploaded your tracks and stream mix from this recording. The files associated with this recording will be deleted as soon as your client has uploaded your tracks and stream mix from this recording.
</div> </div>
</script> </script>
<script type="text/template" id="template-help-recording-controls-disabled">
{% if(data.recordingOpener) { %}
Switch <b>MIX:</b> to <b>Master</b> mode to have control over volume levels.
{% } else { %}
Only the person who opened the recording can control the volume levels.
{% } %}
</script>

View File

@ -151,8 +151,8 @@
<!-- Track Template --> <!-- Track Template -->
<script type="text/template" id="template-session-track"> <script type="text/template" id="template-session-track">
<div track-id="{trackId}" class="session-track track" client-id="{clientId}"> <div track-id="{trackId}" class="session-track track" client-id="{clientId}">
<div class="track-vu-left" mixer-id="{mixerId}_vul"></div> <div class="track-vu-left" mixer-id="{vuMixerId}_vul"></div>
<div class="track-vu-right" mixer-id="{mixerId}_vur"></div> <div class="track-vu-right" mixer-id="{vuMixerId}_vur"></div>
<div class="track-label">{name}</div> <div class="track-label">{name}</div>
<div id="div-track-close" track-id="{trackId}" class="track-close op30"> <div id="div-track-close" track-id="{trackId}" class="track-close op30">
<%= image_tag "content/icon_closetrack.png", {:width => 12, :height => 12} %> <%= image_tag "content/icon_closetrack.png", {:width => 12, :height => 12} %>
@ -173,7 +173,7 @@
</div> </div>
</div> </div>
--> -->
<div class="track-icon-mute {muteClass}" control="mute" mixer-id="{mixerId}"> <div class="track-icon-mute {muteClass}" control="mute" mixer-id="{muteMixerId}">
</div> </div>
<!-- TODO - connection class from curly param --> <!-- TODO - connection class from curly param -->
<div mixer-id="{mixerId}_connection" class="track-connection grey">CONNECTION</div> <div mixer-id="{mixerId}_connection" class="track-connection grey">CONNECTION</div>

View File

@ -272,6 +272,7 @@ FactoryGirl.define do
factory :track, :class => JamRuby::Track do factory :track, :class => JamRuby::Track do
sound "mono" sound "mono"
sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"} sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"}
sequence(:client_resource_id) { |n| "resource_id#{n}"}
end end
factory :video_source, :class => JamRuby::VideoSource do factory :video_source, :class => JamRuby::VideoSource do