kithara_stream/hooks.rs
1use std::sync::{Arc, Mutex};
2
3use crate::{preroll::PrerollHint, source::PendingReason};
4
5/// Lightweight read-side signal fed into [`DecoderHooks::on_chunk`].
6///
7/// Mirrors the meaningful shape of `DecoderChunkOutcome` (in
8/// `kithara-decode`) without the PCM payload — hooks emit events based
9/// on byte-cursor state from the [`Timeline`](crate::Timeline), not on
10/// the raw audio frames.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum ReaderChunkSignal {
14 /// Decoder produced a PCM chunk.
15 Chunk,
16 /// Decoder is alive but produced nothing this call (typed reason).
17 Pending(PendingReason),
18 /// Natural end of stream — no more chunks will arrive.
19 Eof,
20}
21
22/// Lightweight seek-side signal fed into [`DecoderHooks::on_seek`].
23///
24/// Mirrors the meaningful shape of `DecoderSeekOutcome` (in
25/// `kithara-decode`) for event-emission purposes only.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum ReaderSeekSignal {
29 /// Decoder parked at the destination. `landed_byte` is the absolute
30 /// byte offset the decoder picked (granule-aligned), when the
31 /// backend exposes one. `preroll` hints at an earlier byte position
32 /// the source must keep available so the decoder can warm its MDCT
33 /// state before emitting the first user-visible chunk. HLS uses this
34 /// to delay segment eviction; random-access sources (file) ignore it.
35 Landed {
36 landed_byte: Option<u64>,
37 preroll: PrerollHint,
38 },
39 /// Seek target was past the decoder's known duration. The decoder
40 /// is now parked at EOF.
41 PastEof,
42}
43
44/// Reader-side hooks invoked by the decoder layer's `HookedDecoder`
45/// right before it forwards the inner decoder's typed outcome to the
46/// caller.
47///
48/// One call per `next_chunk` / `seek` — granularity is decoder
49/// operations, not byte-level reads.
50pub trait DecoderHooks: Send + Sync {
51 /// Called once per `next_chunk` after the inner decoder produced
52 /// an outcome.
53 fn on_chunk(&mut self, signal: ReaderChunkSignal);
54
55 /// Called once per `seek` after the inner decoder parked at the
56 /// destination (or signalled `PastEof`).
57 fn on_seek(&mut self, signal: ReaderSeekSignal);
58}
59
60/// Shared, lockable hook handle. Used so that `Source::take_reader_hooks`
61/// can hand off Clone-able ownership and the hook implementation can
62/// hold `&mut self` state behind a single lock.
63pub type SharedHooks = Arc<Mutex<dyn DecoderHooks>>;