Compare commits

...

13 Commits

Author SHA1 Message Date
Seth Call 49caddd79f * support recording showing up everywhere 2015-07-02 11:29:32 -05:00
Seth Call c96a19d1b4 add webcam viewer 2015-07-02 11:02:14 -05:00
Seth Call 6f9d6c2fb5 * cleanup notifications 2015-07-02 10:43:16 -05:00
Seth Call 3130ab31a2 * fix webcam_viewer init problem 2015-07-01 18:19:32 -05:00
Seth Call 512e89074b * wip 2015-07-01 15:24:33 -05:00
Seth Call 7e6e79ccca * wip 2015-07-01 13:25:31 -05:00
Seth Call bc0add9aa0 * wip 2015-06-30 20:25:41 -05:00
Seth Call 4be1117a02 * wip 2015-06-26 13:03:32 -05:00
Seth Call 910b052204 *wip 2015-06-20 08:56:52 -05:00
Seth Call 7a803bb682 * wip 2015-06-17 19:03:01 -05:00
Seth Call a769dd37bf * wip 2015-06-15 19:35:59 -05:00
Seth Call c28d8f3e7f * wip 2015-06-15 13:44:23 -05:00
Seth Call 60302fc1fe * wip 2015-06-10 15:00:47 -05:00
145 changed files with 8111 additions and 307 deletions

View File

