src/follower.ts (2494 bytes)
1 // The reader follows the audio: which cue to mark, which page to show. Pure. 2 // 3 // At each step the follow compares the page of the audio (see audioPage 4 // in cue-pages.ts) with the page shown, and turns the page when they 5 // differ. A page turn by the user holds the follow: the page stays until 6 // the audio reaches it, the audio jumps, or the user asks to follow again. 7 8 export type FollowMode = 'highlight' | 'pages' | 'off'; 9 10 /** 11 * Why the follow runs. 12 * 13 * - `tick`: a new state of the clock in the normal run. 14 * - `realign`: the alignment changed, because a page was read. 15 * - `seek`: the audio jumped. The player jumped, or the user moved the 16 * audio from this app. 17 * - `follow`: the user asked to follow: the Follow button, or a new 18 * follow mode. 19 */ 20 export type FollowEvent = 'tick' | 'realign' | 'seek' | 'follow'; 21 22 export interface FollowInput { 23 mode: FollowMode; 24 currentPage: number; 25 /** The cue of now, or -1 before the first cue. */ 26 cue: number; 27 /** True when the cue of now is matched: it has tokens on a page. */ 28 matched: boolean; 29 /** The page of the audio now, or null when no page stands for the cue of now. */ 30 page: number | null; 31 /** True while a page turn by the user holds the follow. */ 32 held: boolean; 33 event: FollowEvent; 34 } 35 36 export interface FollowOutput { 37 /** The cue to mark on the page, or null for no mark. */ 38 highlightCue: number | null; 39 /** The page to show, or null to stay. */ 40 turnToPage: number | null; 41 /** True when the follow stays held. */ 42 held: boolean; 43 } 44 45 /** 46 * One step of the follow. 47 * 48 * A seek or the Follow button ends the hold. The hold also ends when the 49 * audio is on the page shown. A tick or a new alignment does not end it. 50 * While the follow is held, the page does not turn. Else the page turns 51 * when the page of the audio is not the page shown. In `highlight` mode a 52 * matched cue is marked, also while the follow is held: the mark shows 53 * only on the page of the cue. `off` does nothing. 54 */ 55 export function follow(input: FollowInput): FollowOutput { 56 const { mode, currentPage, cue, matched, page, event } = input; 57 let held = input.held && (event === 'tick' || event === 'realign'); 58 if (mode === 'off' || cue < 0) return { highlightCue: null, turnToPage: null, held }; 59 if (page === currentPage) held = false; 60 return { 61 highlightCue: mode === 'highlight' && matched ? cue : null, 62 turnToPage: !held && page !== null && page !== currentPage ? page : null, 63 held, 64 }; 65 }