Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


src/player-state.ts (2033 bytes)

1 // The state line of the audio player, as SubRead Overlay reports it. Pure.
2 //
3 // SubRead Overlay is an Android app with notification access. It reads the
4 // media session of the player and gives one line to other apps:
5 //
6 //     playing=1;position=96153;speed=1.0;package=de.ph1b.audiobook
7 //
8 // The position is in milliseconds, for the moment of the query. A problem is
9 // one line `error=<reason>`. This is a port of player_state.lua from the
10 // SubRead plugin for KOReader.
11 
12 export interface PlayerState {
13   playing: boolean;
14   /** Position in milliseconds. Null when the player reports none. */
15   positionMs: number | null;
16   /** Playback speed. 1 when the line has none, or a value of zero or less. */
17   speed: number;
18   /** Package name of the player app. Empty when the line has none. */
19   package: string;
20 }
21 
22 export interface PlayerError {
23   /** `no_overlay`, `no_notification_access`, `no_player`, `empty` or `malformed`. */
24   error: string;
25 }
26 
27 export function isPlayerError(x: PlayerState | PlayerError): x is PlayerError {
28   return 'error' in x;
29 }
30 
31 const FIELD = /([\w]+)=([^;]*)/g;
32 
33 /** Parses a state line. */
34 export function parseState(line: string | null | undefined): PlayerState | PlayerError {
35   if (typeof line !== 'string' || line === '') return { error: 'empty' };
36   const fields = new Map<string, string>();
37   for (const m of line.matchAll(FIELD)) fields.set(m[1]!, m[2]!);
38   const error = fields.get('error');
39   if (error !== undefined) return { error };
40   const playing = fields.get('playing');
41   const position = fields.get('position');
42   if (playing === undefined || position === undefined) return { error: 'malformed' };
43   const positionMs = Number(position);
44   if (position.trim() === '' || Number.isNaN(positionMs)) return { error: 'malformed' };
45   let speed = Number(fields.get('speed') ?? '1');
46   if (Number.isNaN(speed) || speed <= 0) speed = 1;
47   return {
48     playing: playing === '1',
49     positionMs: positionMs >= 0 ? positionMs : null,
50     speed,
51     package: fields.get('package') ?? '',
52   };
53 }