From 7f7a84d74716dc06737e953b09014fca25bea4b1 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Wed, 16 Oct 2024 11:11:52 +0200 Subject: [PATCH] make playbin authoritative on the current play queue position --- src/play_queue.vala | 54 ++++++++++++++++---------------------------- src/playbin.vala | 55 ++++++++++++++++++++++++++++----------------- src/ui/window.vala | 14 ++++++------ 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/play_queue.vala b/src/play_queue.vala index 1e49892..0a8df44 100644 --- a/src/play_queue.vala +++ b/src/play_queue.vala @@ -15,51 +15,35 @@ internal class Audrey.PlayQueue : GLib.Object, GLib.ListModel, Gtk.SelectionMode this.inner.items_changed.connect (this.on_inner_items_changed); } - // only called by playbin on track transition + // only called by playbin // does not emit user_selected - internal void playbin_advance (uint new_position) - requires (this.current_position < this.get_n_items ()) - requires (this.current_position+1 == new_position) - ensures (this.current_position == new_position) - { - this.current_position += 1; - if (this.current_position < this.inner.get_n_items ()) { - // previous track unselected, this newly selected - this.selection_changed (this.current_position-1, 2); + internal void playbin_select (uint new_position) { + var previous_position = this.current_position; + this.current_position = new_position; + + if (previous_position < this.inner.get_n_items ()) { + if (new_position < this.inner.get_n_items ()) { + if (previous_position < new_position) { + this.selection_changed (previous_position, new_position-previous_position+1); + } else { + this.selection_changed (new_position, previous_position-new_position+1); + } + } else { + this.selection_changed (previous_position, 1); + } } else { - // end of play queue reached - // only previous track unselected - this.selection_changed (this.current_position-1, 1); + if (new_position < this.inner.get_n_items ()) { + this.selection_changed (new_position, 1); + } } } // called by anything else that wishes to switch tracks // emits user_selected public void user_select (uint position) - requires (position <= this.get_n_items ()) + requires (position < this.get_n_items ()) { - var previous_position = this.current_position; - this.current_position = position; this.user_selected (position); - - if (previous_position == this.get_n_items ()) { - // nothing selected before - if (position == this.get_n_items ()) { - // ...nothing selected after - } else { - // select new - this.selection_changed (position, 1); - } - } else { - if (position == this.get_n_items ()) { - // unselect previous - this.selection_changed (previous_position, 1); - } else if (position < previous_position) { - this.selection_changed (position, previous_position-position+1); - } else { - this.selection_changed (previous_position, position-previous_position+1); - } - } } private void on_inner_items_changed (GLib.ListModel inner, uint position, uint removed, uint added) { diff --git a/src/playbin.vala b/src/playbin.vala index 741b08c..d1e74c9 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -25,18 +25,21 @@ class Playbin : GLib.Object { public PlaybinState state { get; private set; default = PlaybinState.STOPPED; } - // true if a timer should update the postion property + // true if a timer should update the position property private bool update_position = false; public int64 position { get; private set; default = 0; } public Subsonic api { get; set; default = null; } // sent when a new song starts playing + // continues: whether the track is a gapless continuation public signal void now_playing (bool continues, uint index, Song song, int64 duration); - // FIXME this should be synced with the selection model, right?? - public uint playing_index { get; private set; } + // the index of the track in the play queue that is currently playing + // must equal play queue len iff state is STOPPED + public uint current_position { get; private set; } + // whether we are expecting a gapless continuation next private bool next_gapless; private void source_setup (Gst.Element playbin, dynamic Gst.Element source) { @@ -45,7 +48,7 @@ class Playbin : GLib.Object { // ASSUMPTION: about-to-finish will be signalled exactly once per track // even if seeking backwards after - GLib.AsyncQueue next_uri = new GLib.AsyncQueue (); + private GLib.AsyncQueue next_uri = new GLib.AsyncQueue (); private ListModel _play_queue = null; private ulong _play_queue_items_changed; @@ -79,27 +82,30 @@ class Playbin : GLib.Object { return; } - if (this.playing_index >= position) { - if (this.playing_index < position+removed) { + if (this.current_position >= position) { + if (this.current_position < position+removed) { // current track was removed, start playing something else // TODO check if it was actually reordered - this.begin_playback (position); + if (position == play_queue.get_n_items ()) { + this.stop (); + } else { + this.select_track (position); + } } else { // unaffected // fix up playing index though - this.playing_index += added; - this.playing_index -= removed; + this.current_position = this.current_position + added - removed; } - } else if (this.playing_index+1 == position) { + } else if (this.current_position+1 == position) { // next track was changed // try to fix up gapless transition string? next_uri = this.next_uri.try_pop (); if (next_uri != null) { // we're in luck, about-to-finish hasn't been triggered yet // we can get away with replacing it - if (this.playing_index+1 < play_queue.get_n_items ()) { - Song song = (Song) play_queue.get_item (this.playing_index+1); + if (this.current_position+1 < play_queue.get_n_items ()) { + Song song = (Song) play_queue.get_item (this.current_position+1); this.next_uri.push (this.api.stream_uri (song.id)); } else { this.next_uri.push (""); @@ -165,17 +171,17 @@ class Playbin : GLib.Object { bool continues = this.next_gapless; if (this.next_gapless) { // advance position in play queue - this.playing_index += 1; + this.current_position += 1; } else { this.next_gapless = true; } - var now_playing = (Song) play_queue.get_item (this.playing_index); + var now_playing = (Song) play_queue.get_item (this.current_position); if (this.api.stream_uri (now_playing.id) == (string) this.playbin.current_uri) { - this.now_playing (continues, this.playing_index, now_playing, duration); + this.now_playing (continues, this.current_position, now_playing, duration); - if (this.playing_index+1 < play_queue.get_n_items ()) { - Song song = (Song) play_queue.get_item (this.playing_index+1); + if (this.current_position+1 < play_queue.get_n_items ()) { + Song song = (Song) play_queue.get_item (this.current_position+1); this.next_uri.push (this.api.stream_uri (song.id)); } else { this.next_uri.push (""); @@ -183,7 +189,7 @@ class Playbin : GLib.Object { } else { // edge case // just flush everything and pray next stream-start is fine - this.begin_playback (this.playing_index); + this.select_track (this.current_position); } }); @@ -207,10 +213,13 @@ class Playbin : GLib.Object { } } - public void begin_playback (uint position) { + // manually changes which track in the play queue to play + public void select_track (uint position) + requires (position < this.play_queue.get_n_items ()) + { this.state = PlaybinState.PLAYING; - this.playing_index = position; + this.current_position = position; this.playbin.set_state (Gst.State.READY); this.playbin.uri = this.api.stream_uri (((Song) this.play_queue.get_item (position)).id); this.playbin.set_state (Gst.State.PLAYING); @@ -234,4 +243,10 @@ class Playbin : GLib.Object { this.playbin.set_state (Gst.State.PLAYING); this.state = PlaybinState.PLAYING; } + + public void stop () { + this.playbin.set_state (Gst.State.READY); + this.state = PlaybinState.STOPPED; + this.current_position = this.play_queue.get_n_items (); + } } diff --git a/src/ui/window.vala b/src/ui/window.vala index a70f8ed..70f23c4 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -65,13 +65,11 @@ class Ui.Window : Adw.ApplicationWindow { this.song = song; this.duration = duration; api.scrobble.begin (song.id); - if (continues) { - this.play_queue_model.playbin_advance (position); - } + this.play_queue_model.playbin_select (position); }); this.play_queue_model.user_selected.connect ((position) => { - this.playbin.begin_playback (position); + this.playbin.select_track (position); }); public_api = api; @@ -191,12 +189,14 @@ class Ui.Window : Adw.ApplicationWindow { } [GtkCallback] private void on_skip_forward_clicked () { - this.play_queue_model.user_select (this.playbin.playing_index+1); + if (this.playbin.current_position+1 < this.playbin.play_queue.get_n_items ()) { + this.playbin.select_track (this.playbin.current_position+1); + } } [GtkCallback] private void on_skip_backward_clicked () { - if (this.playbin.playing_index > 0) { - this.play_queue_model.user_select (this.playbin.playing_index-1); + if (this.playbin.current_position > 0) { + this.play_queue_model.user_select (this.playbin.current_position-1); } }