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 = `
{selectedMixdown.name}
` when 'downloading' customMixName = `
Loading selected mix...
` when 'ready' customMixName = `
{selectedMixdown.name}
` disabled = false else if selectedMixdown.myPackage customMixName = `
Creating mixdown...
` else customMixName = `
{selectedMixdown.name}
` 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 = `
Loading {trackName}...
` when 'download_fail' customMixName = `
{trackName}
` when 'ready' customMixName = `
{trackName}
` disabled = false else customMixName = `
{trackName}
` else if jamTrack?.client_state == 'downloading' downloader = `` else if jamTrack?.client_state == 'download_fail' downloader = `` jamTrackTypeHeader = `Full JamTrack {downloader}` header = `

{mediaType}: {mediaName}

{jamTrackTypeHeader}

{customMixName}
` 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 `
Full JamTrack
` 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 = `` when 'QUIET' action = `` when 'QUEUED' action = `` when 'QUEUED_TIMEOUT' action = `` when 'SIGNING' action = `` when 'SIGNING_TIMEOUT' action = `` when 'SIGNED' action = `` when 'ERROR' action = `` else action = `` if editing mixdownName = `` editIcon = `` else mixdownName = mixdown.name editIcon = `` # create hidden objects to deal with alginment issues using a table if !editIcon editIcon = `` download = `` myMixdowns.push `
{mixdownName}
{action} {download} {editIcon}
` 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(``) boundStemActivateClick = this.activateStem.bind(this) boundStemPlayClick = this.downloadStem.bind(this) boundStemChange = this.stemChanged.bind(this) myMixdowns.push `
` myMixes = `
{myMixdowns}
` 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(` {instrumentDescription} {part} `) if jamTrack?.jmep?.Events? && jamTrack.jmep.Events[0].metronome? # tap-in detected; show user tap-in option tracks.push(` Count-in `) stems = `
{tracks}
TRACKS mute
` 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 = `

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.

{stems}
CREATE MIX {errorHtml}
` if @state.showMyMixes showMyMixesText = `hide my mixes
` else showMyMixesText = `show my mixes
` if @state.showCustomMixes showMixControlsText = `hide mix controls
` else showMixControlsText = `show mix controls
` extraControls = `

My Mixes {showMyMixesText}

{myMixes}

Create Custom Mix {showMixControlsText}

{mixControls}
` if helpLink? helpButton = `HELP` `
jamtracks web player
{header} {extraControls}
{helpButton}
helpful resources
read a web player help article
that explains how to use all the features of this web player
go to JamTracks homepage
where you can access all of your JamTracks, search for more JamTracks, and more
download our free iOS app
that you can use to play with your JamTracks on your iPhone, iPad, or iPod Touch
download our free Mac or Windows app
that gives you more powerful features to do more amazing things with your JamTracks
review a list of help articles on the Mac/Win app
to understand all the things you can do with our free desktop app
see more JamTracks by this artist
to check out other songs you might like
download our complete JamTracks catalog
to browse or search through all the music we have in a convenient PDF file
` 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) })