rodio/source/
position.rs

1use std::time::Duration;
2
3use super::SeekError;
4use crate::common::{ChannelCount, SampleRate};
5use crate::Source;
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_span_sample_rate: 0,
15        current_span_channels: 0,
16        current_span_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_span_sample_rate: SampleRate,
27    current_span_channels: ChannelCount,
28    current_span_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{
55    /// Returns the position of the underlying source relative to its start.
56    ///
57    /// If a speedup and or delay is applied after applying a
58    /// [`Source::track_position`] it will not be reflected in the position
59    /// returned by [`get_pos`](TrackPosition::get_pos).
60    ///
61    /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos)
62    /// together with [`Source::try_seek()`] as the the latter does take all
63    /// speedup's and delay's into account. Its recommended therefore to apply
64    /// track_position after speedup's and delay's.
65    #[inline]
66    pub fn get_pos(&self) -> Duration {
67        let seconds = self.samples_counted as f64
68            / self.input.sample_rate() as f64
69            / self.input.channels() as f64
70            + self.offset_duration;
71        Duration::from_secs_f64(seconds)
72    }
73
74    #[inline]
75    fn set_current_span(&mut self) {
76        self.current_span_len = self.current_span_len();
77        self.current_span_sample_rate = self.sample_rate();
78        self.current_span_channels = self.channels();
79    }
80}
81
82impl<I> Iterator for TrackPosition<I>
83where
84    I: Source,
85{
86    type Item = I::Item;
87
88    #[inline]
89    fn next(&mut self) -> Option<I::Item> {
90        // This should only be executed once at the first call to next.
91        if self.current_span_len.is_none() {
92            self.set_current_span();
93        }
94
95        let item = self.input.next();
96        if item.is_some() {
97            self.samples_counted += 1;
98
99            // At the end of a span add the duration of this span to
100            // offset_duration and start collecting samples again.
101            if Some(self.samples_counted) == self.current_span_len() {
102                self.offset_duration += self.samples_counted as f64
103                    / self.current_span_sample_rate as f64
104                    / self.current_span_channels as f64;
105
106                // Reset.
107                self.samples_counted = 0;
108                self.set_current_span();
109            };
110        };
111        item
112    }
113
114    #[inline]
115    fn size_hint(&self) -> (usize, Option<usize>) {
116        self.input.size_hint()
117    }
118}
119
120impl<I> Source for TrackPosition<I>
121where
122    I: Source,
123{
124    #[inline]
125    fn current_span_len(&self) -> Option<usize> {
126        self.input.current_span_len()
127    }
128
129    #[inline]
130    fn channels(&self) -> ChannelCount {
131        self.input.channels()
132    }
133
134    #[inline]
135    fn sample_rate(&self) -> SampleRate {
136        self.input.sample_rate()
137    }
138
139    #[inline]
140    fn total_duration(&self) -> Option<Duration> {
141        self.input.total_duration()
142    }
143
144    #[inline]
145    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
146        let result = self.input.try_seek(pos);
147        if result.is_ok() {
148            self.offset_duration = pos.as_secs_f64();
149            // This assumes that the seek implementation of the codec always
150            // starts again at the beginning of a span. Which is the case with
151            // symphonia.
152            self.samples_counted = 0;
153        }
154        result
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use std::time::Duration;
161
162    use crate::buffer::SamplesBuffer;
163    use crate::source::Source;
164
165    #[test]
166    fn test_position() {
167        let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]);
168        let mut source = inner.track_position();
169
170        assert_eq!(source.get_pos().as_secs_f32(), 0.0);
171        source.next();
172        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
173
174        source.next();
175        assert_eq!(source.get_pos().as_secs_f32(), 2.0);
176
177        assert!(source.try_seek(Duration::new(1, 0)).is_ok());
178        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
179    }
180
181    #[test]
182    fn test_position_in_presence_of_speedup() {
183        let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]);
184        let mut source = inner.speed(2.0).track_position();
185
186        assert_eq!(source.get_pos().as_secs_f32(), 0.0);
187        source.next();
188        assert_eq!(source.get_pos().as_secs_f32(), 0.5);
189
190        source.next();
191        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
192
193        assert!(source.try_seek(Duration::new(1, 0)).is_ok());
194        assert_eq!(source.get_pos().as_secs_f32(), 1.0);
195    }
196}