use std::fs::File; use std::time::Instant; use core::time::Duration; use rodio::Decoder; use rodio::source::Buffered; use rodio::OutputStream; use rodio::Sink; use rodio::Source; use std::fmt; pub type Song = Buffered>; pub struct Player { // [Buffered] is a pointer to a linked-list representation of the song, and so cloning // it is as cheap as cloning an [Arc]. This should always point to the start of the // song though, and so should not be changed after being initialized. song: Song, sink: Option<(Sink, OutputStream)>, duration: Duration, /// The position of the playhead when playback started or was last stopped start_position: Duration, /// The [`Instant`] playback started playback_started: Option, } impl Player { /// Create a new player from a song /// /// IMPORTANT: Because computing the duration of a song is currently a very expensive /// task, this DOES NOT COMPUTE THE DURATION. All calculations that require a /// duration assume a duration of 2:30s until a duration is manually calculated with /// [`compute_duration()`] AND passed back into [`set_duration()`]. pub fn new(song: File) -> Result { let song = Decoder::new(song) .map_err(PlayerError::Decoder)? .buffered(); let duration = Duration::from_secs(150); let mut player = Self { sink: None, start_position: Duration::ZERO, playback_started: None, song, duration, }; player.get_or_set_sink()?; Ok(player) } /// Check if an audio sink is available /// /// Returns `true` if an output device is currently loaded, or `false` if a device was /// not availble last time we tried to access one. pub fn has_output_device(&self) -> bool { self.sink.is_some() } /// Attempt to re-request access to the output sink /// /// Returns `&self.sink` /// /// Can produce a [PlayerError::PlayError] fn try_set_sink(&mut self) -> Result, PlayerError> { self.sink = Some(OutputStream::try_default()) .and_then(|result| if let Err(&rodio::StreamError::NoDevice) = result.as_ref() { None // This is okay and doesn't need to raise an error } else { Some(result) // We'll report this error }) .transpose() .map_err(PlayerError::Stream)? .map(|(stream, handle)| Sink::try_new(&handle).map(|sink| (sink, stream))) .transpose() .map_err(PlayerError::Play)?; Ok(self.sink.as_ref().map(|(s, _)| s)) } /// Return the current active sink, or attempt to request a new one /// /// Equivilent to calling [`try_set_sink()`] if the sink is missing fn get_or_set_sink(&mut self) -> Result, PlayerError> { if self.sink.is_some() { Ok(self.sink.as_ref().map(|(s, _)| s)) } else { let song = self.song.clone(); let out = self.try_set_sink(); if let Ok(Some(sink)) = out.as_ref() { sink.pause(); sink.append(song); } out } } /// Attempt to resume playback, or start fresh /// /// Returns `true` if playback was resumed, `false` if there is no audio sink /// available to start play, and [PlayerError] if there was a problem requesting /// access to the audio sink. pub fn play(&mut self) -> Result { if let Some(sink) = self.get_or_set_sink()? { sink.play(); self.playback_started = Some(Instant::now()); Ok(true) } else { Ok(false) } } /// Pause playback if playing, do nothing otherwise /// /// May be resumed later with [`play()`] pub fn pause(&mut self) { if let Some((sink, _)) = &self.sink { sink.pause(); self.start_position = self.position(); self.playback_started = None; } } /// Returns true if the song is currently playing /// /// That is, if playback has started, has not been paused, and has not ended naturally /// yet. pub fn is_playing(&self) -> bool { self.playback_started.is_some() && self.position() < self.duration } /// Toggle playing /// /// This calls [`play()`] is the track is currently paused or stopped, and calls /// [`pause()`] is the track is currently playing. If the track was already playing, /// this will always return `Ok(true)`. pub fn toggle_play(&mut self) -> Result { if self.position() == self.duration { self.seek(Duration::ZERO) .and_then(|device| if device { self.play() } else { Ok(false) } ) } else if self.playback_started.is_none() { self.play() } else { self.pause(); Ok(true) } } /// Return the duration of this track /// /// This is cheap and exact. All processing is done before hand. #[allow(dead_code)] pub fn get_duration(&self) -> Duration { self.duration } /// Attempt to seek to a given duration /// /// This is pretty expensive, since due to technical limitations, this means resetting /// the player to the beginning before skipping the given duration. /// /// This can fail if there is no current output sink, and the attempt to access a /// new one fails. If this is the case, false will be returned, unless the reason the /// access failed was due to an error. pub fn seek(&mut self, duration: Duration) -> Result { let was_stopped = self.sink .as_mut() .map_or(false, |(s, _)| s.is_paused() || s.empty()); let song = self.song.clone(); if let Some(sink) = self.try_set_sink()? { sink.pause(); sink.append( song.skip_duration(duration) ); if was_stopped { self.playback_started = None; } else { sink.play(); self.playback_started = Some(Instant::now()); } self.start_position = duration; Ok(true) } else { Ok(false) } } /// Seek to a specific percentage (out of 1.0) /// /// Performs a [`seek()`] operation, seeking to a given percentage of the song's full /// length. See [`seek()`] for more details pub fn seek_percentage(&mut self, percent: f32) -> Result { self.seek( self.duration.mul_f32(percent) ) } /// How far into the song the playback head currently is /// /// Computes the duration of the song that is before the playback head. This is /// really an approximation based on how much time has elapsed since playback started, /// but it should be close enough for almost all purposes. pub fn position(&self) -> Duration { self.duration.min( self.start_position + self.playback_started.map_or( Duration::ZERO, |ts| Instant::now() - ts, ) ) } /// Computes the position as a fraction of the song duration /// /// 0.0 represents the beginning of the song, while 1.0 represents the end. See /// [`position()`] for more information. pub fn position_percent(&self) -> f32 { // nightly: self.position().div_duration_f32(self.duration) self.position().as_secs_f32() / self.duration.as_secs_f32() } /// Computes the exact duration of the current song. /// /// This is an EXPENSIVE, BLOCKING long-running function that should be run in a /// seperate thread. Hopefully this will be optimized in the future, but this is what /// we have for now. /// /// This does not set the duration of this song, so be sure to call [`set_duration()`] /// with the result of this call after it finishes executing. /// /// Note: This doesn't actually preform the calculation, but instead returns a /// `'static FnOnce` that can be used to do so, in order to simplify running the /// transaction in another thread. /// /// Technical Details: Currently, this involves decoding and inefficiently counting /// the number of samples in the song. Hopefully, we'll be able to do this more /// efficiently in the future, pending mostly on /// pub fn compute_duration(&self) -> impl FnOnce() -> Duration { let sample_rate = self.song.sample_rate() as u64; let n_channels = self.song.channels() as u64; let song_clone = self.song.clone(); move|| { let n_samples = song_clone.count() as u64; // expensive! let n_whole_seconds = n_samples / n_channels / sample_rate; let remaining_samples = n_samples % sample_rate; let n_nanos = remaining_samples * 1_000_000_000 / sample_rate; Duration::new(n_whole_seconds, n_nanos as u32) } } /// Set the duration of the song /// /// This struct does not automatically compute its own duration, so until this method /// is called, we just assume the duration is 2:30s for any calculations that require /// it. /// /// In order to use an exact duration, use another thread to run /// [`compute_duration()`] and pass the result into this method. pub fn set_duration(&mut self, duration: Duration) { self.duration = duration; } } #[derive(Debug)] pub enum PlayerError { Decoder(rodio::decoder::DecoderError), Play(rodio::PlayError), Stream(rodio::StreamError), } impl fmt::Display for PlayerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Decoder(e) => write!(f, "Could not decode the provided song: {}", e), Self::Play(e) => write!(f, "Problem playing to the audio output: {}", e), Self::Stream(e) => write!(f, "Problem connecting to the audio output: {}", e), } } } impl std::error::Error for PlayerError { }