@ -94,6 +94,7 @@ gem 'react-rails', '~> 1.0'
source 'https://rails-assets.org' do
gem 'rails-assets-reflux'
gem 'rails-assets-classnames'
end
group :development, :test do

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -198,50 +198,56 @@
function loggedIn(header, payload) {
server.signedIn = true;
server.clientID = payload.client_id;
server.publicIP = payload.public_ip;
// reason for setTimeout:
// loggedIn causes an absolute ton of initialization to happen, and errors sometimes happen
// but because loggedIn(header,payload) is a callback from a websocket, the browser doesn't show a stack trace...
if (context.jamClient !== undefined) {
context.jamClient.connected = true;
context.jamClient.clientID = server.clientID;
}
setTimeout(function() {
server.signedIn = true;
server.clientID = payload.client_id;
server.publicIP = payload.public_ip;
clearConnectTimeout();
if (context.jamClient !== undefined) {
context.jamClient.connected = true;
context.jamClient.clientID = server.clientID;
}
heartbeatStateReset();
clearConnectTimeout();
app.clientId = payload.client_id;
heartbeatStateReset();
if(isClientMode()) {
// tell the backend that we have logged in
context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
$.cookie('client_id', payload.client_id);
}
app.clientId = payload.client_id;
// this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
// where there is no device on startup for the current profile.
// So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
// it to go through unless we've passed through .OnLoggedIn
server.connected = true;
server.reconnecting = false;
server.connecting = false;
initialConnectAttempt = false;
if (isClientMode()) {
// tell the backend that we have logged in
context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
$.cookie('client_id', payload.client_id);
}
heartbeatMS = payload.heartbeat_interval * 1000;
connection_expire_time = payload.connection_expire_time * 1000;
logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's');
heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
connectDeferred.resolve();
$self.triggerHandler(EVENTS.CONNECTION_UP)
// this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
// where there is no device on startup for the current profile.
// So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
// it to go through unless we've passed through .OnLoggedIn
server.connected = true;
server.reconnecting = false;
server.connecting = false;
initialConnectAttempt = false;
activeElementEvent('afterConnect', payload);
heartbeatMS = payload.heartbeat_interval * 1000;
connection_expire_time = payload.connection_expire_time * 1000;
logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's');
heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
connectDeferred.resolve();
$self.triggerHandler(EVENTS.CONNECTION_UP)
if(payload.client_update && context.JK.ClientUpdateInstance) {
context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size)
}
activeElementEvent('afterConnect', payload);
if (payload.client_update && context.JK.ClientUpdateInstance) {
context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size)
}
}, 0)
}
function heartbeatAck(header, payload) {

View File

@ -161,7 +161,7 @@
/**
setTimeout(function() {
var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 2);
var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 4);
// this is some ugly logic coming up, here's why:
// we need the id (guid) that the backend generated for the new track we just added

View File

@ -38,6 +38,7 @@
//= require jquery.exists
//= require jquery.payment
//= require jquery.visible
//= require classnames
//= require reflux
//= require howler.core.js
//= require jstz
@ -54,11 +55,12 @@
//= require react
//= require react_ujs
//= require react-init
//= require react-components
//= require web/signup_helper
//= require web/signin_helper
//= require web/signin
//= require web/tracking
//= require webcam_viewer
//= require react-components
//= require_directory .
//= require_directory ./dialog
//= require_directory ./wizard

View File

@ -37,20 +37,16 @@
}
function onGenericEvent(type, text) {
context.setTimeout(function() {
var alert = ALERT_TYPES[type];
if(alert && alert.title) {
app.notify({
"title": ALERT_TYPES[type].title,
"text": text,
"icon_url": "/assets/content/icon_alert_big.png"
});
}
else {
logger.debug("Unhandled Backend Event type %o, data %o", type, text)
}
}, 1);
var alert = ALERT_TYPES[type];
if(alert && alert.title) {
context.NotificationActions.backendNotification({msg: alert.title, detail: alert.message, backend_detail:text, help: alert.help})
}
else {
logger.debug("Unhandled Backend Event type %o, data %o", type, text)
}
}
function alertCallback(type, text) {
@ -77,8 +73,11 @@
}
if (type === 2) { // BACKEND_MIXER_CHANGE
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
context.MixerActions.mixersChanged(type, text)
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
}
else if (type === ALERT_NAMES.NO_VALID_AUDIO_CONFIG) { // NO_VALID_AUDIO_CONFIG
if(context.JK.GearUtilsInstance && context.JK.GearUtilsInstance.isRestartingAudio()) {
@ -101,28 +100,36 @@
onStunEvent();
}
else if (type === 26) { // DEAD_USER_REMOVE_EVENT
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onDeadUserRemove(type, text);
MixerActions.deadUserRemove(text);
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onDeadUserRemove(type, text);
}
else if (type === 27) { // WINDOW_CLOSE_BACKGROUND_MODE
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onWindowBackgrounded(type, text);
SessionActions.windowBackgrounded()
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onWindowBackgrounded(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_FAIL) {
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onBroadcastFailure(type, text);
SessionActions.broadcastFailure(text)
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onBroadcastFailure(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_ACTIVE) {
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onBroadcastSuccess(type, text);
SessionActions.broadcastSuccess(text)
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onBroadcastSuccess(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_STOPPED) {
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onBroadcastStopped(type, text);
SessionActions.broadcastStopped(text)
//if(context.JK.CurrentSessionModel)
//context.JK.CurrentSessionModel.onBroadcastStopped(type, text);
}
else if(type === ALERT_NAMES.RECORD_PLAYBACK_STATE) {
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
//if(context.JK.CurrentSessionModel)
// context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
context.MediaPlaybackActions.playbackStateChange(text);
}
else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) &&
(ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) {

View File

@ -216,7 +216,7 @@
updateUri = uri;
updateSize = size;
if(context.JK.CurrentSessionModel && context.JK.CurrentSessionModel.inSession()) {
if(context.SessionStore.inSession()) {
logger.debug("deferring client update because in session")
return;
}

View File

@ -3,7 +3,7 @@
$ = jQuery
context = window
context.JK ||= {};
broadcastActions = context.JK.Actions.Broadcast
broadcastActions = @BroadcastActions
context.JK.ClientInit = class ClientInit
constructor: () ->
@ -21,7 +21,10 @@ context.JK.ClientInit = class ClientInit
this.watchBroadcast()
checkBroadcast: () =>
broadcastActions.load.triggerPromise()
broadcastActions.load.triggerPromise().catch(() ->
false
)
watchBroadcast: () =>
if context.JK.currentUserId

View File

@ -120,11 +120,11 @@
openingRecording = true;
// tell the server we are about to start a recording
rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id})
rest.startPlayClaimedRecording({id: context.SessionStore.id(), claimed_recording_id: claimedRecording.id})
.done(function(response) {
// update session info
context.JK.CurrentSessionModel.updateSession(response);
context.SessionActions.updateSession.trigger(response);
var recordingId = $(this).attr('data-recording-id');
var openRecordingResult = context.jamClient.OpenRecording(claimedRecording.recording);
@ -138,7 +138,7 @@
"icon_url": "/assets/content/icon_alert_big.png"
});
rest.stopPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id})
rest.stopPlayClaimedRecording({id: context.SessionStore.id(), claimed_recording_id: claimedRecording.id})
.fail(function(jqXHR) {
app.notify({
"title": "Couldn't Stop Recording Playback",

View File

@ -85,7 +85,7 @@
var backingTrack = $(this).data('server-model');
// tell the server we are about to open a backing track:
rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name})
rest.openBackingTrack({id: context.SessionStore.id(), backing_track_path: backingTrack.name})
.done(function(response) {
var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name, false);
@ -99,7 +99,7 @@
// else {
// logger.error("unable to open backing track")
// }
context.JK.CurrentSessionModel.refreshCurrentSession(true);
context.SessionActions.syncWithServer()
})
.fail(function(jqXHR) {

View File

@ -86,10 +86,10 @@
var jamTrack = $(this).data('server-model');
// tell the server we are about to open a jamtrack
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
rest.openJamTrack({id: context.SessionStore.id(), jam_track_id: jamTrack.id})
.done(function(response) {
$dialog.data('result', {success:true, jamTrack: jamTrack})
context.JK.CurrentSessionModel.updateSession(response);
context.SessionActions.updateSession.trigger(response);
app.layout.closeDialog('open-jam-track-dialog');
})
.fail(function(jqXHR) {

View File

@ -54,6 +54,7 @@
function events() {
$('#btn-rate-session-cancel', $scopeSelector).click(function(evt) {
closeDialog();
return false;
});
$('#btn-rate-session-up', $scopeSelector).click(function(evt) {
if ($(this).hasClass('selected')) {

View File

@ -57,7 +57,7 @@
});
}
else if (localResult.aggregate_state == 'PARTIALLY_MISSING') {
logger.error("unable to open recording due to some missing tracks: %o", localResults);
logger.error("unable to open recording due to some missing tracks: %o", recording, localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "Some of your tracks associated with the recording are missing. This is a bug in the application.",

View File

@ -0,0 +1,36 @@
$ = jQuery
context = window
context.JK ||= {}
MIX_MODES = context.JK.MIX_MODES
context.JK.SessionMasterMixDialog = class SessionMasterMixDialog
constructor: (@app) ->
@rest = context.JK.Rest()
@logger = context.JK.logger
@screen = null
@dialogId = 'session-master-mix-dialog'
@dialog = null
@closeBtn = null
initialize:() =>
dialogBindings =
'beforeShow' : @beforeShow
'afterShow' : @afterShow
'afterHide' : @afterHide
@dialog = $('[layout-id="' + @dialogId + '"]')
@app.bindDialog(@dialogId, dialogBindings)
@content = @dialog.find(".dialog-inner")
beforeShow:() =>
@logger.debug("session-master-mix-dlg: beforeShow")
context.jamClient.SetMixerMode(MIX_MODES.MASTER)
afterShow:() =>
@logger.debug("session-master-mix-dlg: afterShow")
afterHide:() =>
context.jamClient.SetMixerMode(MIX_MODES.PERSONAL)

View File

@ -6,11 +6,12 @@
var gearUtils = context.JK.GearUtilsInstance;
var $dialog;
var $screen = $('#session-settings');
var $selectedFilenames = $screen.find('#selected-filenames');
var $uploadSpinner = $screen.find('.upload-spinner');
var $selectedFilenames = $('#settings-selected-filenames');
//var $selectedFilenames = $screen.find('#selected-filenames');
var $uploadSpinner = $screen.find('.spinner-small');
//var $selectedFilenames = $('#settings-selected-filenames');
var $inputFiles = $screen.find('#session-select-files');
var $btnSelectFiles = $screen.find('.btn-select-files');
var $inputBox = $screen.find('.inputbox')
var rest = new JK.Rest();
var sessionId;
@ -21,7 +22,7 @@
context.JK.GenreSelectorHelper.render('#session-settings-genre');
$dialog = $('[layout-id="session-settings"]');
var currentSession = sessionScreen.getCurrentSession();
var currentSession = context.SessionStore.currentSession;
sessionId = currentSession.id;
// id
@ -65,13 +66,21 @@
$('#session-settings-fan-access').val('listen-chat-band');
}
// notation files
/**
// notation files in the account screen. ugh.
$selectedFilenames.empty();
for (var i=0; i < currentSession.music_notations.length; i++) {
var notation = currentSession.music_notations[i];
$selectedFilenames.append('<a href="' + notation.file_url + '" rel="external">' + notation.file_name + '</a>&nbsp;');
}*/
$inputBox.empty();
for (var i=0; i < currentSession.music_notations.length; i++) {
var notation = currentSession.music_notations[i];
addNotation(notation)
}
context.JK.dropdown($('#session-settings-language'));
context.JK.dropdown($('#session-settings-musician-access'));
context.JK.dropdown($('#session-settings-fan-access'));
@ -81,6 +90,29 @@
$('#session-settings-fan-access').easyDropDown(easyDropDownState)
}
function addNotation(notation) {
var $notation = $('<div class="notation-entry"><div>' + notation.file_name + '</div><a href="#" data-id="' + notation.id + '">X</a></div>')
$notation.find('a').on('click', function(e) {
if($(this).attr('data-deleting')) {
// ignore duplicate delete attempts
return false;
}
$(this).attr('data-deleting', true)
var $notationEntry = $(this).closest('.notation-entry').find('div').text('deleting...')
rest.deleteMusicNotation({id: notation.id})
.done(function() {
$notation.remove()
})
.fail(app.ajaxError)
return false;
})
$inputBox.append($notation);
}
function saveSettings(evt) {
var data = {};
@ -111,16 +143,14 @@
data.fan_access = false;
data.fan_chat = false;
}
else if (fanAccess == 'listen-chat-each') {
data.fan_access = true;
data.fan_chat = false;
}
else if (fanAccess == 'listen-chat-band') {
else if (fanAccess == 'listen-chat') {
data.fan_access = true;
data.fan_chat = true;
}
rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved);
return false;
}
function uploadNotations(notations) {
@ -177,7 +207,7 @@
}
})
.always(function() {
$btnSelectFiles.text('SELECT FILES...').data('uploading', null)
$btnSelectFiles.text('ADD FILES...').data('uploading', null)
$uploadSpinner.hide();
});
}
@ -203,10 +233,9 @@
else {
// upload as soon as user picks their files.
uploadNotations($inputFiles.get(0).files)
.done(function() {
context._.each(fileNames, function(fileName) {
var $text = $('<span>').text(fileName);
$selectedFilenames.append($text);
.done(function(response) {
context._.each(response, function(notation) {
addNotation(notation)
})
})
}
@ -225,13 +254,13 @@
function settingsSaved(response) {
// No response returned from this call. 204.
sessionScreen.refreshCurrentSession(true);
context.SessionActions.syncWithServer()
app.layout.closeDialog('session-settings');
}
function events() {
$('#session-settings-dialog-submit').on('click', saveSettings);
$('#session-settings-dialog').on('submit', saveSettings)
$inputFiles.on('change', changeSelectedFiles);
$btnSelectFiles.on('click', toggleSelectFiles);
}

View File

@ -11,6 +11,7 @@
var $draggingFaderHandle = null;
var $draggingFader = null;
var $floater = null;
var draggingOrientation = null;
var logger = g.JK.logger;
@ -20,6 +21,7 @@
e.stopPropagation();
var $fader = $(this);
var floaterConvert = $fader.data('floaterConverter')
var sessionModel = window.JK.CurrentSessionModel || null;
var mediaControlsDisabled = $fader.data('media-controls-disabled');
@ -43,7 +45,7 @@
}
}
draggingOrientation = $fader.attr('orientation');
draggingOrientation = $fader.attr('data-orientation');
var offset = $fader.offset();
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
@ -53,6 +55,10 @@
return false;
}
if(floaterConvert) {
window.JK.FaderHelpers.setFloaterValue($fader.find('.floater'), floaterConvert(faderPct))
}
$fader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: false})
setHandlePosition($fader, faderPct);
@ -61,9 +67,9 @@
function setHandlePosition($fader, value) {
var ratio, position;
var $handle = $fader.find('div[control="fader-handle"]');
var $handle = $fader.find('div[data-control="fader-handle"]');
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
var handleCssAttribute = getHandleCssAttribute($fader);
// required because this method is entered directly when from a callback
@ -81,7 +87,7 @@
}
function faderValue($fader, e, offset) {
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
var getPercentFunction = getVerticalFaderPercent;
var relativePosition = offset.top;
if (orientation && orientation == 'horizontal') {
@ -92,7 +98,7 @@
}
function getHandleCssAttribute($fader) {
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
return (orientation === 'horizontal') ? 'left' : 'top';
}
@ -134,12 +140,34 @@
return false;
}
// simple snap feature to stick to the mid point
if(faderPct > 46 && faderPct < 54 && $draggingFader.data('snap')) {
faderPct = 50
var orientation = $draggingFader.attr('data-orientation');
if(orientation == 'horizontal') {
var width = $draggingFader.width()
var left = width / 2
ui.position.left = left
}
else {
var height = $draggingFader.height()
var top = height / 2
ui.position.top = top
}
}
var floaterConvert = $draggingFaderHandle.data('floaterConverter')
if(floaterConvert && $floater) {
window.JK.FaderHelpers.setFloaterValue($floater, floaterConvert(faderPct))
}
$draggingFader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: true})
}
function onFaderDragStart(e, ui) {
$draggingFaderHandle = $(this);
$draggingFader = $draggingFaderHandle.closest('div[control="fader"]');
$draggingFader = $draggingFaderHandle.closest('div[data-control="fader"]');
$floater = $draggingFaderHandle.find('.floater')
draggingOrientation = $draggingFader.attr('orientation');
var mediaControlsDisabled = $draggingFaderHandle.data('media-controls-disabled');
@ -210,12 +238,12 @@
selector.html(g._.template(templateSource, options));
selector.find('div[control="fader"]')
selector.find('div[data-control="fader"]')
.data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
selector.find('div[control="fader-handle"]').draggable({
selector.find('div[data-control="fader-handle"]').draggable({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
@ -233,6 +261,43 @@
}
},
renderFader2: function (selector, userOptions, floaterConverter) {
selector = $(selector);
if (userOptions === undefined) {
throw ("renderFader: userOptions is required");
}
var renderDefaults = {
faderType: "vertical"
};
var options = $.extend({}, renderDefaults, userOptions);
selector.find('div[data-control="fader"]')
.data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
.data('floaterConverter', floaterConverter)
.data('snap', userOptions.snap)
selector.find('div[data-control="fader-handle"]').draggable({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
containment: "parent",
axis: options.faderType === 'horizontal' ? 'x' : 'y'
}).data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
.data('floaterConverter', floaterConverter)
.data('snap', userOptions.snap)
// Embed any custom styles, applied to the .fader below selector
if ("style" in options) {
for (var key in options.style) {
selector.find(' .fader').css(key, options.style[key]);
}
}
},
convertLinearToDb: function (input) {
// deal with extremes better
@ -263,27 +328,48 @@
// composite function resembling audio taper
if (input <= 1) { return -80; }
if (input <= 28) { return (2 * input - 80); }
if (input <= 79) { return (0.5 * input - 38); }
if (input < 99) { return (0.875 * input - 67.5); }
if (input <= 28) { return Math.round((2 * input - 80)); } // -78 to -24 db
if (input <= 79) { return Math.round((0.5 * input - 38)); } // -24 to 1.5 db
if (input < 99) { return Math.round((0.875 * input - 67.5)); } // 1.625 - 19.125 db
if (input >= 99) { return 20; }
},
convertAudioTaperToPercent: function(db) {
if(db <= -78) { return 0}
if(db <= -24) { return (db + 80) / 2 }
if(db <= 1.5) { return (db + 38) / .5 }
if(db <= 19.125) { return (db + 67.5) / 0.875 }
return 100;
},
setFaderValue: function (faderId, faderValue) {
var $fader = $('[fader-id="' + faderId + '"]');
setFaderValue: function (faderId, faderValue, floaterValue) {
var $fader = $('[data-fader-id="' + faderId + '"]');
this.setHandlePosition($fader, faderValue);
if(floaterValue !== undefined) {
var $floater = $fader.find('.floater')
this.setFloaterValue($floater, floaterValue)
}
},
showFader: function(faderId) {
var $fader = $('[data-fader-id="' + faderId + '"]');
$fader.find('div[data-control="fader-handle"]').show()
},
setHandlePosition: function ($fader, faderValue) {
draggingOrientation = $fader.attr('orientation');
draggingOrientation = $fader.attr('data-orientation');
setHandlePosition($fader, faderValue);
draggingOrientation = null;
},
setFloaterValue: function($floater, floaterValue) {
$floater.text(floaterValue)
},
initialize: function () {
$('body').on('click', 'div[control="fader"]', faderClick);
$('body').on('click', 'div[data-control="fader"]', faderClick);
}
};

View File

@ -4,6 +4,7 @@
context.JK = context.JK || {};
context.JK.FakeJamClient = function(app, p2pMessageFactory) {
var ChannelGroupIds = context.JK.ChannelGroupIds
var logger = context.JK.logger;
logger.info("*** Fake JamClient instance initialized. ***");
@ -169,22 +170,22 @@
function FTUEGetMusicInputs() {
dbg('FTUEGetMusicInputs');
return {
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetMusicOutputs() {
dbg('FTUEGetMusicOutputs');
return {
"o~11~Multichannel (FW AP Multi)~0^o~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"o~11~Multichannel (FWAPMulti)~0^o~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetChatInputs() {
dbg('FTUEGetChatInputs');
return {
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetChannels() {
@ -449,7 +450,7 @@
}
function GetASIODevices() {
var response =[{"device_id":0,"device_name":"Realtek High Definition Audio","device_type": 0,"interfaces":[{"interface_id":0,"interface_name":"Realtek HDA SPDIF Out","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":1,"interface_name":"Realtek HD Audio rear output","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":2,"interface_name":"Realtek HD Audio Mic input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":3,"interface_name":"Realtek HD Audio Line input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":4,"interface_name":"Realtek HD Digital input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"}]},{"interface_id":5,"interface_name":"Realtek HD Audio Stereo input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]}],"wavert_supported":false},{"device_id":1,"device_name":"M-Audio FW Audiophile","device_type": 1,"interfaces":[{"interface_id":0,"interface_name":"FW AP Multi","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":1,"interface_name":"FW AP 1/2","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":2,"interface_name":"FW AP SPDIF","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":3,"interface_name":"FW AP 3/4","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":2,"device_name":"Virtual Audio Cable","device_type": 2,"interfaces":[{"interface_id":0,"interface_name":"Virtual Cable 2","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]},{"interface_id":1,"interface_name":"Virtual Cable 1","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":3,"device_name":"WebCamDV WDM Audio Capture","device_type": 3,"interfaces":[{"interface_id":0,"interface_name":"WebCamDV Audio","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"},{"is_input":false,"pin_id":1,"pin_name":"Volume Control"}]}],"wavert_supported":false}];
var response =[{"device_id":0,"device_name":"Realtek High Definition Audio","device_type": 0,"interfaces":[{"interface_id":0,"interface_name":"Realtek HDA SPDIF Out","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":1,"interface_name":"Realtek HD Audio rear output","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":2,"interface_name":"Realtek HD Audio Mic input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":3,"interface_name":"Realtek HD Audio Line input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":4,"interface_name":"Realtek HD Digital input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"}]},{"interface_id":5,"interface_name":"Realtek HD Audio Stereo input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]}],"wavert_supported":false},{"device_id":1,"device_name":"M-Audio FW Audiophile","device_type": 1,"interfaces":[{"interface_id":0,"interface_name":"FWAPMulti","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":1,"interface_name":"FW AP 1/2","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":2,"interface_name":"FW AP SPDIF","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":3,"interface_name":"FW AP 3/4","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":2,"device_name":"Virtual Audio Cable","device_type": 2,"interfaces":[{"interface_id":0,"interface_name":"Virtual Cable 2","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]},{"interface_id":1,"interface_name":"Virtual Cable 1","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":3,"device_name":"WebCamDV WDM Audio Capture","device_type": 3,"interfaces":[{"interface_id":0,"interface_name":"WebCamDV Audio","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"},{"is_input":false,"pin_id":1,"pin_name":"Volume Control"}]}],"wavert_supported":false}];
return response;
}
@ -474,12 +475,22 @@
}
function SessionGetControlState(mixerIds, isMasterOrPersonal) {
dbg("SessionGetControlState");
var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12];
var groups =
[ChannelGroupIds.MasterGroup,
ChannelGroupIds.MonitorGroup,
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.UserMusicInputGroup,
ChannelGroupIds.UserChatInputGroup,
ChannelGroupIds.PeerMediaTrackGroup,
ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup];
var names = [
"FW AP Multi",
"FW AP Multi",
"FW AP Multi",
"FW AP Multi",
"FWAPMulti",
"FWAPMulti",
"FWAPMulti",
"FWAPMulti",
"",
"",
"",
@ -533,6 +544,7 @@
stereo: true,
volume_left: -40,
volume_right:-40,
pan: 0,
instrument_id:50, // see globals.js
mode: isMasterOrPersonal,
rid: mixerIds[i]
@ -542,10 +554,10 @@
}
function SessionGetIDs() {
return [
"FW AP Multi_0_10000",
"FW AP Multi_1_10100",
"FW AP Multi_2_10200",
"FW AP Multi_3_10500",
"FWAPMulti_0_10000",
"FWAPMulti_1_10100",
"FWAPMulti_2_10200",
"FWAPMulti_3_10500",
"User@208.191.152.98#",
"User@208.191.152.98_*"
];
@ -612,9 +624,9 @@
function doCallbacks() {
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"];
//var ids = ["FWAPMulti_2_10200", "FWAPMulti_0_10000"];
var ids= ["i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1",
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~2"];
var args = [];
for (var i=0; i<ids.length; i++) {

View File

@ -94,7 +94,7 @@
function onStartRecording(from, payload) {
logger.debug("received start recording request from " + from);
if(context.JK.CurrentSessionModel.recordingModel.isRecording()) {
if(context.SessionStore.isRecording()) {
// reject the request to start the recording
context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.startRecordingAck(payload.recordingId, false, "already-recording", null)));
}

View File

@ -123,23 +123,23 @@
0: {"title": "", "message": ""}, // NO_EVENT,
1: {"title": "", "message": ""}, // BACKEND_ERROR: generic error - eg P2P message error
2: {"title": "", "message": ""}, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated
3: {"title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_JTR,
4: {"title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // PACKET_LOSS
5: {"title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_LATE,
6: {"title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // JTR_QUEUE_DEPTH,
7: {"title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // NETWORK_JTR,
8: {"title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // NETWORK_PING,
9: {"title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // BITRATE_THROTTLE_WARN,
10:{"title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // BANDWIDTH_LOW
3: {"title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // PACKET_JTR,
4: {"title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // PACKET_LOSS
5: {"title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // PACKET_LATE,
6: {"title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // JTR_QUEUE_DEPTH,
7: {"title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // NETWORK_JTR,
8: {"title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // NETWORK_PING,
9: {"title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // BITRATE_THROTTLE_WARN,
10:{"title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // BANDWIDTH_LOW
// IO related events
11:{"title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // INPUT_IO_RATE
12:{"title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // INPUT_IO_JTR,
13:{"title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // OUTPUT_IO_RATE
14:{"title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // OUTPUT_IO_JTR,
11:{"title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // INPUT_IO_RATE
12:{"title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // INPUT_IO_JTR,
13:{"title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // OUTPUT_IO_RATE
14:{"title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // OUTPUT_IO_JTR,
// CPU load related
15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // CPU_LOAD
15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // CPU_LOAD
16: {"title": "Decode Violations", "message": ""}, // DECODE_VIOLATIONS,
17: {"title": "", "message": ""}, // LAST_THRESHOLD
18: {"title": "Wifi Alert", "message": ""}, // WIFI_NETWORK_ALERT, //user or peer is using wifi
@ -162,10 +162,10 @@
33: {"title": "Client No Longer Pinned", "message": "This client is no longer designated as the source of the broadcast."}, // SESSION_LIVEBROADCAST_UNPINNED, //node unpinned by user
34: {"title": "", "message": ""}, // BACKEND_STATUS_MSG, //status/informational message
35: {"title": "LAN Unpredictable", "message": "Your local network is adding considerable variance to transmit times. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
35: {"title": "LAN Unpredictable", "message": "Your local network is adding considerable variance to transmit times. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
36: {"title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_LATENCY_HIGH,
36: {"title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems"}, // LOCAL_NETWORK_LATENCY_HIGH,
37: {"title": "", "message": ""}, // RECORDING_CLOSE, //update and remove tracks from front-end
38: {"title": "No Audio Sent", "message": ""}, // PEER_REPORTS_NO_AUDIO_RECV, //update and remove tracks from front-end
39: {"title": "", "message": ""}, // SHOW_PREFERENCES, //show preferences dialog
@ -313,4 +313,35 @@
context.JK.NAMED_MESSAGES = {
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
}
})(window,jQuery);
context.JK.ChannelGroupIds = {
"MasterGroup": 0,
"MonitorGroup": 1,
"MasterCatGroup" : 2,
"MonitorCatGroup" : 3,
"AudioInputMusicGroup": 4,
"AudioInputChatGroup": 5,
"MediaTrackGroup": 6,
"StreamOutMusicGroup": 7,
"StreamOutChatGroup": 8,
"StreamOutMediaGroup" : 9,
"UserMusicInputGroup": 10,
"UserChatInputGroup": 11,
"UserMediaInputGroup": 12,
"PeerAudioInputMusicGroup": 13,
"PeerMediaTrackGroup": 14,
"JamTrackGroup": 15,
"MetronomeGroup": 16
};
context.JK.CategoryGroupIds = {
"AudioInputMusic" : "AudioInputMusic",
"AudioInputChat" : "AudioInputChat",
"UserMusic" : "UserMusic",
"UserChat" : "UserChat",
"UserMedia" : "UserMedia",
"MediaTrack" : "MediaTrack",
"Metronome" : "Metronome"
}
})(window,jQuery);

View File

@ -95,6 +95,14 @@
});
}
function deleteMusicNotation(options) {
return $.ajax({
type: "DELETE",
url: "/api/music_notations/" +options.id
});
}
function legacyJoinSession(options) {
var sessionId = options["session_id"];
delete options["session_id"];
@ -478,6 +486,14 @@
});
}
function deleteParticipant(clientId) {
var url = "/api/participants/" + clientId;
return $.ajax({
type: "DELETE",
url: url
});
}
function login(options) {
var url = '/api/auths/login';
@ -1800,6 +1816,7 @@
this.createScheduledSession = createScheduledSession;
this.uploadMusicNotations = uploadMusicNotations;
this.getMusicNotation = getMusicNotation;
this.deleteMusicNotation = deleteMusicNotation;
this.getBroadcastNotification = getBroadcastNotification;
this.quietBroadcastNotification = quietBroadcastNotification;
this.legacyJoinSession = legacyJoinSession;
@ -1854,6 +1871,7 @@
this.addRecordingLike = addRecordingLike;
this.addPlayablePlay = addPlayablePlay;
this.getSession = getSession;
this.deleteParticipant = deleteParticipant;
this.getClientDownloads = getClientDownloads;
this.createEmailInvitations = createEmailInvitations;
this.createMusicianInvite = createMusicianInvite;

View File

@ -308,7 +308,6 @@ context.JK.JamTrackScreen=class JamTrackScreen
rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
alert("TODO")
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else

View File

@ -24,7 +24,7 @@
$.fn.metronomePlaybackMode = function(options) {
options = options || {mode: 'self'}
options = $.extend(false, {mode: 'self', positions: ['top']}, options);
return this.each(function(index) {
@ -78,8 +78,8 @@
spikeLength:0,
width:180,
closeWhenOthersOpen: true,
offsetParent: $parent.offsetParent(),
positions:['top'],
offsetParent: options.offsetParent || $parent.offsetParent(),
positions: options.positions,
preShow: function() {
$parent.find('.down-arrow').removeClass('down-arrow').addClass('up-arrow')
},

View File

@ -0,0 +1,25 @@
//= require bugsnag
//= require bind-polyfill
//= require jquery
//= require jquery.monkeypatch
//= require jquery_ujs
//= require jquery.ui.draggable
//= require jquery.ui.droppable
//= require jquery.bt
//= require jquery.icheck
//= require jquery.easydropdown
//= require jquery.metronomePlaybackMode
//= require classnames
//= require reflux
//= require AAC_underscore
//= require AAA_Log
//= require globals
//= require jam_rest
//= require ga
//= require utils
//= require playbackControls
//= require webcam_viewer
//= require react
//= require react_ujs
//= require react-init
//= require react-components

View File

@ -0,0 +1,37 @@
context = window
$ = jQuery
panHelper = class PanHelper
###
Convert the pan value that comes from a backend mixer
to a 0-100 % usable by a draggable panner element
###
convertPanToPercent: (mixerPan) ->
value = (((mixerPan + 90) / 90) * 100) / 2
if value < 0
0
else if value > 100
100
else
value
###
Convert the % value of a draggable panner element
to a mixer-ready pan value
###
convertPercentToPan: (percent) ->
value = 2 * percent / 100 * 90 - 90
if value < -90
-90
else if value > 90
90
else
Math.round(value)
convertPercentToPanForDisplay: (percent) ->
Math.abs(context.JK.PanHelpers.convertPercentToPan(percent))
context.JK.PanHelpers = new panHelper()

View File

@ -1,6 +1,7 @@
/**
* Playback widget (play, pause , etc)
*/
(function(context, $) {
"use strict";
@ -18,7 +19,7 @@
context.JK = context.JK || {};
context.JK.PlaybackControls = function($parentElement, options){
options = $.extend(false, {playmodeControlsVisible:false}, options);
options = $.extend(false, {playmodeControlsVisible:false, mediaActions:null}, options);
var logger = context.JK.logger;
if($parentElement.length == 0) {
@ -68,23 +69,42 @@
if(endReached) {
update(0, playbackDurationMs, playbackPlaying);
}
$self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode});
if(options.mediaActions) {
options.mediaActions.mediaStartPlay({playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode})
}
else {
$self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode});
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
var sessionModel = context.JK.CurrentSessionModel || null;
context.JK.GA.trackJamTrackPlaySession(sessionModel.id(), true)
context.JK.GA.trackJamTrackPlaySession(context.SessionStore.id(), true)
}
}
function stopPlay(endReached) {
logger.debug("STOP PLAY CLICKED")
updateIsPlaying(false);
$self.triggerHandler('stop', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached});
if(options.mediaActions) {
logger.debug("mediaStopPlay", endReached)
options.mediaActions.mediaStopPlay({playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached})
}
else {
$self.triggerHandler('stop', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached});
}
}
function pausePlay(endReached) {
updateIsPlaying(false);
$self.triggerHandler('pause', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached});
if(options.mediaActions) {
options.mediaActions.mediaPausePlay({playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached})
}
else {
$self.triggerHandler('pause', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode, endReached : endReached});
}
}
function updateOffsetBasedOnPosition(offsetLeft) {
@ -93,8 +113,13 @@
playbackPositionMs = parseInt((offsetLeft / sliderBarWidth) * playbackDurationMs);
updateCurrentTimeText(playbackPositionMs);
if(canUpdateBackend) {
if(options.mediaActions) {
options.mediaActions.mediaChangePosition({positionMs: playbackPositionMs, playbackMonitorMode: playbackMonitorMode})
}
else {
$self.triggerHandler('change-position', {positionMs: playbackPositionMs, playbackMonitorMode: playbackMonitorMode});
canUpdateBackend = false;
}
canUpdateBackend = false;
}
}
@ -127,34 +152,17 @@
}
$playButton.on('click', function(e) {
var sessionModel = context.JK.CurrentSessionModel || null;
//if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
// context.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $playButton})
// return false;
//}
console.log("CLICKED PLAY")
startPlay();
return false;
});
$pauseButton.on('click', function(e) {
var sessionModel = context.JK.CurrentSessionModel || null;
//if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
// context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton})
// return false;
//}
pausePlay();
return false;
});
$stopButton.on('click', function(e) {
var sessionModel = context.JK.CurrentSessionModel || null;
//if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
// context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton})
// return false;
//}
stopPlay();
return false;
});
@ -211,53 +219,61 @@
throw "unknown playbackMonitorMode: " + playbackMonitorMode;
}
}
function executeMonitor(positionMs, durationMs, isPlaying) {
if(positionMs < 0) {
// bug in backend?
positionMs = 0;
}
if(positionMs > 0) {
seenActivity = true;
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) {
updateIsPlaying(isPlaying);
}
else {
update(positionMs, durationMs, isPlaying);
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
if(playbackPlaying) {
$jamTrackGetReady.attr('data-current-time', positionMs)
}
else {
// this is so the jamtrack 'Get Ready!' stays hidden when it's not playing
$jamTrackGetReady.attr('data-current-time', -1)
}
}
monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500);
}
function monitorRecordingPlayback() {
if(!monitoring) {
return;
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs();
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
var durationMs = duration.media_len;
var start = duration.start; // needed to understand start offset, and prevent slider from moving in tapins
if(options.mediaActions) {
options.mediaActions.positionUpdate(playbackMonitorMode)
}
else {
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
}
var isPlaying = context.jamClient.isSessionTrackPlaying();
if(positionMs < 0) {
// bug in backend?
positionMs = 0;
}
if(positionMs > 0) {
seenActivity = true;
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) {
updateIsPlaying(isPlaying);
}
else {
update(positionMs, durationMs, isPlaying);
}
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
if(playbackPlaying) {
$jamTrackGetReady.attr('data-current-time', positionMs)
if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs();
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
var durationMs = duration.media_len;
}
else {
// this is so the jamtrack 'Get Ready!' stays hidden when it's not playing
$jamTrackGetReady.attr('data-current-time', -1)
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
}
}
var isPlaying = context.jamClient.isSessionTrackPlaying();
monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500);
executeMonitor(positionMs, durationMs, isPlaying)
}
}
function update(currentTimeMs, durationTimeMs, isPlaying, offsetStart) {
@ -304,7 +320,11 @@
}
function updateCurrentTimeText(timeMs) {
$currentTime.text(context.JK.prettyPrintSeconds(parseInt(timeMs / 1000)));
var time = context.JK.prettyPrintSeconds(parseInt(timeMs / 1000))
$currentTime.text(time);
if(options.mediaActions) {
options.mediaActions.currentTimeChanged(time)
}
}
function updateSliderPosition(timeMs) {
@ -362,6 +382,12 @@
}
function startMonitor(_playbackMonitorMode) {
logger.debug("startMonitor: " + _playbackMonitorMode)
if(monitoring && _playbackMonitorMode == playbackMonitorMode) {
return;
}
monitoring = true;
// resets everything to zero
init();
@ -376,6 +402,11 @@
logger.debug("playbackControl.startMonitor " + playbackMonitorMode + "")
styleControls();
if(monitorPlaybackTimeout != null) {
clearTimeout(monitorPlaybackTimeout);
monitorPlaybackTimeout = null;
}
monitorRecordingPlayback();
}
@ -407,6 +438,7 @@
this.setPlaybackMode = setPlaybackMode;
this.startMonitor = startMonitor;
this.stopMonitor = stopMonitor;
this.executeMonitor = executeMonitor;
this.onPlayStopEvent = onPlayStopEvent;
this.onPlayStartEvent = onPlayStartEvent;
this.onPlayPauseEvent = onPlayPauseEvent;

View File

@ -1,3 +1,14 @@
//= require ./react-components/actions/BroadcastActions
//= require ./react-components/stores/BroadcastStore
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/SessionStore
//= require ./react-components/stores/MixerStore
//= require ./react-components/stores/SessionNotificationStore
//= require ./react-components/stores/MediaPlaybackStore
//= require ./react-components/stores/SessionMyTracksStore
//= require ./react-components/stores/SessionOtherTracksStore
//= require ./react-components/stores/SessionMediaTracksStore
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components

View File

@ -0,0 +1,204 @@
context = window
PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE
EVENTS = context.JK.EVENTS
logger = context.JK.logger
mixins = []
# this check ensures we attempt to listen if this component is created in a popup
reactContext = if window.opener? then window.opener else window
MixerStore = reactContext.MixerStore
MixerActions = reactContext.MixerActions
MediaPlaybackStore = reactContext.MediaPlaybackStore
SessionActions = reactContext.SessionActions
MediaPlaybackActions = reactContext.MediaPlaybackActions
mixins.push(Reflux.listenTo(MixerStore,"onInputsChanged"))
mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
@MediaControls = React.createClass({
mixins: mixins
tempos : [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, 208 ]
onMediaStateChanged: (changes) ->
if changes.playbackStateChanged
if @state.controls?
if changes.playbackState == 'play_start'
@state.controls.onPlayStartEvent()
else if changes.playbackState == 'play_stop'
@state.controls.onPlayStopEvent()
else if changes.playbackState == 'play_pause'
@state.controls.onPlayPauseEvent();
else if changes.positionUpdateChanged
if @state.controls?
@state.controls.executeMonitor(changes.positionMs, changes.durationMs, changes.isPlaying)
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
if @state.controls?
mediaSummary = mixers.mediaSummary
metro = mixers.metro
@monitorControls(@state.controls, mediaSummary)
@setState({mediaSummary: mediaSummary, metro: metro})
@updateMetronomeDetails(metro, @state.initializedMetronomeControls)
updateMetronomeDetails: (metro, initializedMetronomeControls) ->
logger.debug("MediaControls: setting tempo/sound/cricket", metro)
$root = jQuery(this.getDOMNode())
$root.find("select.metro-tempo").val(metro.tempo)
$root.find("select.metro-sound").val(metro.sound)
if initializedMetronomeControls
mode = if metro.cricket then 'cricket' else 'self'
logger.debug("settingcricket", mode)
$root.find('#metronome-playback-select').metronomeSetPlaybackMode(mode)
monitorControls: (controls, mediaSummary) ->
if mediaSummary.mediaOpen
if mediaSummary.jamTrackOpen
controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK)
else if mediaSummary.backingTrackOpen
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
else if mediaSummary.metronomeOpen
controls.startMonitor(PLAYBACK_MONITOR_MODE.METRONOME)
else if mediaSummary.recordingOpen
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
else
logger.debug("unable to determine mediaOpen type", mediaSummary)
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
else
controls.stopMonitor()
metronomePlaybackModeChanged: (e, data) ->
mode = data.playbackMode # will be either 'self' or 'cricket'
logger.debug("setting metronome playback mode: ", mode)
isCricket = mode == 'cricket';
SessionActions.metronomeCricketChange(isCricket)
onMetronomeChanged: () ->
@setMetronomeFromForm()
setMetronomeFromForm: () ->
$root = jQuery(this.getDOMNode())
tempo = $root.find("select.metro-tempo:visible option:selected").val()
sound = $root.find("select.metro-sound:visible option:selected").val()
t = parseInt(tempo)
s = null
if tempo == NaN || tempo == 0 || tempo == null
t = 120
if sound == null || typeof(sound)=='undefined' || sound == ""
s = "Beep"
else
s = sound
logger.debug("Setting tempo and sound:", t, s)
MixerActions.metronomeChanged(t, s, 1, 0)
render: () ->
tempo_options = []
for tempo in @tempos
tempo_options.push(`<option value={tempo}>{tempo}</option>`)
`<div className="media-controls has-mix">
<div className="jam-track-get-ready">
<div className="spinner-small"></div>
<span>Get Ready!</span>
</div>
<div className="play-buttons">
<a className="play-button" href="#">
<img src="/assets/content/icon_playbutton.png" width="20" height="20" className="playbutton" />
<img src="/assets/content/icon_pausebutton.png" width="20" height="20" className="pausebutton" />
</a>
<a className="stop-button" href="#">
<img src="/assets/content/icon_stopbutton.png" width="20" height="20" className="stopbutton" />
</a>
</div>
<div className="metronome-playback-options">
<span id="metronome-playback-select"></span>
</div>
<div className="metronome-options">
<div className="metronome-selects">
<div class="metronome-field">
<select className="metronome-select metro-sound" title="Metronome Sound" name="metronome_sound">
<option value="Beep">Knock</option>
<option value="Click">Tap</option>
<option value="Snare">Snare</option>
<option value="Kick">Kick</option>
</select>
<label htmlFor="metronome_sound">Sound:</label>
</div>
<div class="metronome-field">
<select className="metronome-select metro-tempo" title="Metronome Tempo" name="metronome_tempo">
{tempo_options}
</select>
<label htmlFor="metronome_tempo">Tempo:</label>
</div>
</div>
</div>
<div className="recording-time start-time">0:00</div>
<div className="recording-playback">
<div className="recording-slider"><img src="/assets/content/slider_playcontrols.png" height="16" width="5" /></div>
</div>
<div className="recording-time duration-time">0:00</div>
<div className="recording-current">0:00</div>
<div className="playback-mode-buttons icheckbuttons">
<input type="radio" name="playback-mode" defaultChecked="checked" value="preview-to-all" className="preview-to-all" /><label htmlFor="playback-mode-preview-all" className="radio-text">Preview to All</label>
<input type="radio" name="playback-mode" value="preview-to-me" className="preview-to-me" /><label htmlFor="playback-mode-preview-me" className="radio-text">Preview Only to Me</label>
</div>
</div>`
getInitialState: () ->
{controls: null, mediaSummary: {}, initializedMetronomeControls: false}
tryPrepareMetronome: (metro) ->
if @state.mediaSummary.metronomeOpen && !@state.initializedMetronomeControls
$root = jQuery(this.getDOMNode())
$root.on("change", ".metronome-select", @onMetronomeChanged)
$root.find('#metronome-playback-select').metronomePlaybackMode({positions:['bottom'], offsetParent:$('#minimal-container')}).on(EVENTS.METRONOME_PLAYBACK_MODE_SELECTED, @metronomePlaybackModeChanged)
@updateMetronomeDetails(metro, true)
@setState({initializedMetronomeControls: true})
componentDidUpdate: (prevProps, prevState) ->
@tryPrepareMetronome(@state.metro)
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
controls = context.JK.PlaybackControls($root, {mediaActions: MediaPlaybackActions})
mediaSummary = MixerStore.mixers.mediaSummary
metro = MixerStore.mixers.metro
@monitorControls(controls, mediaSummary)
@tryPrepareMetronome(metro)
@setState({mediaSummary: mediaSummary, controls: controls, metro: metro})
})

View File

@ -0,0 +1,126 @@
context = window
logger = context.JK.logger
mixins = []
if window.opener?
SessionActions = window.opener.SessionActions
MediaPlaybackStore = window.opener.MediaPlaybackStore
MixerActions = window.opener.MixerActions
mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
@PopupMediaControls = React.createClass({
mixins: mixins
onMediaStateChanged: (changes) ->
if changes.currentTimeChanged && @root?
@setState({time: changes.time})
showMetronome: (e) ->
e.preventDefault()
SessionActions.showNativeMetronomeGui()
getInitialState: () ->
{time: '0:00'}
close: () ->
window.close()
render: () ->
closeLinkText = null
header = null
extraControls = null
# give the users options to close it
if @props.mediaSummary.jamTrackOpen
mediaType = "JamTrack"
mediaName = @props.jamTracks[0].name
closeLinkText = 'close JamTrack'
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
else if @props.mediaSummary.backingTrackOpen
mediaType = "Audio File"
mediaName = context.JK.getNameOfFile(@props.backingTracks[0].shortFilename)
closeLinkText = 'close audio file'
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
extraControls =
`<div>
<div className="field">
<input type="checkbox" name="loop" /><label htmlFor="loop">Loop audio file playback</label>
</div>
<br className="clearall"/>
</div>`
else if @props.mediaSummary.metronomeOpen
mediaType = "Metronome"
closeLinkText = 'close metronome'
header = `<h3>Metronome</h3>`
extraControls =
`<div>
<a className="display-metronome" onClick={this.showMetronome}>Display visual metronome</a>
</div>`
else if @props.mediaSummary.recordingOpen
mediaType = "Recording"
mediaName = @props.recordedTracks[0].recordingName
closeLinkText = 'close recording'
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
else
mediaType = ""
`<div className="media-controls-popup">
{header}
<MediaControls />
{extraControls}
<a className="close-link" onClick={this.close}>{closeLinkText}</a>
</div>`
windowUnloaded: () ->
SessionActions.closeMedia() unless window.DontAutoCloseMedia
componentDidMount: () ->
$(window).unload(@windowUnloaded)
@root = jQuery(this.getDOMNode())
$loop = @root.find('input[name="loop"]')
context.JK.checkbox($loop)
$loop.on('ifChecked', () =>
logger.debug("@props", @props)
# it doesn't matter if you do personal or master, because backend just syncs both
MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, true)
)
$loop.on('ifUnchecked', () =>
# it doesn't matter if you do personal or master, because backend just syncs both
MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, false)
)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
#mysteryTopMargin = 20
mysteryTopMargin = 0
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -0,0 +1,135 @@
context = window
mixins = []
# this check ensures we attempt to listen if this component is created in a popup
if window.opener
mixins.push(Reflux.listenTo(window.opener.RecordingStore,"onRecordingStateChanged"))
@PopupRecordingStartStop = React.createClass({
mixins: mixins
onRecordingStateChanged: (recordingState) ->
this.setState(isRecording: recordingState.isRecording, recordedOnce: this.state.recordedOnce || recordingState.isRecording)
startStopRecording: () ->
if this.state.isRecording
window.opener.RecordingActions.stopRecording()
else
window.opener.RecordingActions.startRecording()
onNoteShowHide: () ->
this.setState(showNote: !this.state.showNote)
getInitialState: () ->
{isRecording: window.ParentIsRecording, showNote: true, recordedOnce: false}
render: () ->
recordingVerb = if this.state.isRecording then 'Stop' else 'Start'
recordingBtnClasses = classNames({
"currently-recording" : this.state.isRecording,
"control" : true
})
noteJSX = `<div className="important-note">
<h5>
Important Note
</h5>
<div className="contents">
While playing in your session, you are listening to your own personal mix. This recording will use the master mix,
which may sound very different. To hear and adjust your master mix settings, click the MIXER button in the session toolbar.
</div>
</div>`
recordingJSX = `<div className="recording-options">
<div className="field">
<input type="radio" name="recording-input-type" id="recording-input-both" defaultChecked="checked" />
<label htmlFor="recording-input-both">Record both video and audio</label>
<div className="clearall"></div>
</div>
<div className="field">
<input type="radio" name="recording-input-type" id="recording-input-audio" />
<label htmlFor="recording-input-audio">Record audio only</label>
<div className="clearall"></div>
</div>
</div>`
if this.state.showNote
noteText = 'hide note'
else
noteText = 'show note'
noteShowHideJSX = `<a href="#" className="note-show-hide" onClick={this.onNoteShowHide}>{noteText}</a>`
note = null
recordingOptions = null
noteShowHide = null
if this.state.showNote && !this.state.isRecording && !this.state.recordedOnce
# should we show the note itself? Only if not recording, too
note = noteJSX
if !this.state.isRecording && !this.state.recordedOnce
noteShowHide = noteShowHideJSX
if gon.global.video_available == "full"
recordingOptions = recordingJSX
`<div className="recording-start-stop">
<div className="control-holder">
<a className={recordingBtnClasses} onClick={this.startStopRecording}>
<span className="helper" />
<img src="/assets/content/recordbutton-off.png" width="20" height="20" />
<span id="recording-status">{recordingVerb} Recording</span>
</a>
</div>
{recordingOptions}
{note}
{noteShowHide}
</div>`
windowUnloaded: () ->
window.opener.RecordingActions.recordingControlsClosed()
componentDidMount: () ->
$(window).unload(@windowUnloaded)
$root = jQuery(this.getDOMNode())
$recordingType = $root.find('input[type="radio"]')
context.JK.checkbox($recordingType)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
mysteryTopMargin = 20
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -0,0 +1,13 @@
context = window
logger = context.JK.logger
@PopupWrapper = React.createClass({
getInitialState: () ->
{ready: false}
render: () ->
logger.debug("PopupProps", window.PopupProps)
return React.createElement(window[this.props.component], window.PopupProps)
})

View File

@ -0,0 +1,89 @@
context = window
MixerActions = @MixerActions
@SessionBackingTrack = React.createClass({
mixins: [@MasterPersonalMixersMixin]
propTypes: {
mode: React.PropTypes.bool.isRequired
}
handleMute: (e) ->
e.preventDefault()
mixer = @mixer()
unless mixer?
logger.debug("ignoring mute because no media mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([mixer], muting)
render: () ->
mixers = @mixers()
muteMixer = mixers.mixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"backing-track" : true
})
pan = if mixers.mixer? then mixers.mixer?.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="name">{this.props.shortFilename}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@mixers()}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@mixers()}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,75 @@
context = window
@SessionChatMixer= React.createClass({
handleMute: (e) ->
e.preventDefault()
unless @props.mixers.mixer
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@props.mixers.mixer], muting)
render: () ->
muteMixer = @props.mixers.muteMixer
vuMixer = @props.mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
pan = if @props.mixers.mixer? then @props.mixers.mixer.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className="session-track chat-mixer">
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">Session Voice Chat Output</div>
<div className="track-instrument"><img height="45" src="/assets/content/icon_instrument_voice45.png" width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={this.props.mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,26 @@
context = window
@SessionInviteMusiciansBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
onAppInit: (app) ->
@app = app
@inviteMusiciansUtil = new JK.InviteMusiciansUtil(@app)
@inviteMusiciansUtil.initialize(JK.FriendSelectorDialogInstance)
openInviteDialog : (e) ->
e.preventDefault()
friendInput = @inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians', context.SessionStore.currentSessionId)
@inviteMusiciansUtil.loadFriends()
$(friendInput).show()
@app.layout.showDialog('select-invites')
render: () ->
`<a className="session-invite-musicians" onClick={this.openInviteDialog}>
<img src="/assets/content/icon_add.png" width="19" height="19" />
Invite Musicians
</a>`
})

View File

@ -0,0 +1,89 @@
context = window
MixerActions = @MixerActions
@SessionJamTrack = React.createClass({
mixins: [@MasterPersonalMixersMixin]
propTypes: {
mode: React.PropTypes.bool.isRequired
}
handleMute: (e) ->
e.preventDefault()
mixer = @mixer()
unless mixer?
logger.debug("ignoring mute because no media mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([mixer], muting)
render: () ->
mixers = @mixers()
muteMixer = mixers.mixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"jam-track" : true
})
pan = if mixers.mixer? then mixers.mixer?.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="name">{this.props.part}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<div className="track-instrument"><img height="24" src={this.props.instrumentIcon} width="24" /></div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@mixers()}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@mixers()}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,81 @@
context = window
MixerActions = @MixerActions
@SessionJamTrackCategory = React.createClass({
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.props.mixers.mixer], muting)
render: () ->
# today, all mixers are the same for a remote participant; so just grab the 1st
mixers = @props.mixers
muteMixer = mixers.muteMixer
vuMixer = mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"jam-track-category" : true
})
pan = mixers.mixer.pan
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="jam-track-header">JamTrack:</div>
<div className="name">{this.props.jamTrackName}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,22 @@
context = window
@SessionLeaveBtn = React.createClass({
onLeave: (e) ->
e.preventDefault()
@rateSession()
SessionActions.leaveSession.trigger({location: '/client#/home'})
rateSession: () ->
unless @rateSessionDialog?
@rateSessionDialog = new context.JK.RateSessionDialog(context.JK.app);
@rateSessionDialog.initialize();
@rateSessionDialog.showDialog();
render: () ->
`<a className="session-leave button-grey right leave" onClick={this.onLeave}>
X LEAVE
</a>`
})

View File

@ -0,0 +1,53 @@
context = window
rest = context.JK.Rest()
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
MIX_MODES = context.JK.MIX_MODES
@SessionMasterCategoryControls = React.createClass({
mixins: [Reflux.listenTo(@SessionMediaTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
inputGroupMixers = mixers.getAudioInputCategoryMixer(MIX_MODES.MASTER)
chatGroupMixers = mixers.getChatCategoryMixer(MIX_MODES.MASTER)
@setState({inputGroupMixers: inputGroupMixers, chatGroupMixers: chatGroupMixers})
render: () ->
categoryControls = []
if @state.inputGroupMixers?
input =
mixers: @state.inputGroupMixers
categoryControls.push(`<SessionMusicMixer key={input.mixers.mixer.id} {...input} />`)
if @state.chatGroupMixers?
input =
mixers: @state.chatGroupMixers
categoryControls.push(`<SessionChatMixer key={input.mixers.mixer.id} {...input} />`)
`<div className="session-category-controls">
<h2>master output</h2>
<div className="session-tracks-scroller">
{categoryControls}
</div>
</div>`
getInitialState:() ->
{inputGroupMixers: null, chatGroupMixers: null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,55 @@
context = window
rest = context.JK.Rest()
SessionActions = @SessionActions
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
MIX_MODES = context.JK.MIX_MODES
EVENTS = context.JK.EVENTS
ChannelGroupIds = context.JK.ChannelGroupIds
@SessionMasterMediaTracks = React.createClass({
mixins: [@SessionMediaTracksMixin, Reflux.listenTo(@SessionMediaTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
render: () ->
mediaTracks = []
if this.state.mediaSummary.mediaOpen
if this.state.mediaSummary.backingTrackOpen
for backingTrack in @state.backingTracks
backingTrack.mode = MIX_MODES.MASTER
mediaTracks.push(`<SessionBackingTrack key={backingTrack.track.id} {...backingTrack} />`)
else if this.state.mediaSummary.jamTrackOpen
mediaTracks.push(`<SessionJamTrackCategory key="JamTrackCategory" jamTrackName={this.state.jamTrackName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.MASTER} />`)
for jamTrack in @state.jamTracks
jamTrack.mode = MIX_MODES.MASTER
mediaTracks.push(`<SessionJamTrack key={jamTrack.id} {...jamTrack} />`)
else if this.state.mediaSummary.recordingOpen
mediaTracks.push(`<SessionRecordedCategory key="RecordedCategory" recordingName={this.state.recordingName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.MASTER} />`)
for recordedTrack in @state.recordedTracks
recordedTrack.mode = MIX_MODES.MASTER
mediaTracks.push(`<SessionRecordedTrack key={recordedTrack.track.id} {...recordedTrack} />`)
else if this.state.mediaSummary.metronomeOpen
@state.metronome.mode = MIX_MODES.MASTER
mediaTracks.push(`<SessionMetronome key={this.state.metronome.id} {...this.state.metronome} />`)
`<div className="session-media-tracks">
<h2>recorded audio</h2>
<div className="session-tracks-scroller">
{mediaTracks}
</div>
</div>`
getInitialState:() ->
{mediaSummary:{mediaOpen: false}, isRecording: false, backingTracks: [], jamTracks: [], recordedTracks: [], metronome: null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,13 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@SessionMasterMix = React.createClass({
render: () ->
`<div id="master-tracks">
<SessionMasterMyTracks mode={MIX_MODES.MASTER} />
<SessionMasterOtherTracks mode={MIX_MODES.MASTER} />
<SessionMasterMediaTracks mode={MIX_MODES.MASTER} />
<SessionMasterCategoryControls mode={MIX_MODES.MASTER} />
</div>`
})

View File

@ -0,0 +1,40 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
MIX_MODES = context.JK.MIX_MODES
logger = context.JK.logger
@SessionMasterMyTracks = React.createClass({
mixins: [@SessionMyTracksMixin, Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
render: () ->
content = null
tracks = []
if this.state.tracks.length > 0
for track in this.state.tracks
track.mode = MIX_MODES.MASTER
tracks.push(`<SessionMyTrack key={track.track.client_track_id} {...track} />`)
if @state.chat
@state.chat.mode = @props.mode
tracks.push(`<SessionMyChat key="chat" {...this.state.chat} />`)
else if this.state.session? && this.state.session.inSession()
logger.debug("no 'my inputs' for master mix")
`<div className="session-my-tracks">
<h2>my live tracks</h2>
<div className="session-tracks-scroller">
{content}
{tracks}
</div>
</div>`
getInitialState:() ->
{tracks:[], session: null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,93 @@
context = window
MixerActions = @MixerActions
@SessionMasterOtherTrack = React.createClass({
handleMute: (e) ->
e.preventDefault()
unless @props.mixers.mixer?
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@props.mixers.mixer], muting)
render: () ->
muteMixer = @props.mixers.muteMixer
vuMixer = @props.mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
pan = if @props.mixers.mixer? then @props.mixers.mixer.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
# <div className="track-icon-equalizer" />
`<div className="session-track my-track">
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">{this.props.name}</div>
<div className="track-avatar"><img src={this.props.photoUrl}/></div>
<div className="track-instrument"><img height="45" src={this.props.instrumentIcon} width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={this.props.mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:this.props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:this.props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
componentWillUpdate: (nextProps, nextState) ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
# disable hover effects if there is no mixer
if nextProps.mixers.mixer?
$mute.off("click", false)
$pan.off("click", false)
else
$mute.on("click", false)
$pan.on("click", false)
})

View File

@ -0,0 +1,66 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
@SessionMasterOtherTracks = React.createClass({
mixins: [Reflux.listenTo(@SessionOtherTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
noAudioUsers = mixers.noAudioUsers
tracks = []
if session.inSession()
for participant in session.otherParticipants()
name = participant.user.name;
firstTrack = participant.tracks[0]
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url)
for track in participant.tracks
mixerData = mixers.findMixerForTrack(participant.client_id, track, false, @props.mode)
instrumentIcon = context.JK.getInstrumentIcon45(firstTrack.instrument_id)
trackState = {
participant: participant,
track: track,
mixers: mixerData,
name: name,
instrumentIcon: instrumentIcon,
photoUrl: photoUrl,
hasMixer: mixerData.mixer? ,
noAudio: noAudioUsers[participant.client_id]
}
tracks.push(trackState)
# todo: sessionModel.setAudioEstablished
this.setState(tracks: tracks, session: session)
render: () ->
tracks = []
for track in @state.tracks
tracks.push(`<SessionMasterOtherTrack key={track.track.client_track_id} {...track} />`)
`<div className="session-other-tracks">
<h2>other live tracks</h2>
<div className="session-tracks-scroller">
{tracks}
</div>
</div>`
getInitialState:() ->
{tracks:[], session: null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,309 @@
context = window
rest = context.JK.Rest()
SessionActions = @SessionActions
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
MIX_MODES = context.JK.MIX_MODES
EVENTS = context.JK.EVENTS
ChannelGroupIds = context.JK.ChannelGroupIds
@SessionMediaTracks = React.createClass({
mixins: [@SessionMediaTracksMixin, Reflux.listenTo(@SessionMediaTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
inputsChangedProcessed: (state) ->
if state.mediaSummary.mediaOpen
if !@state.childWindow?
logger.debug("OPENING CHILD WINDOW")
childWindow = window.open("/popups/media-controls", 'Media Controls', 'scrollbars=yes,toolbar=no,status=no,height=155,width=350')
childWindow.PopupProps = state
state.childWindow = childWindow
else
if !state.metronomeFlickerTimeout? # if the metronomeFlickerTimeout is active, we don't consider closing the childWindow
@checkCloseWindow()
state.childWindow = null
checkCloseWindow: () ->
if @state.childWindow?
logger.debug("CLOSING CHILD WINDOW")
@state.childWindow.DontAutoCloseMedia = true
@state.childWindow.close()
closeAudio: (e) ->
e.preventDefault()
SessionActions.closeMedia()
cancelDownloadJamTrack: (e) ->
e.preventDefault()
logger.debug("closing DownloadJamTrack widget")
@state.downloadJamTrack.root.remove()
@state.downloadJamTrack.destroy()
SessionActions.downloadingJamTrack(false)
@setState({downloadJamTrack: null})
openRecording: (e) ->
e.preventDefault()
# just ignore the click if they are currently recording for now
if @state.isRecording
@app.notify({
"title": "Currently Recording",
"text": "You can't open a recording while creating a recording.",
"icon_url": "/assets/content/icon_alert_big.png"
})
return
@app.layout.showDialog('localRecordings') unless @app.layout.isDialogShowing('localRecordings')
openBackingTrack: (e) ->
e.preventDefault()
if @state.backingTrackDialogOpen
logger.debug("backing track dialog already open")
return
# just ignore the click if they are currently recording for now
if @state.isRecording
@app.notify({
"title": "Currently Recording",
"text": "You can't open a backing track while creating a recording.",
"icon_url": "/assets/content/icon_alert_big.png"
});
return
@setState({backingTrackDialogOpen: true})
context.jamClient.ShowSelectBackingTrackDialog("window.JK.HandleBackingTrackSelectedCallback2");
openMetronome: (e) ->
if @state.isRecording
@app.notify({
"title": "Currently Recording",
"text": "You can't open a metronome while creating a recording.",
"icon_url": "/assets/content/icon_alert_big.png"
})
return
SessionActions.openMetronome()
openJamTrack: (e) ->
e.preventDefault()
if @state.isRecording
@app.notify({
"title": "Currently Recording",
"text": "You can't open a jam track while creating a recording.",
"icon_url": "/assets/content/icon_alert_big.png"
})
return
@app.layout.showDialog('open-jam-track-dialog').one(EVENTS.DIALOG_CLOSED, (e, data) =>
# once the dialog is closed, see if the user has a jamtrack selected
if !data.canceled && data.result.jamTrack
@loadJamTrack(data.result.jamTrack)
else
logger.debug("OpenJamTrack dialog closed with no selection; ignoring", data)
)
loadJamTrack: (jamTrack) ->
if @state.downloadJamTrack
# if there was one showing before somehow, destroy it.
logger.warn("destroying existing JamTrack")
@state.downloadJamTrack.root.remove()
@state.downloadJamTrack.destroy()
#set to null
downloadJamTrack = new context.JK.DownloadJamTrack(@app, jamTrack, 'large');
# the widget indicates when it gets to any transition; we can hide it once it reaches completion
$(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, (e, data) =>
if data.state == downloadJamTrack.states.synchronized
logger.debug("jamtrack synchronized; hide widget and show tracks")
downloadJamTrack.root.remove()
downloadJamTrack.destroy()
downloadJamTrack = null
this.setState({downloadJamTrack: null})
# XXX: test with this removed; it should be unnecessary
context.jamClient.JamTrackStopPlay();
sampleRate = context.jamClient.GetSampleRate()
sampleRateForFilename = if sampleRate == 48 then '48' else '44'
fqId = jamTrack.id + '-' + sampleRateForFilename
if jamTrack.jmep
logger.debug("setting jmep data")
context.jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep)
else
logger.debug("no jmep data for jamtrack")
# JamTrackPlay means 'load'
result = context.jamClient.JamTrackPlay(fqId);
SessionActions.downloadingJamTrack(false)
console.log("JamTrackPlay: result", )
if !result
@app.notify(
{
title: "JamTrack Can Not Open",
text: "Unable to open your JamTrack. Please contact support@jamkazam.com"
}
, null, true)
else
participantCnt = context.SessionStore.participants().length
rest.playJamTrack(jamTrack.id)
.done(() =>
@app.refreshUser();
)
context.stats.write('web.jamtrack.open', {
value: 1,
session_size: participantCnt,
user_id: context.JK.currentUserId,
user_name: context.JK.currentUserName
})
)
@setState({downloadJamTrack: downloadJamTrack})
render: () ->
scrollerClassData = {'session-tracks-scroller': true}
mediaOptions = `<div className="media-options">
<div className="open-media-file-header">
<div className="vertical-helper" />
<img src="/assets/content/icon_folder.png" width="22" height="20" />
<span className="open-text">Open:</span>
</div>
<ul className="open-media-file-options">
<li>
<a className="open-recording" onClick={this.openRecording}>Recording</a>
</li>
<li>
<a className="open-jamtrack" onClick={this.openJamTrack}>JamTrack</a>
</li>
<li>
<a className="open-backingtrack" onClick={this.openBackingTrack}>Audio File</a>
</li>
</ul>
<div className="use-metronome-header">
<img src="/assets/content/icon_metronome.png" width="22" height="20" />
<a className="open-metronome" onClick={this.openMetronome}>Use Metronome</a>
</div>
</div>`
contents = null
mediaTracks = []
if this.state.downloadJamTrack?
closeOptions =
`<div>
<a className="closeAudio" onClick={this.cancelDownloadJamTrack}>
<img src="/assets/content/icon_close.png" width="18" height="20" />
Close JamTrack
</a>
<div className="download-jamtrack-holder"></div>
</div>`
contents = closeOptions
else if this.state.mediaSummary.mediaOpen
# give the users options to close it
if this.state.mediaSummary.jamTrackOpen
mediaType = "JamTrack"
else if this.state.mediaSummary.backingTrackOpen
mediaType = "Audio File"
else if this.state.mediaSummary.metronomeOpen
mediaType = "Metronome"
else if this.state.mediaSummary.recordingOpen
mediaType = "Recording"
else
mediaType = ""
closeOptions = `<a className="closeAudio" onClick={this.closeAudio}>
<img src="/assets/content/icon_close.png" width="18" height="20" />
Close {mediaType}
</a>`
if this.state.mediaSummary.backingTrackOpen
for backingTrack in @state.backingTracks
backingTrack.mode = MIX_MODES.PERSONAL
mediaTracks.push(`<SessionBackingTrack key={backingTrack.track.id} {...backingTrack} />`)
else if this.state.mediaSummary.jamTrackOpen
mediaTracks.push(`<SessionJamTrackCategory key="JamTrackCategory" jamTrackName={this.state.jamTrackName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.PERSONAL} />`)
for jamTrack in @state.jamTracks
jamTrack.mode = MIX_MODES.PERSONAL
mediaTracks.push(`<SessionJamTrack key={jamTrack.id} {...jamTrack} />`)
else if this.state.mediaSummary.recordingOpen
mediaTracks.push(`<SessionRecordedCategory key="RecordedCategory" recordingName={this.state.recordingName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.PERSONAL} />`)
for recordedTrack in @state.recordedTracks
recordedTrack.mode = MIX_MODES.PERSONAL
mediaTracks.push(`<SessionRecordedTrack key={recordedTrack.track.id} {...recordedTrack} />`)
else if this.state.mediaSummary.metronomeOpen
@state.metronome.mode = MIX_MODES.PERSONAL
mediaTracks.push(`<SessionMetronome key={this.state.metronome.id} {...this.state.metronome} />`)
contents = closeOptions
else
scrollerClassData['media-options-showing'] = true
contents = mediaOptions
scrollerClasses = classNames(scrollerClassData)
`<div className="session-media-tracks">
<h2>recorded audio</h2>
{contents}
<div className={scrollerClasses}>
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{mediaTracks}
</ReactCSSTransitionGroup>
</div>
</div>`
getInitialState:() ->
{mediaSummary:{mediaOpen: false}, isRecording: false, backingTracks: [], jamTracks: [], recordedTracks: [], metronome: null}
onAppInit: (app) ->
@app = app
handleBackingTrackSelectedCallback: (result) ->
@setState({backingTrackDialogOpen: false})
SessionActions.openBackingTrack(result)
componentDidMount: () ->
context.JK.HandleBackingTrackSelectedCallback2 = @handleBackingTrackSelectedCallback
componentDidUpdate: () ->
if @state.downloadJamTrack?
$holder = $(@getDOMNode()).find('.download-jamtrack-holder')
if $holder.find('.download-jamtrack').length == 0
SessionActions.downloadingJamTrack(true)
$holder.append(@state.downloadJamTrack.root)
# kick off the download JamTrack process
@state.downloadJamTrack.init()
@checkCloseWindow() if !@state.mediaSummary.mediaOpen && !@state.metronomeFlickerTimeout?
})

View File

@ -0,0 +1,81 @@
context = window
MixerActions = @MixerActions
@SessionMetronome = React.createClass({
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.props.mixers.mixer], muting)
render: () ->
# today, all mixers are the same for a remote participant; so just grab the 1st
mixers = @props.mixers
muteMixer = mixers.muteMixer
vuMixer = mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"metronome" : true
})
pan = if mixers.mixer? then mixers.mixer?.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="name">Metronome</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-instrument"><img height="24" src={this.props.instrumentIcon} width="24" /></div>
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,14 @@
context = window
@SessionMixerBtn = React.createClass({
openDialog: (e) ->
e.preventDefault()
context.JK.app.layout.showDialog('session-master-mix-dialog')
render: () ->
`<a className="session-mixer button-grey left" onClick={this.openDialog}>
<img src="/assets/content/icon_settings_sm.png" align="texttop" height="12" width="12"/>
MIXER
</a>`
})

View File

@ -0,0 +1,75 @@
context = window
@SessionMusicMixer= React.createClass({
handleMute: (e) ->
e.preventDefault()
unless @props.mixers.mixer
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@props.mixers.mixer], muting)
render: () ->
muteMixer = @props.mixers.muteMixer
vuMixer = @props.mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
pan = if @props.mixers.mixer? then @props.mixers.mixer.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className="session-track music-mixer">
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">Session Music Output</div>
<div className="track-instrument"><img height="45" src="/assets/content/icon_instrument_voice45.png" width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={this.props.mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,69 @@
context = window
MixerActions = @MixerActions
@SessionMyChat = React.createClass({
mixins: [@MasterPersonalMixersMixin]
handleMute: (e) ->
e.preventDefault()
mixers = @mixers()
unless mixers.mixer
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([mixers.mixer, mixers.oppositeMixer], muting)
render: () ->
mixers = @mixers()
muteMixer = mixers.muteMixer
vuMixer = mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
# <div className="track-icon-equalizer" />
`<div className="session-track my-track">
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">{this.props.name}</div>
<div className="track-avatar"><img src={this.props.photoUrl}/></div>
<div className="track-instrument"><img height="45" src='/assets/content/icon_instrument_chat45.png' width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@mixers()}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,96 @@
context = window
MixerActions = @MixerActions
@SessionMyTrack = React.createClass({
handleMute: (e) ->
e.preventDefault()
unless this.props.mixers.mixer
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.props.mixers.mixer, this.props.mixers.oppositeMixer], muting)
render: () ->
muteMixer = this.props.mixers.muteMixer
vuMixer = this.props.mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
pan = if this.props.mixers.mixer? then this.props.mixers.mixer.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
# <div className="track-icon-equalizer" />
`<div className="session-track my-track">
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">{this.props.name}</div>
<div className="track-avatar"><img src={this.props.photoUrl}/></div>
<div className="track-instrument"><img height="45" src={this.props.instrumentIcon} width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={this.props.mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
context.jamClient.SessionSetUserName(this.props.clientId, this.props.name)
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:this.props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:this.props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
componentWillUpdate: (nextProps, nextState) ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
# disable hover effects if there is no mixer
if nextProps.mixers.mixer?
$mute.off("click", false)
$pan.off("click", false)
else
$mute.on("click", false)
$pan.on("click", false)
})

View File

@ -0,0 +1,50 @@
context = window
MIX_MODES = context.JK.MIX_MODES
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@SessionMyTracks = React.createClass({
mixins: [@SessionMyTracksMixin, Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
render: () ->
content = null
tracks = []
if @state.tracks.length > 0
for track in @state.tracks
track.mode = MIX_MODES.PERSONAL
tracks.push(`<SessionMyTrack key={track.track.client_track_id} {...track} />`)
if @state.chat
@state.chat.mode = @props.mode
tracks.push(`<SessionMyChat key="chat" {...this.state.chat} />`)
else if @state.session? && @state.session.inSession()
content = `<div className="session-mytracks-notracks">
<p className="notice">
You have not set up any inputs for your instrument or vocals.
If you want to hear yourself play through the JamKazam app,
and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
<a href="#" className="open-ftue-no-tracks">click here now.</a>
</p>
</div>`
`<div className="session-my-tracks">
<h2>my live tracks</h2>
<SessionTrackSettingsBtn />
<div className="session-tracks-scroller">
{content}
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{tracks}
</ReactCSSTransitionGroup>
</div>
</div>`
getInitialState:() ->
{tracks:[], session: null, chat:null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,29 @@
context = window
@SessionNotification = React.createClass({
render: () ->
classes = classNames({
'session-notification' : true
'has-details' : @props.detail?
})
help = `<a rel="external" href={this.props.help} className="notify-help">?</a>` if @props.help?
title = `<div className="msg">{this.props.title}{help}</div>`
extra = `<div className="detail">{this.props.extra}</div>` if @props.extra?
`<div className={classes}>
{title}
{extra}
</div>`
componentDidMount: () ->
$root = $(@getDOMNode())
context.JK.popExternalLinks($root)
if @props.detail?
context.JK.hoverBubble($root, @props.detail, {offsetParent:$root.closest('.top-parent'), positions: ['left', 'bottom']})
})

View File

@ -0,0 +1,41 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
NotificationActions = @NotificationActions
@SessionNotifications = React.createClass({
mixins: [Reflux.listenTo(@SessionNotificationStore,"onNotificationsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
onNotificationsChanged: (notifications) ->
@setState({notifications: notifications})
getInitialState: () ->
{notifications: []}
clearNotifications: (e) ->
e.preventDefault()
NotificationActions.clear()
render: () ->
notifications = []
for notification in @state.notifications
notifications.push(`<SessionNotification key={notification.id} {...notification} />`)
`<div className="session-notifications">
<h2>notifications</h2>
<a className="session-clear-notifications" onClick={this.clearNotifications}>
<img src="/assets/content/icon_close.png" width="18" height="20" />
Clear Notifications
</a>
<div className="session-tracks-scroller">
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{notifications}
</ReactCSSTransitionGroup>
</div>
</div>`
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,121 @@
context = window
MixerActions = @MixerActions
@SessionOtherTrack = React.createClass({
handleMute: (e) ->
e.preventDefault()
unless this.props.hasMixer
logger.debug("ignoring mute; no mixer")
return
muting = $(e.currentTarget).is('.enabled')
mixers = if this.props.tracks.length > 0 then this.props.tracks[0].mixers else {}
MixerActions.mute([mixers.mixer], muting)
render: () ->
# today, all mixers are the same for a remote participant; so just grab the 1st
mixers = if this.props.tracks.length > 0 then this.props.tracks[0].mixers else {}
muteMixer = mixers.muteMixer
vuMixer = mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"my-track" : true
"has-mixer" : this.props.hasMixer
"no-mixer" : !this.props.hasMixer
"has-audio" : this.props.noAudio != true
"no-audio" : this.props.noAudio == true
})
pan = if mixers.mixer? then mixers.mixer?.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div className="name">{this.props.name}</div>
<div className="track-avatar"><img src={this.props.photoUrl}/></div>
<div className="track-instrument"><img height="45" src={this.props.instrumentIcon} width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
if this.props.participant.client_id?
context.jamClient.SessionSetUserName(this.props.participant.client_id, this.props.name)
else
logger.error("no participant client ID")
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
mixers = if this.props.tracks.length > 0 then this.props.tracks[0].mixers else {}
{mixers:mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
mixers = if this.props.tracks.length > 0 then this.props.tracks[0].mixers else {}
{mixers:mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
unless this.props.hasMixer
$mute.on("mouseenter", false)
$mute.on("mouseleave", false)
$pan.on("mouseentere", false)
$pan.on("mouseleave", false)
componentWillUpdate: (nextProps, nextState) ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
# disable hover effects if there is no mixer
if nextProps.hasMixer
$mute.off("mouseenter", false)
$mute.off("mouseleave", false)
$pan.off("mouseenter", false)
$pan.off("mouseleave", false)
else
$mute.on("mouseenter", false)
$mute.on("mouseleave", false)
$pan.on("mouseentere", false)
$pan.on("mouseleave", false)
})

View File

@ -0,0 +1,83 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
@SessionOtherTracks = React.createClass({
mixins: [Reflux.listenTo(@SessionOtherTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
noAudioUsers = mixers.noAudioUsers
participants = []
if session.inSession()
for participant in session.otherParticipants()
tracks = []
name = participant.user.name;
firstTrack = participant.tracks[0]
hasMixer = false
for track in participant.tracks
# try to find mixer info for this track
mixerFinder = [participant.client_id, track, false] # so that other callers can re-find their mixer data
mixerData = mixers.findMixerForTrack(participant.client_id, track, false, @props.mode)
if mixerData.mixer?
hasMixer = true
tracks.push(track: track, mixers: mixerData, mixerFinder: mixerFinder)
# todo: sessionModel.setAudioEstablished
instrumentIcon = context.JK.getInstrumentIcon45(firstTrack.instrument_id)
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url)
participantState = {
participant: participant,
tracks: tracks,
name: name,
instrumentIcon: instrumentIcon,
photoUrl: photoUrl,
hasMixer: hasMixer,
noAudio: noAudioUsers[participant.client_id]
}
participants.push(participantState)
this.setState(participants: participants, session: session)
render: () ->
content = null
participants = []
noOthers = `<div className="when-empty">No other musicians are in your session.</div>`
if this.state.participants.length > 0
for participant in this.state.participants
participants.push(`<SessionOtherTrack key={participant.client_id} {...participant} />`)
else if this.state.session? && this.state.session.inSession()
content = noOthers
`<div className="session-other-tracks">
<h2>other live tracks</h2>
<SessionInviteMusiciansBtn />
<div className="session-tracks-scroller">
{content}
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{participants}
</ReactCSSTransitionGroup>
</div>
</div>`
getInitialState:() ->
{participants:[], session: null}
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,24 @@
context = window
RecordingActions = @RecordingActions
@SessionRecordBtn = React.createClass({
mixins: [Reflux.listenTo(@MixerStore,"onSessionMixerChange")]
onSessionMixerChange: (sessionMixers) ->
this.setState({isRecording: sessionMixers.session.isRecording})
getInitialState: () ->
{childWindow: null, isRecording: false}
openRecording: () ->
RecordingActions.openRecordingControls()
render: () ->
`<a className="session-record button-grey left" onClick={this.openRecording}>
<img src="/assets/content/icon_settings_sm.png" align="texttop" height="12" width="12"/>
RECORD
</a>`
})

View File

@ -0,0 +1,84 @@
context = window
MixerActions = @MixerActions
@SessionRecordedCategory = React.createClass({
propTypes: {
mode: React.PropTypes.bool.isRequired
}
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.props.mixers.mixer], muting)
render: () ->
# today, all mixers are the same for a remote participant; so just grab the 1st
mixers = @props.mixers
muteMixer = mixers.muteMixer
vuMixer = mixers.vuMixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"recorded-category" : true
})
pan = mixers.mixer.pan
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="name">{this.props.recordingName}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,89 @@
context = window
MixerActions = @MixerActions
@SessionRecordedTrack = React.createClass({
mixins: [@MasterPersonalMixersMixin]
propTypes: {
mode: React.PropTypes.bool.isRequired
}
handleMute: (e) ->
e.preventDefault()
mixer = @mixer()
unless mixer?
logger.debug("ignoring mute because no media mixer")
return
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([mixer], muting)
render: () ->
mixers = @mixers()
muteMixer = mixers.mixer
muteMixerId = muteMixer?.id
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
componentClasses = classNames({
"session-track" : true
"recorded-track" : true
})
pan = if mixers.mixer? then mixers.mixer?.pan else 0
panStyle = {
transform: "rotate(#{pan}deg)"
WebkitTransform: "rotate(#{pan}deg)"
}
`<div className={componentClasses}>
<div className="session-track-contents">
<div className="name">{this.props.userName}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="best" mixers={mixers} />
<div className="track-buttons">
<div className="track-icon-pan" style={panStyle}/>
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
</div>
<div className="track-instrument"><img height="24" src={this.props.instrumentIcon} width="24" /></div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>
</div>`
componentDidMount: () ->
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
() =>
{mixers:@mixers()}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@mixers()}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')})
})

View File

@ -0,0 +1,20 @@
context = window
@SessionResyncBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
resync: (e) ->
e.preventDefault()
SessionActions.audioResync()
render: () ->
`<a className="session-resync button-grey resync left" onClick={this.resync}>
<img src="/assets/content/icon_resync.png" align="texttop" height="12" width="12"/>
RESYNC
</a>`
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,78 @@
context = window
MIX_MODES = context.JK.MIX_MODES
SessionActions = @SessionActions
@SessionScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@SessionActions.allowLeaveSession, "onAllowLeaveSession")]
render: () ->
`<div className="session-container">
<div className="in-session-controls">
<SessionSettingsBtn />
<SessionVolumeSettingsBtn />
<SessionShareBtn />
<SessionRecordBtn />
<SessionVideoBtn />
<SessionMixerBtn />
<SessionResyncBtn />
<SessionLeaveBtn />
</div>
<div className="tracks" id="new-tracks">
<SessionMyTracks mode={MIX_MODES.PERSONAL} />
<SessionOtherTracks mode={MIX_MODES.PERSONAL} />
<SessionMediaTracks mode={MIX_MODES.PERSONAL} />
<SessionNotifications />
</div>
</div>`
componentDidMount: () ->
@logger = context.JK.logger
beforeShow: (data) ->
@logger.debug("session beforeShow")
@allowLeave = false
afterShow: (data) ->
@logger.debug("session afterShow")
SessionActions.joinSession.trigger(data.id)
beforeHide: () ->
context.JK.HelpBubbleHelper.clearJamTrackGuide();
beforeLeave: (data) ->
@logger.debug("session beforeLeave", @allowLeave)
if @allowLeave
return true
else
leaveSessionWarningDialog = new context.JK.LeaveSessionWarningDialog(context.JK.app,
() =>
@allowLeave = true
context.location.hash = data.hash
)
leaveSessionWarningDialog.initialize()
@app.layout.showDialog('leave-session-warning')
return false
beforeDisconnect: () ->
@logger.debug("session beforeDisconnect")
onAllowLeaveSession: () ->
@allowLeave = true
onAppInit: (@app) ->
screenBindings = {
'beforeShow': @beforeShow,
'afterShow': @afterShow,
'beforeHide': @beforeHide,
'beforeLeave' : @beforeLeave,
'beforeDisconnect' : @beforeDisconnect,
};
@app.bindScreen('session', screenBindings);
})

View File

@ -0,0 +1,160 @@
context = window
MIX_MODES = context.JK.MIX_MODES
MixerActions = @MixerActions
@SessionSelfVolumeHover = React.createClass({
mixins: [Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
inputGroupMixers = mixers.getAudioInputCategoryMixer(MIX_MODES.PERSONAL)
chatGroupMixers = mixers.getChatCategoryMixer( MIX_MODES.PERSONAL)
@setState({inputGroupMixers: inputGroupMixers, chatGroupMixers: chatGroupMixers})
getInitialState: () ->
{inputGroupMixers: @props.inputGroupMixers, chatGroupMixers: @props.chatGroupMixers}
handleAudioInputMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@state.inputGroupMixers.muteMixer], muting)
handleChatInputMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@state.chatGroupMixers.muteMixer], muting)
handleAudioInputMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
MixerActions.mute([@state.inputGroupMixers.muteMixer], muting)
handleChatMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
MixerActions.mute([@state.chatGroupMixers.muteMixer], muting)
render: () ->
monitorMuteMixer = @state.inputGroupMixers.muteMixer
monitorMuteMixerId = monitorMuteMixer?.id
monitorVolumeLeft = @state.inputGroupMixers.mixer?.volume_left
monitorMuteClasses = classNames({
'track-icon-mute': true
'enabled' : !monitorMuteMixer?.mute
'muted' : monitorMuteMixer?.mute
})
chatMuteMixer = @state.chatGroupMixers.muteMixer
chatMuteMixerId = chatMuteMixer?.id
chatVolumeLeft = @state.chatGroupMixers.mixer?.volume_left
chatMuteClasses = classNames({
'track-icon-mute': true
'enabled' : !chatMuteMixer?.mute
'muted' : chatMuteMixer?.mute
})
`<div id="self-volume-hover" >
<div className="monitor-mixer mixer-holder">
<h3>Music</h3>
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="best" mixers={this.state.inputGroupMixers} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vur" mixers={this.state.inputGroupMixers} />
</div>
<div className="track-label">
<div>Volume</div>
<div>{monitorVolumeLeft}dB</div>
</div>
<SessionTrackGain mixers={this.state.inputGroupMixers} />
<div className={monitorMuteClasses} data-control="mute" data-mixer-id={monitorMuteMixerId} onClick={this.handleAudioInputMute}/>
<input type="checkbox" name="mute"/>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">
<p>Use this slider to control the volume of all the music in the session in your headphones or speakers.</p>
<p>This will not affect the volume for other musicians in the session.</p>
<p>To adjust master levels for all musicians for recordings and broadcasts, use Mixer button in the toolbar.</p>
</div>
</div>
<div className="chat-mixer mixer-holder">
<h3>Chat</h3>
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="best" mixers={this.state.chatGroupMixers} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vur" mixers={this.state.chatGroupMixers} />
</div>
<div className="track-label">
<div>Volume</div>
<div>{chatVolumeLeft}dB</div>
</div>
<SessionTrackGain mixers={this.state.chatGroupMixers} />
<div className={chatMuteClasses} data-control="mute" data-mixer-id={chatMuteMixerId} onClick={this.handleChatMute}/>
<input type="checkbox" name="mute"/>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">
<p>Use this slider to control the volume of all the voice chat in the session in your headphones or speakers.</p>
<p>This will not affect the volume for other musicians in the session.</p>
</div>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
# initialize icheck
$chatMuteCheckbox = $root.find('.chat-mixer input')
context.JK.checkbox($chatMuteCheckbox)
$chatMuteCheckbox.on('ifChanged', @handleChatMuteCheckbox);
if @state.chatGroupMixers.muteMixer.mute
$chatMuteCheckbox.iCheck('check').attr('checked', true)
else
$chatMuteCheckbox.iCheck('uncheck').attr('checked', false)
$audioInputMuteCheckbox = $root.find('.monitor-mixer input')
context.JK.checkbox($audioInputMuteCheckbox)
$audioInputMuteCheckbox.on('ifChanged', @handleAudioInputMuteCheckbox);
if @state.inputGroupMixers.muteMixer.mute
$audioInputMuteCheckbox.iCheck('check').attr('checked', true)
else
$audioInputMuteCheckbox.iCheck('uncheck').attr('checked', false)
componentWillUpdate: (nextProps, nextState) ->
$root = jQuery(this.getDOMNode())
# re-initialize icheck
$chatMuteCheckbox = $root.find('.chat-mixer input')
if nextState.chatGroupMixers.muteMixer?.mute
$chatMuteCheckbox.iCheck('check').attr('checked', true)
else
$chatMuteCheckbox.iCheck('uncheck').attr('checked', false)
$audioInputMuteCheckbox = $root.find('.monitor-mixer input')
if nextState.inputGroupMixers.muteMixer?.mute
$audioInputMuteCheckbox.iCheck('check').attr('checked', true)
else
$audioInputMuteCheckbox.iCheck('uncheck').attr('checked', false)
})

View File

@ -0,0 +1,20 @@
context = window
@SessionSettingsBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
openSettings: (e) ->
e.preventDefault()
@app.layout.showDialog('session-settings')
render: () ->
`<a className="session-settings button-grey left" onClick={this.openSettings}>
<img src="/assets/content/icon_settings_sm.png" align="texttop" height="12" width="12"/>
SETTINGS
</a>`
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,20 @@
context = window
@SessionShareBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
onShare: (e) ->
e.preventDefault()
@app.layout.showDialog('share-dialog')
render: () ->
`<a className="session-share button-grey left" onClick={this.onShare}>
<img src="/assets/content/icon_share.png" align="texttop" height="12" width="12"/>
SHARE
</a>`
onAppInit: (app) ->
@app = app
})

View File

@ -0,0 +1,51 @@
context = window
logger = context.JK.logger
@SessionTrackGain = React.createClass({
getInitialState: () ->
{
mixers: @props.mixers,
behaviors: @props.behaviors || {}
}
faderChanged: (e, data) ->
$target = $(this)
groupId = $target.data('groupId')
mixers = [@state.mixers.mixer]
MixerActions.faderChanged(data, mixers, groupId)
render: () ->
mixerId = this.state.mixers?.mixer?.id
`<div className="track-gain">
<div className="fader vertical" data-control="fader" data-fader-id={mixerId} data-orientation="vertical">
<div className="handle" data-control="fader-handle">
<img src="/assets/content/slider_gain_vertical.png" width="28" height="11"/>
</div>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
if !$root.is('.track-gain')
logger.error("unknown root node")
$fader = $root.attr('data-mixer-id', @state.mixers.mixer.id).data('groupId', @state.mixers.mixer.groupId).data('mixer', @state.mixers.mixer).data('opposite-mixer', @state.mixers.oppositeMixer)
if @state.behaviors.mediaControlsDisabled
$fader.data('media-controls-disabled', true).data('media-track-opener', @state.behaviors.mediaTrackOpener) # this we be applied later to the fader handle $element
$fader.data('showHelpAboutMediaMixers', @state.behaviors.showHelpAboutMediaMixers)
context.JK.FaderHelpers.renderFader2($fader, {faderType: 'vertical'});
# Initialize gain position
MixerActions.initGain(@state.mixers.mixer)
# watch for fader change events
$fader.on('fader_change', @faderChanged);
})

View File

@ -0,0 +1,54 @@
context = window
logger = context.JK.logger
@SessionTrackPan = React.createClass({
getInitialState: () ->
{
mixers: this.props.mixers,
behaviors: this.props.behaviors || {}
}
panChanged: (e, data) ->
$target = $(this)
groupId = $target.data('groupId')
mixers = [@state.mixers.mixer]
MixerActions.panChanged(data, mixers, groupId)
render: () ->
mixerId = this.state.mixers?.mixer?.id
`<div className="track-pan">
<div className="left-label">Left</div>
<div className="right-label">Right</div>
<div className="fader horizontal" data-control="fader" data-fader-id={mixerId} data-orientation="horizontal">
<div className="handle" data-control="fader-handle">
<div className="floater"></div>
<img src="/assets/content/slider_gain_horiz.png" width="11" height="28"/>
</div>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
if !$root.is('.track-pan')
logger.error("unknown root node")
$fader = $root.attr('data-mixer-id', this.state.mixers.mixer.id).data('groupId', this.state.mixers.mixer.groupId).data('mixer', this.state.mixers.mixer).data('opposite-mixer', this.state.mixers.oppositeMixer)
if this.state.behaviors.mediaControlsDisabled
$fader.data('media-controls-disabled', true).data('media-track-opener', this.state.behaviors.mediaTrackOpener) # this we be applied later to the fader handle $element
$fader.data('showHelpAboutMediaMixers', this.state.behaviors.showHelpAboutMediaMixers)
context.JK.FaderHelpers.renderFader2($fader, {faderType: 'horizontal', snap:true}, context.JK.PanHelpers.convertPercentToPanForDisplay)
# Initialize gain position
MixerActions.initPan(this.state.mixers.mixer)
# watch for fader change events
$fader.on('fader_change', this.panChanged)
})

View File

@ -0,0 +1,43 @@
context = window
MixerActions = @MixerActions
@SessionTrackPanHover = React.createClass({
mixins: [Reflux.listenTo(@SessionMyTracksStore, "onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
newMixers = mixers.refreshMixer(@state.mixers)
this.setState({mixers: newMixers})
getInitialState: () ->
{mixers: this.props.mixers}
render: () ->
`<div className="track-pan-hover">
<div className="textual-help">
<p>
Use this slider to pan the audio of this track left or right in your personal mix.
This will not pan audio for other musicians in the session.
To pan audio in the master mix for recordings and broadcasts, use the Mixer button in the toolbar.
</p>
</div>
<div className="session-pan">
<SessionTrackPan mixers={this.state.mixers} />
</div>
</div>`
componentWillUpdate: (nextProps, nextState) ->
$root = jQuery(this.getDOMNode())
# if the mixers go dead, whack our selves out of existence
unless nextState.mixers?
$container = $root.closest('.react-holder')
$container.data('bt').btOff()
return
})

View File

@ -0,0 +1,22 @@
context = window
logger = context.JK.logger
@SessionTrackSettingsBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
onConfigureSettings: (e) ->
e.preventDefault();
@app.layout.showDialog('configure-tracks')
onAppInit: (app) ->
@app = app
render: () ->
`<a className="session-track-settings" onClick={this.onConfigureSettings}>
<img src="/assets/content/icon_settings_lg.png" width="18" height="18" />
<span>Settings</span>
</a>`
})

View File

@ -0,0 +1,87 @@
context = window
ptrCount = 0
@SessionTrackVU = React.createClass({
render: () ->
lights = []
redSwitch = Math.round(this.props.lightCount * 0.66);
lightClass = 'vu-red-off'
if this.props.orientation == 'horizontal'
for i in [0..this.props.lightCount-1]
lightClass = if i >= redSwitch then 'vu-red-off' else 'vu-green-off'
lightClasses = classNames('vulight', 'vu' + i, lightClass)
lights.push(`<td key={i} width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td>`)
tableClasses = classNames('vu', 'horizontal')
`<table className={tableClasses} data-light-count={this.props.lightCount}>
<tbody>
<tr>
{lights}
</tr>
</tbody>
</table>`
else
for i in [0..this.props.lightCount-1].reverse()
lightClass = if (i >= redSwitch) then "vu-red-off" else "vu-green-off"
lightClasses = classNames('vulight', 'vu' + i, lightClass)
lights.push(`<tr key={i}><td width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td></tr>`)
tableClasses = classNames('vu', 'vertical')
`<table className={tableClasses} data-light-count={this.props.lightCount}>
<tbody>
{lights}
</tbody>
</table>`
getInitialState: () ->
{registered: null, ptr: @props.ptr || "STV#{ptrCount++}"}
registerVU: (mixer) ->
mixerChanged = false
if @state.registered? && mixer?
# see if the mixer ID changed; if so, we need to unregister and re-register
if @state.registered.mixer.id != mixer.id
logger.debug("unregistering vu due to mixer change", @state.registered.mixer)
context.JK.VuHelpers.unregisterVU(@state.registered.mixer, @state.registered.ptr)
mixerChanged = true
if !mixerChanged && (@state.registered? || !mixer?)
return
$root = $(this.getDOMNode())
if mixerChanged
logger.debug("re-registering VU")
else
logger.debug("registered VU")
context.JK.VuHelpers.registerVU(@props.side, mixer, @state.ptr, @props.orientation == 'horizontal', @props.lightCount, $root.find('td'))
@setState(registered: {mixer: mixer, ptr: @state.ptr})
componentWillReceiveProps: (nextProps) ->
@registerVU(nextProps.mixers?.vuMixer)
componentDidMount: () ->
@registerVU(@props.mixers?.vuMixer)
componentWillUnmount: () ->
if @state.registered?
logger.debug("unregistered VU")
context.JK.VuHelpers.unregisterVU(@state.registered.mixer, @state.registered.ptr)
})

View File

@ -0,0 +1,109 @@
context = window
ChannelGroupIds = context.JK.ChannelGroupIds
MixerActions = @MixerActions
ptrCount = 0
@SessionTrackVolumeHover = React.createClass({
btElement: null
mixins: [Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
newMixers = mixers.refreshMixer(@state.mixers)
this.setState({mixers: newMixers})
getInitialState: () ->
{mixers: this.props.mixers, ptr: "STVH#{ptrCount++}" }
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
if @state.mixers.mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || @state.mixers.mixer.group_id == ChannelGroupIds.AudioInputChatGroup
MixerActions.mute([this.state.mixers.mixer, this.state.mixers.oppositeMixer], muting)
else
MixerActions.mute([this.state.mixers.mixer], muting)
handleMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
if @state.mixers.mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || @state.mixers.mixer.group_id == ChannelGroupIds.AudioInputChatGroup
MixerActions.mute([this.state.mixers.mixer, this.state.mixers.oppositeMixer], muting)
else
MixerActions.mute([this.state.mixers.mixer], muting)
render: () ->
muteMixer = this.state.mixers?.muteMixer
muteMixerId = muteMixer?.id
volume_left = this.state.mixers?.mixer?.volume_left
classes = classNames({
'track-icon-mute': true
'enabled' : !muteMixer?.mute
'muted' : muteMixer?.mute
})
`<div className="track-volume-hover">
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="left" mixers={this.state.mixers} ptr={this.state.ptr} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="right" mixers={this.state.mixers} ptr={this.state.ptr} />
</div>
<div className="track-label">
<div>Volume</div>
<div>{volume_left}dB</div>
</div>
<SessionTrackGain mixers={this.state.mixers} />
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<input type="checkbox" name="mute"/>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">
<p>Use this slider to control the volume of this track in your personal mix.</p>
<p>This will not affect the volume of this track for other musicians in the session.</p>
<p>To adjust master levels for all musicians for recordings and broadcasts, use Mixer button in the toolbar.</p>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
# initialize icheck
$checkbox = $root.find('input')
context.JK.checkbox($checkbox)
$checkbox.on('ifChanged', this.handleMuteCheckbox);
if @state.mixers.muteMixer.mute
$checkbox.iCheck('check').attr('checked', true)
else
$checkbox.iCheck('uncheck').attr('checked', false)
componentWillUpdate: (nextProps, nextState) ->
$root = jQuery(this.getDOMNode())
# if the mixers go dead, whack our selves out of existence
unless nextState.mixers?
$container = $root.closest('.react-holder')
$container.data('bt').btOff()
return
# re-initialize icheck
$checkbox = $root.find('input')
if nextState.mixers?.muteMixer?.mute
$checkbox.iCheck('check').attr('checked', true)
else
$checkbox.iCheck('uncheck').attr('checked', false)
})

View File

@ -0,0 +1,16 @@
context = window
SessionActions = @SessionActions
@SessionVideoBtn = React.createClass({
sessionWebCam: (e) ->
e.preventDefault();
SessionActions.toggleSessionVideo()
render: () ->
`<a className="session-share button-grey-toggle left" onClick={this.sessionWebCam}>
<img src="/assets/content/icon_cam.png" align="texttop" height="12" width="12"/>
VIDEO
</a>`
})

View File

@ -0,0 +1,28 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@SessionVolumeSettingsBtn = React.createClass({
mixins: [Reflux.listenTo(@MixerStore,"onInputsChanged")]
onInputsChanged: (sessionMixers) ->
this.setState(sessionMixers)
render: () ->
`<a className="session-volume-settings button-grey left">
<img src="/assets/content/icon_settings_sm.png" align="texttop" height="12" width="12"/>
VOLUME
</a>`
componentDidMount: () ->
$root = $(this.getDOMNode())
context.JK.interactReactBubble(
$root,
'SessionSelfVolumeHover',
() =>
{inputGroupMixers: @state.mixers.getAudioInputCategoryMixer(MIX_MODES.PERSONAL), chatGroupMixers: @state.mixers.getChatCategoryMixer( MIX_MODES.PERSONAL)}
,
{width:470, positions:['right', 'bottom', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -0,0 +1,19 @@
context = window
@TestComponent = React.createClass({
getInitialState: () ->
{something: 1}
tick: () ->
console.log("tick")
this.setState({something: this.state.something + 1})
componentDidMount: () ->
console.log("here")
setInterval(@tick, 1000)
render: () ->
console.log("render")
`<div>{this.state.something}</div>`
})

View File

@ -0,0 +1,5 @@
context = window
@AppActions = Reflux.createActions({
appInit: {}
})

View File

@ -1,6 +1,6 @@
context = window
BroadcastActions = Reflux.createActions({
@BroadcastActions = Reflux.createActions({
load: {asyncResult: true},
hide: {}
})

View File

@ -0,0 +1,11 @@
context = window
@MediaPlaybackActions = Reflux.createActions({
playbackStateChange: {}
positionUpdate:{}
mediaStartPlay: {}
mediaStopPlay: {}
mediaPausePlay: {}
mediaChangePosition: {}
currentTimeChanged: {}
})

View File

@ -0,0 +1,16 @@
context = window
@MixerActions = Reflux.createActions({
mute: {}
faderChanged: {}
initGain: {}
panChanged: {}
initPan: {}
mixersChanged: {}
syncTracks: {}
mixerModeChanged: {}
loopChanged: {}
openMetronome: {}
metronomeChanged: {}
deadUserRemove: {}
})

View File

@ -0,0 +1,9 @@
context = window
@NotificationActions = Reflux.createActions({
clear:{}
backendNotification: {}
frontendNotification: {}
sessionEnded: {}
})

View File

@ -0,0 +1,14 @@
context = window
@RecordingActions = Reflux.createActions({
initModel: {}
startRecording: {}
stopRecording: {}
startingRecording:{}
startedRecording: {}
stoppingRecording: {}
stoppedRecording: {}
abortedRecording: {}
openRecordingControls: {}
recordingControlsClosed: {}
})

View File

@ -0,0 +1,22 @@
context = window
@SessionActions = Reflux.createActions({
joinSession: {}
leaveSession: {}
mixersChanged: {}
allowLeaveSession: {}
syncWithServer: {}
toggleSessionVideo : {}
audioResync: {}
openBackingTrack: {}
closeMedia: {}
updateSession: {}
downloadingJamTrack : {}
openMetronome: {}
showNativeMetronomeGui: {}
metronomeCricketChange: {}
windowBackgrounded: {}
broadcastFailure: {}
broadcastSuccess: {}
broadcastStopped: {}
})

View File

@ -0,0 +1,5 @@
context = window
@SessionMyTracksActions = Reflux.createActions({
})

View File

@ -0,0 +1,830 @@
context = window
ChannelGroupIds = context.JK.ChannelGroupIds
CategoryGroupIds = context.JK.CategoryGroupIds
MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixMode) ->
@mixMode = MIX_MODES.PERSONAL # TODO - remove mixMode from MixerHelper? Or at least stop using it in most functions
@app = @session.app
@mixersByResourceId = {}
@mixersByTrackId = {}
@allMixers = {}
@currentMixerRangeMin = null
@currentMixerRangeMax = null
@mediaSummary = {}
@mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup]
@muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup,
ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]
@organize()
organize: () ->
for masterMixer in @masterMixers
@allMixers['M' + masterMixer.id] = masterMixer; # populate allMixers by mixer.id
# populate mixer pair
mixerPair = {}
@mixersByResourceId[masterMixer.rid] = mixerPair
@mixersByTrackId[masterMixer.id] = mixerPair
mixerPair.master = masterMixer;
for personalMixer in @personalMixers
@allMixers['P' + personalMixer.id] = personalMixer
# populate other side of mixer pair
mixerPair = @mixersByResourceId[personalMixer.rid]
unless 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;
@groupTypes()
@chatMixer = @resolveChatMixer()
groupTypes: () ->
localMediaMixers = @mixersForGroupIds(@mediaTrackGroups, MIX_MODES.MASTER)
peerLocalMediaMixers = @mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER)
#logger.debug("localMediaMixers", localMediaMixers)
#logger.debug("peerLocalMediaMixers", peerLocalMediaMixers)
# get the server data regarding various media tracks
recordedBackingTracks = @session.recordedBackingTracks()
backingTracks = @session.backingTracks()
recordedJamTracks = @session.recordedJamTracks()
jamTracks = @session.jamTracks()
###
with mixer info, we use these to decide what kind of tracks are open in the backend
each mixer has a media_type field, which describes the type of media track it is.
* JamTrack
* BackingTrack
* RecordingTrack
* MetronomeTrack
* "" - adhoc track (not supported visually)
it is supposed to be the case that there are only one type of track open at a time, however, that's a business policy/logic
constraint; and may be buggy. **So, we should render whatever we have, so that it's obvious what's really going on.**
so, let's group up all mixers by type, and then ask them to be rendered
###
@recordingTrackMixers = []
@backingTrackMixers = []
@jamTrackMixers = []
@metronomeTrackMixers = []
@adhocTrackMixers = []
groupByType = (mixers, isLocalMixer) =>
for mixer in mixers
mediaType = mixer.media_type
groupId = mixer.group_id
if mediaType == 'MetronomeTrack' || groupId == ChannelGroupIds.MetronomeGroup
# Metronomes come across with a blank media type, so check group_id:
@metronomeTrackMixers.push(mixer)
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# additional check; if we can match an id in backing tracks or recorded backing track,
# we need to remove it from the recorded track set, but move it to the backing track set
isJamTrack = false;
if jamTracks
# check if the ID matches that of an open jam track
for jamTrack in jamTracks
if mixer.id == jamTrack.id
isJamTrack = true;
break
if !isJamTrack && recordedJamTracks
# then check if the ID matches that of a open, recorded jam track
for recordedJamTrack in recordedJamTracks
if mixer.id == recordedJamTrack.id
isJamTrack = true
break
if isJamTrack
@jamTrackMixers.push(mixer)
else
isBackingTrack = false
if recordedBackingTracks
for recordedBackingTrack in recordedBackingTracks
if mixer.id == 'L' + recordedBackingTrack.client_track_id
isBackingTrack = true
break
if backingTracks
for backingTrack in backingTracks
if mixer.id == 'L' + backingTrack.client_track_id
isBackingTrack = true
break
if isBackingTrack
@backingTrackMixers.push(mixer)
else
# couldn't resolve this as a JamTrack or Backing track, must be a normal recorded file
@recordingTrackMixers.push(mixer)
else if mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack'
@backingTrackMixers.push(mixer)
else if mediaType == 'JamTrack'
@jamTrackMixers.push(mixer);
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# mediaType == null is for backwards compat with older clients. Can be removed soon
@recordingTrackMixers.push(mixer)
else
logger.warn("Unknown track type: " + mediaType)
@adhocTrackMixers.push(mixer)
groupByType(localMediaMixers, true);
groupByType(peerLocalMediaMixers, false);
###
if recordingTrackMixers.length > 0
renderRecordingTracks(recordingTrackMixers)
if backingTrackMixers.length > 0
renderBackingTracks(backingTrackMixers)
if jamTrackMixers.length > 0
renderJamTracks(jamTrackMixers);
if metronomeTrackMixers.length > 0 && @session.jamTracks() == null && @session.recordedJamTracks() == null
renderMetronomeTracks(metronomeTrackMixers);
checkMetronomeTransition();
###
@backingTracks = @resolveBackingTracks()
@jamTracks = @resolveJamTracks()
@recordedTracks = @resolveRecordedTracks()
@metronome = @resolveMetronome()
if @adhocTrackMixers.length > 0
logger.warn("some tracks are open that we don't know how to show")
@mediaSummary =
recordingOpen: @recordedTracks.length > 0
jamTrackOpen: @jamTracks.length > 0
backingTrackOpen: @backingTracks.length > 0
metronomeOpen: @metronome?
# figure out if any media is open
mediaOpenSummary = false
for mediaType, mediaOpen of @mediaSummary
mediaOpenSummary = true if mediaOpen
@mediaSummary.mediaOpen = mediaOpenSummary
# this method is pretty complicated because it forks on a key bit of state:
# sessionModel.isPlayingRecording()
# a backing track opened as part of a recording has a different behavior and presence on the server (recording.recorded_backing_tracks)
# than a backing track opend ad-hoc (connection.backing_tracks)
resolveBackingTracks: () ->
backingTracks = []
return backingTracks unless @backingTrackMixers.length > 0
# find both client and server representation of the backing track
serverBackingTracks = []
backingTrackMixers = @backingTrackMixers
if @session.isPlayingRecording()
backingTrackMixers = context._.filter(backingTrackMixers, (mixer) -> return mixer.managed || !mixer.managed?)
serverBackingTracks = @session.recordedBackingTracks()
else
serverBackingTracks = @session.backingTracks();
backingTrackMixers = context._.filter(backingTrackMixers, (mixer) -> return !mixer.managed)
if backingTrackMixers.length > 1
logger.error("multiple, managed backing track mixers encountered", backingTrackMixers)
@app.notify({
title: "Multiple Backing Tracks Encountered",
text: "Only one backing track can be open a time.",
icon_url: "/assets/content/icon_alert_big.png"
});
return backingTracks;
# we don't render backing tracks unless we have server data to accompany
if !serverBackingTracks? || serverBackingTracks.length == 0
return backingTracks
noCorrespondingTracks = false
for mixer in backingTrackMixers
# find the track or tracks that correspond to the mixer
correspondingTracks = []
noCorrespondingTracks = false
if @session.isPlayingRecording()
for backingTrack in serverBackingTracks
# occurs if this client is the one that opened the track, # occurs if this client is a remote participant
if mixer.persisted_track_id == backingTrack.client_track_id || mixer.id == 'L' + backingTrack.client_track_id
correspondingTracks.push(backingTrack)
else
# if this is just an open backing track, then we can assume that the 1st backingTrackMixer is ours
correspondingTracks.push(serverBackingTracks[0])
if correspondingTracks.length == 0
noCorrespondingTracks = true
logger.debug("renderBackingTracks: could not map backing tracks")
@app.notify({
title: "Unable to Open Backing Track",
text: "Could not correlate server and client tracks",
icon_url: "/assets/content/icon_alert_big.png"
});
break
# now we have backing track and mixer in hand; we can render
serverBackingTrack = correspondingTracks[0]
oppositeMixer = @getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL);
isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup
data =
isOpener: isOpener
shortFilename: context.JK.getNameOfFile(serverBackingTrack.filename)
instrumentIcon: context.JK.getInstrumentIcon45(serverBackingTrack.instrument_id)
photoUrl: "/assets/content/icon_recording.png"
showLoop: isOpener && !@session.isPlayingRecording()
track: serverBackingTrack
mixers: @mediaMixers(mixer, isOpener)
backingTracks.push(data)
backingTracks
resolveJamTracks: () ->
_jamTracks = []
return _jamTracks unless @jamTrackMixers.length > 0
jamTrackMixers = @jamTrackMixers.slice();
jamTracks = []
jamTrackName = null;
if @session.isPlayingRecording()
# only return managed mixers for recorded backing tracks
jamTracks = @session.recordedJamTracks()
jamTrackName = @session.recordedJamTrackName()
else
# only return un-managed (ad-hoc) mixers for normal backing tracks
jamTracks = @session.jamTracks()
jamTrackName = @session.jamTrackName()
# pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer)
# if it's a locally opened track (JamTrackGroup), then we can say this person is the opener
isOpener = jamTrackMixers[0].group_id == ChannelGroupIds.JamTrackGroup;
if jamTracks
noCorrespondingTracks = false
for jamTrack in jamTracks
mixer = null
preMasteredClass = ""
# find the track or tracks that correspond to the mixer
correspondingTracks = []
for matchMixer in @jamTrackMixers
if matchMixer.id == jamTrack.id
correspondingTracks.push(jamTrack)
mixer = matchMixer
if correspondingTracks.length == 0
noCorrespondingTracks = true
logger.error("could not correlate jam tracks", jamTrackMixers, jamTracks)
@app.notify({
title: "Unable to Open JamTrack",
text: "Could not correlate server and client tracks",
icon_url: "/assets/content/icon_alert_big.png"})
return _jamTracks
#jamTracks = $.grep(jamTracks, (value) =>
# $.inArray(value, correspondingTracks) < 0
#)
# prune found mixers
jamTrackMixers.splice(mixer);
oneOfTheTracks = correspondingTracks[0];
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument.id);
part = oneOfTheTracks.part
part = '' unless name?
data =
name: jamTrackName
part: part
isOpener: isOpener
instrumentIcon: instrumentIcon
track: oneOfTheTracks
mixers: @mediaMixers(mixer, isOpener)
_jamTracks.push(data)
_jamTracks
resolveRecordedTracks: () ->
recordedTracks = []
return recordedTracks unless @recordingTrackMixers.length > 0
serverRecordedTracks = @session.recordedTracks()
isOpener = @recordingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup
# using the server's info in conjuction with the client's, draw the recording tracks
if serverRecordedTracks
recordingName = @session.recordingName()
noCorrespondingTracks = false
for mixer in @recordingTrackMixers
preMasteredClass = ""
correspondingTracks = []
for recordedTrack in serverRecordedTracks
if mixer.id.indexOf("L") == 0
if mixer.id.substring(1) == recordedTrack.client_track_id
correspondingTracks.push(recordedTrack)
else if mixer.id.indexOf("C") == 0
if mixer.id.substring(1) == recordedTrack.client_id
correspondingTracks.push(recordedTrack)
preMasteredClass = "pre-mastered-track"
else
# this should not be possible
alert("Invalid state: the recorded track had neither persisted_track_id or persisted_client_id")
if correspondingTracks.length == 0
noCorrespondingTracks = true
logger.debug("unable to correlate all recorded tracks", recordingMixers, serverRecordedTracks)
@app.notify({
title: "Unable to Open Recording",
text: "Could not correlate server and client tracks",
icon_url: "/assets/content/icon_alert_big.png"});
return recordedTracks
serverRecordedTracks = $.grep(serverRecordedTracks,
(value) =>
$.inArray(value, correspondingTracks) < 0
)
oneOfTheTracks = correspondingTracks[0]
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument_id)
userName = oneOfTheTracks.user.name
userName = oneOfTheTracks.user.first_name + ' ' + oneOfTheTracks.user.last_name unless userName?
data =
recordingName: recordingName
isOpener: isOpener
userName: userName
instrumentIcon: instrumentIcon
track: oneOfTheTracks
mixers: @mediaMixers(mixer, isOpener)
recordedTracks.push(data)
recordedTracks
resolveMetronome: () ->
metronome = null
return metronome if @metronomeTrackMixers.length == 0
mixer = @metronomeTrackMixers[0]
instrumentIcon = "/assets/content/icon_metronome.png"
oppositeMixer = @getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL);
metronome =
instrumentIcon: instrumentIcon
mixers: {mixer: mixer, oppositeMixer: oppositeMixer, vuMixer: mixer, muteMixer: mixer}
metronome
resolveChatMixer: () ->
masterChatMixers = @mixersForGroupId(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.MASTER);
return null if masterChatMixers.length == 0
personalChatMixers = @mixersForGroupId(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.PERSONAL);
if personalChatMixers.length == 0
logger.warn("unable to find personal mixer for voice chat");
return null
masterChatMixer = masterChatMixers[0];
personalChatMixer = personalChatMixers[0];
{
master: {
mixer: masterChatMixer
muteMixer: masterChatMixer
vuMixer: masterChatMixer
oppositeMixer: personalChatMixer
}
personal: {
mixer: personalChatMixer
muteMixer: personalChatMixer
vuMixer: personalChatMixer
oppositeMixer: masterChatMixer
}
}
# supply the master mixer of a media track, and this function will harvest out the rest
mediaMixers:(masterMixer, isOpener) ->
personalMixer = if isOpener then @getMixerByResourceId(masterMixer.rid, MIX_MODES.PERSONAL) else null
personalVuMixer = if isOpener then personalMixer else masterMixer
{
isOpener: isOpener
master: {
mixer: masterMixer
muteMixer: masterMixer
vuMixer: masterMixer
}
personal: {
mixer: personalMixer
muteMixer: personalMixer
vuMixer: personalVuMixer
}
}
mixersForGroupIds: (groupIds, mixMode) ->
foundMixers = []
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
for groupId in groupIds
if mixer.group_id == groupId
foundMixers.push(mixer)
foundMixers
mixersForGroupId: (groupId, mixMode) ->
foundMixers = [];
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
if mixer.group_id == groupId
foundMixers.push(mixer)
foundMixers
getMixer: (mixerId, mode) ->
mode = @mixMode unless mode?
@allMixers[(if mode then 'M' else 'P') + mixerId]
getMixerByTrackId: (trackId, mode) ->
mixerPair = @mixersByTrackId[trackId]
return null unless mixerPair
if mode == undefined
return mixerPair
else
if mode == MIX_MODES.MASTER
return mixerPair.master
else
return mixerPair.personal
groupedMixersForClientId: (clientId, groupIds, usedMixers, mixMode) ->
foundMixers = {};
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
unless mixer?
logger.debug("empty mixer: ", mixers)
continue
if mixer.client_id == clientId
for groupId in groupIds
if mixer.group_id == groupId
if (mixer.groupId != ChannelGroupIds.UserMusicInputGroup) && !(mixer.id of usedMixers)
mixers = foundMixers[mixer.group_id]
if !mixers
mixers = []
foundMixers[mixer.group_id] = mixers
mixers.push(mixer)
foundMixers
getMixerByResourceId:(resourceId, mode) ->
mixerPair = @mixersByResourceId[resourceId];
return null if(!mixerPair)
if !mode?
return mixerPair;
else
if mode == MIX_MODES.MASTER
return mixerPair.master
else
return mixerPair.personal
findMixerForTrack: (client_id, track, myTrack, mode = MIX_MODES.PERSONAL) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
vuMixer = null
muteMixer = null
if myTrack
# when it's your track, look it up by the backend resource ID
mixer = @getMixerByTrackId(track.client_track_id, mode)
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, !mode)
if mode == 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, @allMixers)
else
switch mode
when 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)
# sanity check
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
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 )
when MIX_MODES.PERSONAL
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", client_id, track.client_track_id)
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
{
mixer: mixer,
oppositeMixer: oppositeMixer,
vuMixer: vuMixer,
muteMixer: muteMixer
}
mute: (mixerId, mode, muting) ->
mode = @mixMode unless mode?
@fillTrackVolumeObject(mixerId, mode)
context.trackVolumeObject.mute = muting
context.jamClient.SessionSetControlState(mixerId, mode)
# keep state of mixer in sync with backend
mixer = @getMixer(mixerId, mode)
mixer.mute = muting
faderChanged: (data, mixers, groupId) ->
for mixer in mixers
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
@setMixerVolume(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
if groupId == ChannelGroupIds.UserMusicInputGroup
# there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
initGain: (mixer) ->
gainPercent = context.JK.FaderHelpers.convertAudioTaperToPercent(mixer.volume_left)
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent)
context.JK.FaderHelpers.showFader(mixer.id)
panChanged: (data, mixers, groupId) ->
# media tracks are the only controls that sometimes set two mixers right now
for mixer in mixers
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
@setMixerPan(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.pan = context.trackVolumeObject.pan
initPan: (mixer) ->
panPercent= context.JK.PanHelpers.convertPanToPercent(mixer.pan)
context.JK.FaderHelpers.setFaderValue(mixer.id, panPercent, Math.abs(mixer.pan))
context.JK.FaderHelpers.showFader(mixer.id)
setMixerPan: (mixer, panPercent) ->
context.trackVolumeObject.pan = context.JK.PanHelpers.convertPercentToPan(panPercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
loopChanged: (mixer, shouldLoop) ->
@fillTrackVolumeObject(mixer.id, mixer.mode)
context.trackVolumeObject.loop = shouldLoop
context.jamClient.SessionSetControlState(mixer.id, mixer.mode)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.loop = context.trackVolumeObject.loop
setMixerVolume: (mixer, volumePercent) ->
###
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
// has been set in currentMixerRangeMin-Max.
// All that needs doing is to translate the incoming percent
// into the real value ont the sliders range. Set Left/Right
// volumes on trackVolumeObject, and call SetControlState to stick.
###
context.trackVolumeObject.volL = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.trackVolumeObject.volR = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
percentFromMixerValue: (min, max, value) ->
try
range = Math.abs(max - min)
magnitude = value - min
percent = Math.round(100*(magnitude/range))
percent
catch err
0
percentToMixerValue:(min, max, percent) ->
range = Math.abs(max - min);
multiplier = percent/100; # Change 85 into 0.85
value = min + (multiplier * range);
# Protect against percents < 0 and > 100
if value < min
value = min;
if value > max
value = max;
return value;
fillTrackVolumeObject: (mixerId, mode, broadcast) ->
_broadcast = true
if broadcast?
_broadcast = broadcast
mixer = @getMixer(mixerId, mode)
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.pan = mixer.pan
# today we treat all tracks as mono, but this is required to make a stereo track happy
# context.trackVolumeObject.volR = mixer.volume_right;
context.trackVolumeObject.volR = mixer.volume_left;
context.trackVolumeObject.loop = mixer.loop;
# trackVolumeObject doesn't have a place for range min/max
@currentMixerRangeMin = mixer.range_low;
@currentMixerRangeMax = mixer.range_high;
mixer
updateVU: (mixerId, mode, leftValue, leftClipping, rightValue, rightClipping) ->
mixer = @getMixer(mixerId, mode)
if mixer?
context.JK.VuHelpers.updateVU3(mixer, leftValue, leftClipping, rightValue, rightClipping)
###
if mixer
if mixer.stereo # // stereo track
if mixerId.substr(-4) == "_vul"
context.JK.VuHelpers.updateVU2('vul', mixer, value)
else
context.JK.VuHelpers.updateVU2('vur', mixer, value)
else
if mixerId.substr(-4) == "_vul"
# Do the left
context.JK.VuHelpers.updateVU2('vul', mixer, value)
# Do the right
context.JK.VuHelpers.updateVU2('vur', mixer, value)
###
getTrackInfo: () ->
context.JK.TrackHelpers.getTrackInfo(context.jamClient, @masterMixers)
getGroupMixer: (categoryId, mode) ->
groupId = if mode == MIX_MODES.MASTER then ChannelGroupIds.MasterCatGroup else ChannelGroupIds.MonitorCatGroup
mixers = @mixersForGroupId(groupId, mode)
if mixers.length == 0
logger.warn("could not find mixer with group ID: " + groupId + ', mode:' + mode)
return null
found = null
for mixer in mixers
if mixer.name == categoryId
found = mixer
break
unless found?
logger.warn("could not find mixer with categoryId: " + categoryId)
return null
else
{
mixer: found,
muteMixer : found,
vuMixer: found,
oppositeMixer: found
}
getAudioInputCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.AudioInputMusic, mode)
getChatCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.AudioInputChat, mode)
getMediaCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.MediaTrack, mode)
getUserMediaCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.UserMedia, mode)
refreshMixer: (mixers) ->
return null unless mixers? && mixers.mixer?
mixer = @getMixer(mixers.mixer.id, mixers.mixer.mode)
if mixer?
oppositeMixer = if mixers.oppositeMixer then @getMixer(mixers.oppositeMixer.id, mixers.oppositeMixer.mode) else null
{
mixer: mixer
vuMixer: @getMixer(mixers.vuMixer.id, mixers.vuMixer.mode)
muteMixer: @getMixer(mixers.muteMixer.id, mixers.muteMixer.mode)
oppositeMixer: oppositeMixer
}
else
return null
recordingName: () ->
@session.recordingName()
jamTrackName: () ->
@session.jamTrackName()

View File

@ -0,0 +1,112 @@
context = window
@SessionHelper = class SessionHelper
constructor: (app, session, participantsEverSeen, isRecording, downloadingJamTrack) ->
@app = app
@session = session
@participantsEverSeen = participantsEverSeen
@isRecording = isRecording
@downloadingJamTrack = downloadingJamTrack
inSession: () ->
@session?
participants: () ->
if @session
return @session.participants
else
[]
otherParticipants: () ->
others = []
for participant in @participants()
myTrack = @app.clientId == participant.client_id
others.push(participant) unless myTrack
others
# if any participant has the metronome open, then we say this session has the metronome open
isMetronomeOpen: () ->
metronomeOpen = false;
for participant in @participants()
if participant.metronome_open
metronomeOpen = true
break
metronomeOpen
isPlayingRecording: () ->
# this is the server's state; there is no guarantee that the local tracks
# requested from the backend will have corresponding track information
return !!(@session && @session.claimed_recording);
recordedTracks: () ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_tracks
else
null
recordedBackingTracks: () ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_backing_tracks
else
null
backingTracks: () ->
backingTracks = []
# this may be wrong if we loosen the idea that only one person can have a backing track open.
# but for now, the 1st person we find with a backing track open is all there is to find...
for participant in @participants()
if participant.backing_tracks.length > 0
backingTracks = participant.backing_tracks
break
backingTracks
backingTrack: () ->
result = null
if @session
# TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668
result =
path: @session.backing_track_path
result
jamTracks: () ->
if @session && @session.jam_track
@session.jam_track.tracks.filter((track)->
track.track_type == 'Track'
)
else
null
jamTrackName: () ->
@session?.jam_track?.name
recordedJamTracks:() ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_jam_track_tracks
else
null
recordedJamTrackName: () ->
jam_track = @session?.claimed_recording?.recording?.jam_track
if jam_track? then jam_track.name else null
recordingName: () ->
@session?.claimed_recording?.name
getParticipant: (clientId) ->
found = null
for participant in @participants()
if participant.client_id == clientId
found = participant
break
logger.warn('unable to find participant with clientId: ' + clientId) unless found
found
id: () ->
@session.id

View File

@ -0,0 +1,17 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@MasterPersonalMixersMixin = {
mixer: () ->
if @props.mode == MIX_MODES.MASTER
@props.mixers['master'].mixer
else
@props.mixers['personal'].mixer
mixers: () ->
if @props.mode == MIX_MODES.MASTER
@props.mixers['master']
else
@props.mixers['personal']
}

View File

@ -0,0 +1,51 @@
context = window
MIX_MODES = context.JK.MIX_MODES
logger = context.JK.logger
@SessionMediaTracksMixin = {
metronomeTrulyGoneCheck: () ->
logger.debug("metronome is completely gone")
@setState({metronomeFlickerTimeout: null})
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
# the backend delete/adds the metronome rapidly when the user hits play. this is custom code to deal with that
metronomeFlickerTimeout = @state.metronomeFlickerTimeout
if mixers.metronome?
if metronomeFlickerTimeout?
logger.debug("canceling metronome flicker timeout because metronome mixer reappeared")
clearTimeout(metronomeFlickerTimeout)
metronomeFlickerTimeout = null
else
if @state.metronomeIsShowing
logger.debug("setting metronome flicker timeout")
clearTimeout(metronomeFlickerTimeout) if metronomeFlickerTimeout?
metronomeFlickerTimeout = setTimeout(@metronomeTrulyGoneCheck, 1000)
metronomeIsShowing = mixers.metronome?
state =
isRecording: session.isRecording
mediaSummary: mixers.mediaSummary
backingTracks: mixers.backingTracks
jamTracks: mixers.jamTracks
recordedTracks: mixers.recordedTracks
metronome: mixers.metronome
mediaCategoryMixer: mixers.getMediaCategoryMixer(@props.mode)
recordingName: mixers.recordingName()
jamTrackName: mixers.jamTrackName()
metronomeIsShowing: metronomeIsShowing
metronomeFlickerTimeout: metronomeFlickerTimeout
@inputsChangedProcessed(state) if @inputsChangedProcessed?
@setState(state)
}

View File

@ -0,0 +1,45 @@
context = window
@SessionMyTracksMixin = {
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
tracks = []
if session.inSession()
participant = session.getParticipant(@app.clientId)
if participant
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
chatMixer = mixers.chatMixer
chat = null
if chatMixer
chat =
mixers: chatMixer
mode: @props.mode
photoUrl: photoUrl
name = participant.user.name;
for track in participant.tracks
# try to find mixer info for this track
mixerFinder = [participant.client_id, track, true] # so that other callers can re-find their mixer data
mixerData = mixers.findMixerForTrack(participant.client_id, track, true, @props.mode)
# todo: sessionModel.setAudioEstablished
instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, name: name, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id})
else
logger.debug("SessionMyTracks: unable to find participant")
this.setState(tracks: tracks, session:session, chat: chat)
}

View File

@ -0,0 +1,6 @@
context = window
@SessionOtherTracksMixin = {
}

View File

@ -0,0 +1,12 @@
$ = jQuery
context = window
logger = context.JK.logger
@AppStore = Reflux.createStore(
{
listenables: @AppActions
onAppInit: (app) ->
@trigger(app)
}
)

View File

@ -0,0 +1,120 @@
$ = jQuery
context = window
logger = context.JK.logger
PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE
RecordingActions = @RecordingActions
@MediaPlaybackStore = Reflux.createStore(
{
listenables: @MediaPlaybackActions
playbackStateChanged: false
positionUpdateChanged: false
currentTimeChanged: false
playbackState: null
positionMs: 0
durationMs: 0
isRecording: false
sessionHelper: null
init: () ->
this.listenTo(context.SessionStore, this.onSessionChanged);
onCurrentTimeChanged: (time) ->
@time = time
@currentTimeChanged = true
@issueChange()
onSessionChanged: (session) ->
@isRecording = session.isRecording
@sessionHelper = session
onMediaStartPlay: (data) ->
logger.debug("calling jamClient.SessionStartPlay");
context.jamClient.SessionStartPlay(data.playbackMode);
onMediaStopPlay: (data) ->
# if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording
if @sessionHelper.jamTracks() && @isRecording
logger.debug("preemptive jamtrack stop")
@startStopRecording();
if !data.endReached
logger.debug("calling jamClient.SessionStopPlay. endReached:", data.endReached)
context.jamClient.SessionStopPlay()
onMediaPausePlay: (data) ->
# if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording
if @sessionHelper.jamTracks() && @isRecording
logger.debug("preemptive jamtrack stop")
@startStopRecording();
if !data.endReached
logger.debug("calling jamClient.SessionPausePlay. endReached:", data.endReached)
context.jamClient.SessionPausePlay()
startStopRecording: () ->
if @isRecording
RecordingActions.stopRecording.trigger()
else
RecordingActions.startRecording.trigger()
onMediaChangePosition: (data) ->
seek = data.positionMs;
if data.playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK
# if positionMs == 0, then seek it back to whatever the earliest play start is to catch all the prelude
if(seek == 0)
duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
seek = duration.start;
logger.debug("calling jamClient.SessionTrackSeekMs(" + seek + ")");
if data.playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK
context.jamClient.SessionJamTrackSeekMs(seek);
else
context.jamClient.SessionTrackSeekMs(seek);
issueChange: () ->
@state =
playbackState: @playbackState
playbackStateChanged: @playbackStateChanged
positionUpdateChanged: @positionUpdateChanged
currentTimeChanged: @currentTimeChanged
positionMs: @positionMs
durationMs: @durationMs
isPlaying: @isPlaying
time: @time
this.trigger(@state)
@playbackStateChanged = false
@positionUpdateChanged = false
@currentTimeChanged = false
onPlaybackStateChange: (text) ->
@playbackState = text
@playbackStateChanged = true
@issueChange()
onPositionUpdate: (playbackMode) ->
if playbackMode == PLAYBACK_MONITOR_MODE.JAMTRACK
@positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs()
duration = context.jamClient.SessionGetJamTracksPlayDurationMs()
@durationMs = duration.media_len
else
@positionMs = context.jamClient.SessionCurrrentPlayPosMs()
@durationMs = context.jamClient.SessionGetTracksPlayDurationMs()
@isPlaying = context.jamClient.isSessionTrackPlaying()
@positionUpdateChanged = true
@issueChange()
}
)

View File

@ -0,0 +1,240 @@
context = window
logger = context.JK.logger
MIX_MODES = context.JK.MIX_MODES
rest = context.JK.Rest()
@MixerStore = Reflux.createStore(
{
METRO_SOUND_LOOKUP: {
0 : "BuiltIn",
1 : "SineWave",
2 : "Beep",
3 : "Click",
4 : "Kick",
5 : "Snare",
6 : "MetroFile"
}
metro: {tempo: 120, cricket: false, sound: "Beep" }
noAudioUsers : {}
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.SessionStore, this.onSessionChange)
this.listenTo(context.MixerActions.mute, this.onMute)
this.listenTo(context.MixerActions.faderChanged, this.onFaderChanged)
this.listenTo(context.MixerActions.initGain, this.onInitGain)
this.listenTo(context.MixerActions.initPan, this.onInitPan)
this.listenTo(context.MixerActions.panChanged, this.onPanChanged)
this.listenTo(context.MixerActions.mixersChanged, this.onMixersChanged)
this.listenTo(context.MixerActions.syncTracks, this.onSyncTracks)
this.listenTo(context.MixerActions.mixerModeChanged, this.onMixerModeChanged)
this.listenTo(context.MixerActions.loopChanged, this.onLoopChanged)
this.listenTo(context.MixerActions.openMetronome, this.onOpenMetronome)
this.listenTo(context.MixerActions.metronomeChanged, this.onMetronomeChanged)
this.listenTo(context.MixerActions.deadUserRemove, this.onDeadUserRemove)
context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback
context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback
context.JK.HandleBridgeCallback2 = @handleBridgeCallback
context.JK.HandleBackingTrackSelectedCallback2 = @handleBackingTrackSelectedCallback
issueChange: () ->
@trigger({session: @session, mixers: @mixers})
handleVolumeChangeCallback: (mixerId, isLeft, value, isMuted) ->
# TODO
# Visually update mixer
# There is no need to actually set the back-end mixer value as the
# back-end will already have updated the audio mixer directly prior to sending
# me this event. I simply need to visually show the new fader position.
# TODO: Use mixer's range
#faderValue = percentFromMixerValue(-80, 20, value);
#context.JK.FaderHelpers.setFaderValue(mixerId, faderValue);
#var $muteControl = $('[control="mute"][mixer-id="' + mixerId + '"]');
#_toggleVisualMuteControl($muteControl, isMuted);
logger.debug("volume change")
handleMetronomeCallback: (args) ->
logger.debug("MetronomeCallback: ", args)
@metro.tempo = args.bpm
@metro.cricket = args.cricket;
@metro.sound = @METRO_SOUND_LOOKUP[args.sound];
# This isn't actually there, so we rely on the metroSound as set from select on form:
# metroSound = args.sound
SessionActions.syncWithServer()
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
handleBridgeCallback: (vuData) ->
eventName = null
mixerId = null
value = null
vuInfo = null
for vuInfo in vuData
eventName = vuInfo[0];
vuVal = 0.0;
if eventName == "vu"
mixerId = vuInfo[1];
mode = vuInfo[2];
leftValue = vuInfo[3];
leftClipping = vuInfo[4];
rightValue = vuInfo[5];
rightClipping = vuInfo[6];
# 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
@mixers.updateVU(mixerId, mode, (leftValue + 80) / 80, leftClipping, (rightValue + 80) / 80, rightClipping)
#@mixers.updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping)
handleBackingTrackSelectedCallback: () ->
logger.debug("backing track selected")
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
context.jamClient.SetVURefreshRate(150)
context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback2")
context.jamClient.setMetronomeOpenCallback("JK.HandleMetronomeCallback2")
sessionEnded: () ->
@noAudioUsers = {}
onSessionChange: (session) ->
@sessionEnded() unless session.inSession()
@session = session
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onMute: (mixers, muting) ->
for mixer in mixers
@mixers.mute(mixer.id, mixer.mode, muting);
# simulate a state change to cause a UI redraw
@issueChange()
onFaderChanged: (data, mixers, groupId) ->
@mixers.faderChanged(data, mixers, groupId)
@issueChange()
onPanChanged: (data, mixers, groupId) ->
@mixers.panChanged(data, mixers, groupId)
@issueChange()
onLoopChanged: (mixer, shouldLoop) ->
@mixers.loopChanged(mixer, shouldLoop)
onOpenMetronome: () ->
context.jamClient.SessionStopPlay()
context.jamClient.SessionOpenMetronome(@mixers.metro.tempo, @mixers.metro.sound, 1, 0)
onMetronomeChanged: (tempo, sound) ->
logger.debug("onMetronomeChanged", tempo, sound)
@metro.tempo = tempo
@metro.sound = sound
context.jamClient.SessionSetMetronome(@metro.tempo, @metro.sound, 1, 0);
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onDeadUserRemove: (clientId) ->
return unless @session.inSession()
participant = @session.participantsEverSeen[clientId];
if participant?
logger.debug("todo :notify dead user")
# XXX TODO trigger some notification store
#app.notify({
# "title": ALERT_TYPES[type].title,
# "text": participant.user.name + " is no longer sending audio.",
# "icon_url": context.JK.resolveAvatarUrl(participant.user.photo_url)
#});
@noAudioUsers[clientId] = true
@issueChange()
onInitGain: (mixer) ->
@mixers.initGain(mixer)
onInitPan: (mixer) ->
@mixers.initPan(mixer)
onMixersChanged: (type, text) ->
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
logger.debug("MixerStore: onMixersChanged")
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo())
@issueChange()
onMixerModeChanged: (mode) ->
if mode == MIX_MODES.MASTER
@app.layout.showDialog('session-master-mix-dialog') unless @app.layout.isDialogShowing('session-master-mix-dialog')
else
@app.layout.closeDialog('session-master-mix-dialog') if @app.layout.isDialogShowing('session-master-mix-dialog')
onSyncTracks: () ->
logger.debug("MixerStore: onSyncTracks")
unless @session.inSession()
logger.debug("dropping queued up sync tracks because no longer in session")
return
allTracks = @mixers.getTrackInfo()
inputTracks = allTracks.userTracks;
backingTracks = allTracks.backingTracks;
metronomeTracks = allTracks.metronomeTracks;
# create a trackSync request based on backend data
syncTrackRequest = {}
syncTrackRequest.client_id = @app.clientId
syncTrackRequest.tracks = inputTracks
syncTrackRequest.backing_tracks = backingTracks
syncTrackRequest.metronome_open = metronomeTracks.length > 0
syncTrackRequest.id = @session.id()
rest.putTrackSyncChange(syncTrackRequest)
.fail((jqXHR)=>
if jqXHR.status != 404
@app.notify({
"title": "Can't Sync Local Tracks",
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
"icon_url": "/assets/content/icon_alert_big.png"
})
else
logger.debug("Unable to sync local tracks because session is gone.")
)
}
)

View File

@ -0,0 +1,70 @@
$ = jQuery
context = window
logger = context.JK.logger
@RecordingStore = Reflux.createStore(
{
listenables: @RecordingActions
recordingWindow: null
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (app) ->
@app = app
onInitModel: (recordingModel) ->
@recordingModel = recordingModel
this.trigger({isRecording: @recordingModel.isRecording()})
onStartRecording: () ->
@recordingModel.startRecording()
onStopRecording: () ->
@recordingModel.stopRecording()
onStartingRecording: (details) ->
details.cause = 'starting'
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
onStartedRecording: (details) ->
details.cause = 'started'
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
onStoppingRecording: (details) ->
details.cause = 'stopping'
this.trigger(details)
onStoppedRecording: (details) ->
details.cause = 'stopped'
this.trigger(details)
onAbortedRecording: (details) ->
details.cause = 'aborted'
this.trigger(details)
onOpenRecordingControls: () ->
logger.debug("recording controls opening")
if @recordingWindow?
@recordingWindow.close()
@popupRecordingControls()
onRecordingControlsClosed: () ->
logger.debug("recording controls closed")
@recordingWindow = null
popupRecordingControls: () ->
@recordingWindow = window.open("/popups/recording-controls", 'Recording', 'scrollbars=yes,toolbar=no,status=no,height=315,width=350')
@recordingWindow.ParentRecordingStore = context.RecordingStore
@recordingWindow.ParentIsRecording = @recordingModel.isRecording()
}
)

View File

@ -0,0 +1,21 @@
$ = jQuery
context = window
logger = context.JK.logger
@SessionMediaTracksStore = Reflux.createStore(
{
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.MixerStore, this.onSessionMixerChange)
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onSessionMixerChange: (sessionMixers) ->
this.trigger(sessionMixers)
}
)

View File

@ -0,0 +1,21 @@
$ = jQuery
context = window
logger = context.JK.logger
@SessionMyTracksStore = Reflux.createStore(
{
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.MixerStore, this.onSessionMixerChange)
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onSessionMixerChange: (sessionMixers) ->
this.trigger(sessionMixers)
}
)

View File

@ -0,0 +1,61 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
@SessionNotificationStore = Reflux.createStore(
{
listenables: @NotificationActions
notifications: []
count: 0
issueChange: () ->
@trigger(@notifications)
onClear: () ->
@notifications = []
@issueChange()
onSessionEnded: () ->
@notifications = []
@issueChange()
processNotification: (notification) ->
notification.id = ++@count
title = 'n/a'
extra = null
if notification.backend_detail?
if notification.backend_detail == 'Network Issues'
title = 'Network Issues'
extra = notification.msg
else
title = notification.msg
extra = notification.backend_detail
else
title = notification.msg
detail = if notification.detail? && notification.detail != "" then notification.detail else null
data =
title: title
extra: extra
detail: detail
help: notification.help
@notifications.unshift(data)
if @notifications.length > 100
@notifications.pop();
@issueChange()
onBackendNotification: (notification) ->
@processNotification(notification)
onFrontendNotification: (notification) ->
@processNotification(notification)
}
)

View File

@ -0,0 +1,21 @@
$ = jQuery
context = window
logger = context.JK.logger
@SessionOtherTracksStore = Reflux.createStore(
{
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.MixerStore, this.onSessionMixerChange)
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onSessionMixerChange: (sessionMixers) ->
this.trigger(sessionMixers)
}
)

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@
context.JK = context.JK || {};
var logger = context.JK.logger;
context.JK.RecordingModel = function(app, sessionModel, _rest, _jamClient) {
context.JK.RecordingModel = function(app, _rest, _jamClient) {
var currentRecording = null; // the JSON response from the server for a recording
var currentOrLastRecordingId = null;
var currentRecordingId = null;
@ -31,7 +31,7 @@
var waitingOnStopTimer = null;
var jamClient = _jamClient;
var sessionModel = sessionModel;
var sessionId = null;
var $self = $(this);
function isRecording (recordingId) {
@ -46,7 +46,7 @@
}
/** called every time a session is joined, to ensure clean state */
function reset() {
function reset(_sessionId) {
currentlyRecording = false;
waitingOnServerStop = false;
waitingOnClientStop = false;
@ -57,9 +57,11 @@
currentRecording = null;
currentRecordingId = null;
stoppingRecording = false;
sessionId = _sessionId
}
function groupTracksToClient(recording) {
// group N tracks to the same client Id
var groupedTracks = {};
@ -84,7 +86,9 @@
currentlyRecording = true;
stoppingRecording = false;
currentRecording = rest.startRecording({"music_session_id": sessionModel.id()})
context.RecordingActions.startingRecording({isRecording: false})
currentRecording = rest.startRecording({"music_session_id": sessionId})
.done(function(recording) {
currentRecordingId = recording.id;
currentOrLastRecordingId = recording.id;
@ -94,8 +98,10 @@
jamClient.StartRecording(recording["id"], groupedTracks);
})
.fail(function(jqXHR) {
$self.triggerHandler('startedRecording', { clientId: app.clientId, reason: 'rest', detail: arguments });
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
$self.triggerHandler('startedRecording', details);
currentlyRecording = false;
context.RecordingActions.startedRecording(details);
})
@ -116,6 +122,7 @@
waitingOnStopTimer = setTimeout(timeoutTransitionToStop, 5000);
$self.triggerHandler('stoppingRecording', {reason: reason, detail: detail});
context.RecordingActions.stoppingRecording({reason: reason, detail: detail, isRecording:true})
// this path assumes that the currentRecording info has, or can be, retrieved
// failure for currentRecording is handled elsewhere
@ -145,7 +152,9 @@
else {
logger.error("unable to stop recording %o", arguments);
transitionToStopped();
$self.triggerHandler('stoppedRecording', {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments});
var details = {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
}
});
});
@ -168,7 +177,9 @@
if(!waitingOnClientStop && !waitingOnServerStop) {
transitionToStopped();
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: errorReason, detail: errorDetail});
var details = {recordingId: recordingId, reason: errorReason, detail: errorDetail, isRecording: false}
$self.triggerHandler('stoppedRecording', details)
context.RecordingActions.stoppedRecording(details)
}
}
@ -198,12 +209,16 @@
if(success) {
$self.triggerHandler('startedRecording', {clientId: app.clientId})
var details = {clientId: app.clientId, isRecording:true}
$self.triggerHandler('startedRecording', details)
context.RecordingActions.startedRecording(details)
}
else {
currentlyRecording = false;
logger.error("unable to start the recording %o, %o", reason, detail);
$self.triggerHandler('startedRecording', { clientId: app.clientId, reason: reason, detail: detail});
var details = { clientId: app.clientId, reason: reason, detail: detail, isRecording: false}
$self.triggerHandler('startedRecording', details);
context.RecordingActions.startedRecording(details)
}
}
@ -221,7 +236,9 @@
else {
transitionToStopped();
logger.error("backend unable to stop the recording %o, %o", reason, detail);
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail : detail});
var details = {recordingId: recordingId, reason: reason, detail : detail, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
}
}
@ -242,9 +259,14 @@
currentOrLastRecordingId = recording.id;
});
$self.triggerHandler('startingRecording', {recordingId: recordingId});
var details = {recordingId: recordingId, isRecording: false}
$self.triggerHandler('startingRecording', details);
context.RecordingActions.startingRecording(details)
currentlyRecording = true;
$self.triggerHandler('startedRecording', {clientId: clientId, recordingId: recordingId});
details = {clientId: clientId, recordingId: recordingId, isRecording: true}
$self.triggerHandler('startedRecording', details);
context.RecordingActions.startedRecording(details)
}
function handleRecordingStopped(recordingId, result) {
@ -253,7 +275,10 @@
var detail = result.detail;
$self.triggerHandler('stoppingRecording', {recordingId: recordingId, reason: reason, detail: detail });
var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: true }
$self.triggerHandler('stoppingRecording', details);
context.RecordingActions.stoppingRecording(details)
// the backend says the recording must be stopped.
// tell the server to stop it too
rest.stopRecording({
@ -265,18 +290,26 @@
.fail(function(jqXHR, textStatus, errorMessage) {
if(jqXHR.status == 422) {
logger.debug("recording already stopped %o", arguments);
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
}
else if(jqXHR.status == 404) {
logger.debug("recording is already deleted %o", arguments);
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
}
else {
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: textStatus, detail: errorMessage});
var details = {recordingId: recordingId, reason: textStatus, detail: errorMessage, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
}
})
.done(function() {
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false}
$self.triggerHandler('stoppedRecording', details);
context.RecordingActions.stoppedRecording(details)
})
}
@ -287,7 +320,9 @@
stoppingRecording = false;
$self.triggerHandler('abortedRecording', {recordingId: recordingId, reason: reason, detail: detail });
var details = {recordingId: recordingId, reason: reason, detail: detail, isRecording: false }
$self.triggerHandler('abortedRecording', details);
context.RecordingActions.abortedRecording(details)
// the backend says the recording must be stopped.
// tell the server to stop it too
rest.stopRecording({

View File

@ -1425,13 +1425,13 @@
var metronome = {}
$('.session-recording-name').text(name);//sessionModel.getCurrentSession().backing_track_path);
var noCorrespondingTracks = false;
var mixer = metronomeTrackMixers[0]
var preMasteredClass = "";
// find the track or tracks that correspond to the mixer
var correspondingTracks = []
correspondingTracks.push(metronome);
var noCorrespondingTracks = false;
var mixer = metronomeTrackMixers[0]
var preMasteredClass = "";
// find the track or tracks that correspond to the mixer
var correspondingTracks = []
correspondingTracks.push(metronome);
if(correspondingTracks.length == 0) {
noCorrespondingTracks = true;
app.notify({
@ -1957,7 +1957,7 @@
// Given a mixerID and a value between 0.0-1.0,
// light up the proper VU lights.
function _updateVU(mixerId, value, isClipping) {
function _updateVU(mixerId, value, isClipping) {
// Special-case for mono tracks. If mono, and it's a _vul id,
// update both sides, otherwise do nothing.
@ -2158,8 +2158,8 @@
setFormFromMetronome();
// This isn't actually there, so we rely on the metroSound as set from select on form:
// metroSound = args.sound
context.JK.CurrentSessionModel.refreshCurrentSession(true);
// metroSound = args.sound
context.JK.CurrentSessionModel.refreshCurrentSession(true);
}
function handleVolumeChangeCallback(mixerId, isLeft, value, isMuted) {
@ -2193,8 +2193,8 @@
// 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);
_updateVU(mixerId + "_vul", (leftValue + 80) / 80, leftClipping);
_updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping);
}
else if(eventName === 'connection_status') {
var mixerId = vuInfo[1];
@ -3025,6 +3025,7 @@
function closeMetronomeTrack() {
rest.closeMetronome({id: sessionModel.id()})
.done(function() {
logger.debug("session: SessionCloseMetronome")
context.jamClient.SessionCloseMetronome();
sessionModel.refreshCurrentSession(true);
})
@ -3285,7 +3286,7 @@
'beforeLeave' : beforeLeave,
'beforeDisconnect' : beforeDisconnect,
};
app.bindScreen('session', screenBindings);
//app.bindScreen('session', screenBindings);
$recordingManagerViewer = $('#recording-manager-viewer');
$screen = $('#session-screen');

View File

@ -263,8 +263,8 @@
var recordingId = payload.recording_id;
if(recordingId && context.JK.CurrentSessionModel.recordingModel.isRecording(recordingId)) {
context.JK.CurrentSessionModel.recordingModel.onServerStopRecording(recordingId);
if(recordingId && context.RecordingStore.recordingModel.isRecording(recordingId)) {
context.RecordingStore.recordingModel.onServerStopRecording(recordingId);
}
else {
app.notify({
@ -305,11 +305,11 @@
logger.debug("Handling SOURCE_UP_REQUESTED msg " + JSON.stringify(payload));
var current_session_id = context.JK.CurrentSessionModel.id();
var current_session_id = context.SessionStore.id();
if (!current_session_id) {
// we are not in a session
var last_session = context.JK.CurrentSessionModel.getCurrentOrLastSession();
var last_session = context.SessionStore.getCurrentOrLastSession();
if(last_session && last_session.id == payload.music_session) {
// the last session we were in was responsible for this message. not that odd at all
logger.debug("SOURCE_UP_REQUESTED came in for session_id" + payload.music_session + ", but was dropped because we have left that session")
@ -328,7 +328,7 @@
'', payload.bitrate)
}
else {
var last_session = context.JK.CurrentSessionModel.getCurrentOrLastSession();
var last_session = context.SessionStore.getCurrentOrLastSession();
if(last_session && last_session.id == payload.music_session) {
// the last session we were in was responsible for this message. not that odd at all
logger.debug("SOURCE_UP_REQUESTED came in for session_id" + payload.music_session + ", but was dropped because we have left that session and are in a new one")
@ -346,11 +346,11 @@
function registerSourceDownRequested() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SOURCE_DOWN_REQUESTED, function(header, payload) {
logger.debug("Handling SOURCE_DOWN_REQUESTED msg " + JSON.stringify(payload));
var current_session_id = context.JK.CurrentSessionModel.id();
var current_session_id = context.SessionStore.id();
if (!current_session_id) {
// we are not in a session
var last_session = context.JK.CurrentSessionModel.getCurrentOrLastSession();
var last_session = context.SessionStore.getCurrentOrLastSession();
if(last_session && last_session.id == payload.music_session) {
// the last session we were in was responsible for this message. not that odd at all
logger.debug("SOURCE_DOWN_REQUESTED came in for session_id" + payload.music_session + ", but was dropped because we have left that session")
@ -367,7 +367,7 @@
context.jamClient.SessionLiveBroadcastStop();
}
else {
var last_session = context.JK.CurrentSessionModel.getCurrentOrLastSession();
var last_session = context.SessionStore.getCurrentOrLastSession();
if(last_session && last_session.id == payload.music_session) {
// the last session we were in was responsible for this message. not that odd at all
logger.debug("SOURCE_DOWN_REQUESTED came in for session_id" + payload.music_session + ", but was dropped because we have left that session and are in a new one")

Some files were not shown because too many files have changed in this diff Show More