@@ -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)
})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee
index 68b0d5b67..a9ad12cd5 100644
--- a/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/SessionTrackVU.js.jsx.coffee
@@ -17,12 +17,14 @@ context = window
lights.push(`
| `)
- tableClasses = classNames('vu', 'horizontal', this.props.side + '-' + this.props.mixers.mixer?.id)
+ tableClasses = classNames('vu', 'horizontal')
`
-
- {lights}
-
+
+
+ {lights}
+
+
`
else
@@ -31,11 +33,39 @@ context = window
lightClasses = classNames('vulight', 'vu' + i, lightClass)
- lights.push(`
|
`)
+ lights.push(`
|
`)
- tableClasses = classNames('vu', 'vertical', this.props.side + '-' + this.props.mixers.mixer?.id)
+ tableClasses = classNames('vu', 'vertical')
`
`
+
+ 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)
+
})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee
index f8147e2ed..503b215a3 100644
--- a/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/SessionTrackVolumeHover.js.jsx.coffee
@@ -58,7 +58,7 @@ MixerActions = @MixerActions
-
+
diff --git a/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee
index 0a9ee0b9b..e530b1bc0 100644
--- a/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/SessionVolumeSettingsBtn.js.jsx.coffee
@@ -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')})
diff --git a/web/app/assets/javascripts/react-components/actions/MediaPlaybackActions.js.coffee b/web/app/assets/javascripts/react-components/actions/MediaPlaybackActions.js.coffee
new file mode 100644
index 000000000..0996e5745
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/actions/MediaPlaybackActions.js.coffee
@@ -0,0 +1,11 @@
+context = window
+
+@MediaPlaybackActions = Reflux.createActions({
+ playbackStateChange: {}
+ positionUpdate:{}
+ mediaStartPlay: {}
+ mediaStopPlay: {}
+ mediaPausePlay: {}
+ mediaChangePosition: {}
+ currentTimeChanged: {}
+})
diff --git a/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee b/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
index 9aa71fa0d..5d5678207 100644
--- a/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
+++ b/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
@@ -9,4 +9,8 @@ context = window
mixersChanged: {}
syncTracks: {}
mixerModeChanged: {}
+ loopChanged: {}
+ openMetronome: {}
+ metronomeChanged: {}
+ deadUserRemove: {}
})
diff --git a/web/app/assets/javascripts/react-components/actions/NotificationActions.js.coffee b/web/app/assets/javascripts/react-components/actions/NotificationActions.js.coffee
new file mode 100644
index 000000000..481dc8ff8
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/actions/NotificationActions.js.coffee
@@ -0,0 +1,9 @@
+context = window
+
+@NotificationActions = Reflux.createActions({
+ clear:{}
+ backendNotification: {}
+ frontendNotification: {}
+ sessionEnded: {}
+})
+
diff --git a/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee b/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee
index 50d071cf2..634e7d419 100644
--- a/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee
+++ b/web/app/assets/javascripts/react-components/actions/SessionActions.js.coffee
@@ -8,4 +8,15 @@ context = window
syncWithServer: {}
toggleSessionVideo : {}
audioResync: {}
+ openBackingTrack: {}
+ closeMedia: {}
+ updateSession: {}
+ downloadingJamTrack : {}
+ openMetronome: {}
+ showNativeMetronomeGui: {}
+ metronomeCricketChange: {}
+ windowBackgrounded: {}
+ broadcastFailure: {}
+ broadcastSuccess: {}
+ broadcastStopped: {}
})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
index 07107298d..11c83bf08 100644
--- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
+++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
@@ -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)
\ No newline at end of file
+ getChatCategoryMixer: (mode) ->
+ @getGroupMixer(CategoryGroupIds.AudioInputChat, mode)
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee
index 839d53b45..8fe3d6098 100644
--- a/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee
+++ b/web/app/assets/javascripts/react-components/helpers/SessionHelper.js.coffee
@@ -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
diff --git a/web/app/assets/javascripts/react-components/stores/MediaPlaybackStore.js.coffee b/web/app/assets/javascripts/react-components/stores/MediaPlaybackStore.js.coffee
new file mode 100644
index 000000000..051e0047e
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/MediaPlaybackStore.js.coffee
@@ -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()
+
+ }
+)
diff --git a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
index 620ca65dd..70c97763a 100644
--- a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
@@ -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")
diff --git a/web/app/assets/javascripts/react-components/stores/SessionMediaTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionMediaTracksStore.js.coffee
new file mode 100644
index 000000000..08e0544ce
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/SessionMediaTracksStore.js.coffee
@@ -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)
+ }
+)
diff --git a/web/app/assets/javascripts/react-components/stores/SessionNotificationStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionNotificationStore.js.coffee
new file mode 100644
index 000000000..03d20f5dc
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/SessionNotificationStore.js.coffee
@@ -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)
+ }
+)
+
diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
index 6a9ed99df..1742ab26e 100644
--- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
@@ -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
+
}
)
\ No newline at end of file
diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js
index 1175e8ad8..4f8ed92c6 100644
--- a/web/app/assets/javascripts/session.js
+++ b/web/app/assets/javascripts/session.js
@@ -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);
})
diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js
index cf733c451..19c43bc94 100644
--- a/web/app/assets/javascripts/sidebar.js
+++ b/web/app/assets/javascripts/sidebar.js
@@ -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")
diff --git a/web/app/assets/javascripts/sync_viewer.js.coffee b/web/app/assets/javascripts/sync_viewer.js.coffee
index 0bf819d2e..a3122282e 100644
--- a/web/app/assets/javascripts/sync_viewer.js.coffee
+++ b/web/app/assets/javascripts/sync_viewer.js.coffee
@@ -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
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 1a11989bc..5332f9b88 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -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.",
diff --git a/web/app/assets/javascripts/vuHelpers.js b/web/app/assets/javascripts/vuHelpers.js
index 07e47d7e1..d49fde42c 100644
--- a/web/app/assets/javascripts/vuHelpers.js
+++ b/web/app/assets/javascripts/vuHelpers.js
@@ -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.
diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss
index 2f242f0b0..7441918f3 100644
--- a/web/app/assets/stylesheets/client/common.css.scss
+++ b/web/app/assets/stylesheets/client/common.css.scss
@@ -343,3 +343,7 @@ $labelFontSize: 12px;
text-transform: capitalize
}
+.vertical-helper {
+ display: inline-block;
+ height: 100%;
+}
diff --git a/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss b/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss
index 79d52c274..ff88057ea 100644
--- a/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss
+++ b/web/app/assets/stylesheets/client/metronomePlaybackModeSelect.css.scss
@@ -1,6 +1,7 @@
@import "client/common";
.metronome-playback-mode-selector-popup {
+ text-align:left;
.bt-content {
width:180px;
background-color:#333;
diff --git a/web/app/assets/stylesheets/client/react-components/MediaControls.scss.scss b/web/app/assets/stylesheets/client/react-components/MediaControls.scss.scss
new file mode 100644
index 000000000..5bf94e719
--- /dev/null
+++ b/web/app/assets/stylesheets/client/react-components/MediaControls.scss.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss
index 237d06fbf..7674ab62e 100644
--- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss
+++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss
@@ -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;
}
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/minimal/media_controls.css.scss b/web/app/assets/stylesheets/minimal/media_controls.css.scss
new file mode 100644
index 000000000..2abdda8fc
--- /dev/null
+++ b/web/app/assets/stylesheets/minimal/media_controls.css.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/minimal/minimal.css.scss b/web/app/assets/stylesheets/minimal/minimal.css.scss
index 7ea6d2c7c..be342382b 100644
--- a/web/app/assets/stylesheets/minimal/minimal.css.scss
+++ b/web/app/assets/stylesheets/minimal/minimal.css.scss
@@ -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
*/
\ No newline at end of file
diff --git a/web/app/controllers/popups_controller.rb b/web/app/controllers/popups_controller.rb
index e05e2f8ee..1af9f57de 100644
--- a/web/app/controllers/popups_controller.rb
+++ b/web/app/controllers/popups_controller.rb
@@ -6,4 +6,8 @@ class PopupsController < ApplicationController
render :layout => "minimal"
end
+ def media_controls
+ render :layout => "minimal"
+ end
+
end
\ No newline at end of file
diff --git a/web/app/views/api_music_sessions/jam_track_open.rabl b/web/app/views/api_music_sessions/jam_track_open.rabl
new file mode 100644
index 000000000..e34b6943d
--- /dev/null
+++ b/web/app/views/api_music_sessions/jam_track_open.rabl
@@ -0,0 +1,3 @@
+object @music_session
+
+extends "api_music_sessions/show"
\ No newline at end of file
diff --git a/web/app/views/api_music_sessions/metronome_close.rabl b/web/app/views/api_music_sessions/metronome_close.rabl
new file mode 100644
index 000000000..e34b6943d
--- /dev/null
+++ b/web/app/views/api_music_sessions/metronome_close.rabl
@@ -0,0 +1,3 @@
+object @music_session
+
+extends "api_music_sessions/show"
\ No newline at end of file
diff --git a/web/app/views/api_music_sessions/metronome_open.rabl b/web/app/views/api_music_sessions/metronome_open.rabl
new file mode 100644
index 000000000..e34b6943d
--- /dev/null
+++ b/web/app/views/api_music_sessions/metronome_open.rabl
@@ -0,0 +1,3 @@
+object @music_session
+
+extends "api_music_sessions/show"
\ No newline at end of file
diff --git a/web/app/views/api_music_sessions/open_jam_track.rabl b/web/app/views/api_music_sessions/open_jam_track.rabl
deleted file mode 100644
index f79061b5b..000000000
--- a/web/app/views/api_music_sessions/open_jam_track.rabl
+++ /dev/null
@@ -1,3 +0,0 @@
-object @music_session
-
-attributes :id
\ No newline at end of file
diff --git a/web/app/views/clients/_metronome_playback_mode.slim b/web/app/views/clients/_metronome_playback_mode.slim
index 2af251789..da6cdfe3e 100644
--- a/web/app/views/clients/_metronome_playback_mode.slim
+++ b/web/app/views/clients/_metronome_playback_mode.slim
@@ -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
\ No newline at end of file
diff --git a/web/app/views/clients/_metronome_playback_mode2.slim b/web/app/views/clients/_metronome_playback_mode2.slim
new file mode 100644
index 000000000..cdf4b19ff
--- /dev/null
+++ b/web/app/views/clients/_metronome_playback_mode2.slim
@@ -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
\ No newline at end of file
diff --git a/web/app/views/landings/affiliate_program.html.slim b/web/app/views/landings/affiliate_program.html.slim
index 34859ff76..64b61ba87 100644
--- a/web/app/views/landings/affiliate_program.html.slim
+++ b/web/app/views/landings/affiliate_program.html.slim
@@ -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
diff --git a/web/app/views/popups/media_controls.html.slim b/web/app/views/popups/media_controls.html.slim
new file mode 100644
index 000000000..edb4f088e
--- /dev/null
+++ b/web/app/views/popups/media_controls.html.slim
@@ -0,0 +1,3 @@
+- provide(:page_name, 'media-controls-popup popup')
+= render "clients/metronome_playback_mode2"
+= react_component 'PopupWrapper', {component: 'PopupMediaControls'}
\ No newline at end of file
diff --git a/web/config/routes.rb b/web/config/routes.rb
index b51157a0c..90c6547f5 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -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