hopefully streamline playbin state machine
This commit is contained in:
parent
1584ebc161
commit
353596d65d
4 changed files with 116 additions and 76 deletions
|
@ -1,7 +1,13 @@
|
||||||
|
enum PlaybinState {
|
||||||
|
STOPPED,
|
||||||
|
PAUSED,
|
||||||
|
PLAYING,
|
||||||
|
}
|
||||||
|
|
||||||
class Playbin : Object {
|
class Playbin : Object {
|
||||||
// dynamic: undocumented vala feature
|
// dynamic: undocumented vala feature
|
||||||
// lets us access the about-to-finish signal
|
// 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
|
// used to prevent stale seeks
|
||||||
private uint64 stream_counter = 0;
|
private uint64 stream_counter = 0;
|
||||||
|
@ -21,21 +27,25 @@ class Playbin : Object {
|
||||||
set { this.playbin.mute = value; }
|
set { this.playbin.mute = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlaybinState state { get; private set; default = PlaybinState.STOPPED; }
|
||||||
|
|
||||||
private bool seeking = false;
|
private bool seeking = false;
|
||||||
public int64 position { get; private set; }
|
public int64 position { get; private set; }
|
||||||
public int64 duration { get; private set; }
|
public int64 duration { get; private set; }
|
||||||
|
|
||||||
public signal void stream_started (string uri);
|
private bool notify_next_transition;
|
||||||
public signal void stream_over ();
|
|
||||||
|
// 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) {
|
private void source_setup (Gst.Element playbin, dynamic Gst.Element source) {
|
||||||
source.user_agent = "audrey/linux";
|
source.user_agent = "audrey/linux";
|
||||||
}
|
}
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.playbin = Gst.ElementFactory.make ("playbin3", null);
|
|
||||||
assert (this.playbin != null);
|
|
||||||
|
|
||||||
this.playbin.source_setup.connect (this.source_setup);
|
this.playbin.source_setup.connect (this.source_setup);
|
||||||
this.playbin.about_to_finish.connect (this.about_to_finish);
|
this.playbin.about_to_finish.connect (this.about_to_finish);
|
||||||
|
|
||||||
|
@ -78,24 +88,13 @@ class Playbin : Object {
|
||||||
Gst.State new_state;
|
Gst.State new_state;
|
||||||
message.parse_state_changed (out old_state, out new_state, null);
|
message.parse_state_changed (out old_state, out new_state, null);
|
||||||
|
|
||||||
switch (new_state) {
|
if (new_state == Gst.State.PLAYING) {
|
||||||
case Gst.State.NULL:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Gst.State.READY:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Gst.State.PAUSED:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Gst.State.PLAYING:
|
|
||||||
if (this.queued_seek_position < 0) {
|
if (this.queued_seek_position < 0) {
|
||||||
this.seeking = false;
|
this.seeking = false;
|
||||||
} else if (this.queued_seek_counter == this.stream_counter) {
|
} else if (this.queued_seek_counter == this.stream_counter) {
|
||||||
assert (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH, this.queued_seek_position));
|
assert (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH, this.queued_seek_position));
|
||||||
}
|
}
|
||||||
this.queued_seek_position = -1;
|
this.queued_seek_position = -1;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -106,11 +105,16 @@ class Playbin : Object {
|
||||||
assert (this.playbin.query_duration (Gst.Format.TIME, out new_duration));
|
assert (this.playbin.query_duration (Gst.Format.TIME, out new_duration));
|
||||||
this.duration = 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) => {
|
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;
|
private int64 queued_seek_position = -1;
|
||||||
|
|
||||||
public void seek (int64 position) {
|
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.seeking = true;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
} else {
|
} 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.set_state (Gst.State.READY);
|
||||||
this.playbin.uri = uri;
|
this.playbin.uri = uri;
|
||||||
this.playbin.set_state (Gst.State.PLAYING);
|
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;
|
Mutex next_uri_lock;
|
||||||
string? next_uri;
|
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_lock.lock ();
|
||||||
this.next_uri = next_uri;
|
this.next_uri = next_uri;
|
||||||
this.next_uri_lock.unlock ();
|
this.next_uri_lock.unlock ();
|
||||||
|
@ -158,10 +165,19 @@ class Playbin : Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause () {
|
public void pause () {
|
||||||
|
assert (this.state != PlaybinState.STOPPED);
|
||||||
this.playbin.set_state (Gst.State.PAUSED);
|
this.playbin.set_state (Gst.State.PAUSED);
|
||||||
|
this.state = PlaybinState.PAUSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void play () {
|
public void play () {
|
||||||
|
assert (this.state != PlaybinState.STOPPED);
|
||||||
this.playbin.set_state (Gst.State.PLAYING);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,10 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel {
|
||||||
public ListStore inner = new ListStore (typeof (Song));
|
public ListStore inner = new ListStore (typeof (Song));
|
||||||
public uint playing_index { get; private set; default = 0; }
|
public uint playing_index { get; private set; default = 0; }
|
||||||
|
|
||||||
private bool starting_stream = false;
|
public signal void begin_playback (Song song);
|
||||||
|
public signal void prepare_next (Song? song);
|
||||||
public signal void stop_stream ();
|
public signal void playback_continues (Song song);
|
||||||
public signal void start_stream (Song song);
|
public signal void stop_playback ();
|
||||||
// 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 ();
|
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.inner.items_changed.connect ((position, removed, added) => {
|
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) {
|
if (this.playing_index < position+removed) {
|
||||||
this.playing_index = position;
|
this.playing_index = position;
|
||||||
if (this.playing_index < this.inner.get_n_items ()) {
|
if (this.playing_index < this.inner.get_n_items ()) {
|
||||||
this.starting_stream = true;
|
this.begin_playback ((Song) this.inner.get_item (this.playing_index));
|
||||||
this.now_playing ((Song) this.inner.get_item (this.playing_index));
|
this.prepare_next ((Song?) this.inner.get_item (this.playing_index+1));
|
||||||
this.start_stream((Song) this.inner.get_item (this.playing_index));
|
|
||||||
} else {
|
} else {
|
||||||
this.stop_stream ();
|
this.stop_playback ();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.playing_index += added;
|
this.playing_index += added;
|
||||||
this.playing_index -= removed;
|
this.playing_index -= removed;
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
this.items_changed (position, removed, added);
|
||||||
|
@ -38,15 +33,17 @@ class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void on_stream_start () {
|
public void song_transition () {
|
||||||
if (!starting_stream) {
|
this.playing_index += 1;
|
||||||
this.playing_index += 1;
|
this.selection_changed (this.playing_index-1, 2);
|
||||||
this.selection_changed (this.playing_index-1, 2);
|
this.playback_continues ((Song) this.inner.get_item (this.playing_index));
|
||||||
this.now_playing ((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));
|
public void playback_finished () {
|
||||||
starting_stream = false;
|
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) {
|
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.selection_changed (position, 1);
|
||||||
|
|
||||||
this.starting_stream = true;
|
this.begin_playback ((Song) this.inner.get_item (this.playing_index));
|
||||||
this.now_playing ((Song) this.inner.get_item (this.playing_index));
|
this.prepare_next ((Song) this.inner.get_item (this.playing_index+1));
|
||||||
this.start_stream ((Song) this.inner.get_item (this.playing_index));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -123,9 +119,10 @@ public class Ui.PlayQueue : Adw.NavigationPage {
|
||||||
[GtkChild] private unowned Gtk.ColumnView view;
|
[GtkChild] private unowned Gtk.ColumnView view;
|
||||||
PlayQueueStore store = new PlayQueueStore ();
|
PlayQueueStore store = new PlayQueueStore ();
|
||||||
|
|
||||||
public signal void play_now (Song song);
|
public signal void begin_playback (Song song);
|
||||||
public signal void play_next (Song? song);
|
public signal void prepare_next (Song? next_song);
|
||||||
public signal void now_playing (Song song);
|
public signal void playback_continues (Song song);
|
||||||
|
public signal void stop_playback ();
|
||||||
|
|
||||||
public bool can_clear_all { get; private set; default = false; }
|
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.store.inner.remove_all ();
|
||||||
this.can_clear_all = false;
|
this.can_clear_all = false;
|
||||||
|
|
||||||
this.store.start_stream.connect ((song) => this.play_now(song));
|
this.store.begin_playback.connect ((song) => this.begin_playback (song));
|
||||||
this.store.now_playing.connect ((song) => this.now_playing(song));
|
this.store.prepare_next.connect ((next_song) => this.prepare_next (next_song));
|
||||||
this.store.prepare_next_song.connect ((song) => this.play_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) {
|
public void queue (Song song) {
|
||||||
this.store.inner.append (song);
|
this.store.inner.append (song);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void on_stream_start () {
|
internal void song_transition () {
|
||||||
this.store.on_stream_start ();
|
this.store.song_transition ();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void playback_finished () {
|
||||||
|
this.store.playback_finished ();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void restart () {
|
internal void restart () {
|
||||||
|
|
|
@ -69,7 +69,7 @@ template $UiWindow: Adw.ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
Picture {
|
Picture {
|
||||||
paintable: bind template.playing_cover_art;
|
paintable: bind template.playing_cover_art;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,7 @@ paintable: bind template.playing_cover_art;
|
||||||
Button {
|
Button {
|
||||||
icon-name: "media-skip-backward";
|
icon-name: "media-skip-backward";
|
||||||
valign: center;
|
valign: center;
|
||||||
|
sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as <bool>;
|
||||||
|
|
||||||
clicked => $on_skip_backward_clicked ();
|
clicked => $on_skip_backward_clicked ();
|
||||||
}
|
}
|
||||||
|
@ -171,13 +172,15 @@ paintable: bind template.playing_cover_art;
|
||||||
Button {
|
Button {
|
||||||
icon-name: "media-seek-backward";
|
icon-name: "media-seek-backward";
|
||||||
valign: center;
|
valign: center;
|
||||||
|
sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as <bool>;
|
||||||
|
|
||||||
clicked => $seek_backward ();
|
clicked => $seek_backward ();
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
icon-name: bind $play_button_icon_name (template.playing) as <string>;
|
icon-name: bind $play_pause_icon_name (template.playbin as <$Playbin>.state as <$PlaybinState>) as <string>;
|
||||||
valign: center;
|
valign: center;
|
||||||
|
sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as <bool>;
|
||||||
|
|
||||||
clicked => $on_play_pause_clicked ();
|
clicked => $on_play_pause_clicked ();
|
||||||
}
|
}
|
||||||
|
@ -185,6 +188,7 @@ paintable: bind template.playing_cover_art;
|
||||||
Button {
|
Button {
|
||||||
icon-name: "media-seek-forward";
|
icon-name: "media-seek-forward";
|
||||||
valign: center;
|
valign: center;
|
||||||
|
sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as <bool>;
|
||||||
|
|
||||||
clicked => $seek_forward ();
|
clicked => $seek_forward ();
|
||||||
}
|
}
|
||||||
|
@ -192,6 +196,7 @@ paintable: bind template.playing_cover_art;
|
||||||
Button {
|
Button {
|
||||||
icon-name: "media-skip-forward";
|
icon-name: "media-skip-forward";
|
||||||
valign: center;
|
valign: center;
|
||||||
|
sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as <bool>;
|
||||||
|
|
||||||
clicked => $on_skip_forward_clicked ();
|
clicked => $on_skip_forward_clicked ();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ class Ui.Window : Adw.ApplicationWindow {
|
||||||
[GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks;
|
[GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks;
|
||||||
|
|
||||||
private Setup setup;
|
private Setup setup;
|
||||||
|
|
||||||
public bool playing { get; private set; default = false; }
|
|
||||||
|
|
||||||
public int64 position { get; private set; }
|
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;
|
this.song = song;
|
||||||
api.scrobble.begin (song.id);
|
api.scrobble.begin (song.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.play_queue.play_now.connect ((song) => {
|
this.play_queue.prepare_next.connect ((next_song) => {
|
||||||
playbin.play_now (api.stream_uri (song.id));
|
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) {
|
this.playbin.song_transition.connect ((uri) => {
|
||||||
playbin.set_next_uri (null);
|
this.play_queue.song_transition ();
|
||||||
} else {
|
});
|
||||||
playbin.set_next_uri (api.stream_uri (song.id));
|
|
||||||
}
|
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 ();
|
this.setup.load ();
|
||||||
|
@ -166,17 +177,23 @@ class Ui.Window : Adw.ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback] private void on_play_pause_clicked () {
|
[GtkCallback] private void on_play_pause_clicked () {
|
||||||
if (this.playing) {
|
if (this.playbin.state == PlaybinState.PLAYING) {
|
||||||
this.playbin.pause();
|
this.playbin.pause();
|
||||||
this.playing = false;
|
|
||||||
} else {
|
} else {
|
||||||
this.playbin.play();
|
this.playbin.play();
|
||||||
this.playing = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback] private string play_button_icon_name (bool playing) {
|
[GtkCallback] private string play_pause_icon_name (PlaybinState state) {
|
||||||
return playing ? "media-playback-pause" : "media-playback-start";
|
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) {
|
[GtkCallback] private string mute_button_icon_name (bool mute) {
|
||||||
|
|
Loading…
Reference in a new issue