jam-cloud/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx....

960 lines
35 KiB
CoffeeScript

context = window
logger = context.JK.logger
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
rest = context.JK.Rest()
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
# this check ensures we attempt to listen if this component is created in a popup
reactContext = if window.opener? then window.opener else window
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
if window.opener?
try
m = window.opener.MixerStore
catch e
reactContext = window
if true # /iPhone|iPad|iPod|android/i.test(navigator.userAgent)
# iPad or iPhone
reactContext = window
AppActions = reactContext.AppActions
JamTrackPlayerActions = reactContext.JamTrackPlayerActions
JamTrackPlayerStore = reactContext.JamTrackPlayerStore
UserStore = reactContext.UserStore
mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged'))
mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
@PopupJamTrackPlayer = React.createClass({
mixins: mixins
computeWeight: (jam_track_track) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.track_type == 'Click' then 10000
else
jam_track_track.position
onJamTrackPlayerStoreChanged: (changes) ->
#logger.debug("PopupMediaControls: jamtrack changed", changes)
@setState({jamTrackState: changes})
onUserChanged: (changes) ->
@setState({user: changes.user})
showMetronome: (e) ->
e.preventDefault()
SessionActions.showNativeMetronomeGui()
getInitialState: () ->
state = {}
state.jamTrackState = JamTrackPlayerStore.getState()
state.showCustomMixes = true
state.showMyMixes = true
state.user = UserStore.getState().user
return state
close: () ->
window.close()
help: (e) ->
e.preventDefault()
AppActions.openExternalUrl($(e.target).attr('href'))
render: () ->
closeLinkText = null
header = null
extraControls = null
jamTrack = @state.jamTrackState?.jamTrack
mediaType = "JamTrack"
mediaName = jamTrack?.name
closeLinkText = 'CLOSE JAMTRACK'
helpLink = 'https://jamkazam.desk.com/customer/portal/articles/2138903-using-custom-mixes-to-slow-tempo-change-pitch'
selectedMixdown = jamTrack?.activeMixdown
selectedStem = jamTrack?.activeStem
if selectedMixdown?
jamTrackTypeHeader = 'Custom Mix'
disabled = true
if selectedMixdown.client_state?
switch selectedMixdown.client_state
when 'download_fail'
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png"/></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
if selectedMixdown.myPackage
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif"/></h5>`
else
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
else if selectedStem?
if selectedStem.instrument
instrumentId = selectedStem.instrument.id
instrumentDescription = selectedStem.instrument.description
part = "(#{selectedStem.part})" if selectedStem.part? && selectedStem.part != instrumentDescription
part = '' unless part
jamTrackTypeHeader = 'Track'
trackName = "#{instrumentDescription} #{part}"
disabled = true
if selectedStem.client_state?
switch selectedStem.client_state
when 'downloading'
customMixName = `<h5>Loading {trackName}... <img src="/assets/shared/spinner.gif"/></h5>`
when 'download_fail'
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
when 'ready'
customMixName = `<h5>{trackName}</h5>`
disabled = false
else
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
else
if jamTrack?.client_state == 'downloading'
downloader = `<img src="/assets/shared/spinner.gif"/>`
else if jamTrack?.client_state == 'download_fail'
downloader = `<img src="/assets/content/icon-mix-fail@2X.png"/>`
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
header = `
<div className="header">
<h3>{mediaType}: {mediaName}</h3>
<h4>{jamTrackTypeHeader}</h4>
{customMixName}
</div>`
myMixes = null
if @state.showMyMixes
if jamTrack?
myMixdowns = []
boundPlayClick = this.jamTrackPlay.bind(this, jamTrack);
boundDownloadClick = this.jamTrackDownload.bind(this, jamTrack);
active = jamTrack.last_mixdown_id == null && jamTrack.last_stem_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}/>
<a href={this.createJamTrackUrl(jamTrack)} target="_blank" onClick={boundDownloadClick}><img
src="/assets/content/icon_download@2X.png" className="mixdown-download"/></a>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit"
onClick={false}/>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete"
onClick={false}/>
</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);
boundDownloadNotReadyClick = this.downloadNotReady.bind(this, mixdown)
boundDownloadReadyClick = this.downloadMixdownReady.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}/>`
# create hidden objects to deal with alginment issues using a table
if !editIcon
editIcon = `<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}}
className="mixdown-edit" onClick={false}/>`
download = `<a onClick={boundDownloadReadyClick} href={this.downloadMixdownUrl(mixdown)} target="_blank"><img
src="/assets/content/icon_download@2X.png" className="mixdown-download"/></a>`
myMixdowns.push `
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
<div className="mixdown-name">
{mixdownName}
</div>
<div className="mixdown-actions">
{action}
{download}
{editIcon}
<img src="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick}/>
</div>
</div>`
active = jamTrack.last_stem_id?
trackOptions = []
jamTrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a)
bWeight = @computeWeight(b)
return aWeight - bWeight
)
for track in jamTrack.tracks
if track.track_type == 'Track' || track.track_type == 'Click'
if track.instrument
instrumentId = track.instrument.id
instrumentDescription = track.instrument.description
if track.part? && track.part != instrumentDescription
part = "(#{track.part})"
else
part = ''
trackOptions.push(`<option key={track.id} value={track.id}>{instrumentDescription} {part}</option>`)
boundStemActivateClick = this.activateStem.bind(this)
boundStemPlayClick = this.downloadStem.bind(this)
boundStemChange = this.stemChanged.bind(this)
myMixdowns.push `
<div key={track.id} className={classNames({'stem-track' : true, 'mixdown-display': true, 'active' : active})}>
<div className="mixdown-name">
<select className="active-stem-select" name="active-stem-select" onChange={boundStemChange}
defaultValue={jamTrack.last_stem_id}>
<option key="null" value="">or select a track</option>
{trackOptions}
</select>
</div>
<div className="mixdown-actions">
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundStemActivateClick}/>
<a href={this.createStemUrl(jamTrack.id, jamTrack.last_stem_id)} target="_blank"
onClick={boundStemPlayClick}><img src="/assets/content/icon_download@2X.png"
className="mixdown-download"/></a>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit"
onClick={false}/>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete"
onClick={false}/>
</div>
</div>`
myMixes = `<div key="my-mixes" className="my-mixes">{myMixdowns}</div>`
else
# nothing
mixControls = null
if @state.showCustomMixes
tracks = []
if jamTrack?
for track in jamTrack.tracks
if track.track_type == 'Track' || track.track_type == 'Click'
if track.track_type == 'Click'
instrumentId = track.instrument.id
instrumentDescription = 'Clicktrack'
part = ''
else if track.instrument
instrumentId = track.instrument.id
instrumentDescription = track.instrument.description
if track.part? && track.part != instrumentDescription
part = "(#{track.part})"
else
part = ''
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24(instrumentId)}
className="instrument-icon"/> {instrumentDescription} {part}</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id}
defaultChecked={track.track_type == 'Click'}/></td>
</tr>`)
if jamTrack?.jmep?.Events? && jamTrack.jmep.Events[0].metronome?
# tap-in detected; show user tap-in option
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24('computer')} className="instrument-icon"/> Count-in</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id="count-in"/></td>
</tr>`)
stems = `<div key="stems" className="stems">
<table>
<thead>
<col align="left"/>
<col align="right"/>
<tr>
<th>TRACKS</th>
<th className="mute">mute</th>
</tr>
</thead>
<tbody>
{tracks}
</tbody>
</table>
</div>`
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>Mute or unmute any tracks 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.</p>
{stems}
<div className="field">
<label>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>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 className="show-hide-my-mixes" onClick={this.toggleMyMixes}>hide my mixes
<div className="details-arrow arrow-up"/>
</a>`
else
showMyMixesText = `<a className="show-hide-my-mixes" onClick={this.toggleMyMixes}>show my mixes
<div className="details-arrow arrow-down"/>
</a>`
if @state.showCustomMixes
showMixControlsText = `<a className="show-hide-mix-controls" onClick={this.toggleCustomMixes}>hide mix controls
<div className="details-arrow arrow-up"/>
</a>`
else
showMixControlsText = `<a className="show-hide-mix-controls" 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={false}>
{mixControls}
</ReactCSSTransitionGroup>
</div>`
if helpLink?
helpButton = `<a className="help-link button-grey" href={helpLink} target="_blank">HELP</a>`
`<div className="jamtrack-player-main">
<div className="jamtrack-player-controls">
<div className="media-header">
jamtracks web player
</div>
<div className="media-controls-popup">
{header}
<BrowserMediaControls disabled={this.disableLoading}/>
{extraControls}
<div className="actions">
{helpButton}
</div>
</div>
</div>
<div className="helpful-resources">
<div className="helpful-header">
helpful resources
</div>
<div className="helpful-section">
<a href="https://jamkazam.desk.com/customer/portal/articles/2166273-playing-your-jamtracks-in-a-browser" target="_blank">read a web player help article</a><br/>
that explains how to use all the features of this web player
</div>
<div className="helpful-section">
<a href="/client#/jamtrack" target="_blank">go to JamTracks homepage</a><br/>
where you can access all of your JamTracks, search for more JamTracks, and more
</div>
<div className="helpful-section">
<a href="https://itunes.apple.com/us/app/jamtracks/id1060927816?mt=8" target="_blank">download our free iOS app</a><br/>
that you can use to play with your JamTracks on your iPhone, iPad, or iPod Touch
</div>
<div className="helpful-section">
<a href="/downloads" target="_blank">download our free Mac or Windows app</a><br/>
that gives you more powerful features to do more amazing things with your JamTracks
</div>
<div className="helpful-section">
<a href="https://jamkazam.desk.com/customer/en/portal/articles/2126113-playing-your-jamtracks-in-our-free-windows-or-mac-app" target="_blank">review a list of help articles on the Mac/Win app</a><br/>
to understand all the things you can do with our free desktop app
</div>
<div className="helpful-section">
<a onClick={this.seeAllByArtist} target="_blank">see more JamTracks by this artist</a><br/>
to check out other songs you might like
</div>
<div className="helpful-section">
<a href="https://s3.amazonaws.com/jamkazam-public/public/lists/all-jamkazam-jamtracks.pdf" target="_blank">download our complete JamTracks catalog</a><br/>
to browse or search through all the music we have in a convenient PDF file
</div>
</div>
</div>`
seeAllByArtist: (e) ->
e.preventDefault()
jamTrack = @state.jamTrackState?.jamTrack
console.log("seeAllByArtist context", jamTrack)
window.open('/client?artist=' + encodeURIComponent(jamTrack.original_artist) + '#/jamtrack/search')
windowUnloaded: () ->
JamTrackPlayerActions.windowUnloaded() unless window.DontAutoCloseMedia
toggleMyMixes: (e) ->
e.preventDefault()
@setState({showMyMixes: !@state.showMyMixes})
toggleCustomMixes: (e) ->
e.preventDefault()
@setState({showCustomMixes: !@state.showCustomMixes})
downloadNotReady: (mixdown, e) ->
alert("This mix is not yet ready to download.")
e.preventDefault()
verificationCheck: () ->
if @state.user?.email_needs_verification
alert("Check your email and click the verification link. Refresh this page when done, and try again.")
return @state.user?.email_needs_verification
downloadMixdownReady: (mixdown, e) ->
if @verificationCheck()
e.preventDefault()
return
if mixdown.myPackage?.signing_state == 'SIGNED'
if /iPhone|iPad|iPod/i.test(navigator.userAgent)
# fall through
else
e.preventDefault()
new window.Fingerprint2().get((result, components) => (
iframe = document.createElement("iframe")
iframe.src = @downloadMixdownUrl(mixdown, result)
iframe.style.display = "none"
document.body.appendChild(iframe);
))
else
alert("The mix is not yet ready to download")
downloadMixdownUrl: (mixdown, result) ->
window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{mixdown.id}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=#{result}"
activateStem: (e) ->
e.preventDefault()
return if @verificationCheck()
$select = $(this.getDOMNode()).find('.active-stem-select')
selectedTrackId = $select.val()
if !selectedTrackId? || selectedTrackId == ''
alert("You must pick a track from the dropdown in order to play it.")
else
@setState({editingMixdownId: null})
e.preventDefault()
if @disableLoading
alert('Certain actions are disabled while a track is being loaded.')
return
# make this package the active one
JamTrackPlayerActions.openStem(selectedTrackId)
addUpgradeToCart: (jamtrack) ->
console.log("adding upgrade to cart")
params = {id: jamtrack.id, variant: 'download'}
rest.addJamtrackToShoppingCart(params).done((response) =>
console.log("added item to shopping cart. fast_redeem? " + response.fast_redeem)
if response.fast_reedem
if context.JK.currentUserId?
window.open('/client#/redeemComplete')
else
window.open('/client#/redeemSignup')
else
window.open('/client#/shoppingCart')
).fail(((jqxhr) =>
handled = false
if jqxhr.status == 422
body = JSON.parse(jqxhr.responseText)
if body.errors?.cart_id?[0] == 'has already been taken'
console.log("already taken, just show shopping cart")
window.open('/client#/shoppingCart')
return
else if body.errors && body.errors.base
handled = true
alert("You can not have a mix of free and non-free items in your shopping cart.\n\nIf you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.")
if !handled
alert("Error adding to shoppig cart. " + jqxhr.responseText)
))
downloadStem: (e) ->
if @verificationCheck()
e.preventDefault()
return
$select = $(this.getDOMNode()).find('.active-stem-select')
selectedTrackId = $select.val()
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
if !selectedTrackId? || selectedTrackId == ''
e.preventDefault()
alert("You must select a track in order to download and also click the folder icon.")
else
if /iPhone|iPad|iPod/i.test(navigator.userAgent)
if @state.jamTrackState.jamTrack?.last_stem_id
# fall through
else
e.preventDefault()
alert("You must select a track in order to download and also click the folder icon.")
else
e.preventDefault()
try
new window.Fingerprint2().get((result, components) => (
iframe = document.createElement("iframe")
iframe.src = @createStemUrl(@state.jamTrackState.jamTrack.id, selectedTrackId, result)
iframe.style.display = "none"
document.body.appendChild(iframe);
))
catch error
logger.error("not working: ", error)
alert("Unable to download. Please try a different browser.")
createStemUrl: (jamTrackId, stemId, result) ->
window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{jamTrackId}/stems/#{stemId}/download.mp3?file_type=mp3&download=1&mark=#{result}"
stemChanged: () ->
mixdownPlay: (mixdown, e) ->
@setState({editingMixdownId: null})
e.preventDefault()
if @disableLoading
alert('Certain actions are disabled while a track is being loaded.')
return
# make this package the active one
JamTrackPlayerActions.openMixdown(mixdown)
jamTrackPlay: (jamtrack, e) ->
e.preventDefault()
# user wants to select the full track
if @disableLoading
alert('Certain actions are disabled while a track is being loaded.')
return
JamTrackPlayerActions.activateNoMixdown(jamtrack)
jamTrackDownload: (jamTrack, e) ->
if @verificationCheck()
e.preventDefault()
return
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
if /iPhone|iPad|iPod/i.test(navigator.userAgent)
# fall through
else
e.preventDefault()
new window.Fingerprint2().get((result, components) => (
iframe = document.createElement("iframe")
iframe.src = @createJamTrackUrl(jamTrack, result)
iframe.style.display = "none"
document.body.appendChild(iframe);
))
createJamTrackUrl: (jamTrack, result) ->
window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{jamTrack.id}/stems/master/download.mp3?file_type=mp3&download=1&mark=#{result}"
onEditKeydown: (mixdown, e) ->
logger.debug("on edit keydown", e)
if e.keyCode == 13 # enter
@mixdownSave(mixdown, e)
else if e.keyCode == 27 # esc
@setState({editingMixdownId: null})
mixdownEdit: (mixdown) ->
@setState({editingMixdownId: mixdown.id})
mixdownSave: (mixdown, e) ->
e.preventDefault()
$input = $(this.getDOMNode()).find('input.edit-name')
newValue = $input.val()
logger.debug("editing mixdown name to be: " + newValue)
JamTrackPlayerActions.editMixdown({id: mixdown.id, name: newValue})
@setState({editingMixdownId: null})
mixdownDelete: (mixdown) ->
if @state.editingMixdownId?
@setState({editingMixdownId: null})
return
if confirm("Delete this custom mix?")
JamTrackPlayerActions.deleteMixdown(mixdown)
mixdownError: (mixdown) ->
myPackage = mixdown.myPackage
if myPackage?
switch myPackage.signing_state
when 'QUIET_TIMEOUT'
action = 'Custom mix never got created. Retry?'
when 'QUEUED_TIMEOUT'
action = 'Custom mix was never built. Retry?'
when 'SIGNING_TIMEOUT'
action = 'Custom mix took took long to build. Retry?'
when 'ERROR'
action = 'Custom mix failed to build. Retry?'
else
action = 'Custom mix never got created. Retry?'
return unless action?
if confirm(action)
JamTrackPlayerActions.enqueueMixdown(mixdown, @enqueueDone)
enqueueDone: (enqueued) ->
@promptEstimate(enqueued)
promptEstimate: (enqueued) ->
time = enqueued.queue_time
if time == 0
alert("Your custom mix will take about 1 minute to be created.")
else
guess = Math.ceil(time / 60.0)
if guess == 1
msg = '1 minute'
else
msg = "#{guess} minutes"
alert("Your custom mix will take about #{msg} to be created.")
createMix: (e) ->
e.preventDefault()
return if @state.creatingMix
$root = $(@getDOMNode())
name = $root.find('input[name="mix-name"]').val()
speed = $root.find('select[name="mix-speed"]').val()
pitch = $root.find('select[name="mix-pitch"]').val()
if name == null || name == ''
@setState({createMixdownErrors: {errors: {'Mix Name': ["can't be blank"]}}})
return
# sanitize junk out of speed/pitch
if speed == '' || speed.indexOf('separator') > -1
speed = undefined
else
speed = parseInt(speed)
if pitch == '' || pitch.indexOf('separator') > -1
pitch = undefined
else
pitch = parseInt(pitch)
count_in = false
# get mute state of all tracks
$mutes = $(@getDOMNode()).find('.stems .stem .stem-mute')
tracks = []
$mutes.each((i, mute) =>
$mute = $(mute)
stemId = $mute.attr('data-stem-id')
muted = $mute.is(':checked')
if stemId == 'count-in'
count_in = !muted
else
tracks.push({id: stemId, mute: muted})
)
mixdown = {
jamTrackID: @state.jamTrackState.jamTrack.id,
name: name,
settings: {speed: speed, pitch: pitch, "count-in": count_in, tracks: tracks}
}
JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)
@setState({creatingMixdown: true, createMixdownErrors: null})
createMixdownDone: (created) ->
logger.debug("created (within PopupMediaControls)", created)
@setState({creatingMixdown: false, showCustomMixes: true, showMyMixes: true})
@promptEstimate(created)
createMixdownFail: (jqXHR) ->
logger.debug("create mixdown fail (within PopupMediaControls)", jqXHR.status)
@setState({creatingMixdown: false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
logger.warn("failed to create mixdown", response, jqXHR.responseText)
@setState({createMixdownErrors: response})
fetchJamTrackInfo: () ->
rest.getJamTrack({plan_code: gon.jamtrack_id})
.done((response) =>
JamTrackPlayerActions.open(response, false);
@fetchUserInfo()
)
.fail((jqXHR) =>
alert("Unable to fetch JamTrack information. Try logging in.")
)
fetchUserInfo: () ->
rest.getUserDetail()
.done((response) =>
rest.postUserEvent({name: 'jamtrack_web_player_open'})
context.stats.write('web.jamtrack_web_player.open', {
value: 1,
user_id: context.JK.currentUserId,
user_name: context.JK.currentUserName
})
if !response.first_opened_jamtrack_web_player
setTimeout((() =>
logger.debug("first time user")
rest.userOpenedJamTrackWebPlayer()
$root = $(@getDOMNode())
#context.JK.prodBubble($root.find('.create-mix-btn'), 'first-time-jamtrack-web-player', {}, {positions:['left'], offsetParent: $root})
), 1500)
)
componentDidMount: () ->
$(window).unload(@windowUnloaded)
@root = jQuery(this.getDOMNode())
$loop = @root.find('input[name="loop"]')
context.JK.checkbox($loop)
$loop.on('ifChecked', () =>
# it doesn't matter if you do personal or master, because backend just syncs both
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, true)
)
$loop.on('ifUnchecked', () =>
# it doesn't matter if you do personal or master, because backend just syncs both
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, false)
)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
@fetchJamTrackInfo()
componentDidUpdate: () ->
@resizeWindow()
setTimeout(@resizeWindow, 1000)
resizeWindow: () =>
return
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
#mysteryTopMargin = 20
mysteryTopMargin = 0
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(450, height + offset)
computeDisableLoading: (state) ->
@disableLoading = false
return unless nextState?
selectedMixdown = state?.jamTrackState?.jamTrack?.activeMixdown
mixdownDownloading = false
if selectedMixdown?
switch selectedMixdown.client_state
when 'downloading'
mixdownDownloading = true
selectedStem = state?.jamTrackState?.jamTrack?.activeStem
stemDownloading = false
if selectedStem?
switch selectedStem.client_state
when 'downloading'
stemDownloading = true
@disableLoading = state?.jamTrackState?.jamTrack?.client_state == 'downloading' || mixdownDownloading || stemDownloading
componentWillMount: () ->
@computeDisableLoading(@state)
componentWillUpdate: (nextProps, nextState) ->
@computeDisableLoading(nextState)
})