2014-11-28 20:49:41 -05:00
|
|
|
CocoView = require 'views/core/CocoView'
|
2014-01-03 13:32:13 -05:00
|
|
|
template = require 'templates/play/level/playback'
|
2014-11-28 20:49:41 -05:00
|
|
|
{me} = require 'core/auth'
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-07-23 10:02:45 -04:00
|
|
|
module.exports = class LevelPlaybackView extends CocoView
|
2014-06-30 22:16:26 -04:00
|
|
|
id: 'playback-view'
|
2014-01-03 13:32:13 -05:00
|
|
|
template: template
|
|
|
|
|
|
|
|
subscriptions:
|
2014-08-27 15:24:03 -04:00
|
|
|
'level:disable-controls': 'onDisableControls'
|
|
|
|
'level:enable-controls': 'onEnableControls'
|
|
|
|
'level:set-playing': 'onSetPlaying'
|
|
|
|
'level:toggle-playing': 'onTogglePlay'
|
|
|
|
'level:scrub-forward': 'onScrubForward'
|
|
|
|
'level:scrub-back': 'onScrubBack'
|
|
|
|
'level:set-volume': 'onSetVolume'
|
2014-01-03 13:32:13 -05:00
|
|
|
'surface:frame-changed': 'onFrameChanged'
|
|
|
|
'god:new-world-created': 'onNewWorld'
|
2014-08-25 00:39:34 -04:00
|
|
|
'god:streaming-world-updated': 'onNewWorld'
|
2014-08-27 15:24:03 -04:00
|
|
|
'level:set-letterbox': 'onSetLetterbox'
|
2014-08-23 16:54:52 -04:00
|
|
|
'tome:cast-spells': 'onTomeCast'
|
2014-08-24 15:33:46 -04:00
|
|
|
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
2014-08-26 01:05:24 -04:00
|
|
|
'playback:stop-real-time-playback': 'onStopRealTimePlayback'
|
2014-08-29 18:10:04 -04:00
|
|
|
'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
events:
|
2014-02-26 13:42:25 -05:00
|
|
|
'click #music-button': 'onToggleMusic'
|
2014-08-27 15:24:03 -04:00
|
|
|
'click #zoom-in-button': -> Backbone.Mediator.publish 'camera:zoom-in', {} unless @shouldIgnore()
|
|
|
|
'click #zoom-out-button': -> Backbone.Mediator.publish 'camera:zoom-out', {} unless @shouldIgnore()
|
2014-01-03 13:32:13 -05:00
|
|
|
'click #volume-button': 'onToggleVolume'
|
|
|
|
'click #play-button': 'onTogglePlay'
|
2014-08-26 01:05:24 -04:00
|
|
|
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} unless @realTime
|
2014-03-29 19:38:45 -04:00
|
|
|
'mouseenter #timeProgress': 'onProgressEnter'
|
|
|
|
'mouseleave #timeProgress': 'onProgressLeave'
|
|
|
|
'mousemove #timeProgress': 'onProgressHover'
|
2014-10-27 16:32:25 -04:00
|
|
|
'tapstart #timeProgress': 'onProgressTapStart'
|
|
|
|
'tapend #timeProgress': 'onProgressTapEnd'
|
|
|
|
'tapmove #timeProgress': 'onProgressTapMove'
|
2014-01-29 15:18:37 -05:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
shortcuts:
|
|
|
|
'⌘+p, p, ctrl+p': 'onTogglePlay'
|
2014-02-24 12:58:12 -05:00
|
|
|
'⌘+[, ctrl+[': 'onScrubBack'
|
2014-05-24 11:31:59 -04:00
|
|
|
'⌘+⇧+[, ctrl+⇧+[': 'onSingleScrubBack'
|
2014-02-24 12:58:12 -05:00
|
|
|
'⌘+], ctrl+]': 'onScrubForward'
|
2014-05-24 11:31:59 -04:00
|
|
|
'⌘+⇧+], ctrl+⇧+]': 'onSingleScrubForward'
|
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
constructor: ->
|
|
|
|
super(arguments...)
|
2014-03-29 19:38:45 -04:00
|
|
|
me.on('change:music', @updateMusicButton, @)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-11 17:24:06 -05:00
|
|
|
afterRender: ->
|
2014-01-03 13:32:13 -05:00
|
|
|
super()
|
2014-03-29 19:38:45 -04:00
|
|
|
@$progressScrubber = $('.scrubber .progress', @$el)
|
2014-01-03 13:32:13 -05:00
|
|
|
@hookUpScrubber()
|
|
|
|
@updateMusicButton()
|
2014-03-24 12:07:09 -04:00
|
|
|
$(window).on('resize', @onWindowResize)
|
2014-04-11 21:19:52 -04:00
|
|
|
ua = navigator.userAgent.toLowerCase()
|
|
|
|
if /safari/.test(ua) and not /chrome/.test(ua)
|
|
|
|
@$el.find('.toggle-fullscreen').hide()
|
2014-08-22 18:32:23 -04:00
|
|
|
@timePopup ?= new HoverPopup
|
|
|
|
t = $.i18n.t
|
|
|
|
@second = t 'units.second'
|
|
|
|
@seconds = t 'units.seconds'
|
|
|
|
@minute = t 'units.minute'
|
|
|
|
@minutes = t 'units.minutes'
|
|
|
|
@goto = t 'play_level.time_goto'
|
|
|
|
@current = t 'play_level.time_current'
|
|
|
|
@total = t 'play_level.time_total'
|
2014-12-28 16:25:20 -05:00
|
|
|
@$el.find('#play-button').css('visibility', 'hidden') if @options.level.get 'hidesPlayButton' # Don't show for first few levels, confuses new players.
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-03-29 19:38:45 -04:00
|
|
|
updatePopupContent: ->
|
2014-04-22 14:11:08 -04:00
|
|
|
@timePopup?.updateContent "<h2>#{@timeToString @newTime}</h2>#{@formatTime(@current, @currentTime)}<br/>#{@formatTime(@total, @totalTime)}"
|
2014-03-29 19:38:45 -04:00
|
|
|
|
|
|
|
# These functions could go to some helper class
|
|
|
|
|
|
|
|
pad2: (num) ->
|
2014-06-30 22:16:26 -04:00
|
|
|
if not num? or num is 0 then '00' else ((if num < 10 then '0' else '') + num)
|
2014-03-29 19:38:45 -04:00
|
|
|
|
|
|
|
formatTime: (text, time) =>
|
|
|
|
"#{text}\t#{@timeToString time}"
|
|
|
|
|
|
|
|
timeToString: (time=0, withUnits=false) ->
|
|
|
|
mins = Math.floor(time / 60)
|
2014-03-29 20:30:12 -04:00
|
|
|
secs = (time - mins * 60).toFixed(1)
|
2014-03-29 19:38:45 -04:00
|
|
|
if withUnits
|
2014-06-30 22:16:26 -04:00
|
|
|
ret = ''
|
|
|
|
ret = (mins + ' ' + (if mins is 1 then @minute else @minutes)) if (mins > 0)
|
|
|
|
ret = (ret + ' ' + secs + ' ' + (if secs is 1 then @second else @seconds)) if (secs > 0 or mins is 0)
|
2014-03-29 19:38:45 -04:00
|
|
|
else
|
2014-03-29 20:30:12 -04:00
|
|
|
"#{mins}:#{@pad2 secs}"
|
2014-03-29 19:38:45 -04:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
# callbacks
|
|
|
|
|
|
|
|
updateMusicButton: ->
|
|
|
|
@$el.find('#music-button').toggleClass('music-on', me.get('music'))
|
|
|
|
|
|
|
|
onSetLetterbox: (e) ->
|
2014-08-24 01:24:00 -04:00
|
|
|
return if @realTime
|
2014-08-23 16:54:52 -04:00
|
|
|
@togglePlaybackControls !e.on
|
2014-01-03 13:32:13 -05:00
|
|
|
@disabled = e.on
|
|
|
|
|
2014-08-23 16:54:52 -04:00
|
|
|
togglePlaybackControls: (to) ->
|
|
|
|
buttons = @$el.find '#play-button, .scrubber-handle'
|
|
|
|
buttons.css 'visibility', if to then 'visible' else 'hidden'
|
|
|
|
|
|
|
|
onTomeCast: (e) ->
|
|
|
|
return unless e.realTime
|
2014-08-30 00:46:26 -04:00
|
|
|
@realTime = true
|
|
|
|
@togglePlaybackControls false
|
|
|
|
Backbone.Mediator.publish 'playback:real-time-playback-started', {}
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound 'real-time-playback-start'
|
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
|
|
|
|
|
|
|
onRealTimeMultiplayerCast: (e) ->
|
2014-08-23 16:54:52 -04:00
|
|
|
@realTime = true
|
|
|
|
@togglePlaybackControls false
|
2014-08-30 00:46:26 -04:00
|
|
|
Backbone.Mediator.publish 'playback:real-time-playback-waiting', {}
|
2014-08-23 16:54:52 -04:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
onWindowResize: (s...) =>
|
|
|
|
@barWidth = $('.progress', @$el).width()
|
|
|
|
|
2014-02-11 17:24:06 -05:00
|
|
|
onNewWorld: (e) ->
|
2014-08-22 18:32:23 -04:00
|
|
|
@updateBarWidth e.world.frames.length, e.world.maxTotalFrames, e.world.dt
|
|
|
|
|
|
|
|
updateBarWidth: (loadedFrameCount, maxTotalFrames, dt) ->
|
2014-09-25 00:01:58 -04:00
|
|
|
@totalTime = (loadedFrameCount - 1) * dt
|
|
|
|
pct = parseInt(100 * loadedFrameCount / (maxTotalFrames - 1)) + '%'
|
2014-02-13 12:26:21 -05:00
|
|
|
@barWidth = $('.progress', @$el).css('width', pct).show().width()
|
2014-03-17 00:43:14 -04:00
|
|
|
$('.scrubber .progress', @$el).slider('enable', true)
|
2014-03-29 19:38:45 -04:00
|
|
|
@newTime = 0
|
|
|
|
@currentTime = 0
|
2014-08-22 18:32:23 -04:00
|
|
|
@lastLoadedFrameCount = loadedFrameCount
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-11 17:24:06 -05:00
|
|
|
onDisableControls: (e) ->
|
2014-11-14 21:25:01 -05:00
|
|
|
if not e.controls or ('playback' in e.controls)
|
2014-01-03 13:32:13 -05:00
|
|
|
@disabled = true
|
|
|
|
$('button', @$el).addClass('disabled')
|
|
|
|
try
|
2014-03-29 19:38:45 -04:00
|
|
|
@$progressScrubber.slider('disable', true)
|
2014-05-15 00:54:36 -04:00
|
|
|
catch error
|
|
|
|
console.warn('error disabling scrubber', error)
|
2014-04-11 17:03:13 -04:00
|
|
|
@timePopup?.disable()
|
2014-11-15 15:46:57 -05:00
|
|
|
$('#volume-button', @$el).removeClass('disabled')
|
|
|
|
@$el.addClass 'controls-disabled'
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-11 17:24:06 -05:00
|
|
|
onEnableControls: (e) ->
|
2014-08-24 01:24:00 -04:00
|
|
|
return if @realTime
|
2014-11-14 21:25:01 -05:00
|
|
|
if not e.controls or ('playback' in e.controls)
|
2014-01-03 13:32:13 -05:00
|
|
|
@disabled = false
|
|
|
|
$('button', @$el).removeClass('disabled')
|
|
|
|
try
|
2014-03-29 19:38:45 -04:00
|
|
|
@$progressScrubber.slider('enable', true)
|
2014-05-15 00:54:36 -04:00
|
|
|
catch error
|
|
|
|
console.warn('error enabling scrubber', error)
|
2014-04-11 17:03:13 -04:00
|
|
|
@timePopup?.enable()
|
2014-11-15 15:46:57 -05:00
|
|
|
@$el.removeClass 'controls-disabled'
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-02-11 17:24:06 -05:00
|
|
|
onSetPlaying: (e) ->
|
2014-01-03 13:32:13 -05:00
|
|
|
@playing = (e ? {}).playing ? true
|
|
|
|
button = @$el.find '#play-button'
|
|
|
|
ended = button.hasClass 'ended'
|
2014-09-05 15:39:30 -04:00
|
|
|
changed = button.hasClass('playing') isnt @playing
|
2014-01-03 13:32:13 -05:00
|
|
|
button.toggleClass('playing', @playing and not ended).toggleClass('paused', not @playing and not ended)
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound (if @playing then 'playback-play' else 'playback-pause')
|
2014-01-03 13:32:13 -05:00
|
|
|
return # don't stripe the bar
|
|
|
|
bar = @$el.find '.scrubber .progress'
|
|
|
|
bar.toggleClass('progress-striped', @playing and not ended).toggleClass('active', @playing and not ended)
|
|
|
|
|
|
|
|
onSetVolume: (e) ->
|
|
|
|
classes = ['vol-off', 'vol-down', 'vol-up']
|
|
|
|
button = $('#volume-button', @$el)
|
|
|
|
button.removeClass(c) for c in classes
|
|
|
|
button.addClass(classes[0]) if e.volume <= 0.0
|
|
|
|
button.addClass(classes[1]) if e.volume > 0.0 and e.volume < 1.0
|
|
|
|
button.addClass(classes[2]) if e.volume >= 1.0
|
2014-01-29 15:18:37 -05:00
|
|
|
|
2014-05-24 11:31:59 -04:00
|
|
|
onScrub: (e, options) ->
|
2014-08-28 20:08:05 -04:00
|
|
|
e?.preventDefault?()
|
2014-05-24 11:31:59 -04:00
|
|
|
options.scrubDuration = 500
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish('level:set-time', options)
|
2014-05-24 11:31:59 -04:00
|
|
|
|
|
|
|
onScrubForward: (e) ->
|
|
|
|
@onScrub e, ratioOffset: 0.05
|
|
|
|
|
|
|
|
onSingleScrubForward: (e) ->
|
|
|
|
@onScrub e, frameOffset: 1
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
onScrubBack: (e) ->
|
2014-05-24 11:31:59 -04:00
|
|
|
@onScrub e, ratioOffset: -0.05
|
|
|
|
|
|
|
|
onSingleScrubBack: (e) ->
|
|
|
|
@onScrub e, frameOffset: -1
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
onFrameChanged: (e) ->
|
|
|
|
if e.progress isnt @lastProgress
|
2014-03-29 19:38:45 -04:00
|
|
|
@currentTime = e.frame / e.world.frameRate
|
2014-04-28 19:31:51 -04:00
|
|
|
@updatePopupContent() if @timePopup?.shown
|
2014-08-22 18:32:23 -04:00
|
|
|
@updateProgress(e.progress, e.world)
|
2014-01-03 13:32:13 -05:00
|
|
|
@updatePlayButton(e.progress)
|
|
|
|
@lastProgress = e.progress
|
|
|
|
|
2014-03-29 19:38:45 -04:00
|
|
|
onProgressEnter: (e) ->
|
2014-04-11 17:03:13 -04:00
|
|
|
# Why it needs itself as parameter you ask? Ask Twitter instead.
|
|
|
|
@timePopup?.enter @timePopup
|
2014-03-29 19:38:45 -04:00
|
|
|
|
|
|
|
onProgressLeave: (e) ->
|
2014-04-11 17:03:13 -04:00
|
|
|
@timePopup?.leave @timePopup
|
2014-03-29 19:38:45 -04:00
|
|
|
|
2014-10-27 16:32:25 -04:00
|
|
|
onProgressHover: (e, offsetX) ->
|
2014-03-29 19:38:45 -04:00
|
|
|
timeRatio = @$progressScrubber.width() / @totalTime
|
2014-10-27 16:32:25 -04:00
|
|
|
offsetX ?= e.clientX - $(e.target).closest('#timeProgress').offset().left
|
2014-11-19 18:46:50 -05:00
|
|
|
offsetX = Math.max 0, offsetX
|
2014-07-15 12:35:14 -04:00
|
|
|
@newTime = offsetX / timeRatio
|
2014-03-29 19:38:45 -04:00
|
|
|
@updatePopupContent()
|
2014-04-11 17:03:13 -04:00
|
|
|
@timePopup?.onHover e
|
2014-03-29 19:38:45 -04:00
|
|
|
|
2014-04-11 17:03:13 -04:00
|
|
|
# Show it instantaneously if close enough to current time.
|
|
|
|
if @timePopup and Math.abs(@currentTime - @newTime) < 1 and not @timePopup.shown
|
|
|
|
@timePopup.show()
|
2014-03-29 19:38:45 -04:00
|
|
|
|
2014-10-27 16:32:25 -04:00
|
|
|
onProgressTapStart: (e, touchData) ->
|
|
|
|
return unless application.isIPadApp
|
|
|
|
@onProgressEnter e
|
|
|
|
screenOffsetX = e.clientX ? touchData?.position.x ? 0
|
|
|
|
offsetX = screenOffsetX - $(e.target).closest('#timeProgress').offset().left
|
|
|
|
offsetX = Math.max offsetX, 0
|
|
|
|
@scrubTo offsetX / @$progressScrubber.width()
|
|
|
|
@onTogglePlay() if @$el.find('#play-button').hasClass 'playing'
|
|
|
|
|
|
|
|
onProgressTapEnd: (e, touchData) ->
|
|
|
|
return unless application.isIPadApp
|
|
|
|
@onProgressLeave e
|
|
|
|
|
|
|
|
onProgressTapMove: (e, touchData) ->
|
|
|
|
return unless application.isIPadApp # Not sure why the tap events would fire when it's not one.
|
|
|
|
screenOffsetX = e.clientX ? touchData?.position.x ? 0
|
|
|
|
offsetX = screenOffsetX - $(e.target).closest('#timeProgress').offset().left
|
|
|
|
offsetX = Math.max offsetX, 0
|
|
|
|
@onProgressHover e, offsetX
|
|
|
|
@scrubTo offsetX / @$progressScrubber.width()
|
|
|
|
|
2014-08-22 18:32:23 -04:00
|
|
|
updateProgress: (progress, world) ->
|
|
|
|
if world.frames.length isnt @lastLoadedFrameCount
|
|
|
|
@updateBarWidth world.frames.length, world.maxTotalFrames, world.dt
|
2014-08-24 15:33:46 -04:00
|
|
|
wasLoaded = @worldCompletelyLoaded
|
2014-08-23 16:54:52 -04:00
|
|
|
@worldCompletelyLoaded = world.frames.length is world.totalFrames
|
2014-08-24 15:33:46 -04:00
|
|
|
if @realTime and @worldCompletelyLoaded and not wasLoaded
|
|
|
|
Backbone.Mediator.publish 'playback:real-time-playback-ended', {}
|
2014-09-25 01:59:13 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-letterbox', on: false
|
2014-08-22 17:59:32 -04:00
|
|
|
$('.scrubber .progress-bar', @$el).css('width', "#{progress * 100}%")
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
updatePlayButton: (progress) ->
|
2014-11-05 20:47:23 -05:00
|
|
|
playButton = @$el.find('#play-button')
|
|
|
|
wasEnded = playButton.hasClass('ended')
|
2014-08-23 16:54:52 -04:00
|
|
|
if @worldCompletelyLoaded and progress >= 0.99 and @lastProgress < 0.99
|
2014-11-05 20:47:23 -05:00
|
|
|
playButton.removeClass('playing').removeClass('paused').addClass('ended')
|
2014-09-25 01:59:13 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-letterbox', on: false if @realTime
|
2014-08-24 15:33:46 -04:00
|
|
|
Backbone.Mediator.publish 'playback:real-time-playback-ended', {} if @realTime
|
2014-05-03 16:26:37 -04:00
|
|
|
if progress < 0.99 and @lastProgress >= 0.99
|
2014-11-05 20:47:23 -05:00
|
|
|
playButton.removeClass('ended')
|
|
|
|
playButton.addClass(if @playing then 'playing' else 'paused')
|
|
|
|
isEnded = playButton.hasClass('ended')
|
|
|
|
if wasEnded isnt isEnded
|
|
|
|
Backbone.Mediator.publish 'playback:ended-changed', ended: isEnded
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-08-24 15:33:46 -04:00
|
|
|
onRealTimePlaybackEnded: (e) ->
|
|
|
|
return unless @realTime
|
|
|
|
@realTime = false
|
|
|
|
@togglePlaybackControls true
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound 'real-time-playback-end'
|
2014-08-24 15:33:46 -04:00
|
|
|
|
2014-08-26 01:05:24 -04:00
|
|
|
onStopRealTimePlayback: (e) ->
|
2014-09-25 12:48:14 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-letterbox', on: false
|
2014-08-26 01:05:24 -04:00
|
|
|
Backbone.Mediator.publish 'playback:real-time-playback-ended', {}
|
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
# to refactor
|
|
|
|
|
|
|
|
hookUpScrubber: ->
|
|
|
|
@sliderIncrements = 500 # max slider width before we skip pixels
|
2014-03-29 19:38:45 -04:00
|
|
|
@$progressScrubber.slider(
|
2014-01-03 13:32:13 -05:00
|
|
|
max: @sliderIncrements
|
2014-06-30 22:16:26 -04:00
|
|
|
animate: 'slow'
|
2014-03-17 00:04:36 -04:00
|
|
|
slide: (event, ui) =>
|
2014-08-23 17:55:58 -04:00
|
|
|
return if @shouldIgnore()
|
2014-09-05 15:39:30 -04:00
|
|
|
++@slideCount
|
|
|
|
oldRatio = @getScrubRatio()
|
2014-03-17 00:04:36 -04:00
|
|
|
@scrubTo ui.value / @sliderIncrements
|
2014-09-05 15:39:30 -04:00
|
|
|
if ratioChange = @getScrubRatio() - oldRatio
|
|
|
|
sound = "playback-scrub-slide-#{if ratioChange > 0 then 'forward' else 'back'}-#{@slideCount % 3}"
|
2015-09-02 08:32:45 -04:00
|
|
|
unless /back/.test sound # We don't have the back sounds in yet: http://discourse.codecombat.com/t/bug-some-mp3-lost/4830
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound sound, (Math.min 1, Math.abs ratioChange * 50)
|
2014-03-17 00:04:36 -04:00
|
|
|
|
|
|
|
start: (event, ui) =>
|
2014-08-23 17:55:58 -04:00
|
|
|
return if @shouldIgnore()
|
2014-03-17 00:04:36 -04:00
|
|
|
@slideCount = 0
|
2014-09-25 01:12:00 -04:00
|
|
|
@wasPlaying = @playing and not $('#play-button').hasClass('ended')
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound 'playback-scrub-start', 0.5
|
2014-09-11 14:08:25 -04:00
|
|
|
|
2014-03-17 00:04:36 -04:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
stop: (event, ui) =>
|
2014-08-23 17:55:58 -04:00
|
|
|
return if @shouldIgnore()
|
2014-01-03 13:32:13 -05:00
|
|
|
@actualProgress = ui.value / @sliderIncrements
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'playback:manually-scrubbed', ratio: @actualProgress # For scripts
|
|
|
|
Backbone.Mediator.publish 'level:set-playing', {playing: @wasPlaying}
|
2014-03-17 00:04:36 -04:00
|
|
|
if @slideCount < 3
|
2014-01-03 13:32:13 -05:00
|
|
|
@wasPlaying = false
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
2014-01-03 13:32:13 -05:00
|
|
|
@$el.find('.scrubber-handle').effect('bounce', {times: 2})
|
2014-09-05 15:39:30 -04:00
|
|
|
else
|
2015-09-09 17:36:05 -04:00
|
|
|
@playSound 'playback-scrub-end', 0.5
|
2014-09-11 14:08:25 -04:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
getScrubRatio: ->
|
2014-03-29 19:38:45 -04:00
|
|
|
@$progressScrubber.find('.progress-bar').width() / @$progressScrubber.width()
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
scrubTo: (ratio, duration=0) ->
|
2014-03-17 00:43:14 -04:00
|
|
|
return if @shouldIgnore()
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-time', ratio: ratio, scrubDuration: duration
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-08-23 16:54:52 -04:00
|
|
|
shouldIgnore: -> return @disabled or @realTime
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
onTogglePlay: (e) ->
|
2014-08-28 20:08:05 -04:00
|
|
|
e?.preventDefault?()
|
2014-01-03 13:32:13 -05:00
|
|
|
return if @shouldIgnore()
|
|
|
|
button = $('#play-button')
|
|
|
|
willPlay = button.hasClass('paused') or button.hasClass('ended')
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-playing', playing: willPlay
|
2014-01-03 13:32:13 -05:00
|
|
|
$(document.activeElement).blur()
|
|
|
|
|
|
|
|
onToggleVolume: (e) ->
|
|
|
|
button = $(e.target).closest('#volume-button')
|
|
|
|
classes = ['vol-off', 'vol-down', 'vol-up']
|
|
|
|
volumes = [0, 0.4, 1.0]
|
|
|
|
for oldClass, i in classes
|
|
|
|
if button.hasClass oldClass
|
|
|
|
newI = (i + 1) % classes.length
|
|
|
|
break
|
|
|
|
else if i is classes.length - 1 # no oldClass
|
|
|
|
newI = 2
|
2014-08-27 15:24:03 -04:00
|
|
|
Backbone.Mediator.publish 'level:set-volume', volume: volumes[newI]
|
2014-01-03 13:32:13 -05:00
|
|
|
$(document.activeElement).blur()
|
|
|
|
|
2014-02-26 13:42:25 -05:00
|
|
|
onToggleMusic: (e) ->
|
|
|
|
e?.preventDefault()
|
2014-10-02 20:21:06 -04:00
|
|
|
me.set('music', not me.get('music', true))
|
2014-06-11 17:17:31 -04:00
|
|
|
me.patch()
|
2014-02-26 13:42:25 -05:00
|
|
|
$(document.activeElement).blur()
|
2014-03-29 19:38:45 -04:00
|
|
|
|
|
|
|
destroy: ->
|
|
|
|
me.off('change:music', @updateMusicButton, @)
|
|
|
|
$(window).off('resize', @onWindowResize)
|
|
|
|
@onWindowResize = null
|
|
|
|
super()
|
2014-11-04 22:03:35 -05:00
|
|
|
|
|
|
|
# popover that shows at the current mouse position on the progressbar, using the bootstrap popover.
|
|
|
|
# Could make this into a jQuery plugins itself theoretically.
|
|
|
|
class HoverPopup extends $.fn.popover.Constructor
|
|
|
|
constructor: () ->
|
|
|
|
@enabled = true
|
|
|
|
@shown = false
|
|
|
|
@type = 'HoverPopup'
|
|
|
|
@options =
|
|
|
|
placement: 'top'
|
|
|
|
container: 'body'
|
|
|
|
animation: true
|
|
|
|
html: true
|
|
|
|
delay:
|
|
|
|
show: 400
|
|
|
|
@$element = $('#timeProgress')
|
|
|
|
@$tip = $('#timePopover')
|
|
|
|
|
|
|
|
@content = ''
|
|
|
|
|
|
|
|
getContent: -> @content
|
|
|
|
|
|
|
|
show: ->
|
|
|
|
unless @shown
|
|
|
|
super()
|
|
|
|
@shown = true
|
|
|
|
|
|
|
|
updateContent: (@content) ->
|
|
|
|
@setContent()
|
|
|
|
@$tip.addClass('fade top in')
|
|
|
|
|
|
|
|
onHover: (@e) ->
|
|
|
|
pos = @getPosition()
|
|
|
|
actualWidth = @$tip[0].offsetWidth
|
|
|
|
actualHeight = @$tip[0].offsetHeight
|
|
|
|
calculatedOffset =
|
|
|
|
top: pos.top - actualHeight
|
|
|
|
left: pos.left + pos.width / 2 - actualWidth / 2
|
2015-01-30 10:51:57 -05:00
|
|
|
@applyPlacement(calculatedOffset, 'top')
|
2014-11-04 22:03:35 -05:00
|
|
|
|
|
|
|
getPosition: ->
|
|
|
|
top: @$element.offset().top
|
|
|
|
left: if @e? then @e.pageX else @$element.offset().left
|
|
|
|
height: 0
|
|
|
|
width: 0
|
|
|
|
|
|
|
|
hide: ->
|
|
|
|
super()
|
|
|
|
@shown = false
|
|
|
|
|
|
|
|
disable: ->
|
|
|
|
super()
|
|
|
|
@hide()
|