shuffle gapless playback

This commit is contained in:
Erica Z 2024-10-12 12:57:37 +00:00
parent c1e023276d
commit 216be6798e
4 changed files with 37 additions and 13 deletions

View file

@ -11,12 +11,14 @@ template $WaveletPlayQueue: Adw.NavigationPage {
ScrolledWindow { ScrolledWindow {
ListView list_view { ListView list_view {
single-click-activate: true; single-click-activate: true;
show-separators: true;
activate => $on_song_activate (); activate => $on_song_activate ();
factory: BuilderListItemFactory { factory: BuilderListItemFactory {
template ListItem { template ListItem {
child: Label { child: Label {
styles [ "bold" ]
halign: start; halign: start;
label: bind template.item as <$WaveletSong>.title; label: bind template.item as <$WaveletSong>.title;
}; };

View file

@ -23,7 +23,9 @@ public class Wavelet.PlayQueue : Adw.NavigationPage {
[GtkChild] private unowned Gtk.ListView list_view; [GtkChild] private unowned Gtk.ListView list_view;
private ListStore songs; private ListStore songs;
private uint next_song;
// this is the index of the song that will play on next on_stream_start
private uint next_stream_index;
public signal void play_now (Song song); public signal void play_now (Song song);
public signal void now_playing (Song song); public signal void now_playing (Song song);
@ -31,7 +33,7 @@ public class Wavelet.PlayQueue : Adw.NavigationPage {
construct { construct {
this.songs = new ListStore (typeof (Song)); this.songs = new ListStore (typeof (Song));
this.next_song = 0; this.next_stream_index = 0;
this.list_view.model = new Gtk.NoSelection (this.songs); this.list_view.model = new Gtk.NoSelection (this.songs);
} }
@ -41,22 +43,34 @@ public class Wavelet.PlayQueue : Adw.NavigationPage {
} }
public void queue (Song song) { public void queue (Song song) {
uint new_index = this.songs.get_n_items ();
this.songs.append (song); this.songs.append (song);
if (new_index == next_stream_index) {
this.play_next (song);
}
} }
[GtkCallback] private void on_song_activate (uint position) { [GtkCallback] private void on_song_activate (uint position) {
this.next_song = position; this.next_stream_index = position;
Song song = (Song) this.songs.get_item (position); Song song = (Song) this.songs.get_item (position);
this.play_next (song);
this.play_now (song); this.play_now (song);
} }
internal void on_stream_start (Playbin playbin) { internal void on_stream_start (Playbin playbin) {
Song song = (Song) this.songs.get_item (this.next_song); Song song = (Song) this.songs.get_item (this.next_stream_index);
this.now_playing (song); this.now_playing (song);
// prepare for next song gapless // prepare for next song ahead of time (gapless)
this.next_song += 1; this.next_stream_index += 1;
Song? next_song = (Song?) this.songs.get_item (this.next_song); Song? next_song = (Song?) this.songs.get_item (this.next_stream_index);
this.play_next (next_song); this.play_next (next_song);
} }
internal void restart () {
this.next_stream_index = 0;
Song song = (Song) this.songs.get_item (0);
this.play_next (song);
this.play_now (song);
}
} }

View file

@ -44,6 +44,7 @@ class Playbin : Object {
public int64 duration { get; private set; } public int64 duration { get; private set; }
public signal void stream_started (); public signal void stream_started ();
public signal void stream_over ();
construct { construct {
this.playbin = Gst.ElementFactory.make ("playbin3", null); this.playbin = Gst.ElementFactory.make ("playbin3", null);
@ -81,8 +82,6 @@ class Playbin : Object {
} }
if (Gst.MessageType.STREAM_START in message.type) { if (Gst.MessageType.STREAM_START in message.type) {
print ("stream start\n");
int64 new_duration; int64 new_duration;
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;
@ -95,7 +94,16 @@ class Playbin : Object {
} }
if (Gst.MessageType.EOS in message.type) { if (Gst.MessageType.EOS in message.type) {
print ("eos\n"); string next_uri;
this.next_uri_lock.lock ();
next_uri = this.next_uri;
this.next_uri_lock.unlock ();
if (next_uri == null) {
// no next track was arranged, we're done
this.stream_over ();
}
} }
return true; return true;
@ -181,8 +189,6 @@ class Playbin : Object {
// called when uri can be switched for gapless playback // called when uri can be switched for gapless playback
// need async queue because this might be called from a gstreamer thread // need async queue because this might be called from a gstreamer thread
private void on_about_to_finish (dynamic Gst.Element playbin) { private void on_about_to_finish (dynamic Gst.Element playbin) {
print("about to finish\n");
this.next_uri_lock.lock (); this.next_uri_lock.lock ();
string? next_uri = this.next_uri; string? next_uri = this.next_uri;
this.next_uri_lock.unlock (); this.next_uri_lock.unlock ();

View file

@ -41,6 +41,7 @@ public class Wavelet.Window : Adw.ApplicationWindow {
private Cancellable cancel_loading_art; private Cancellable cancel_loading_art;
public bool cover_art_loading { get; set; default = false; } public bool cover_art_loading { get; set; default = false; }
public Gdk.Paintable playing_cover_art { get; set; } public Gdk.Paintable playing_cover_art { get; set; }
private Gdk.Paintable next_cover_art;
internal Playbin playbin { get; default = new Playbin (); } internal Playbin playbin { get; default = new Playbin (); }
@ -65,13 +66,14 @@ public class Wavelet.Window : Adw.ApplicationWindow {
error ("could not get random songs: %s", e.message); error ("could not get random songs: %s", e.message);
} }
this.shuffle_all_tracks.sensitive = true; this.shuffle_all_tracks.sensitive = true;
this.play_queue.restart ();
}); });
}); });
playbin.stream_started.connect (this.play_queue.on_stream_start); playbin.stream_started.connect (this.play_queue.on_stream_start);
this.play_queue.now_playing.connect ((song) => { this.play_queue.now_playing.connect ((song) => {
print ("now playing %s\n", song.title);
this.song = song; this.song = song;
}); });