subread/clock.lua (3204 bytes)
1 --[[-- 2 Play clock for SubRead. 3 4 Pure Lua. No KOReader dependency, so it can be unit tested on a PC. 5 6 The caller gives the time. `now` is a monotonic time in seconds, from any 7 source. The clock keeps: 8 * position: where the audio player is, in seconds, 9 * speed: how fast the player runs, 1.0 is normal, 10 * offset: how much later the narration is than the subtitle times. 11 An audio file with a 20 s intro has offset 20. 12 The cue time is the position minus the offset. 13 --]]-- 14 15 local Clock = {} 16 Clock.__index = Clock 17 18 Clock.SPEED_MIN = 0.5 19 Clock.SPEED_MAX = 3.0 20 21 --- Makes a new clock. It is paused. 22 function Clock.new(opts) 23 opts = opts or {} 24 local self = setmetatable({}, Clock) 25 self.position = opts.position or 0 26 self.speed = opts.speed or 1.0 27 self.offset = opts.offset or 0 28 self.running = false 29 self.started_at = nil 30 return self 31 end 32 33 function Clock:isRunning() 34 return self.running 35 end 36 37 --- Returns the player position, in seconds. 38 function Clock:getPosition(now) 39 if not self.running then return self.position end 40 return self.position + (now - self.started_at) * self.speed 41 end 42 43 --- Returns the time to look up in the cue index, in seconds. 44 function Clock:getCueTime(now) 45 return self:getPosition(now) - self.offset 46 end 47 48 --- Starts the clock at the current position. 49 function Clock:start(now) 50 if self.running then return end 51 self.started_at = now 52 self.running = true 53 end 54 55 --- Stops the clock and keeps the position. 56 function Clock:pause(now) 57 if not self.running then return end 58 self.position = self:getPosition(now) 59 self.running = false 60 self.started_at = nil 61 end 62 63 --- Starts or stops the clock. Returns the new state. 64 function Clock:toggle(now) 65 if self.running then 66 self:pause(now) 67 else 68 self:start(now) 69 end 70 return self.running 71 end 72 73 --- Moves the player position to an absolute value. 74 function Clock:seek(position, now) 75 if position < 0 then position = 0 end 76 self.position = position 77 if self.running then self.started_at = now end 78 end 79 80 --- Moves the player position by a number of seconds. 81 function Clock:skip(delta, now) 82 self:seek(self:getPosition(now) + delta, now) 83 end 84 85 --- Moves the player position to the given cue time. 86 function Clock:seekCueTime(cue_time, now) 87 self:seek(cue_time + self.offset, now) 88 end 89 90 --- Sets the speed. The position is kept. 91 function Clock:setSpeed(speed, now) 92 if speed < Clock.SPEED_MIN then speed = Clock.SPEED_MIN end 93 if speed > Clock.SPEED_MAX then speed = Clock.SPEED_MAX end 94 self.position = self:getPosition(now) 95 if self.running then self.started_at = now end 96 self.speed = speed 97 end 98 99 --- Sets the offset. The position is kept, so the cue time moves. 100 function Clock:setOffset(offset) 101 self.offset = offset 102 end 103 104 --- Returns the real seconds until the clock reaches the given cue time. 105 -- Returns nil when the clock is paused or the time is already past. 106 function Clock:realSecondsUntilCueTime(cue_time, now) 107 if not self.running then return nil end 108 local delta = (cue_time + self.offset) - self:getPosition(now) 109 if delta <= 0 then return nil end 110 return delta / self.speed 111 end 112 113 return Clock