From 8a0c748d5140f650762766bfc4724036f149da82 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Sun, 13 Oct 2024 15:21:29 +0000 Subject: [PATCH] hopefully streamline playbin state machine --- src/playbin.vala | 64 +++++++++++++++++++++++++--------------- src/ui/play_queue.vala | 66 ++++++++++++++++++++++-------------------- src/ui/window.blp | 9 ++++-- src/ui/window.vala | 53 +++++++++++++++++++++------------ 4 files changed, 116 insertions(+), 76 deletions(-) diff --git a/src/playbin.vala b/src/playbin.vala index 91339d2..a8b7a58 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -1,7 +1,13 @@ +enum PlaybinState { + STOPPED, + PAUSED, + PLAYING, +} + class Playbin : Object { // dynamic: undocumented vala feature // lets us access the about-to-finish signal - private dynamic Gst.Element playbin; + private dynamic Gst.Element playbin = Gst.ElementFactory.make ("playbin3", null); // used to prevent stale seeks private uint64 stream_counter = 0; @@ -21,21 +27,25 @@ class Playbin : Object { set { this.playbin.mute = value; } } + public PlaybinState state { get; private set; default = PlaybinState.STOPPED; } + private bool seeking = false; public int64 position { get; private set; } public int64 duration { get; private set; } - public signal void stream_started (string uri); - public signal void stream_over (); + private bool notify_next_transition; + + // public void begin_playback (string uri); + // public void prepare_next (string? next_uri); + public signal void song_transition (string uri); + public signal void playback_finished (); + // public void stop_playback (); private void source_setup (Gst.Element playbin, dynamic Gst.Element source) { source.user_agent = "audrey/linux"; } construct { - this.playbin = Gst.ElementFactory.make ("playbin3", null); - assert (this.playbin != null); - this.playbin.source_setup.connect (this.source_setup); this.playbin.about_to_finish.connect (this.about_to_finish); @@ -78,24 +88,13 @@ class Playbin : Object { Gst.State new_state; message.parse_state_changed (out old_state, out new_state, null); - switch (new_state) { - case Gst.State.NULL: - break; - - case Gst.State.READY: - break; - - case Gst.State.PAUSED: - break; - - case Gst.State.PLAYING: + if (new_state == Gst.State.PLAYING) { if (this.queued_seek_position < 0) { this.seeking = false; } else if (this.queued_seek_counter == this.stream_counter) { assert (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH, this.queued_seek_position)); } this.queued_seek_position = -1; - break; } }); @@ -106,11 +105,16 @@ class Playbin : Object { assert (this.playbin.query_duration (Gst.Format.TIME, out new_duration)); this.duration = new_duration; - this.stream_started ((string) this.playbin.current_uri); + if (notify_next_transition) { + this.song_transition ((string) this.playbin.current_uri); + } else { + notify_next_transition = true; + } }); bus.message["eos"].connect ((message) => { - this.stream_over (); + assert (notify_next_transition); + this.playback_finished (); }); } @@ -118,7 +122,9 @@ class Playbin : Object { private int64 queued_seek_position = -1; public void seek (int64 position) { - if (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH/* | Gst.SeekFlags.KEY_UNIT*/, position)) { + if (this.state == PlaybinState.STOPPED) return; + + if (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH, position)) { this.seeking = true; this.position = position; } else { @@ -128,18 +134,19 @@ class Playbin : Object { } } - public void play_now (string uri) { + public void begin_playback (string uri) { this.playbin.set_state (Gst.State.READY); this.playbin.uri = uri; this.playbin.set_state (Gst.State.PLAYING); - this.set_next_uri (uri); + this.state = PlaybinState.PLAYING; + this.notify_next_transition = false; } Mutex next_uri_lock; string? next_uri; - public void set_next_uri (string? next_uri) { + public void prepare_next (string? next_uri) { this.next_uri_lock.lock (); this.next_uri = next_uri; this.next_uri_lock.unlock (); @@ -158,10 +165,19 @@ class Playbin : Object { } public void pause () { + assert (this.state != PlaybinState.STOPPED); this.playbin.set_state (Gst.State.PAUSED); + this.state = PlaybinState.PAUSED; } public void play () { + assert (this.state != PlaybinState.STOPPED); this.playbin.set_state (Gst.State.PLAYING); + this.state = PlaybinState.PLAYING; + } + + public void stop_playback() { + this.playbin.set_state (Gst.State.READY); + this.state = PlaybinState.STOPPED; } } diff --git a/src/ui/play_queue.vala b/src/ui/play_queue.vala index 66eeae9..0b06ecc 100644 --- a/src/ui/play_queue.vala +++ b/src/ui/play_queue.vala @@ -2,14 +2,10 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel { public ListStore inner = new ListStore (typeof (Song)); public uint playing_index { get; private set; default = 0; } - private bool starting_stream = false; - - public signal void stop_stream (); - public signal void start_stream (Song song); - // public void on_song_start (); - public signal void now_playing (Song song); - public signal void prepare_next_song (Song? song); - // public void on_stream_end (); + public signal void begin_playback (Song song); + public signal void prepare_next (Song? song); + public signal void playback_continues (Song song); + public signal void stop_playback (); construct { this.inner.items_changed.connect ((position, removed, added) => { @@ -17,18 +13,17 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel { if (this.playing_index < position+removed) { this.playing_index = position; if (this.playing_index < this.inner.get_n_items ()) { - this.starting_stream = true; - this.now_playing ((Song) this.inner.get_item (this.playing_index)); - this.start_stream((Song) this.inner.get_item (this.playing_index)); + this.begin_playback ((Song) this.inner.get_item (this.playing_index)); + this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); } else { - this.stop_stream (); + this.stop_playback (); } } else { this.playing_index += added; this.playing_index -= removed; } } else { - this.prepare_next_song ((Song?) this.inner.get_item (this.playing_index+1)); + this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); } this.items_changed (position, removed, added); @@ -38,15 +33,17 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel { }); } - public void on_stream_start () { - if (!starting_stream) { - this.playing_index += 1; - this.selection_changed (this.playing_index-1, 2); - this.now_playing ((Song) this.inner.get_item (this.playing_index)); - } + public void song_transition () { + this.playing_index += 1; + this.selection_changed (this.playing_index-1, 2); + this.playback_continues ((Song) this.inner.get_item (this.playing_index)); + this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1)); + } - this.prepare_next_song ((Song?) this.inner.get_item (this.playing_index+1)); - starting_stream = false; + public void playback_finished () { + this.playing_index += 1; + assert (this.playing_index == this.inner.get_n_items ()); + this.selection_changed (this.playing_index-1, 1); } Gtk.Bitset get_selection_in_range (uint position, uint n_items) { @@ -78,9 +75,8 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel { } this.selection_changed (position, 1); - this.starting_stream = true; - this.now_playing ((Song) this.inner.get_item (this.playing_index)); - this.start_stream ((Song) this.inner.get_item (this.playing_index)); + this.begin_playback ((Song) this.inner.get_item (this.playing_index)); + this.prepare_next ((Song) this.inner.get_item (this.playing_index+1)); return true; } @@ -123,9 +119,10 @@ public class Ui.PlayQueue : Adw.NavigationPage { [GtkChild] private unowned Gtk.ColumnView view; PlayQueueStore store = new PlayQueueStore (); - public signal void play_now (Song song); - public signal void play_next (Song? song); - public signal void now_playing (Song song); + public signal void begin_playback (Song song); + public signal void prepare_next (Song? next_song); + public signal void playback_continues (Song song); + public signal void stop_playback (); public bool can_clear_all { get; private set; default = false; } @@ -137,17 +134,22 @@ public class Ui.PlayQueue : Adw.NavigationPage { this.store.inner.remove_all (); this.can_clear_all = false; - this.store.start_stream.connect ((song) => this.play_now(song)); - this.store.now_playing.connect ((song) => this.now_playing(song)); - this.store.prepare_next_song.connect ((song) => this.play_next(song)); + this.store.begin_playback.connect ((song) => this.begin_playback (song)); + this.store.prepare_next.connect ((next_song) => this.prepare_next (next_song)); + this.store.playback_continues.connect ((song) => this.playback_continues (song)); + this.store.stop_playback.connect (() => this.stop_playback ()); } public void queue (Song song) { this.store.inner.append (song); } - internal void on_stream_start () { - this.store.on_stream_start (); + internal void song_transition () { + this.store.song_transition (); + } + + internal void playback_finished () { + this.store.playback_finished (); } internal void restart () { diff --git a/src/ui/window.blp b/src/ui/window.blp index 4074847..a835f55 100644 --- a/src/ui/window.blp +++ b/src/ui/window.blp @@ -69,7 +69,7 @@ template $UiWindow: Adw.ApplicationWindow { } Picture { -paintable: bind template.playing_cover_art; + paintable: bind template.playing_cover_art; } }; } @@ -164,6 +164,7 @@ paintable: bind template.playing_cover_art; Button { icon-name: "media-skip-backward"; valign: center; + sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; clicked => $on_skip_backward_clicked (); } @@ -171,13 +172,15 @@ paintable: bind template.playing_cover_art; Button { icon-name: "media-seek-backward"; valign: center; + sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; clicked => $seek_backward (); } Button { - icon-name: bind $play_button_icon_name (template.playing) as ; + icon-name: bind $play_pause_icon_name (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; valign: center; + sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; clicked => $on_play_pause_clicked (); } @@ -185,6 +188,7 @@ paintable: bind template.playing_cover_art; Button { icon-name: "media-seek-forward"; valign: center; + sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; clicked => $seek_forward (); } @@ -192,6 +196,7 @@ paintable: bind template.playing_cover_art; Button { icon-name: "media-skip-forward"; valign: center; + sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ; clicked => $on_skip_forward_clicked (); } diff --git a/src/ui/window.vala b/src/ui/window.vala index 468145a..e236c2c 100644 --- a/src/ui/window.vala +++ b/src/ui/window.vala @@ -8,8 +8,6 @@ class Ui.Window : Adw.ApplicationWindow { [GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks; private Setup setup; - - public bool playing { get; private set; default = false; } public int64 position { get; private set; } @@ -82,23 +80,36 @@ class Ui.Window : Adw.ApplicationWindow { }); }); - playbin.stream_started.connect (this.play_queue.on_stream_start); + this.play_queue.begin_playback.connect ((song) => { + var uri = api.stream_uri (song.id); + this.playbin.begin_playback (uri); - this.play_queue.now_playing.connect ((song) => { - this.playing = true; this.song = song; api.scrobble.begin (song.id); }); - this.play_queue.play_now.connect ((song) => { - playbin.play_now (api.stream_uri (song.id)); + this.play_queue.prepare_next.connect ((next_song) => { + var next_uri = next_song == null ? null : api.stream_uri (next_song.id); + this.playbin.prepare_next (next_uri); }); - this.play_queue.play_next.connect ((song) => { - if (song == null) { - playbin.set_next_uri (null); - } else { - playbin.set_next_uri (api.stream_uri (song.id)); - } + + this.playbin.song_transition.connect ((uri) => { + this.play_queue.song_transition (); + }); + + this.play_queue.playback_continues.connect ((song) => { + this.song = song; + api.scrobble.begin (song.id); + }); + + this.playbin.playback_finished.connect (() => { + this.play_queue.playback_finished (); + this.song = null; + }); + + this.play_queue.stop_playback.connect (() => { + this.playbin.stop_playback (); + this.song = null; }); }); this.setup.load (); @@ -166,17 +177,23 @@ class Ui.Window : Adw.ApplicationWindow { } [GtkCallback] private void on_play_pause_clicked () { - if (this.playing) { + if (this.playbin.state == PlaybinState.PLAYING) { this.playbin.pause(); - this.playing = false; } else { this.playbin.play(); - this.playing = true; } } - [GtkCallback] private string play_button_icon_name (bool playing) { - return playing ? "media-playback-pause" : "media-playback-start"; + [GtkCallback] private string play_pause_icon_name (PlaybinState state) { + if (state == PlaybinState.PLAYING) { + return "media-playback-pause"; + } else { + return "media-playback-start"; + } + } + + [GtkCallback] private bool playbin_active (PlaybinState state) { + return state != PlaybinState.STOPPED; } [GtkCallback] private string mute_button_icon_name (bool mute) {