Recently Written · git

subread.koplugin

KOReader plugin: the book follows the narration of an audiobook, from an .srt made by subread.space

git clone https://github.com/equwal/subread.koplugin

Log | Files | Refs


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