* 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

@ -228,4 +228,5 @@ user_syncs_fix_dup_tracks_2408.sql
deletable_recordings.sql
jam_tracks.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.sound = track["sound"]
t.client_track_id = track["client_track_id"]
t.client_resource_id = track["client_resource_id"]
t.save # todo what if it fails?
self.tracks << t
end

View File

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

View File

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

View File

@ -99,7 +99,7 @@ describe Track do
track.id.should_not be_nil
connection.tracks.length.should == 1
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
found = tracks[0]
expect(found.id).to eq track.id

View File

@ -16,9 +16,18 @@
var logger = g.JK.logger;
function faderClick(e) {
e.stopPropagation();
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');
var offset = $fader.offset();
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
@ -117,6 +126,14 @@
$draggingFaderHandle = $(this);
$draggingFader = $draggingFaderHandle.closest('div[control="fader"]');
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) {
@ -162,13 +179,15 @@
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({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
containment: "parent",
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
if ("style" in options) {

View File

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

View File

@ -18,6 +18,9 @@
var myTracks = [];
var masterMixers = [];
var personalMixers = [];
var allMixers = {};
var mixersByResourceId = {};
var mixersByTrackId = {};
var configureTrackDialog;
var addNewGearDialog;
var localRecordingsDialog = null;
@ -29,7 +32,7 @@
var currentMixerRangeMax = null;
var lookingForMixersCount = 0;
var lookingForMixersTimer = null;
var lookingForMixers = {};
var lookingForMixers = [];
var $recordingTimer = null;
var recordingTimerInterval = null;
var startTimeDate = null;
@ -454,15 +457,44 @@
* you must iterate. Convenience method to locate a particular
* mixer by id.
*/
function getMixer(mixerId, mixMode) {
var foundMixer = null;
var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers;
$.each(mixers, function(index, mixer) {
if (mixer.id === mixerId) {
foundMixer = mixer;
}
});
return foundMixer;
function getMixer(mixerId) {
return allMixers[mixerId];
}
function getMixerByResourceId(resourceId, mode) {
var mixerPair = mixersByResourceId[resourceId];
if(!mixerPair) {return null;}
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() {
@ -492,19 +524,58 @@
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() {
masterMixers = context.jamClient.SessionGetAllControlState(true);
//var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(masterMixerIds, true)});
//masterMixers = masterMixerIds.mixers;
personalMixers = context.jamClient.SessionGetAllControlState(false);
//holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(personalMixerIds, false)});
//personalMixers = personalMixerIds.mixers;
console.log("masterMixers", masterMixers)
console.log("personalMixers", personalMixers)
//logger.debug("masterMixers", masterMixers)
//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
/**
@ -565,7 +636,7 @@
//logger.debug("clientId", clientId, "groupIds", groupIds, "mixers", mixers)
var foundMixers = {};
var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers;
console.log("_groupedMixersForClientId", mixers)
// console.log("_groupedMixersForClientId", mixers)
$.each(mixers, function(index, mixer) {
if (mixer.client_id === clientId) {
for (var i=0; i<groupIds.length; i++) {
@ -694,16 +765,20 @@
$voiceChatGain.on('fader_change', faderChanged);
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent);
//if (mixer.mute) {
_toggleVisualMuteControl($voiceChatMute, mixer, null);
_toggleVisualMuteControl($voiceChatMute, mixer.mute);
//}
}
});
}
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) {
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();
@ -791,7 +866,13 @@
trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass;
trackData.mixerId = mixer.id;
trackData.vuMixerId = mixer.id;
trackData.muteMixerId = mixer.id;
if(sessionModel.isPersonalMixMode() || !isOpener) {
trackData.recordingDisabled = true;
trackData.recordingOpener = isOpener;
}
_addMediaTrack(trackData);
});
@ -817,16 +898,108 @@
var mixer = $muteControl.data('mixer')
var oppositeMixer = $muteControl.data('opposite-mixer')
if(mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || mixer.group_id == ChannelGroupIds.MediaTrackGroup) {
context.jamClient.SessionSetControlState(mixer.id, sessionModel.isMasterMixMode());
context.jamClient.SessionSetControlState(mixer.id, !sessionModel.isMasterMixMode());
logger.debug("muting tracks. current mixer id=" + mixer.id + ", opposite mixer id=" + oppositeMixer.id)
var mixerPair = {}
if(sessionModel.isMasterMixMode()) {
mixerPair.master = mixer;
mixerPair.personal = oppositeMixer;
}
else if(mixer.group_id == ChannelGroupIds.UserMusicInputGroup || mixer.group_id == ChannelGroupIds.PeerAudioInputMusicGroup) {
context.jamClient.SessionSetControlState(mixer.id, sessionModel.isMasterMixMode());
context.jamClient.SessionSetControlState(oppositeMixer.id, !sessionModel.isMasterMixMode());
else {
mixerPair.master = oppositeMixer;
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() {
myTracks = [];
@ -834,6 +1007,8 @@
// Participants are here now, but the mixers don't update right away.
// Draw tracks from participants, then setup timers to look for the
// mixers that go with those participants, if they're missing.
lookingForMixers = [] // clear this back out as we are restarting from scratch
lookingForMixersCount = 0;
$.each(sessionModel.participants(), function(index, participant) {
@ -842,19 +1017,19 @@
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
$.each(participant.tracks, function(index, track) {
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
var myTrack = false;
// Default trackData to participant + no Mixer state.
var trackData = {
trackId: track.id,
connection_id: track.connection_id,
client_track_id: track.client_track_id,
client_resource_id: track.client_resource_id,
clientId: participant.client_id,
name: name,
instrumentIcon: instrumentIcon,
@ -867,53 +1042,15 @@
preMasteredClass: ""
};
// This is the likely cause of multi-track problems.
// This should really become _mixersForClientId and return a list.
// 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 mixerData = findMixerForTrack(participant.client_id, track, myTrack)
var mixer = mixerData.mixer;
var vuMixer = mixerData.vuMixer;
var muteMixer = mixerData.muteMixer;
var oppositeMixer = mixerData.oppositeMixer;
var currentMixers = _groupedMixersForClientId(
participant.client_id,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, sessionModel.getMixMode());
var oppositeMixers = _groupedMixersForClientId(
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;
if (mixer && oppositeMixer) {
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
@ -925,13 +1062,15 @@
trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass;
trackData.mixerId = mixer.id;
trackData.vuMixerId = vuMixer.id;
trackData.oppositeMixer = oppositeMixer;
trackData.muteMixerId = muteMixer.id;
trackData.noaudio = false;
trackData.group_id = mixer.group_id;
trackData.oppositeMixer = oppositeMixer;
context.jamClient.SessionSetUserName(participant.client_id,name);
} else { // No mixer to match, yet
lookingForMixers[track.id] = participant.client_id;
lookingForMixers.push({track: track, clientId: participant.client_id})
trackData.noaudio = true;
if (!(lookingForMixersTimer)) {
logger.debug("waiting for mixer to show up for track: " + track.id)
@ -940,14 +1079,12 @@
}
var allowDelete = myTrack && index > 0;
_addTrack(allowDelete, trackData, mixer);
_addTrack(allowDelete, trackData, mixer, oppositeMixer);
// Show settings icons only for my tracks
if (myTrack) {
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);
}
function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent, groupId) {
function connectTrackToMixer(trackSelector, track, mixerId, gainPercent, groupId) {
var vuOpts = $.extend({}, trackVuOpts);
var faderOpts = $.extend({}, trackFaderOpts);
faderOpts.faderId = mixerId;
@ -963,16 +1100,21 @@
var vuRightSelector = trackSelector + " .track-vu-right";
var faderSelector = trackSelector + " .track-gain";
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);
// Set mixer-id attributes and render VU/Fader
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);
$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);
// Set gain position
context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent);
$fader.on('fader_change', faderChanged);
return $track;
}
// Function called on an interval when participants change. Mixers seem to
@ -984,83 +1126,61 @@
_updateMixers();
var usedMixers = {};
var keysToDelete = [];
for (var key in lookingForMixers) {
var clientId = lookingForMixers[key];
var currentMixers = _groupedMixersForClientId(
clientId,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, sessionModel.getMixMode());
context._.each(lookingForMixers, function(data) {
var clientId = data.clientId;
var track = data.track;
var oppositeMixers = _groupedMixersForClientId(
clientId,
[
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.PeerAudioInputMusicGroup,
ChannelGroupIds.UserMusicInputGroup
],
usedMixers, !sessionModel.getMixMode());
var myTrack = app.clientId == clientId;
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]
}
var mixerData = findMixerForTrack(clientId, track, myTrack)
var mixer = mixerData.mixer;
var oppositeMixer = mixerData.oppositeMixer;
var vuMixer = mixerData.vuMixer;
var muteMixer = mixerData.muteMixer;
if (mixer && oppositeMixer) {
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
logger.debug("found mixer=" + mixer.id + ", participant=" + participant)
usedMixers[mixer.id] = true;
keysToDelete.push(data);
var gainPercent = percentFromMixerValue(
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) || {name:'unknown'}).name;
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)
}
}
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++) {
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 = 0;
lookingForMixers = {};
lookingForMixers = []
context.clearTimeout(lookingForMixersTimer);
lookingForMixersTimer = null;
}
@ -1068,7 +1188,7 @@
// Given a mixerID and a value between 0.0-1.0,
// 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,
// update both sides, otherwise do nothing.
@ -1076,7 +1196,7 @@
var selector;
var pureMixerId = mixerId.replace("_vul", "");
pureMixerId = pureMixerId.replace("_vur", "");
var mixer = getMixer(pureMixerId, sessionModel.getMixMode());
var mixer = getMixer(pureMixerId);
if (mixer) {
if (!(mixer.stereo)) { // mono track
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 $destination = $(parentSelector);
@ -1108,7 +1228,7 @@
var audioOverlay = $('.disabled-track-overlay', newTrack);
var $trackIconMute = newTrack.find('.track-icon-mute')
$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
$destination.append(newTrack);
@ -1116,7 +1236,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, trackData.group_id);
connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id);
var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]');
if (!allowDelete) {
@ -1148,8 +1268,11 @@
// 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, 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);
}
@ -1187,62 +1310,69 @@
//_toggleVisualMuteControl($muteControl, isMuted);
}
function handleBridgeCallback() {
function handleBridgeCallback(vuData) {
var j;
var eventName = null;
var mixerId = null;
var value = null;
var tuples = arguments.length / 3;
for (var i=0; i<tuples; i++) {
eventName = arguments[3*i];
mixerId = arguments[(3*i)+1];
value = arguments[(3*i)+2];
var vuVal = 0.0;
if (eventName === 'left_vu' || eventName === 'right_vu') {
// TODO - no guarantee range will be -80 to 20. Get from the
// GetControlState for this mixer which returns min/max
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0
vuVal = (value + 80) / 100;
if (eventName === 'left_vu') {
mixerId = mixerId + "_vul";
} else {
mixerId = mixerId + "_vur";
}
_updateVU(mixerId, vuVal);
} else if (eventName === 'connection_status') {
// Connection Quality Change
var connectionClass = 'green';
if (value < 7) {
connectionClass = 'yellow';
}
if (value < 4) {
connectionClass = 'red';
}
var clientId = _clientIdForUserInputMixer(mixerId, sessionModel.getMixMode());
var vuInfo = null;
for (j = 0; j < vuData.length; j++) {
vuInfo = vuData[j];
var eventName = vuInfo[0];
var vuVal = 0.0;
if(eventName === "vu") {
var mixerId = vuInfo[1];
var leftValue = vuInfo[2];
var leftClipping = vuInfo[3];
var rightValue = vuInfo[4];
var rightClipping = vuInfo[5];
// TODO - no guarantee range will be -80 to 20. Get from the
// GetControlState for this mixer which returns min/max
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0
_updateVU(mixerId + "_vul", (leftValue + 80) / 100, leftClipping);
_updateVU(mixerId + "_vur", (rightValue + 80) / 100, rightClipping);
}
else if(eventName === 'connection_status') {
var mixerId = vuInfo[1];
var value = vuInfo[2];
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 {
// Examples of other events
// Add media file track: "add", "The_Abyss_4T", 0
logger.debug('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
// Connection Quality Change
var connectionClass = 'green';
if (value < 7) {
connectionClass = 'yellow';
}
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) {
var sessionId = $(evt.currentTarget).attr("action-id");
@ -1265,8 +1395,8 @@
sessionModel.deleteTrack(sessionId, trackId);
}
function _toggleVisualMuteControl($control, currentMixer, oppositeMixer) {
if (currentMixer.mute) {
function _toggleVisualMuteControl($control, mute) {
if (mute) {
$control.removeClass('enabled');
$control.addClass('muted');
} else {
@ -1275,10 +1405,14 @@
}
}
function _toggleAudioMute(mixerId, muting) {
function _toggleAudioMute(mixerId, muting, mode) {
fillTrackVolumeObject(mixerId);
context.trackVolumeObject.mute = muting;
context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixerMode());
if(mode === undefined) {
mode = sessionModel.getMixMode();
}
context.jamClient.SessionSetControlState(mixerId, mode);
}
function showMuteDropdowns($control) {
@ -1292,20 +1426,28 @@
// track icons have a special mute behavior
if($control.is('.track-icon-mute')) {
$.each(mixerIds, function(i,v) {
if(muting) {
// show insta-dropdown providing two options for mute
showMuteDropdowns($control);
}
else {
_toggleAudioMute(v, muting);
}
});
if(!muting) {
_toggleVisualMuteControl($control, muting);
var recordingDisabled = $control.data('recording-disabled');
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 {
$.each(mixerIds, function(i,v) {
@ -1319,29 +1461,23 @@
function fillTrackVolumeObject(mixerId, broadcast) {
_updateMixers();
var mixer = null;
var _broadcast = true;
if (broadcast !== undefined) {
_broadcast = broadcast;
}
for (var i=0; i<mixers.length; i++) {
mixer = mixers[i];
if (mixer.id === mixerId) {
context.trackVolumeObject.clientID = mixer.client_id;
context.trackVolumeObject.broadcast = _broadcast;
context.trackVolumeObject.master = mixer.master;
context.trackVolumeObject.monitor = mixer.monitor;
context.trackVolumeObject.mute = mixer.mute;
context.trackVolumeObject.name = mixer.name;
context.trackVolumeObject.record = mixer.record;
context.trackVolumeObject.volL = mixer.volume_left;
context.trackVolumeObject.volR = mixer.volume_right;
// trackVolumeObject doesn't have a place for range min/max
currentMixerRangeMin = mixer.range_low;
currentMixerRangeMax = mixer.range_high;
break;
}
}
var mixer = getMixer(mixerId);
context.trackVolumeObject.clientID = mixer.client_id;
context.trackVolumeObject.broadcast = _broadcast;
context.trackVolumeObject.master = mixer.master;
context.trackVolumeObject.monitor = mixer.monitor;
context.trackVolumeObject.mute = mixer.mute;
context.trackVolumeObject.name = mixer.name;
context.trackVolumeObject.record = mixer.record;
context.trackVolumeObject.volL = mixer.volume_left;
context.trackVolumeObject.volR = mixer.volume_right;
// trackVolumeObject doesn't have a place for range min/max
currentMixerRangeMin = mixer.range_low;
currentMixerRangeMax = mixer.range_high;
}
// Given a mixer's min/max and current value, return it as
@ -1394,7 +1530,7 @@
context.jamClient.SessionSetMasterLocalMix(dbValue);
// context.jamClient.SessionSetMasterLocalMix(sliderValue);
} else {
context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixerMode());
context.jamClient.SessionSetControlState(mixerId, sessionModel.getMixMode());
}
}

View File

@ -34,7 +34,7 @@
var startTime = null;
var joinDeferred = null;
var mixerMode = MIX_MODES.GLOBAL;
var mixerMode = MIX_MODES.PERSONAL;
server.registerOnSocketClosed(onWebsocketDisconnected);
@ -110,7 +110,7 @@
// see if we already have tracks; if so, we need to run with these
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
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);
var deferred = sessionPageEnterDeferred;
sessionPageEnterDeferred = null;
@ -506,6 +506,10 @@
return mixerMode == MIX_MODES.MASTER;
}
function isPersonalMixMode() {
return mixerMode == MIX_MODES.PERSONAL;
}
function getMixMode() {
return mixerMode;
}
@ -668,6 +672,7 @@
this.inSession = inSession;
this.setMixerMode = setMixerMode;
this.isMasterMixMode = isMasterMixMode;
this.isPersonalMixMode = isPersonalMixMode;
this.getMixMode = getMixMode;
// ALERT HANDLERS

View File

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

View File

@ -20,6 +20,8 @@
*/
renderVU: function(selector, userOptions) {
selector = $(selector);
/**
* The default options for rendering a VU
*/
@ -36,9 +38,13 @@
templateSelector = "#template-vu-h";
}
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) {
// There are 13 VU lights. Figure out how many to
// light based on the incoming value.
var countSelector = 'tr';
var horizontal = ($selector.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';
$selector.each(function() {
var $track = $(this)
var countSelector = 'tr';
var horizontal = ($track.find('table.horizontal').length);
if (horizontal) {
countSelector = 'td';
}
var lightIndex = horizontal ? i : lightCount - i - 1;
allLightsSelector.eq(lightIndex).addClass(colorClass + state);
}
var lightCount = $track.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 = $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[:instrument_id],
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)
end
@ -384,7 +385,8 @@ class ApiMusicSessionsController < ApiController
nil,
params[:instrument_id],
params[:sound],
params[:client_track_id])
params[:client_track_id],
params[:client_resource_id])
respond_with @track, responder: ApiResponder, :status => 200

View File

@ -45,14 +45,14 @@ else
child(:connections => :participants) {
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|
{ :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
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
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

@ -162,4 +162,12 @@
<div class="file-sync-delayed-deletion">
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>
</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 -->
<script type="text/template" id="template-session-track">
<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-right" mixer-id="{mixerId}_vur"></div>
<div class="track-vu-left" mixer-id="{vuMixerId}_vul"></div>
<div class="track-vu-right" mixer-id="{vuMixerId}_vur"></div>
<div class="track-label">{name}</div>
<div id="div-track-close" track-id="{trackId}" class="track-close op30">
<%= image_tag "content/icon_closetrack.png", {:width => 12, :height => 12} %>
@ -173,7 +173,7 @@
</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>
<!-- TODO - connection class from curly param -->
<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
sound "mono"
sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"}
sequence(:client_resource_id) { |n| "resource_id#{n}"}
end
factory :video_source, :class => JamRuby::VideoSource do