asciinema.org/app/assets/javascripts/player/movie.js.coffee

214 lines
4.0 KiB
CoffeeScript

class AsciiIo.Movie
MIN_DELAY: 0.01
constructor: (@options) ->
_.extend(this, Backbone.Events)
@reset()
@startTimeReporter()
reset: ->
@frameNo = 0
@completedFramesTime = 0
@playing = false
@lastFrameAt = undefined
@framesProcessed = 0
@clearPauseState()
@trigger 'reset'
call: (method, args...) ->
@[method].apply this, args
now: ->
(new Date()).getTime()
stdout: ->
@options.stdout
play: ->
return if @isPlaying()
if @isFinished()
@restart()
else if @isPaused()
@resume()
else
@start()
start: ->
@playing = true
@trigger 'started'
@lastFrameAt = @now()
@nextFrame()
stop: ->
@playing = false
@cancelNextFrameProcessing()
now = @now()
@adjustFrameWaitTime(now)
@pausedAt = now
cancelNextFrameProcessing: ->
clearInterval @nextFrameTimeoutId
adjustFrameWaitTime: (now) ->
resumedAt = @resumedAt or @lastFrameAt
currentWaitTime = now - resumedAt
@totalFrameWaitTime += currentWaitTime
restart: ->
@reset()
@start()
pause: ->
return if @isPaused()
@stop()
@trigger 'paused'
resume: ->
return if @isPlaying()
@playing = true
@resumedAt = @now()
frame = @stdout()[@frameNo]
[delay, data] = frame
delayMs = delay * 1000
delayLeft = delayMs - @totalFrameWaitTime
@processFrameWithDelay(delayLeft)
@trigger 'resumed'
togglePlay: ->
if @isPlaying() then @pause() else @play()
isPlaying: ->
@playing
isPaused: ->
!@isPlaying() and !@isFinished() and @frameNo > 0
isFinished: ->
!@isPlaying() and @frameNo >= (@stdout().length - 1)
seek: (percent) ->
@stop()
@rewindTo(percent)
@resume()
rewindTo: (percent) ->
duration = @options.duration
requestedTime = duration * percent / 100
frameNo = 0
time = 0
totalCount = 0
delay = data = undefined
@trigger 'reset'
while time < requestedTime
[delay, data] = @stdout()[frameNo]
if time + delay >= requestedTime
break
frameData = String.fromCharCode.apply(String, data)
@trigger 'data', [frameData]
time += delay
frameNo += 1
@frameNo = frameNo
@completedFramesTime = time * 1000
@lastFrameAt = @now()
wait = requestedTime - time
@totalFrameWaitTime = wait * 1000
startTimeReporter: ->
@timeReportId = setInterval(
=> @trigger('time', @currentTime())
500
)
stopTimeReporter: ->
clearInterval @timeReportId
currentTime: ->
@completedFramesTime + @currentFrameTime()
currentFrameTime: ->
if @isPlaying()
@playingFrameTime()
else if @isPaused()
@pausedFrameTime()
else
0
playingFrameTime: ->
if @frameWasPaused()
@currentFrameWithPauseTime()
else
@currentFrameWithNoPauseTime()
frameWasPaused: ->
!!@pausedAt
currentFrameWithPauseTime: ->
@totalFrameWaitTime + @sinceResumeTime()
currentFrameWithNoPauseTime: ->
@now() - @lastFrameAt
sinceResumeTime: ->
@now() - @resumedAt
pausedFrameTime: ->
@totalFrameWaitTime
clearPauseState: ->
@pausedAt = undefined
@resumedAt = undefined
@totalFrameWaitTime = 0
nextFrame: ->
frame = @stdout()[@frameNo]
if not frame or frame.length is 0
@playing = false
@trigger 'finished'
return false
[delay, data] = frame
if delay <= @MIN_DELAY and @framesProcessed < 100
@framesProcessed += 1
@processFrame()
else
@framesProcessed = 0
realDelay = delay * 1000 * (1.0 / @options.speed)
@processFrameWithDelay(realDelay)
true
processFrameWithDelay: (delay) ->
@nextFrameTimeoutId = setTimeout(
=>
@trigger 'wakeup'
@processFrame()
delay
)
processFrame: ->
frame = @stdout()[@frameNo]
[delay, data] = frame
frameData = String.fromCharCode.apply(String, data)
@trigger 'data', [frameData]
@frameNo += 1
@completedFramesTime += delay * 1000
@lastFrameAt = @now()
@clearPauseState()
@nextFrame()