* VRFS-3519 - 2-player sessions working with custom mix
This commit is contained in:
parent
1733c8689d
commit
1dce984247
|
|
@ -72,7 +72,7 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
||||||
monitorControls: (controls, mediaSummary) ->
|
monitorControls: (controls, mediaSummary) ->
|
||||||
|
|
||||||
if mediaSummary.mediaOpen || mediaSummary.jamTrack?
|
if mediaSummary.mediaOpen || mediaSummary.jamTrack?
|
||||||
if mediaSummary.jamTrack?
|
if mediaSummary.jamTrackOpen?
|
||||||
controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK)
|
controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK)
|
||||||
else if mediaSummary.backingTrackOpen
|
else if mediaSummary.backingTrackOpen
|
||||||
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
|
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
||||||
metronome: mixers.metronome
|
metronome: mixers.metronome
|
||||||
recordingName: mixers.recordingName()
|
recordingName: mixers.recordingName()
|
||||||
jamTrackName: mixers.jamTrackName()
|
jamTrackName: mixers.jamTrackName()
|
||||||
|
jamTrackMixdown: session.jamTrackMixdown()
|
||||||
|
|
||||||
@setState(media: state, downloadingJamTrack: session.downloadingJamTrack)
|
@setState(media: state, downloadingJamTrack: session.downloadingJamTrack)
|
||||||
|
|
||||||
|
|
@ -76,7 +77,8 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
||||||
creatingMixdown: false,
|
creatingMixdown: false,
|
||||||
createMixdownErrors: null,
|
createMixdownErrors: null,
|
||||||
editingMixdownId: null,
|
editingMixdownId: null,
|
||||||
downloadingJamTrack: @props.downloadingJamTrack
|
downloadingJamTrack: @props.downloadingJamTrack,
|
||||||
|
jamTrackMixdown: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
close: () ->
|
close: () ->
|
||||||
|
|
@ -95,234 +97,255 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
||||||
mediaName = @state.media.recordedTracks[0].recordingName
|
mediaName = @state.media.recordedTracks[0].recordingName
|
||||||
closeLinkText = 'close recording'
|
closeLinkText = 'close recording'
|
||||||
header = `<h3>{mediaType}: {mediaName}</h3>`
|
header = `<h3>{mediaType}: {mediaName}</h3>`
|
||||||
else if @state.jamTrackState.jamTrack?
|
else if @state.media.mediaSummary.jamTrackOpen || @state.jamTrackState.jamTrack?
|
||||||
jamTrack = @state.jamTrackState.jamTrack
|
if @state.media.mediaSummary.isOpener || @state.jamTrackState.jamTrack?
|
||||||
mediaType = "JamTrack"
|
# if you opened the JamTrack, then you get all the good info
|
||||||
mediaName = jamTrack.name
|
jamTrack = @state.jamTrackState.jamTrack
|
||||||
closeLinkText = 'CLOSE JAMTRACK'
|
mediaType = "JamTrack"
|
||||||
|
mediaName = jamTrack.name
|
||||||
|
closeLinkText = 'CLOSE JAMTRACK'
|
||||||
|
|
||||||
|
selectedMixdown = jamTrack.activeMixdown
|
||||||
|
|
||||||
|
|
||||||
selectedMixdown = jamTrack.activeMixdown
|
if selectedMixdown?
|
||||||
|
jamTrackTypeHeader = 'Custom Mix'
|
||||||
|
|
||||||
|
disabled = true
|
||||||
|
if selectedMixdown.client_state?
|
||||||
|
switch selectedMixdown.client_state
|
||||||
|
when 'cant_open'
|
||||||
|
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||||
|
when 'keying_timeout'
|
||||||
|
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||||
|
when 'download_fail'
|
||||||
|
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||||
|
when 'keying'
|
||||||
|
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||||
|
when 'downloading'
|
||||||
|
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||||
|
when 'ready'
|
||||||
|
customMixName = `<h5>{selectedMixdown.name}</h5>`
|
||||||
|
disabled = false
|
||||||
|
else
|
||||||
|
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||||
|
|
||||||
if selectedMixdown?
|
|
||||||
jamTrackTypeHeader = 'Custom Mix'
|
|
||||||
|
|
||||||
disabled = true
|
|
||||||
if selectedMixdown.client_state?
|
|
||||||
switch selectedMixdown.client_state
|
|
||||||
when 'cant_open'
|
|
||||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
|
||||||
when 'keying_timeout'
|
|
||||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
|
||||||
when 'download_fail'
|
|
||||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
|
||||||
when 'keying'
|
|
||||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
|
||||||
when 'downloading'
|
|
||||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
|
||||||
when 'ready'
|
|
||||||
customMixName = `<h5>{selectedMixdown.name}</h5>`
|
|
||||||
disabled = false
|
|
||||||
else
|
else
|
||||||
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif" /></h5>`
|
if SessionStore.downloadingJamTrack
|
||||||
|
downloader = `<img src="/assets/shared/spinner.gif" />`
|
||||||
|
|
||||||
else
|
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
|
||||||
if SessionStore.downloadingJamTrack
|
|
||||||
downloader = `<img src="/assets/shared/spinner.gif" />`
|
|
||||||
|
|
||||||
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
|
header = `
|
||||||
|
<div className="header">
|
||||||
|
<h3>{mediaType}: {mediaName}</h3>
|
||||||
|
<h4>{jamTrackTypeHeader}</h4>
|
||||||
|
{customMixName}
|
||||||
|
</div>`
|
||||||
|
|
||||||
header = `
|
myMixes = null
|
||||||
<div className="header">
|
if @state.showMyMixes
|
||||||
<h3>{mediaType}: {mediaName}</h3>
|
myMixdowns = []
|
||||||
<h4>{jamTrackTypeHeader}</h4>
|
|
||||||
{customMixName}
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
myMixes = null
|
boundPlayClick = this.jamTrackPlay.bind(this, jamTrack);
|
||||||
if @state.showMyMixes
|
|
||||||
myMixdowns = []
|
|
||||||
|
|
||||||
boundPlayClick = this.jamTrackPlay.bind(this, jamTrack);
|
active = jamTrack.last_mixdown_id == null
|
||||||
|
|
||||||
active = jamTrack.last_mixdown_id == null
|
|
||||||
|
|
||||||
myMixdowns.push `
|
|
||||||
<div key="full-track" className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
|
|
||||||
<div className="mixdown-name">
|
|
||||||
Full JamTrack
|
|
||||||
</div>
|
|
||||||
<div className="mixdown-actions">
|
|
||||||
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
for mixdown in jamTrack.mixdowns
|
|
||||||
boundPlayClick = this.mixdownPlay.bind(this, mixdown);
|
|
||||||
boundEditClick = this.mixdownEdit.bind(this, mixdown);
|
|
||||||
boundSaveClick = this.mixdownSave.bind(this, mixdown);
|
|
||||||
boundDeleteClick = this.mixdownDelete.bind(this, mixdown);
|
|
||||||
boundErrorClick = this.mixdownError.bind(this, mixdown);
|
|
||||||
boundEditKeydown = this.onEditKeydown.bind(this, mixdown);
|
|
||||||
|
|
||||||
mixdown_package = mixdown.myPackage
|
|
||||||
|
|
||||||
active = mixdown.id == jamTrack.last_mixdown_id
|
|
||||||
|
|
||||||
editing = mixdown.id == @state.editingMixdownId
|
|
||||||
|
|
||||||
# if there is a package, check it's state; otherwise let the user enqueue it
|
|
||||||
if mixdown_package
|
|
||||||
switch mixdown_package.signing_state
|
|
||||||
when 'QUIET_TIMEOUT'
|
|
||||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
|
||||||
when 'QUIET'
|
|
||||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
|
||||||
when 'QUEUED'
|
|
||||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
|
||||||
when 'QUEUED_TIMEOUT'
|
|
||||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
|
||||||
when 'SIGNING'
|
|
||||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
|
||||||
when 'SIGNING_TIMEOUT'
|
|
||||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
|
||||||
when 'SIGNED'
|
|
||||||
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>`
|
|
||||||
when 'ERROR'
|
|
||||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
|
||||||
else
|
|
||||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
|
||||||
|
|
||||||
if editing
|
|
||||||
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name} onKeyDown={boundEditKeydown} />`
|
|
||||||
editIcon = `<img src="/assets/content/icon-save@2X.png" className="mixdown-edit" onClick={boundSaveClick}/>`
|
|
||||||
else
|
|
||||||
mixdownName = mixdown.name
|
|
||||||
editIcon = `<img src="/assets/content/icon-edit@2X.png" className="mixdown-edit" onClick={boundEditClick}/>`
|
|
||||||
|
|
||||||
myMixdowns.push `
|
myMixdowns.push `
|
||||||
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
|
<div key="full-track" className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
|
||||||
<div className="mixdown-name">
|
<div className="mixdown-name">
|
||||||
{mixdownName}
|
Full JamTrack
|
||||||
</div>
|
</div>
|
||||||
<div className="mixdown-actions">
|
<div className="mixdown-actions">
|
||||||
{action}
|
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
{editIcon}
|
for mixdown in jamTrack.mixdowns
|
||||||
|
boundPlayClick = this.mixdownPlay.bind(this, mixdown);
|
||||||
|
boundEditClick = this.mixdownEdit.bind(this, mixdown);
|
||||||
|
boundSaveClick = this.mixdownSave.bind(this, mixdown);
|
||||||
|
boundDeleteClick = this.mixdownDelete.bind(this, mixdown);
|
||||||
|
boundErrorClick = this.mixdownError.bind(this, mixdown);
|
||||||
|
boundEditKeydown = this.onEditKeydown.bind(this, mixdown);
|
||||||
|
|
||||||
<img src ="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick} />
|
mixdown_package = mixdown.myPackage
|
||||||
</div>
|
|
||||||
|
active = mixdown.id == jamTrack.last_mixdown_id
|
||||||
|
|
||||||
|
editing = mixdown.id == @state.editingMixdownId
|
||||||
|
|
||||||
|
# if there is a package, check it's state; otherwise let the user enqueue it
|
||||||
|
if mixdown_package
|
||||||
|
switch mixdown_package.signing_state
|
||||||
|
when 'QUIET_TIMEOUT'
|
||||||
|
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||||
|
when 'QUIET'
|
||||||
|
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||||
|
when 'QUEUED'
|
||||||
|
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||||
|
when 'QUEUED_TIMEOUT'
|
||||||
|
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||||
|
when 'SIGNING'
|
||||||
|
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||||
|
when 'SIGNING_TIMEOUT'
|
||||||
|
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||||
|
when 'SIGNED'
|
||||||
|
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>`
|
||||||
|
when 'ERROR'
|
||||||
|
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||||
|
else
|
||||||
|
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||||
|
|
||||||
|
if editing
|
||||||
|
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name} onKeyDown={boundEditKeydown} />`
|
||||||
|
editIcon = `<img src="/assets/content/icon-save@2X.png" className="mixdown-edit" onClick={boundSaveClick}/>`
|
||||||
|
else
|
||||||
|
mixdownName = mixdown.name
|
||||||
|
editIcon = `<img src="/assets/content/icon-edit@2X.png" className="mixdown-edit" onClick={boundEditClick}/>`
|
||||||
|
|
||||||
|
myMixdowns.push `
|
||||||
|
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
|
||||||
|
<div className="mixdown-name">
|
||||||
|
{mixdownName}
|
||||||
|
</div>
|
||||||
|
<div className="mixdown-actions">
|
||||||
|
{action}
|
||||||
|
|
||||||
|
{editIcon}
|
||||||
|
|
||||||
|
<img src ="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick} />
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
myMixes = `<div key="my-mixes" className="my-mixes">{myMixdowns}</div>`
|
||||||
|
|
||||||
|
mixControls = null
|
||||||
|
if @state.showCustomMixes
|
||||||
|
|
||||||
|
nameClassData = {field: true}
|
||||||
|
if @state.createMixdownErrors?
|
||||||
|
|
||||||
|
errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
|
||||||
|
|
||||||
|
createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown})
|
||||||
|
mixControls = `
|
||||||
|
<div key="create-mix" className="create-mix">
|
||||||
|
<p>Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.</p>
|
||||||
|
<div className="field">
|
||||||
|
<label>Change Tempo:</label>
|
||||||
|
<select name="mix-speed">
|
||||||
|
<option value="">No change</option>
|
||||||
|
<option value="separator-1">------------</option>
|
||||||
|
<option value="-5">Slower by 5%</option>
|
||||||
|
<option value="-10">Slower by 10%</option>
|
||||||
|
<option value="-15">Slower by 15%</option>
|
||||||
|
<option value="-20">Slower by 20%</option>
|
||||||
|
<option value="-25">Slower by 25%</option>
|
||||||
|
<option value="-30">Slower by 30%</option>
|
||||||
|
<option value="-35">Slower by 35%</option>
|
||||||
|
<option value="-40">Slower by 40%</option>
|
||||||
|
<option value="-45">Slower by 45%</option>
|
||||||
|
<option value="-50">Slower by 50%</option>
|
||||||
|
<option value="-60">Slower by 60%</option>
|
||||||
|
<option value="-70">Slower by 70%</option>
|
||||||
|
<option value="-80">Slower by 80%</option>
|
||||||
|
<option value="separator-2">------------</option>
|
||||||
|
<option value="5">Faster by 5%</option>
|
||||||
|
<option value="10">Faster by 10%</option>
|
||||||
|
<option value="15">Faster by 15%</option>
|
||||||
|
<option value="20">Faster by 20%</option>
|
||||||
|
<option value="30">Faster by 30%</option>
|
||||||
|
<option value="40">Faster by 40%</option>
|
||||||
|
<option value="50">Faster by 50%</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<label>Change Pitch:</label>
|
||||||
|
<select name="mix-pitch">
|
||||||
|
<option value="">No change</option>
|
||||||
|
<option value="separator-1">---------------</option>
|
||||||
|
<option value="-1">Down 1 Semitone</option>
|
||||||
|
<option value="-2">Down 2 Semitones</option>
|
||||||
|
<option value="-3">Down 3 Semitones</option>
|
||||||
|
<option value="-4">Down 4 Semitones</option>
|
||||||
|
<option value="-5">Down 5 Semitones</option>
|
||||||
|
<option value="-6">Down 6 Semitones</option>
|
||||||
|
<option value="-7">Down 7 Semitones</option>
|
||||||
|
<option value="-8">Down 8 Semitones</option>
|
||||||
|
<option value="-9">Down 9 Semitones</option>
|
||||||
|
<option value="-10">Down 10 Semitones</option>
|
||||||
|
<option value="-11">Down 11 Semitones</option>
|
||||||
|
<option value="-12">Down 12 Semitones</option>
|
||||||
|
<option value="separator-2">---------------</option>
|
||||||
|
<option value="1">Up 1 Semitone</option>
|
||||||
|
<option value="2">Up 2 Semitones</option>
|
||||||
|
<option value="3">Up 3 Semitones</option>
|
||||||
|
<option value="4">Up 4 Semitones</option>
|
||||||
|
<option value="5">Up 5 Semitones</option>
|
||||||
|
<option value="6">Up 6 Semitones</option>
|
||||||
|
<option value="7">Up 7 Semitones</option>
|
||||||
|
<option value="8">Up 8 Semitones</option>
|
||||||
|
<option value="9">Up 9 Semitones</option>
|
||||||
|
<option value="10">Up 10 Semitones</option>
|
||||||
|
<option value="11">Up 11 Semitones</option>
|
||||||
|
<option value="12">Up 12 Semitones</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className={classNames(nameClassData)}>
|
||||||
|
<label>Mix Name:</label>
|
||||||
|
<input type="text" name="mix-name"/>
|
||||||
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<a className={createMixClasses} onClick={this.createMix}>CREATE MIX</a>
|
||||||
|
{errorHtml}
|
||||||
|
</div>
|
||||||
|
<div className="clearall"/>
|
||||||
|
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
if @state.showMyMixes
|
||||||
|
showMyMixesText = `<a onClick={this.toggleMyMixes}>hide my mixes <div className="details-arrow arrow-up" /></a>`
|
||||||
|
else
|
||||||
|
showMyMixesText = `<a onClick={this.toggleMyMixes}>show my mixes <div className="details-arrow arrow-down" /></a>`
|
||||||
|
|
||||||
|
if @state.showCustomMixes
|
||||||
|
showMixControlsText = `<a onClick={this.toggleCustomMixes}>hide mix controls <div className="details-arrow arrow-up" /></a>`
|
||||||
|
else
|
||||||
|
showMixControlsText = `<a onClick={this.toggleCustomMixes}>show mix controls <div className="details-arrow arrow-down" /></a>`
|
||||||
|
|
||||||
|
|
||||||
|
extraControls = `
|
||||||
|
<div className="extra-controls">
|
||||||
|
<h4>My Mixes {showMyMixesText}</h4>
|
||||||
|
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
||||||
|
{myMixes}
|
||||||
|
</ReactCSSTransitionGroup>
|
||||||
|
<h4 className="custom-mix-header">Create Custom Mix {showMixControlsText}</h4>
|
||||||
|
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
||||||
|
{mixControls}
|
||||||
|
</ReactCSSTransitionGroup>
|
||||||
|
</div>`
|
||||||
|
else
|
||||||
|
|
||||||
|
mediaType = "JamTrack"
|
||||||
|
mediaName = @state.media.jamTrackName
|
||||||
|
closeLinkText = 'CLOSE JAMTRACK'
|
||||||
|
|
||||||
|
# implies we have a mixdown
|
||||||
|
if @state.media.jamTrackMixdown.id?
|
||||||
|
jamTrackTypeHeader = 'Custom Mix'
|
||||||
|
else
|
||||||
|
jamTrackTypeHeader = 'Full JamTrack'
|
||||||
|
|
||||||
|
header = `
|
||||||
|
<div className="header">
|
||||||
|
<h3>{mediaType}: {mediaName}</h3>
|
||||||
|
<h4>{jamTrackTypeHeader}</h4>
|
||||||
|
{customMixName}
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
myMixes = `<div key="my-mixes" className="my-mixes">{myMixdowns}</div>`
|
|
||||||
|
|
||||||
mixControls = null
|
|
||||||
if @state.showCustomMixes
|
|
||||||
|
|
||||||
nameClassData = {field: true}
|
|
||||||
if @state.createMixdownErrors?
|
|
||||||
|
|
||||||
errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
|
|
||||||
|
|
||||||
createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown})
|
|
||||||
mixControls = `
|
|
||||||
<div key="create-mix" className="create-mix">
|
|
||||||
<p>Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.</p>
|
|
||||||
<div className="field">
|
|
||||||
<label>Change Tempo:</label>
|
|
||||||
<select name="mix-speed">
|
|
||||||
<option value="">No change</option>
|
|
||||||
<option value="separator-1">------------</option>
|
|
||||||
<option value="-5">Slower by 5%</option>
|
|
||||||
<option value="-10">Slower by 10%</option>
|
|
||||||
<option value="-15">Slower by 15%</option>
|
|
||||||
<option value="-20">Slower by 20%</option>
|
|
||||||
<option value="-25">Slower by 25%</option>
|
|
||||||
<option value="-30">Slower by 30%</option>
|
|
||||||
<option value="-35">Slower by 35%</option>
|
|
||||||
<option value="-40">Slower by 40%</option>
|
|
||||||
<option value="-45">Slower by 45%</option>
|
|
||||||
<option value="-50">Slower by 50%</option>
|
|
||||||
<option value="-60">Slower by 60%</option>
|
|
||||||
<option value="-70">Slower by 70%</option>
|
|
||||||
<option value="-80">Slower by 80%</option>
|
|
||||||
<option value="separator-2">------------</option>
|
|
||||||
<option value="5">Faster by 5%</option>
|
|
||||||
<option value="10">Faster by 10%</option>
|
|
||||||
<option value="15">Faster by 15%</option>
|
|
||||||
<option value="20">Faster by 20%</option>
|
|
||||||
<option value="30">Faster by 30%</option>
|
|
||||||
<option value="40">Faster by 40%</option>
|
|
||||||
<option value="50">Faster by 50%</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="field">
|
|
||||||
<label>Change Pitch:</label>
|
|
||||||
<select name="mix-pitch">
|
|
||||||
<option value="">No change</option>
|
|
||||||
<option value="separator-1">---------------</option>
|
|
||||||
<option value="-1">Down 1 Semitone</option>
|
|
||||||
<option value="-2">Down 2 Semitones</option>
|
|
||||||
<option value="-3">Down 3 Semitones</option>
|
|
||||||
<option value="-4">Down 4 Semitones</option>
|
|
||||||
<option value="-5">Down 5 Semitones</option>
|
|
||||||
<option value="-6">Down 6 Semitones</option>
|
|
||||||
<option value="-7">Down 7 Semitones</option>
|
|
||||||
<option value="-8">Down 8 Semitones</option>
|
|
||||||
<option value="-9">Down 9 Semitones</option>
|
|
||||||
<option value="-10">Down 10 Semitones</option>
|
|
||||||
<option value="-11">Down 11 Semitones</option>
|
|
||||||
<option value="-12">Down 12 Semitones</option>
|
|
||||||
<option value="separator-2">---------------</option>
|
|
||||||
<option value="1">Up 1 Semitone</option>
|
|
||||||
<option value="2">Up 2 Semitones</option>
|
|
||||||
<option value="3">Up 3 Semitones</option>
|
|
||||||
<option value="4">Up 4 Semitones</option>
|
|
||||||
<option value="5">Up 5 Semitones</option>
|
|
||||||
<option value="6">Up 6 Semitones</option>
|
|
||||||
<option value="7">Up 7 Semitones</option>
|
|
||||||
<option value="8">Up 8 Semitones</option>
|
|
||||||
<option value="9">Up 9 Semitones</option>
|
|
||||||
<option value="10">Up 10 Semitones</option>
|
|
||||||
<option value="11">Up 11 Semitones</option>
|
|
||||||
<option value="12">Up 12 Semitones</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className={classNames(nameClassData)}>
|
|
||||||
<label>Mix Name:</label>
|
|
||||||
<input type="text" name="mix-name"/>
|
|
||||||
</div>
|
|
||||||
<div className="field">
|
|
||||||
<a className={createMixClasses} onClick={this.createMix}>CREATE MIX</a>
|
|
||||||
{errorHtml}
|
|
||||||
</div>
|
|
||||||
<div className="clearall"/>
|
|
||||||
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
if @state.showMyMixes
|
|
||||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>hide my mixes <div className="details-arrow arrow-up" /></a>`
|
|
||||||
else
|
|
||||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>show my mixes <div className="details-arrow arrow-down" /></a>`
|
|
||||||
|
|
||||||
if @state.showCustomMixes
|
|
||||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>hide mix controls <div className="details-arrow arrow-up" /></a>`
|
|
||||||
else
|
|
||||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>show mix controls <div className="details-arrow arrow-down" /></a>`
|
|
||||||
|
|
||||||
|
|
||||||
extraControls = `
|
|
||||||
<div className="extra-controls">
|
|
||||||
<h4>My Mixes {showMyMixesText}</h4>
|
|
||||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
|
||||||
{myMixes}
|
|
||||||
</ReactCSSTransitionGroup>
|
|
||||||
<h4 className="custom-mix-header">Create Custom Mix {showMixControlsText}</h4>
|
|
||||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
|
||||||
{mixControls}
|
|
||||||
</ReactCSSTransitionGroup>
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
else if @state.media.mediaSummary.backingTrackOpen
|
else if @state.media.mediaSummary.backingTrackOpen
|
||||||
mediaType = "Audio File"
|
mediaType = "Audio File"
|
||||||
|
|
@ -444,14 +467,14 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
||||||
time = enqueued.queue_time
|
time = enqueued.queue_time
|
||||||
|
|
||||||
if time == 0
|
if time == 0
|
||||||
alert("It will take approximately 1 minute to create your custom mix.")
|
alert("Your custom mix will take about 1 minute to be created.")
|
||||||
else
|
else
|
||||||
guess = Math.ceil(time / 60.0)
|
guess = Math.ceil(time / 60.0)
|
||||||
if guess == 1
|
if guess == 1
|
||||||
msg = '1 minute'
|
msg = '1 minute'
|
||||||
else
|
else
|
||||||
msg = "#{guess} minutes"
|
msg = "#{guess} minutes"
|
||||||
alert("Your custom mix will take approximately #{msg} to be created.")
|
alert("Your custom mix will take about #{msg} to be created.")
|
||||||
|
|
||||||
|
|
||||||
createMix: (e) ->
|
createMix: (e) ->
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ MIX_MODES = context.JK.MIX_MODES;
|
||||||
backingTracks = @session.backingTracks()
|
backingTracks = @session.backingTracks()
|
||||||
recordedJamTracks = @session.recordedJamTracks()
|
recordedJamTracks = @session.recordedJamTracks()
|
||||||
jamTracks = @session.jamTracks()
|
jamTracks = @session.jamTracks()
|
||||||
|
jamTrackMixdown = @session.jamTrackMixdown()
|
||||||
|
|
||||||
###
|
###
|
||||||
with mixer info, we use these to decide what kind of tracks are open in the backend
|
with mixer info, we use these to decide what kind of tracks are open in the backend
|
||||||
|
|
@ -92,6 +93,7 @@ MIX_MODES = context.JK.MIX_MODES;
|
||||||
@metronomeTrackMixers = []
|
@metronomeTrackMixers = []
|
||||||
@adhocTrackMixers = []
|
@adhocTrackMixers = []
|
||||||
|
|
||||||
|
|
||||||
groupByType = (mixers, isLocalMixer) =>
|
groupByType = (mixers, isLocalMixer) =>
|
||||||
for mixer in mixers
|
for mixer in mixers
|
||||||
mediaType = mixer.media_type
|
mediaType = mixer.media_type
|
||||||
|
|
@ -106,7 +108,10 @@ MIX_MODES = context.JK.MIX_MODES;
|
||||||
|
|
||||||
isJamTrack = false;
|
isJamTrack = false;
|
||||||
|
|
||||||
if jamTracks
|
if mixer.id == jamTrackMixdown.id
|
||||||
|
isJamTrack = true;
|
||||||
|
|
||||||
|
if !isJamTrack && jamTracks
|
||||||
# check if the ID matches that of an open jam track
|
# check if the ID matches that of an open jam track
|
||||||
for jamTrack in jamTracks
|
for jamTrack in jamTracks
|
||||||
if mixer.id == jamTrack.id
|
if mixer.id == jamTrack.id
|
||||||
|
|
@ -186,6 +191,8 @@ MIX_MODES = context.JK.MIX_MODES;
|
||||||
backingTrackOpen: @backingTracks.length > 0
|
backingTrackOpen: @backingTracks.length > 0
|
||||||
metronomeOpen: @session.isMetronomeOpen()
|
metronomeOpen: @session.isMetronomeOpen()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# figure out if any media is open
|
# figure out if any media is open
|
||||||
mediaOpenSummary = false
|
mediaOpenSummary = false
|
||||||
for mediaType, mediaOpen of @mediaSummary
|
for mediaType, mediaOpen of @mediaSummary
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ if jam_instance == 0
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#Resque.redis = "localhost:6380"
|
||||||
|
|
||||||
# now bring in the Jam code
|
# now bring in the Jam code
|
||||||
require 'jam_websockets'
|
require 'jam_websockets'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ module JamWebsockets
|
||||||
|
|
||||||
# subscribe for any messages to users
|
# subscribe for any messages to users
|
||||||
@user_topic.subscribe(:ack => false) do |headers, msg|
|
@user_topic.subscribe(:ack => false) do |headers, msg|
|
||||||
|
time_it('user_topic') {
|
||||||
begin
|
begin
|
||||||
routing_key = headers.routing_key
|
routing_key = headers.routing_key
|
||||||
user_id = routing_key["user.".length..-1]
|
user_id = routing_key["user.".length..-1]
|
||||||
|
|
@ -175,6 +176,7 @@ module JamWebsockets
|
||||||
@log.error "unhandled error in messaging to client"
|
@log.error "unhandled error in messaging to client"
|
||||||
@log.error e
|
@log.error e
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
MQRouter.user_exchange = @users_exchange
|
MQRouter.user_exchange = @users_exchange
|
||||||
|
|
@ -189,6 +191,7 @@ module JamWebsockets
|
||||||
|
|
||||||
# subscribe for any p2p messages to a client
|
# subscribe for any p2p messages to a client
|
||||||
@client_topic.subscribe(:ack => false) do |headers, msg|
|
@client_topic.subscribe(:ack => false) do |headers, msg|
|
||||||
|
time_it('p2p_topic') {
|
||||||
begin
|
begin
|
||||||
routing_key = headers.routing_key
|
routing_key = headers.routing_key
|
||||||
client_id = routing_key["client.".length..-1]
|
client_id = routing_key["client.".length..-1]
|
||||||
|
|
@ -240,6 +243,7 @@ module JamWebsockets
|
||||||
@log.error "unhandled error in messaging to client"
|
@log.error "unhandled error in messaging to client"
|
||||||
@log.error e
|
@log.error e
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
MQRouter.client_exchange = @clients_exchange
|
MQRouter.client_exchange = @clients_exchange
|
||||||
|
|
@ -252,6 +256,7 @@ module JamWebsockets
|
||||||
|
|
||||||
# subscribe for any p2p messages to a client
|
# subscribe for any p2p messages to a client
|
||||||
@subscription_topic.subscribe(:ack => false) do |headers, msg|
|
@subscription_topic.subscribe(:ack => false) do |headers, msg|
|
||||||
|
time_it('subscribe_topic') {
|
||||||
begin
|
begin
|
||||||
routing_key = headers.routing_key
|
routing_key = headers.routing_key
|
||||||
type_and_id = routing_key["subscription.".length..-1]
|
type_and_id = routing_key["subscription.".length..-1]
|
||||||
|
|
@ -276,6 +281,7 @@ module JamWebsockets
|
||||||
@log.error "unhandled error in messaging to client for mount"
|
@log.error "unhandled error in messaging to client for mount"
|
||||||
@log.error e
|
@log.error e
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
MQRouter.subscription_exchange = @subscriptions_exchange
|
MQRouter.subscription_exchange = @subscriptions_exchange
|
||||||
|
|
@ -397,8 +403,10 @@ module JamWebsockets
|
||||||
}
|
}
|
||||||
|
|
||||||
client.onclose {
|
client.onclose {
|
||||||
@log.debug "connection closed. marking stale: #{client.context}"
|
time_it('ws_close') {
|
||||||
cleanup_client(client)
|
@log.debug "connection closed. marking stale: #{client.context}"
|
||||||
|
cleanup_client(client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.onerror { |error|
|
client.onerror { |error|
|
||||||
|
|
@ -478,6 +486,7 @@ module JamWebsockets
|
||||||
end
|
end
|
||||||
|
|
||||||
def route(client_msg, client)
|
def route(client_msg, client)
|
||||||
|
time_it('route') {
|
||||||
message_type = @message_factory.get_message_type(client_msg)
|
message_type = @message_factory.get_message_type(client_msg)
|
||||||
if message_type.nil?
|
if message_type.nil?
|
||||||
Diagnostic.unknown_message_type(client.user_id, client_msg)
|
Diagnostic.unknown_message_type(client.user_id, client_msg)
|
||||||
|
|
@ -516,7 +525,7 @@ module JamWebsockets
|
||||||
else
|
else
|
||||||
raise SessionError, "client_msg.route_to is unknown type: #{client_msg.route_to}"
|
raise SessionError, "client_msg.route_to is unknown type: #{client_msg.route_to}"
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_server_directed(client_msg, client)
|
def handle_server_directed(client_msg, client)
|
||||||
|
|
@ -529,11 +538,11 @@ module JamWebsockets
|
||||||
elsif client_msg.type == ClientMessage::Type::HEARTBEAT
|
elsif client_msg.type == ClientMessage::Type::HEARTBEAT
|
||||||
sane_logging { handle_heartbeat(client_msg.heartbeat, client_msg.message_id, client) }
|
sane_logging { handle_heartbeat(client_msg.heartbeat, client_msg.message_id, client) }
|
||||||
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE_BULK
|
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE_BULK
|
||||||
sane_logging { handle_bulk_subscribe(client_msg.subscribe_bulk, client) }
|
time_it('subscribe_bulk') { sane_logging { handle_bulk_subscribe(client_msg.subscribe_bulk, client) } }
|
||||||
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE
|
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE
|
||||||
sane_logging { handle_subscribe(client_msg.subscribe, client) }
|
time_it('subscribe') { sane_logging { handle_subscribe(client_msg.subscribe, client) } }
|
||||||
elsif client_msg.type == ClientMessage::Type::UNSUBSCRIBE
|
elsif client_msg.type == ClientMessage::Type::UNSUBSCRIBE
|
||||||
sane_logging { handle_unsubscribe(client_msg.unsubscribe, client) }
|
time_it('unsubscribe') { sane_logging { handle_unsubscribe(client_msg.unsubscribe, client) } }
|
||||||
else
|
else
|
||||||
raise SessionError, "unknown message type '#{client_msg.type}' for #{client_msg.route_to}-directed message"
|
raise SessionError, "unknown message type '#{client_msg.type}' for #{client_msg.route_to}-directed message"
|
||||||
end
|
end
|
||||||
|
|
@ -1198,6 +1207,7 @@ module JamWebsockets
|
||||||
|
|
||||||
# removes all resources associated with a client
|
# removes all resources associated with a client
|
||||||
def cleanup_client(client)
|
def cleanup_client(client)
|
||||||
|
time_it('cleanup_client') {
|
||||||
client.close
|
client.close
|
||||||
|
|
||||||
# unregister any subscriptions
|
# unregister any subscriptions
|
||||||
|
|
@ -1225,6 +1235,7 @@ module JamWebsockets
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def stats_logged_in
|
def stats_logged_in
|
||||||
|
|
@ -1245,6 +1256,16 @@ module JamWebsockets
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def time_it(cat, &blk)
|
||||||
|
start = Time.now
|
||||||
|
|
||||||
|
blk.call
|
||||||
|
|
||||||
|
time = Time.now - start
|
||||||
|
|
||||||
|
@log.warn("LONG TIME: #{cat}: #{time}") if time > 1
|
||||||
|
end
|
||||||
|
|
||||||
def sane_logging(&blk)
|
def sane_logging(&blk)
|
||||||
# used around repeated transactions that cause too much ActiveRecord::Base logging
|
# used around repeated transactions that cause too much ActiveRecord::Base logging
|
||||||
begin
|
begin
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ module JamWebsockets
|
||||||
@count=0
|
@count=0
|
||||||
@router = Router.new
|
@router = Router.new
|
||||||
@ar_base_logger = ::Logging::Repository.instance[ActiveRecord::Base]
|
@ar_base_logger = ::Logging::Repository.instance[ActiveRecord::Base]
|
||||||
|
|
||||||
|
@last_conn_check = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(options={})
|
def run(options={})
|
||||||
|
|
@ -26,6 +28,7 @@ module JamWebsockets
|
||||||
rabbitmq_port = options[:rabbitmq_port].to_i
|
rabbitmq_port = options[:rabbitmq_port].to_i
|
||||||
allow_dynamic_registration = options[:allow_dynamic_registration].nil? ? true : options[:allow_dynamic_registration]
|
allow_dynamic_registration = options[:allow_dynamic_registration].nil? ? true : options[:allow_dynamic_registration]
|
||||||
|
|
||||||
|
|
||||||
Stats::init(options)
|
Stats::init(options)
|
||||||
|
|
||||||
calling_thread = options[:calling_thread]
|
calling_thread = options[:calling_thread]
|
||||||
|
|
@ -61,6 +64,18 @@ module JamWebsockets
|
||||||
EventMachine::stop_event_loop
|
EventMachine::stop_event_loop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_em_drift(timer)
|
||||||
|
# if our timer check is a full second off, say what's up
|
||||||
|
if Time.now - @last_conn_check > timer + 1
|
||||||
|
@log.error("significant drift! Should be 2 seconds. Instead was: #{Time.now - @last_conn_check}")
|
||||||
|
end
|
||||||
|
|
||||||
|
@last_conn_check = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def start_websocket_listener(listen_ip, port, trust_port, trust_check, emwebsocket_debug)
|
def start_websocket_listener(listen_ip, port, trust_port, trust_check, emwebsocket_debug)
|
||||||
EventMachine::WebSocket.run(:host => listen_ip, :port => port, :debug => emwebsocket_debug) do |ws|
|
EventMachine::WebSocket.run(:host => listen_ip, :port => port, :debug => emwebsocket_debug) do |ws|
|
||||||
#@log.info "new client #{ws}"
|
#@log.info "new client #{ws}"
|
||||||
|
|
@ -84,8 +99,11 @@ module JamWebsockets
|
||||||
# one cleanup on startup
|
# one cleanup on startup
|
||||||
@router.periodical_check_connections
|
@router.periodical_check_connections
|
||||||
|
|
||||||
EventMachine::PeriodicTimer.new(2) do
|
@last_conn_check = Time.now
|
||||||
safety_net { sane_logging { @router.periodical_check_connections } }
|
timer = 2
|
||||||
|
EventMachine::PeriodicTimer.new(timer) do
|
||||||
|
check_for_em_drift(timer)
|
||||||
|
time_it('conn_expire') { safety_net { sane_logging { @router.periodical_check_connections } } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -94,7 +112,7 @@ module JamWebsockets
|
||||||
@router.periodical_check_clients
|
@router.periodical_check_clients
|
||||||
|
|
||||||
EventMachine::PeriodicTimer.new(30) do
|
EventMachine::PeriodicTimer.new(30) do
|
||||||
safety_net { sane_logging { @router.periodical_check_clients } }
|
time_it('client_expire') { safety_net { sane_logging { @router.periodical_check_clients } } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -103,13 +121,13 @@ module JamWebsockets
|
||||||
@router.periodical_flag_connections
|
@router.periodical_flag_connections
|
||||||
|
|
||||||
EventMachine::PeriodicTimer.new(2) do
|
EventMachine::PeriodicTimer.new(2) do
|
||||||
safety_net { sane_logging { @router.periodical_flag_connections } }
|
time_it('conn_flagger') { safety_net { sane_logging { @router.periodical_flag_connections } } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_stats_dump
|
def start_stats_dump
|
||||||
EventMachine::PeriodicTimer.new(60) do
|
EventMachine::PeriodicTimer.new(60) do
|
||||||
safety_net { @router.periodical_stats_dump }
|
time_it('stats_dump') { safety_net { @router.periodical_stats_dump } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -126,6 +144,18 @@ module JamWebsockets
|
||||||
@log.error("unhandled exception in EM Timer #{e}")
|
@log.error("unhandled exception in EM Timer #{e}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_it(cat, &blk)
|
||||||
|
start = Time.now
|
||||||
|
|
||||||
|
blk.call
|
||||||
|
|
||||||
|
time = Time.now - start
|
||||||
|
|
||||||
|
@log.warn("LONG TIME #{cat}: #{time}") if time > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def sane_logging(&blk)
|
def sane_logging(&blk)
|
||||||
# used around repeated transactions that cause too much ActiveRecord::Base logging
|
# used around repeated transactions that cause too much ActiveRecord::Base logging
|
||||||
# example is handling heartbeats
|
# example is handling heartbeats
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue