This commit is contained in:
Seth Call 2015-06-26 13:03:32 -05:00
parent 910b052204
commit 4be1117a02
62 changed files with 2809 additions and 258 deletions

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})
}
else {
logger.debug("Unhandled Backend Event type %o, data %o", type, text)
}
}
function alertCallback(type, text) {
@ -104,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

@ -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

@ -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

@ -334,4 +334,14 @@
"MetronomeGroup": 16
};
context.JK.CategoryGroupIds = {
"AudioInputMusic" : "AudioInputMusic",
"AudioInputChat" : "AudioInputChat",
"UserMusic" : "UserMusic",
"UserChat" : "UserChat",
"UserMedia" : "UserMedia",
"MediaTrack" : "MediaTrack",
"Metronome" : "Metronome"
}
})(window,jQuery);

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

@ -3,9 +3,12 @@
//= 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
@ -14,6 +17,7 @@
//= require jam_rest
//= require ga
//= require utils
//= require playbackControls
//= require react
//= require react_ujs
//= require react-init

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

@ -4,7 +4,10 @@
//= 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

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,124 @@
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', () =>
console.log("@props", @props)
MixerActions.loopChanged(@props.backingTracks[0].mixers.mixer, true)
)
$loop.on('ifUnchecked', () =>
MixerActions.loopChanged(@props.backingTracks[0].mixers.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

@ -8,7 +8,6 @@ if window.opener
@PopupRecordingStartStop = React.createClass({
# this comes from the parent window
mixins: mixins
onRecordingStateChanged: (recordingState) ->

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,80 @@
context = window
MixerActions = @MixerActions
@SessionBackingTrack = 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
"backing-track" : 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.shortFilename}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="vul" 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('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -0,0 +1,81 @@
context = window
MixerActions = @MixerActions
@SessionJamTrack = 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" : 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.part}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="vul" 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:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -1,13 +1,316 @@
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: [Reflux.listenTo(@SessionMediaTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
onInputsChanged: (sessionMixers) ->
session = sessionMixers.session
mixers = sessionMixers.mixers
backingTracks = mixers.backingTracks
jamTracks = mixers.jamTracks
recordedTracks = mixers.recordedTracks
metronome = mixers.metronome
state =
isRecording: session.isRecording
mediaSummary: mixers.mediaSummary
backingTracks: backingTracks
jamTracks: jamTracks
recordedTracks: recordedTracks
metronome: metronome
if state.mediaSummary.mediaOpen
if !@state.childWindow?
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.childWindow?
@state.childWindow.DontAutoCloseMedia = true
@state.childWindow.close()
state.childWindow = null
@setState(state)
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>`
for backingTrack in @state.backingTracks
mediaTracks.push(`<SessionBackingTrack key={backingTrack.track.id} {...backingTrack} />`)
for jamTrack in @state.jamTracks
mediaTracks.push(`<SessionJamTrack key={jamTrack.id} {...jamTrack} />`)
for recordedTrack in @state.recordedTracks
mediaTracks.push(`<SessionRecordedTrack key={recordedTrack.track.id} {...recordedTrack} />`)
if @state.metronome
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>
<div className="session-tracks-scroller">
{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()
})

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 = mixers.mixer.pan
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="vul" 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('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -15,21 +15,24 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
if session.inSession()
participant = session.getParticipant(@app.clientId)
name = participant.user.name;
if participant
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)
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)
# todo: sessionModel.setAudioEstablished
# todo: sessionModel.setAudioEstablished
instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, name: name, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id})
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, name: name, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id})
# TODO: also deal with chat
# TODO: also deal with chat
else
logger.debug("SessionMyTracks: unable to find participant")
this.setState(tracks: tracks, session:session)

View File

@ -0,0 +1,16 @@
context = window
@SessionNotification = React.createClass({
render: () ->
`<div className="session-notification">
<div className="msg">{this.props.msg}</div>
</div>`
componentDidMount: () ->
$root = $(@getDOMNode())
if @props.detail
context.JK.hoverBubble($root, @props.detail)
})

View File

@ -1,13 +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

@ -35,6 +35,8 @@ MixerActions = @MixerActions
"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

View File

@ -1,5 +1,5 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
@SessionOtherTracks = React.createClass({
@ -9,6 +9,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
session = sessionMixers.session
mixers = sessionMixers.mixers
noAudioUsers = mixers.noAudioUsers
participants = []
@ -36,7 +37,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
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}
participantState = {participant:participant, tracks: tracks, name: name, instrumentIcon: instrumentIcon, photoUrl: photoUrl, hasMixer: hasMixer, noAudio: noAudioUsers[participant.client_id]}
participants.push(participantState)

View File

@ -0,0 +1,81 @@
context = window
MixerActions = @MixerActions
@SessionRecordedTrack = 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
"recorded-track" : 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.userName}</div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="vul" 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:@props.mixers}
,
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
() =>
{mixers:@props.mixers}
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -1,39 +1,61 @@
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)
this.setState({inputGroupMixers: inputGroupMixers, chatGroupMixers: chatGroupMixers})
getInitialState: () ->
{mixers: this.props.mixers}
{inputGroupMixers: @props.inputGroupMixers, chatGroupMixers: @props.chatGroupMixers}
handleMute: (e) ->
handleAudioInputMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.props.mixers.mixer], muting)
MixerActions.mute([@state.inputGroupMixers.muteMixer], muting)
handleMuteCheckbox: (e) ->
handleChatInputMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([@state.chatGroupMixers.muteMixer], muting)
handleAudioInputMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
MixerActions.mute([this.props.mixers.mixer, this.props.mixers.oppositeMixer], muting)
MixerActions.mute([@state.inputGroupMixers.muteMixer], muting)
handleChatMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
MixerActions.mute([@state.chatGroupMixers.muteMixer], muting)
render: () ->
monitorMuteMixer = this.props.inputGroupMixers.muteMixer
monitorMuteMixer = @state.inputGroupMixers.muteMixer
monitorMuteMixerId = monitorMuteMixer?.id
monitorVolumeLeft = this.props.inputGroupMixers.mixer?.volume_left
monitorVolumeLeft = @state.inputGroupMixers.mixer?.volume_left
monitorMuteClasses = classNames({
'track-icon-mute': true
'enabled' : !monitorMuteMixer?.mute
'muted' : monitorMuteMixer?.mute
})
chatMuteMixer = this.props.chatGroupMixers.muteMixer
chatMuteMixer = @state.chatGroupMixers.muteMixer
chatMuteMixerId = chatMuteMixer?.id
chatVolumeLeft = this.props.chatGroupMixers.mixer?.volume_left
chatVolumeLeft = @state.chatGroupMixers.mixer?.volume_left
chatMuteClasses = classNames({
'track-icon-mute': true
'enabled' : !chatMuteMixer?.mute
@ -45,20 +67,20 @@ MixerActions = @MixerActions
<h3>Music</h3>
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vul" mixers={this.props.inputGroupMixers} />
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vul" mixers={this.state.inputGroupMixers} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vur" mixers={this.props.inputGroupMixers} />
<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.props.inputGroupMixers} />
<div className={monitorMuteClasses} data-control="mute" data-mixer-id={monitorMuteMixerId} onClick={this.handleMute}/>
<SessionTrackGain mixers={this.state.inputGroupMixers} />
<div className={monitorMuteClasses} data-control="mute" data-mixer-id={monitorMuteMixerId} onClick={this.handleAudioInputMute}/>
<input type="checkbox" name="mute"/>
<label for="mute">Mute</label>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">
@ -72,20 +94,20 @@ MixerActions = @MixerActions
<h3>Chat</h3>
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vul" mixers={this.props.chatGroupMixers} />
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vul" mixers={this.state.chatGroupMixers} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vur" mixers={this.props.chatGroupMixers} />
<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.props.chatGroupMixers} />
<div className={chatMuteClasses} data-control="mute" data-mixer-id={chatMuteMixerId} onClick={this.handleMute}/>
<SessionTrackGain mixers={this.state.chatGroupMixers} />
<div className={chatMuteClasses} data-control="mute" data-mixer-id={chatMuteMixerId} onClick={this.handleChatMute}/>
<input type="checkbox" name="mute"/>
<label for="mute">Mute</label>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">
@ -99,23 +121,40 @@ MixerActions = @MixerActions
$root = jQuery(this.getDOMNode())
# initialize icheck
$checkbox = $root.find('input')
context.JK.checkbox($checkbox)
$checkbox.on('ifChanged', this.handleMuteCheckbox);
$chatMuteCheckbox = $root.find('.chat-mixer input')
context.JK.checkbox($chatMuteCheckbox)
$chatMuteCheckbox.on('ifChanged', @handleChatMuteCheckbox);
#if this.props.mixers.muteMixer.mute
# $checkbox.iCheck('check').attr('checked', true)
#else
# $checkbox.iCheck('uncheck').attr('checked', false)
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
$checkbox = $root.find('input')
$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)
#if nextState.mixers.muteMixer?.mute
# $checkbox.iCheck('check').attr('checked', true)
#else
# $checkbox.iCheck('uncheck').attr('checked', false)
})

View File

@ -17,12 +17,14 @@ context = window
lights.push(`<td key={i} width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td>`)
tableClasses = classNames('vu', 'horizontal', this.props.side + '-' + this.props.mixers.mixer?.id)
tableClasses = classNames('vu', 'horizontal')
`<table className={tableClasses} data-light-count={this.props.lightCount}>
<tr>
{lights}
</tr>
<tbody>
<tr>
{lights}
</tr>
</tbody>
</table>`
else
@ -31,11 +33,39 @@ context = window
lightClasses = classNames('vulight', 'vu' + i, lightClass)
lights.push(`<tr><td key={i} width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td></tr>`)
lights.push(`<tr key={i}><td width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td></tr>`)
tableClasses = classNames('vu', 'vertical', this.props.side + '-' + this.props.mixers.mixer?.id)
tableClasses = classNames('vu', 'vertical')
`<table className={tableClasses} data-light-count={this.props.lightCount}>
<tbody>
{lights}
</tbody>
</table>`
getInitialState: () ->
{registered: null}
registerVU: (mixerId) ->
return if @state.registered? || !mixerId?
$root = $(this.getDOMNode())
context.JK.VuHelpers.registerVU('single', mixerId, @render, @props.orientation == 'horizontal', @props.lightCount, $root.find('td'))
@setState(registered: {mixerId: mixerId, ptr: @render})
componentWillReceiveProps: (nextProps) ->
@registerVU(nextProps.mixers.mixer?.id)
componentDidMount: () ->
@registerVU(@props.mixers.mixer?.id)
componentWillUnmount: () ->
console.log("UNMOUNTING")
if @state.registered?
context.JK.VuHelpers.unregisterVU(@state.registered.mixerId, @state.registered.ptr)
})

View File

@ -58,7 +58,7 @@ MixerActions = @MixerActions
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<input type="checkbox" name="mute"/>
<label for="mute">Mute</label>
<label htmlFor="mute">Mute</label>
</div>
<div className="textual-help">

View File

@ -1,4 +1,5 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@SessionVolumeSettingsBtn = React.createClass({
@ -20,7 +21,7 @@ context = window
$root,
'SessionSelfVolumeHover',
() =>
{inputGroupMixers: this.state.mixers.getAudioInputChatGroupMixer(), chatGroupMixers: this.state.mixers.getChatGroupMixer()}
{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,11 @@
context = window
@MediaPlaybackActions = Reflux.createActions({
playbackStateChange: {}
positionUpdate:{}
mediaStartPlay: {}
mediaStopPlay: {}
mediaPausePlay: {}
mediaChangePosition: {}
currentTimeChanged: {}
})

View File

@ -9,4 +9,8 @@ context = window
mixersChanged: {}
syncTracks: {}
mixerModeChanged: {}
loopChanged: {}
openMetronome: {}
metronomeChanged: {}
deadUserRemove: {}
})

View File

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

View File

@ -8,4 +8,15 @@ context = window
syncWithServer: {}
toggleSessionVideo : {}
audioResync: {}
openBackingTrack: {}
closeMedia: {}
updateSession: {}
downloadingJamTrack : {}
openMetronome: {}
showNativeMetronomeGui: {}
metronomeCricketChange: {}
windowBackgrounded: {}
broadcastFailure: {}
broadcastSuccess: {}
broadcastStopped: {}
})

View File

@ -1,31 +1,25 @@
context = window
ChannelGroupIds = context.JK.ChannelGroupIds
CategoryGroupIds = context.JK.CategoryGroupIds
MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
constructor: (@session, @masterMixers, @personalMixers, @mixMode) ->
constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixMode) ->
@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()
updateMixers: (type, text, @masterMixers, @personalMixers) ->
@organize()
organize: () ->
for masterMixer in @masterMixers
@allMixers['M' + masterMixer.id] = masterMixer; # populate allMixers by mixer.id
@ -84,20 +78,20 @@ MIX_MODES = context.JK.MIX_MODES;
so, let's group up all mixers by type, and then ask them to be rendered
###
recordingTrackMixers = []
backingTrackMixers = []
jamTrackMixers = []
metronomeTrackMixers = []
adhocTrackMixers = []
@recordingTrackMixers = []
@backingTrackMixers = []
@jamTrackMixers = []
@metronomeTrackMixers = []
@adhocTrackMixers = []
groupByType = (mixers, isLocalMixer) ->
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)
@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
@ -119,7 +113,7 @@ MIX_MODES = context.JK.MIX_MODES;
break
if isJamTrack
jamTrackMixers.push(mixer)
@jamTrackMixers.push(mixer)
else
isBackingTrack = false
if recordedBackingTracks
@ -135,21 +129,21 @@ MIX_MODES = context.JK.MIX_MODES;
break
if isBackingTrack
backingTrackMixers.push(mixer)
@backingTrackMixers.push(mixer)
else
# couldn't resolve this as a JamTrack or Backing track, must be a normal recorded file
recordingTrackMixers.push(mixer)
@recordingTrackMixers.push(mixer)
else if mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack'
backingTrackMixers.push(mixer)
@backingTrackMixers.push(mixer)
else if mediaType == 'JamTrack'
jamTrackMixers.push(mixer);
@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)
@recordingTrackMixers.push(mixer)
else
logger.warn("Unknown track type: " + mediaType)
adhocTrackMixers.push(mixer)
@adhocTrackMixers.push(mixer)
groupByType(localMediaMixers, true);
groupByType(peerLocalMediaMixers, false);
@ -170,17 +164,261 @@ MIX_MODES = context.JK.MIX_MODES;
checkMetronomeTransition();
###
if adhocTrackMixers.length > 0
@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: {mixer: mixer, oppositeMixer: oppositeMixer, vuMixer: mixer, muteMixer: mixer}
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?
oppositeMixer = @getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL)
data =
name: jamTrackName
part: part
isOpener: isOpener
instrumentIcon: instrumentIcon
track: oneOfTheTracks
mixers: {mixer: mixer, oppositeMixer: oppositeMixer, vuMixer: mixer, muteMixer: mixer}
_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?
oppositeMixer = @getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL)
isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup
data =
recordingName: recordingName
isOpener: isOpener
userName: userName
instrumentIcon: instrumentIcon
track: oneOfTheTracks
mixers: {mixer: mixer, oppositeMixer: oppositeMixer, vuMixer: mixer, muteMixer: mixer}
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
mixersForGroupIds: (groupIds, mixMode) ->
foundMixers = []
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
groupIdLen = groupIds.length
for i in groupIdLen
if mixer.group_id == groupIds[i]
for groupId in groupIds
if mixer.group_id == groupId
foundMixers.push(mixer)
foundMixers
@ -231,6 +469,20 @@ MIX_MODES = context.JK.MIX_MODES;
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) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
@ -373,6 +625,17 @@ MIX_MODES = context.JK.MIX_MODES;
context.trackVolumeObject.pan = context.JK.PanHelpers.convertPercentToPan(panPercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
loopChanged: (mixer, shouldLoop) ->
console.log("mixer, shouldLoop", 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
@ -438,15 +701,16 @@ MIX_MODES = context.JK.MIX_MODES;
@currentMixerRangeMax = mixer.range_high;
mixer
updateVU: (mixerId, value, isClipping) ->
selector = null
pureMixerId = mixerId.replace("_vul", "")
pureMixerId = pureMixerId.replace("_vur", "")
mixer = @getMixer(pureMixerId, @mixMode)
updateVU: (mixerId, leftValue, leftClipping, rightValue, rightClipping) ->
mixer = @getMixer(mixerId, @mixMode)
unless mixer
# try again, in the opposite mode (awful that this is necessary)
mixer = @getMixer(pureMixerId, !@mixMode)
mixer = @getMixer(mixerId, !@mixMode)
if mixer
context.JK.VuHelpers.updateVU3(mixer, leftValue, leftClipping, rightValue, rightClipping)
###
if mixer
if mixer.stereo # // stereo track
if mixerId.substr(-4) == "_vul"
@ -459,30 +723,37 @@ MIX_MODES = context.JK.MIX_MODES;
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: (groupId, mode) ->
mixers = @mixersForGroupId(groupId, MIX_MODES.PERSONAL)
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 {}
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 {}
else
mixer = mixers[0]
{
mixer: mixer,
muteMixer : mixer,
vuMixer: mixer,
oppositeMixer: mixer
mixer: found,
muteMixer : found,
vuMixer: found,
oppositeMixer: found
}
console.log("M MIXERS", @masterMixers)
console.log("P MIXERS", @personalMixers)
getAudioInputCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.AudioInputMusic, mode)
getAudioInputChatGroupMixer: () ->
@getGroupMixer(ChannelGroupIds.AudioInputMusicGroup, MIX_MODES.PERSONAL)
getChatGroupMixer: () ->
@getGroupMixer(ChannelGroupIds.AudioInputChatGroup, MIX_MODES.PERSONAL)
getChatCategoryMixer: (mode) ->
@getGroupMixer(CategoryGroupIds.AudioInputChat, mode)

View File

@ -2,10 +2,12 @@ context = window
@SessionHelper = class SessionHelper
constructor: (app, session, isRecording) ->
constructor: (app, session, participantsEverSeen, isRecording, downloadingJamTrack) ->
@app = app
@session = session
@participantsEverSeen = participantsEverSeen
@isRecording = isRecording
@downloadingJamTrack = downloadingJamTrack
inSession: () ->
@session?
@ -55,6 +57,7 @@ context = window
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
@ -62,6 +65,14 @@ context = window
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)->
@ -70,12 +81,22 @@ context = window
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

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

@ -5,6 +5,19 @@ 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);
@ -17,17 +30,47 @@ rest = context.JK.Rest()
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
handleVolumeChangeCallback: () ->
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: () ->
logger.debug("metronome callback")
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) ->
@ -49,8 +92,8 @@ rest = context.JK.Rest()
# 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 + "_vul", (leftValue + 80) / 80, leftClipping)
@mixers.updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping)
@mixers.updateVU(mixerId, (leftValue + 80) / 80, leftClipping, (rightValue + 80) / 80, rightClipping)
#@mixers.updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping)
handleBackingTrackSelectedCallback: () ->
@ -60,17 +103,26 @@ rest = context.JK.Rest()
@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, @mixers?.mixMode || MIX_MODES.PERSONAL)
this.trigger({session: @session, mixers: @mixers})
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onMute: (mixers, muting) ->
@ -78,18 +130,54 @@ rest = context.JK.Rest()
@mixers.mute(mixer.id, mixer.mode, muting);
# simulate a state change to cause a UI redraw
this.trigger({session: @session, mixers: @mixers})
@issueChange()
onFaderChanged: (data, mixerIds, groupId) ->
@mixers.faderChanged(data, mixerIds, groupId)
this.trigger({session: @session, mixers: @mixers})
@issueChange()
onPanChanged: (data, mixerIds, groupId) ->
@mixers.panChanged(data, mixerIds, groupId)
this.trigger({session: @session, mixers: @mixers})
@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)
@ -99,19 +187,23 @@ rest = context.JK.Rest()
onMixersChanged: (type, text) ->
console.log("MixerStore: onMixersChanged")
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @mixers?.mixMode || MIX_MODES.PERSONAL)
console.log("masterMixers", @masterMixers)
console.log("personalMixers", @personalMixers)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo())
this.trigger({session: @session, mixers: @mixers})
@issueChange()
onMixerModeChanged: (mode) ->
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, mode)
this.trigger({session: @session, mixers: @mixers})
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, mode)
@issueChange()
onSyncTracks: () ->
logger.debug("MixerStore: onSyncTracks")

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,39 @@
$ = 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
@notifications.unshift(notification)
if @notifications.length > 100
@notifications.pop();
@issueChange()
onBackendNotification: (notification) ->
@processNotification(notification)
onFrontendNotification: (notification) ->
@processNotification(notification)
}
)

View File

@ -8,6 +8,7 @@ MIX_MODES = context.JK.MIX_MODES
SessionActions = @SessionActions
RecordingActions = @RecordingActions
NotificationActions = @NotificationActions
@SessionStore = Reflux.createStore(
{
@ -33,6 +34,9 @@ RecordingActions = @RecordingActions
isRecording: false
previousAllTracks: {userTracks: [], backingTracks: [], metronomeTracks: []}
webcamViewer: null
openBackingTrack: null
helper: null
downloadingJamTrack: false
init: ->
# Register with the app store to get @app
@ -48,6 +52,131 @@ RecordingActions = @RecordingActions
@sessionUtils = context.JK.SessionUtils
@recordingModel = new context.JK.RecordingModel(@app, rest, context.jamClient);
RecordingActions.initModel(@recordingModel)
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
issueChange: () ->
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
this.trigger(@helper)
onWindowBackgrounded: () ->
@app.user()
.done((userProfile) =>
if userProfile.show_whats_next &&
window.location.pathname.indexOf(gon.client_path) == 0 &&
!@app.layout.isDialogShowing('getting-started')
@app.layout.showDialog('getting-started')
)
return unless @inSession()
# the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen
logger.debug("leaving session because window was closed")
SessionActions.leaveSession({location: '/client#/home'})
onBroadcastFailure: (text) ->
logger.debug("SESSION_LIVEBROADCAST_FAIL alert. reason:" + text);
if @currentSession? && @currentSession.mount?
rest.createSourceChange({
mount_id: @currentSession.mount.id,
source_direction: true,
success: false,
reason: text,
client_id: @app.clientId
})
else
logger.debug("unable to report source change because no mount seen on session")
onBroadcastSuccess: (text) ->
logger.debug("SESSION_LIVEBROADCAST_ACTIVE alert. reason:" + text);
if @currentSession? && @currentSession.mount?
rest.createSourceChange({
mount_id: @currentSession.mount.id,
source_direction: true,
success: true,
reason: text,
client_id: @app.clientId
})
else
logger.debug("unable to report source change because no mount seen on session")
onBroadcastStopped: (text) ->
logger.debug("SESSION_LIVEBROADCAST_STOPPED alert. reason:" + text);
if @currentSession? && @currentSession.mount?
rest.createSourceChange({
mount_id: @currentSession.mount.id,
source_direction: false,
success: true,
reason: text,
client_id: @app.clientId
})
else
logger.debug("unable to report source change because no mount seen on session")
onShowNativeMetronomeGui: () ->
context.jamClient.SessionShowMetronomeGui()
onOpenMetronome: () ->
unstable = @unstableNTPClocks()
if @participants().length > 1 && unstable.length > 0
names = unstable.join(", ")
logger.debug("Unstable clocks: ", names, unstable)
context.JK.Banner.showAlert("Couldn't open metronome", context._.template($('#template-help-metronome-unstable').html(), {names: names}, { variable: 'data' }));
else
data =
value: 1
session_size: @participants().length
user_id: context.JK.currentUserId
user_name: context.JK.currentUserName
context.stats.write('web.metronome.open', data)
rest.openMetronome({id: @currentSessionId})
.done((response) =>
MixerActions.openMetronome()
@updateSessionInfo(response, true)
)
.fail((jqXHR) =>
@app.notify({
"title": "Couldn't open metronome",
"text": "Couldn't inform the server to open metronome. msg=" + jqXHR.responseText,
"icon_url": "/assets/content/icon_alert_big.png"
})
)
onMetronomeCricketChange: (isCricket) ->
context.jamClient.setMetronomeCricketTestState(isCricket);
unstableNTPClocks: () ->
unstable = []
# This should be handled in the below loop, actually:
myState = context.jamClient.getMyNetworkState()
map = null
for participant in @participants()
isSelf = participant.client_id == @app.clientId
if isSelf
isStable = myState.ntp_stable
else
map = context.jamClient.getPeerState(participant.client_id)
isStable = map.ntp_stable
if !isStable
name = participant.user.name
if isSelf
name += " (this computer)"
unstable.push(name)
unstable
onDownloadingJamTrack: (downloading) ->
@downloadingJamTrack = downloading
@issueChange()
onToggleSessionVideo: () ->
logger.debug("toggle session video")
@ -71,6 +200,133 @@ RecordingActions = @RecordingActions
@sessionPageEnterDeferred.resolve(inputTracks);
@sessionPageEnterDeferred = null
onCloseMedia: () ->
logger.debug("SessionStore: onCloseMedia")
if @helper.recordedTracks()
@closeRecording()
else if @helper.jamTracks() || @downloadingJamTrack
@closeJamTrack()
else if @helper.backingTrack() && @helper.backingTrack().path
@closeBackingTrack()
else if @helper.isMetronomeOpen()
@closeMetronomeTrack()
else
logger.error("don't know how to close open media");
closeJamTrack: () ->
logger.debug("closing jam track");
if @isRecording
logger.debug("can't close jamtrack while recording")
@app.notify({title: 'Can Not Close JamTrack', text: 'A JamTrack can not be closed while recording.'})
return
unless @selfOpenedJamTracks()
logger.debug("can't close jamtrack if not the opener")
@app.notify({title: 'Can Not Close JamTrack', text: 'Only the person who opened the JamTrack can close it.'})
return
rest.closeJamTrack({id: @currentSessionId})
.done(() =>
@refreshCurrentSession(true)
)
.fail((jqXHR) =>
@app.notify({
"title": "Couldn't Close JamTrack",
"text": "Couldn't inform the server to close JamTrack. msg=" + jqXHR.responseText,
"icon_url": "/assets/content/icon_alert_big.png"
})
)
context.jamClient.JamTrackStopPlay()
onOpenBackingTrack: (result) ->
unless @inSession()
logger.debug("ignoring backing track selected callback (not in session)")
return
if result.success
logger.debug("backing track selected: " + result.file);
rest.openBackingTrack({id: @currentSessionId, backing_track_path: result.file})
.done(() =>
openResult = context.jamClient.SessionOpenBackingTrackFile(result.file, false);
if openResult
# storing session state in memory, not in response of Session server response. bad.
@openBackingTrack = result.file
else
@app.notify({
"title": "Couldn't Open Backing Track",
"text": "Is the file a valid audio file?",
"icon_url": "/assets/content/icon_alert_big.png"
});
@closeBackingTrack()
)
.fail((jqXHR) =>
@app.notifyServerError(jqXHR, "Unable to Open Backing Track For Playback");
)
closeRecording: () ->
logger.debug("closing recording");
rest.stopPlayClaimedRecording({id: @currentSessionId, claimed_recording_id: @currentSession.claimed_recording.id})
.done((response) =>
#sessionModel.refreshCurrentSession(true);
# update session info
@onUpdateSession(response)
)
.fail((jqXHR) =>
@app.notify({
"title": "Couldn't Stop Recording Playback",
"text": "Couldn't inform the server to stop playback. msg=" + jqXHR.responseText,
"icon_url": "/assets/content/icon_alert_big.png"
})
)
context.jamClient.CloseRecording()
closeMetronomeTrack:() ->
logger.debug("SessionStore: closeMetronomeTrack")
rest.closeMetronome({id: @currentSessionId})
.done(() =>
context.jamClient.SessionCloseMetronome()
@refreshCurrentSession(true)
)
.fail((jqXHR) =>
@app.notify({
"title": "Couldn't Close MetronomeTrack",
"text": "Couldn't inform the server to close MetronomeTrack. msg=" + jqXHR.responseText,
"icon_url": "/assets/content/icon_alert_big.png"
})
)
closeBackingTrack: () ->
if @isRecording
logger.debug("can't close backing track while recording")
return
rest.closeBackingTrack({id: @currentSessionId})
.done(() =>
)
.fail(() =>
@app.notify({
"title": "Couldn't Close Backing Track",
"text": "Couldn't inform the server to close Backing Track. msg=" + jqXHR.responseText,
"icon_url": "/assets/content/icon_alert_big.png"
});
)
# '' closes all open backing tracks
context.jamClient.SessionStopPlay();
context.jamClient.SessionCloseBackingTrackFile('');
onMixersChanged: (type, text, trackInfo) ->
return unless @inSession()
@ -227,7 +483,7 @@ RecordingActions = @RecordingActions
else
@app.notifyAlert(title, "Error reason: " + reason)
this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording))
@issueChange()
notifyWithUserInfo: (title , text, clientId) ->
@findUserBy({clientId: clientId})
@ -504,7 +760,7 @@ RecordingActions = @RecordingActions
# give the session to settle just a little (call a timeout of 1 second)
setTimeout(()=>
# 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: @currentSessionId, jam_track_id: jamTrack.id})
.done((response) =>
logger.debug("jamtrack opened")
# now actually load the jamtrack
@ -567,6 +823,9 @@ RecordingActions = @RecordingActions
@refreshCurrentSessionRest(force)
)
onUpdateSession: (session) ->
@updateSessionInfo(session, true)
updateSessionInfo: (session, force) ->
if force == true || @currentTrackChanges < session.track_changes_counter
logger.debug("updating current track changes from %o to %o", @currentTrackChanges, session.track_changes_counter)
@ -668,7 +927,7 @@ RecordingActions = @RecordingActions
console.log("SESSION CHANGED", sessionData)
this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording))
@issueChange()
ensureConnected: () ->
unless context.JK.JamServer.connected
@ -740,7 +999,7 @@ RecordingActions = @RecordingActions
@sessionEnded()
this.trigger(new context.SessionHelper(@app, @currentSession, @isRecording))
@issueChange()
selfOpenedJamTracks: () ->
@currentSession && (@currentSession.jam_track_initiator_id == context.JK.currentUserId)
@ -776,6 +1035,17 @@ RecordingActions = @RecordingActions
@previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []}
@openBackingTrack = null
@shownAudioMediaMixerHelp = false
@controlsLockedForJamTrackRecording = false;
@controlsLockedForJamTrackRecording = false
@openBackingTrack = null
@downloadingJamTrack = false
NotificationActions.sessionEnded()
id: () ->
@currentSessionId
getCurrentOrLastSession: () ->
@currentOrLastSession
}
)

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({
@ -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) {
@ -3025,6 +3025,7 @@
function closeMetronomeTrack() {
rest.closeMetronome({id: sessionModel.id()})
.done(function() {
logger.debug("session: SessionCloseMetronome")
context.jamClient.SessionCloseMetronome();
sessionModel.refreshCurrentSession(true);
})

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")

View File

@ -672,7 +672,7 @@ context.JK.SyncViewer = class SyncViewer
sendCommand: ($retry, cmd) =>
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
if context.SessionStore.inSession()
context.JK.ackBubble($retry, 'sync-viewer-paused', {}, {offsetParent: $retry.closest('.dialog')})
else
context.jamClient.OnTrySyncCommand(cmd)
@ -817,7 +817,7 @@ context.JK.SyncViewer = class SyncViewer
exportRecording: (e) =>
$export = $(e.target)
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
if context.SessionStore.inSession()
context.JK.ackBubble($export, 'sync-viewer-paused', {}, {offsetParent: $export.closest('.dialog')})
return
@ -837,7 +837,7 @@ context.JK.SyncViewer = class SyncViewer
deleteRecording: (e) =>
$delete = $(e.target)
if context.JK.CurrentSessionModel and context.JK.CurrentSessionModel.inSession()
if context.SessionStore.inSession()
context.JK.ackBubble($delete, 'sync-viewer-paused', {}, {offsetParent: $delete.closest('.dialog')})
return

View File

@ -1422,7 +1422,7 @@
/** validates that no changes are being made to tracks while recording */
context.JK.verifyNotRecordingForTrackChange = function (app) {
if (context.JK.CurrentSessionModel.recordingModel.isRecording()) {
if (context.RecordingStore.recordingModel.isRecording()) {
app.notify({
title: "Currently Recording",
text: "Tracks cannot be modified while recording.",

View File

@ -14,6 +14,8 @@
// take all necessary arguments to complete its work.
context.JK.VuHelpers = {
registeredMixers: [],
/**
* Render a VU meter into the provided selector.
* vuType can be either "horizontal" or "vertical"
@ -95,6 +97,105 @@
},
// type can be 'single' or 'double', meaning how the VU is represented (one set of lights, two)
// mixerId is the ID of the mixer
// and someFunction is used to make the registration (equality check).
registerVU: function(type, mixerId, someFunction, horizontal, lightCount, leftLights, rightLights) {
var registrations = this.registeredMixers[mixerId]
if (!registrations) {
registrations = []
this.registeredMixers[mixerId] = registrations
}
if(type == 'single') {
registrations.push({type:type, ptr: someFunction, horizontal: horizontal, lightCount: lightCount, lights:leftLights})
}
else {
registrations.push({type:type, ptr: someFunction, horizontal: horizontal, lightCount: lightCount, leftLights:leftLights, rightLights: rightLights})
}
},
unregisterVU: function(mixerId, someFunction) {
var registrations = this.registeredMixers[mixerId]
if (!registrations || registrations.length == 0) {
logger.debug("no registration found for: " + type + ":" + mixerId)
return
}
var origLength = registrations.length
registrations = registrations.filter(function(element) {
someFunction !== element.ptr
})
this.registeredMixers[mixerId] = registrations
if(origLength == registrations.length) {
logger.warn("did not find anything to unregister for: " + type + ':' + mixerId)
}
},
updateSingleVU: function(horizontal, lightCount, $lights, value, isClipping) {
var i = 0;
var state = 'on';
var lights = Math.round(value * lightCount);
var redSwitch = Math.round(lightCount * 0.6666667);
var $light = null;
var colorClass = 'vu-green-';
var thisLightSelector = null;
// Remove all light classes from all lights
$lights.removeClass('vu-green-off vu-green-on vu-red-off vu-red-on');
// Set the lights
for (i = 0; i < lightCount; i++) {
colorClass = 'vu-green-';
state = 'on';
if (i >= redSwitch) {
colorClass = 'vu-red-';
}
if (i >= lights) {
state = 'off';
}
var lightIndex = horizontal ? i : lightCount - i - 1;
$lights.eq(lightIndex).addClass(colorClass + state);
}
},
// sentMixerId ends with vul or vur
updateVU3: function(mixer, leftValue, leftClipping, rightValue, rightClipping) {
var registrations = this.registeredMixers[mixer.id]
if (registrations) {
var j;
for(j = 0; j < registrations.length; j++) {
var registration = registrations[j]
var horizontal = registration.horizontal;
var lightCount = registration.lightCount;
if(registration.type == 'single') {
// TODO: find 'active' VU ... is it left value, or right value?
var $lights = registration.lights;
this.updateSingleVU(horizontal, lightCount, $lights, leftValue, leftClipping)
}
else {
if(mixer.stereo) {
this.updateSingleVU(horizontal, lightCount, registration.leftLights, leftValue, leftClipping)
this.updateSingleVU(horizontal, lightCount, registration.rightLights, rightValue, rightClipping)
}
else {
this.updateSingleVU(horizontal, lightCount, registration.leftLights, leftValue, leftClipping)
this.updateSingleVU(horizontal, lightCount, registration.rightLights, leftValue, leftClipping)
}
}
}
}
},
/**
* Given a selector representing a container for a VU meter and
* a value between 0.0 and 1.0, light the appropriate lights.

View File

@ -343,3 +343,7 @@ $labelFontSize: 12px;
text-transform: capitalize
}
.vertical-helper {
display: inline-block;
height: 100%;
}

View File

@ -1,6 +1,7 @@
@import "client/common";
.metronome-playback-mode-selector-popup {
text-align:left;
.bt-content {
width:180px;
background-color:#333;

View File

@ -0,0 +1,206 @@
@import "client/common";
.media-controls {
padding: 3px 0;
width:100%;
min-width:100%;
background-color: #242323;
position: relative;
font-size: 13px;
text-align: center;
@include border_box_sizing;
height: 36px;
display:block;
white-space:nowrap;
.play-buttons {
float:left;
margin-top:5px;
}
.recording-position {
float:left;
margin-left:10px;
}
.recording-time {
float:left;
margin-top:8px;
&.start-time {
margin-left:10px;
}
&.duration-time {
float:right;
}
}
.recording-playback {
float:left;
}
.recording-current {
display:none;
}
.recording-playback {
display:inline-block;
background-image:url(/assets/content/bkg_playcontrols.png);
background-repeat:repeat-x;
position:relative;
width:calc(100% - 115px);
margin-left:83px;
margin-right:20px;
margin-top:8px;
cursor:pointer;
height:16px;
position:absolute;
left:0;
}
.recording-slider {
position:absolute;
left:0;
top:0;
img {
position:absolute;
}
}
.metronome-playback-options {
float:left;
margin-left:10px;
margin-top:8px;
}
.metronome-options {
float:right;
}
&.jamtrack-mode, &.mediafile-mode {
.metronome-playback-options {
display:none;
}
.metronome-text {
display:none;
}
.metronome-options {
display:none;
}
}
&.metronome-mode {
.recording-time {display:none}
.recording-playback {display:none}
.recording-current {display:none}
.playback-mode-buttons {display:none}
.stop-button {display:none}
select {
width:75px;
float:right;
}
label {
float: right !important;
margin-left: 5px;
margin-top: 7px !important;
margin-right: 5px !important;
}
}
.recording-status {
font-size:15px;
}
.recording-status .recording-duration {
font-family:Arial, Helvetica, sans-serif;
display:inline-block;
font-size:18px;
position:absolute;
//top:3px;
right:4px;
}
.recording-slider {
cursor:pointer;
}
&.has-mix {
.recording-status {
display:none;
}
}
&:not(.has-mix) {
border-width: 0; // override screen_common's .error
.play-button {
display:none;
}
.recording-current {
display:none;
}
.recording-position {
display:none;
}
}
.jam-track-get-ready, .media-seeking {
display:none;
position:absolute;
top:-29px;
margin-left:-50px;
width:100px;
vertical-align:middle;
height:32px;
line-height:32px;
left:50%;
.spinner-small {
vertical-align:middle;
display:inline-block;
}
span {
vertical-align:middle;
}
}
.jam-track-get-ready[data-mode="JAMTRACK"][data-current-time="0"] {
display:block;
}
.media-seeking[data-mode="SEEKING"] {
display:block;
}
.playback-mode-buttons {
display:none;
}
.play-button, .stop-button {
outline:none;
}
.stop-button {
margin-left:3px;
}
.play-button img.pausebutton {
display:none;
}
.metronome-controls {
float:left;
}
.metronome-options {
float:right;
}
}

View File

@ -26,6 +26,10 @@
padding: 15px;
height: 100%;
margin-bottom: 15px;
color:$ColorTextTypical;
overflow:hidden;
position:relative;
}
.session-notifications {
@ -62,10 +66,21 @@
}
.session-tracks-scroller {
position: relative;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
position: absolute;
top: 90px;
padding: 0 15px;
box-sizing: border-box;
bottom: 0;
left: 0;
right: 0;
&.media-options-showing {
top:180px;
}
}
p {
@ -73,10 +88,13 @@
margin: 0;
}
.download-jamtrack {
margin-top:20px;
}
.session-track {
float:left;
margin: 10px 0;
color: $ColorTextTypical;
background-color: #242323;
border-radius: 6px;
@ -96,15 +114,71 @@
border-radius: 6px;
}
&.no-mixer {
&.no-mixer, &.no-audio {
.disabled-track-overlay {
width: 100%;
height: 100%;
opacity:0.5;
}
}
// media overrides
&.backing-track, &.recorded-track, &.jam-track, &.metronome {
width:210px;
table.vu {
float: right;
margin-top: 30px;
margin-right: 4px;
}
.track-controls {
float:right;
}
.track-buttons {
float:right;
}
.track-icon-pan {
float:right;
margin-right:20px;
}
.track-icon-mute{
float:right;
}
}
&.metronome {
.track-instrument {
float:left;
margin-left:0;
margin-right: 8px;
margin-top: -3px;
}
.track-controls {
margin-left:0;
}
}
&.recorded-track, &.jam-track {
height:56px;
min-height:56px;
.track-buttons {
margin-top:2px;
}
.track-controls {
margin-left:0;
}
table.vu {
margin-top:10px;
}
.track-instrument {
float: left;
margin: -2px 7px 0 0;
}
}
}
.react-holder {
&.SessionTrackVolumeHover, &.SessionSelfVolumeHover {
height:331px;
@ -423,29 +497,114 @@
}
}
.session-track-settings {
height:18px;
height:20px;
cursor:pointer;
color:white;
padding-bottom:1px; // to line up with SessionOtherTracks
color:$ColorTextTypical;
span {
top: -4px;
top: -5px;
position: relative;
left:3px;
}
}
.session-invite-musicians {
height:19px;
height:20px;
cursor: pointer;
color:white;
color:$ColorTextTypical;
span {
top:-5px;
position:relative;
left:3px;
}
}
.closeAudio, .session-clear-notifications {
cursor: pointer;
color:$ColorTextTypical;
height:20px;
img {
top:-2px
}
span {
top: -5px;
position: relative;
left: 3px;
}
}
.open-media-file-header, .use-metronome-header {
font-size:16px;
line-height:100%;
margin:0;
img {
position:relative;
top:3px;
}
}
.open-media-file-header {
img {
vertical-align:middle;
}
.open-text {
margin-left:5px;
vertical-align:bottom;
}
}
.use-metronome-header {
clear: both;
a {
color:$ColorTextTypical;
&:hover {
text-decoration: underline;
}
}
}
.open-media-file-options {
font-size:14px;
margin: 7px 0 0 7px !important;
color:$ColorTextTypical;
li {
margin-bottom:5px !important;
margin-left:38px !important;
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
color:$ColorTextTypical;
}
}
}
.open-metronome {
margin-left:5px;
}
.media-options {
padding-bottom:10px;
}
.session-notification {
color: white;
background-color: #666666;
border-radius: 6px;
min-height: 36px;
width:100%;
position:relative;
@include border_box_sizing;
padding:6px;
}
}

View File

@ -0,0 +1,45 @@
@import "client/common";
body.media-controls-popup.popup {
text-align:center;
background-color: #242323;
#minimal-container {
padding-bottom:0px;
}
.media-controls-popup {
padding:15px 15px 3px 15px;
}
.field {
margin-top:20px;
}
.icheckbox_minimal {
float:left;
}
label {
float: left;
margin-left: 5px;
margin-top:2px;
}
h3 {
text-align:left;
margin-bottom:5px;
}
.close-link {
margin-top:20px;
font-size:11px;
}
.display-metronome {
font-size:12px;
margin-top:35px;
}
}

View File

@ -8,5 +8,8 @@
*= require icheck/minimal/minimal
*= require minimal/popup
*= require minimal/recording_controls
*= require minimal/media_controls
*= require minimal/minimal_main
*= require client/metronomePlaybackModeSelect
*= require_directory ../client/react-components
*/

View File

@ -6,4 +6,8 @@ class PopupsController < ApplicationController
render :layout => "minimal"
end
def media_controls
render :layout => "minimal"
end
end

View File

@ -0,0 +1,3 @@
object @music_session
extends "api_music_sessions/show"

View File

@ -0,0 +1,3 @@
object @music_session
extends "api_music_sessions/show"

View File

@ -0,0 +1,3 @@
object @music_session
extends "api_music_sessions/show"

View File

@ -1,3 +0,0 @@
object @music_session
attributes :id

View File

@ -8,4 +8,4 @@ script type='text/template' id='template-metronome-playback-mode'
a href='#' - Play cluster test
li data-ui-option="show-metronome-window"
a href='#' - Show visual metronome
a href='#' - Show visual metronome

View File

@ -0,0 +1,8 @@
script type='text/template' id='template-metronome-playback-mode'
p.please-select Please select one:
ul
li data-playback-option="self"
a href='#' - Play metronome
li data-playback-option="cricket"
a href='#' - Play cluster test

View File

@ -12,7 +12,7 @@
h1 Learn How to Make Money by Referring Users
.video-wrapper
.video-container
iframe src="//www.youtube.com/embed/ylYcvTY9CVo" frameborder="0" allowfullscreen
iframe src="//www.youtube.com/embed/96YTnO_H9a4" frameborder="0" allowfullscreen
br clear="all"
.row
h1 JamKazam Affiliate Agreement

View File

@ -0,0 +1,3 @@
- provide(:page_name, 'media-controls-popup popup')
= render "clients/metronome_playback_mode2"
= react_component 'PopupWrapper', {component: 'PopupMediaControls'}

View File

@ -136,7 +136,8 @@ SampleApp::Application.routes.draw do
match '/extras/settings', to: 'extras#settings'
scope '/popups' do
match '/recording-controls', to: 'popups#recording_controls'
match '/recording-controls', to: 'popups#recording_controls'
match '/media-controls', to: 'popups#media_controls'
end
scope '/corp' do