Smarter handling of preloading and loading of tracks that are already loaded.

This commit is contained in:
Konstantin Seiler 2020-02-03 01:37:17 +11:00
parent 8756341201
commit 349e182d41

View file

@ -361,6 +361,7 @@ enum PlayerState {
EndOfTrack { EndOfTrack {
track_id: SpotifyId, track_id: SpotifyId,
play_request_id: u64, play_request_id: u64,
loaded_track: Option<PlayerLoadedTrackData>,
}, },
Invalid, Invalid,
} }
@ -419,11 +420,25 @@ impl PlayerState {
Playing { Playing {
track_id, track_id,
play_request_id, play_request_id,
decoder,
duration_ms,
bytes_per_second,
normalisation_factor,
stream_loader_controller,
stream_position_pcm,
.. ..
} => { } => {
*self = EndOfTrack { *self = EndOfTrack {
track_id, track_id,
play_request_id, play_request_id,
loaded_track: Some(PlayerLoadedTrackData {
decoder,
duration_ms,
bytes_per_second,
normalisation_factor,
stream_loader_controller,
stream_position_pcm,
}),
}; };
} }
_ => panic!("Called playing_to_end_of_track in non-playing state."), _ => panic!("Called playing_to_end_of_track in non-playing state."),
@ -884,6 +899,7 @@ impl PlayerInternal {
| PlayerState::EndOfTrack { | PlayerState::EndOfTrack {
track_id, track_id,
play_request_id, play_request_id,
..
} }
| PlayerState::Loading { | PlayerState::Loading {
track_id, track_id,
@ -902,6 +918,52 @@ impl PlayerInternal {
} }
} }
fn handle_play(&mut self) {
if let PlayerState::Paused {
track_id,
play_request_id,
stream_position_pcm,
..
} = self.state
{
self.state.paused_to_playing();
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
self.send_event(PlayerEvent::Started {
track_id,
play_request_id,
position_ms,
});
self.start_sink();
} else {
warn!("Player::play called from invalid state");
}
}
fn handle_pause(&mut self) {
if let PlayerState::Playing {
track_id,
play_request_id,
stream_position_pcm,
duration_ms,
..
} = self.state
{
self.state.playing_to_paused();
self.stop_sink_if_running();
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
self.send_event(PlayerEvent::Paused {
track_id,
play_request_id,
position_ms,
duration_ms,
});
} else {
warn!("Player::pause called from invalid state");
}
}
fn handle_packet(&mut self, packet: Option<VorbisPacket>, normalisation_factor: f32) { fn handle_packet(&mut self, packet: Option<VorbisPacket>, normalisation_factor: f32) {
match packet { match packet {
Some(mut packet) => { Some(mut packet) => {
@ -929,6 +991,7 @@ impl PlayerInternal {
if let PlayerState::EndOfTrack { if let PlayerState::EndOfTrack {
track_id, track_id,
play_request_id, play_request_id,
..
} = self.state } = self.state
{ {
self.send_event(PlayerEvent::EndOfTrack { self.send_event(PlayerEvent::EndOfTrack {
@ -1035,6 +1098,7 @@ impl PlayerInternal {
self.stop_sink_if_running(); self.stop_sink_if_running();
} }
// emit the correct player event
match self.state { match self.state {
PlayerState::Playing { PlayerState::Playing {
track_id: old_track_id, track_id: old_track_id,
@ -1064,25 +1128,105 @@ impl PlayerInternal {
} }
let mut load_command_processed = false; let mut load_command_processed = false;
if let PlayerPreload::Ready {
track_id: loaded_track_id, // Check if there's a matching loaded track in the EndOfTrack player state.
// This is the case if we're repeating the same track again.
if let PlayerState::EndOfTrack {
track_id: previous_track_id,
ref mut loaded_track,
.. ..
} = self.preload } = self.state
{ {
if (track_id == loaded_track_id) && (position_ms == 0) { if previous_track_id == track_id {
let mut preload = PlayerPreload::None; let loaded_track = mem::replace(&mut *loaded_track, None);
std::mem::swap(&mut preload, &mut self.preload); if let Some(mut loaded_track) = loaded_track {
if let PlayerPreload::Ready { if Self::position_ms_to_pcm(position_ms)
track_id, != loaded_track.stream_position_pcm
loaded_track,
} = preload
{ {
loaded_track
.stream_loader_controller
.set_random_access_mode();
let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking.
// But most likely the track is fully
// loaded already because we played
// to the end of it.
loaded_track.stream_loader_controller.set_stream_mode();
loaded_track.stream_position_pcm =
Self::position_ms_to_pcm(position_ms);
}
self.start_playback(track_id, play_request_id, loaded_track, play); self.start_playback(track_id, play_request_id, loaded_track, play);
load_command_processed = true; load_command_processed = true;
} }
} }
} }
// Check if we are already playing the track. If so, just do a seek and update our info.
if let PlayerState::Playing {
track_id: current_track_id,
play_request_id: ref mut current_play_request_id,
ref mut stream_position_pcm,
ref mut decoder,
ref mut stream_loader_controller,
..
}
| PlayerState::Paused {
track_id: current_track_id,
play_request_id: ref mut current_play_request_id,
ref mut stream_position_pcm,
ref mut decoder,
ref mut stream_loader_controller,
..
} = self.state
{
if current_track_id == track_id {
if Self::position_ms_to_pcm(position_ms) != *stream_position_pcm {
stream_loader_controller.set_random_access_mode();
let _ = decoder.seek(position_ms as i64); // This may be blocking.
stream_loader_controller.set_stream_mode();
*stream_position_pcm = Self::position_ms_to_pcm(position_ms);
}
*current_play_request_id = play_request_id;
if play {
self.handle_play();
} else {
self.handle_pause();
}
load_command_processed = true;
}
}
// Check if the requested track has been preloaded already. If so use the preloaded data.
if !load_command_processed {
if let PlayerPreload::Ready {
track_id: loaded_track_id,
..
} = self.preload
{
if track_id == loaded_track_id {
let preload = std::mem::replace(&mut self.preload, PlayerPreload::None);
if let PlayerPreload::Ready {
track_id,
mut loaded_track,
} = preload
{
if Self::position_ms_to_pcm(position_ms)
!= loaded_track.stream_position_pcm
{
loaded_track
.stream_loader_controller
.set_random_access_mode();
let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking
loaded_track.stream_loader_controller.set_stream_mode();
}
self.start_playback(track_id, play_request_id, loaded_track, play);
load_command_processed = true;
}
}
}
}
// We need to load the track - either from scratch or by completing a preload.
// In any case we go into a Loading state to load the track.
if !load_command_processed { if !load_command_processed {
self.send_event(PlayerEvent::Loading { self.send_event(PlayerEvent::Loading {
track_id, track_id,
@ -1112,8 +1256,9 @@ impl PlayerInternal {
self.preload = PlayerPreload::None; self.preload = PlayerPreload::None;
let loader = loader.or_else(|| Some(self.load_track(track_id, position_ms))); let loader = loader
let loader = loader.unwrap(); .or_else(|| Some(self.load_track(track_id, position_ms)))
.unwrap();
self.state = PlayerState::Loading { self.state = PlayerState::Loading {
track_id, track_id,
@ -1125,6 +1270,8 @@ impl PlayerInternal {
} }
PlayerCommand::Preload { track_id } => { PlayerCommand::Preload { track_id } => {
let mut preload_track = true;
if let PlayerPreload::Loading { if let PlayerPreload::Loading {
track_id: currently_loading, track_id: currently_loading,
.. ..
@ -1134,11 +1281,35 @@ impl PlayerInternal {
.. ..
} = self.preload } = self.preload
{ {
if currently_loading != track_id { if currently_loading == track_id {
// we're already loading the requested track.
preload_track = false;
} else {
// we're loading something else - cancel it.
self.preload = PlayerPreload::None; self.preload = PlayerPreload::None;
} }
} }
if let PlayerPreload::None = self.preload {
if let PlayerState::Playing {
track_id: current_track_id,
..
}
| PlayerState::Paused {
track_id: current_track_id,
..
}
| PlayerState::EndOfTrack {
track_id: current_track_id,
..
} = self.state
{
if current_track_id == track_id {
// we already have the requested track loaded.
preload_track = false;
}
}
if preload_track {
let loader = self.load_track(track_id, 0); let loader = self.load_track(track_id, 0);
self.preload = PlayerPreload::Loading { track_id, loader } self.preload = PlayerPreload::Loading { track_id, loader }
} }
@ -1210,49 +1381,11 @@ impl PlayerInternal {
} }
PlayerCommand::Play => { PlayerCommand::Play => {
if let PlayerState::Paused { self.handle_play();
track_id,
play_request_id,
stream_position_pcm,
..
} = self.state
{
self.state.paused_to_playing();
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
self.send_event(PlayerEvent::Started {
track_id,
play_request_id,
position_ms,
});
self.start_sink();
} else {
warn!("Player::play called from invalid state");
}
} }
PlayerCommand::Pause => { PlayerCommand::Pause => {
if let PlayerState::Playing { self.handle_pause();
track_id,
play_request_id,
stream_position_pcm,
duration_ms,
..
} = self.state
{
self.state.playing_to_paused();
self.stop_sink_if_running();
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
self.send_event(PlayerEvent::Paused {
track_id,
play_request_id,
position_ms,
duration_ms,
});
} else {
warn!("Player::pause called from invalid state");
}
} }
PlayerCommand::Stop => self.handle_player_stop(), PlayerCommand::Stop => self.handle_player_stop(),