rodio/source/
position.rs

1use std::time::Duration;
2
3use crate::{Sample, Source};
4
5use super::SeekError;
6
7/// Internal function that builds a `TrackPosition` object. See trait docs for
8/// details
9pub fn track_position<I>(source: I) -> TrackPosition<I> {
10    TrackPosition {
11        input: source,
12        samples_counted: 0,
13        offset_duration: 0.0,
14        current_frame_sample_rate: 0,
15        current_frame_channels: 0,
16        current_frame_len: None,
17    }
18}
19
20/// Tracks the elapsed duration since the start of the underlying source.
21#[derive(Debug)]
22pub struct TrackPosition<I> {
23    input: I,
24    samples_counted: usize,
25    offset_duration: f64,
26    current_frame_sample_rate: u32,
27    current_frame_channels: u16,
28    current_frame_len: Option<usize>,
29}
30
31impl<I> TrackPosition<I> {
32    /// Returns a reference to the inner source.
33    #[inline]
34    pub fn inner(&self) -> &I {
35        &self.input
36    }
37
38    /// Returns a mutable reference to the inner source.
39    #[inline]
40    pub fn inner_mut(&mut self) -> &mut I {
41        &mut self.input
42    }
43
44    /// Returns the inner source.
45    #[inline]
46    pub fn into_inner(self) -> I {
47        self.input
48    }
49}
50
51impl<I> TrackPosition<I>
52where
53    I: Source,
54    I::Item: Sample,
55{
56    /// Returns the position of the underlying source relative to its start.
57    ///
58    /// If a speedup and or delay is applied after applying a
59    /// [`Source::track_position`] it will not be reflected in the position
60    /// returned by [`get_pos`](TrackPosition::get_pos).
61    ///
62    /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos)
63    /// together with [`Source::try_seek()`] as the the latter does take all
64    /// speedup's and delay's into account. Its recommended therefore to apply
65    /// track_position after speedup's and delay's.
66    #[inline]
67    pub fn get_pos(&self) -> Duration {
68        let seconds = self.samples_counted as f64
69            / self.input.sample_rate() as f64
70            / self.input.channels() as f64
71            + self.offset_duration;
72        Duration::from_secs_f64(seconds)
73    }
74
75    #[inline]
76    fn set_current_frame(&mut self) {
77        self.current_frame_len = self.current_frame_len();
78        self.current_frame_sample_rate = self.sample_rate();
79        self.current_frame_channels = self.channels();
80    }
81}
82
83impl<I> Iterator for TrackPosition<I>
84where
85    I: Source,
86    I::Item: Sample,
87{
88    type Item = I::Item;
89
90    #[inline]
91    fn next(&mut self) -> Option<I::Item> {
92        // This should only be executed once at the first call to next.
93        if self.current_frame_len.is_none() {
94            self.set_current_frame();
95        }
96
97        let item = self.input.next();
98        if item.is_some() {
99            self.samples_counted += 1;
100
101            // At the end of a frame add the duration of this frame to
102            // offset_duration and start collecting samples again.
103            if Some(self.samples_counted) == self.current_frame_len() {
104                self.offset_duration += self.samples_counted as f64
105                    / self.current_frame_sample_rate as f64
106                    / self.current_frame_channels as f64;
107
108                // Reset.
109                self.samples_counted = 0;
110                self.set_current_frame();
111            };
112        };
113        item
114    }
115
116    #[inline]
117    fn size_hint(&self) -> (usize, Option<usize>) {
118        self.input.size_hint()
119    }
120}
121
122impl<I> Source for TrackPosition<I>
123where
124    I: Source,
125    I::Item: Sample,
126{
127    #[inline]
128    fn current_frame_len(&self) -> Option<usize> {
129        self.input.current_frame_len()
130    }
131
132    #[inline]
133    fn channels(&self) -> u16 {
134        self.input.channels()
135    }
136
137    #[inline]
138    fn sample_rate(&self) -> u32 {
139        self.input.sample_rate()
140    }
141
142    #[inline]
143    fn total_duration(&self) -> Option<Duration> {
144        self.input.total_duration()
145    }
146
147    #[inline]
148    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
149        let result = self.input.try_seek(pos);
150        if result.is_ok() {
151            self.offset_duration = pos.as_secs_f64();
152            // This assumes that the seek implementation of the codec always
153            // starts again at the beginning of a frame. Which is the case with
154            // symphonia.
155            self.samples_counted = 0;
156        }
157        result
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use std::time::Duration;
164
165    use crate::buffer::SamplesBuffer;
166    use crate::source::Source;
167
168    #[test]
169    fn test_position() {
170        let inner = SamplesBuffer::new(1, 1, vec![10i16, -10, 10, -10, 20, -20]);
171        let mut source = inner.track_position();
172
173        assert_eq!(source.get_pos().as_secs_f32(), 0.0);
174        source.next();
175        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
176
177        source.next();
178        assert_eq!(source.get_pos().as_secs_f32(), 2.0);
179
180        assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true);
181        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
182    }
183
184    #[test]
185    fn test_position_in_presence_of_speedup() {
186        let inner = SamplesBuffer::new(1, 1, vec![10i16, -10, 10, -10, 20, -20]);
187        let mut source = inner.speed(2.0).track_position();
188
189        assert_eq!(source.get_pos().as_secs_f32(), 0.0);
190        source.next();
191        assert_eq!(source.get_pos().as_secs_f32(), 0.5);
192
193        source.next();
194        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
195
196        assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true);
197        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
198    }
199}