/* window.vala * * Copyright 2024 Erica Z * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * SPDX-License-Identifier: AGPL-3.0-or-later */ [GtkTemplate (ui = "/eu/callcc/Wavelet/window.ui")] public class Wavelet.Window : Adw.ApplicationWindow { [GtkChild] private unowned Gtk.ListBox sidebar; [GtkChild] private unowned Gtk.ListBoxRow sidebar_setup; [GtkChild] private unowned Gtk.ListBoxRow sidebar_play_queue; [GtkChild] private unowned Gtk.Stack stack; [GtkChild] public unowned Wavelet.Setup setup; [GtkChild] public unowned Wavelet.PlayQueue play_queue; [GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks; [GtkChild] public unowned Gtk.Button mute; public bool playing { get; private set; default = false; } [GtkChild] private unowned Gtk.Scale play_position; public int64 position { get; private set; } public double volume { get; set; default = 1.0; } public Song? song { get; set; default = null; } private Cancellable cancel_loading_art; public bool cover_art_loading { get; set; default = false; } public Gdk.Paintable playing_cover_art { get; set; } private Gdk.Paintable next_cover_art; internal Playbin playbin { get; default = new Playbin (); } public Window (Gtk.Application app) { Object (application: app); } construct { this.setup.connected.connect ((api) => { public_api = api; this.shuffle_all_tracks.sensitive = true; this.shuffle_all_tracks.activated.connect (() => { this.shuffle_all_tracks.sensitive = false; this.play_queue.clear (); api.get_random_songs.begin (null, (song) => { this.play_queue.queue (song); }, (obj, res) => { try { api.get_random_songs.end (res); } catch (Error e) { error ("could not get random songs: %s", e.message); } this.shuffle_all_tracks.sensitive = true; this.play_queue.restart (); }); }); playbin.stream_started.connect (this.play_queue.on_stream_start); this.play_queue.now_playing.connect ((song) => { this.playing = true; this.song = song; }); this.play_queue.play_now.connect ((song) => { playbin.play_now.begin (api.stream_uri (song.id)); }); 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.setup.load (); this.sidebar.select_row (this.sidebar.get_row_at_index (0)); this.notify["song"].connect (() => { if (this.cancel_loading_art != null) { this.cancel_loading_art.cancel (); } this.cancel_loading_art = new Cancellable (); this.playing_cover_art = Gdk.Paintable.empty (100, 100); if (this.song != null) { this.cover_art_loading = true; string song_id = this.song.id; public_api.cover_art.begin (song_id, 100, this.cancel_loading_art, (obj, res) => { try { this.playing_cover_art = Gdk.Texture.for_pixbuf (public_api.cover_art.end (res)); this.cover_art_loading = false; } catch (Error e) { if (!(e is IOError.CANCELLED)) { warning ("could not load cover for %s: %s", song_id, e.message); this.cover_art_loading = false; } } }); } }); this.song = null; this.playbin.set_position.connect ((sender, new_position) => { // only set if we aren't seeking if (this.seek_timeout_id == 0) { this.position = new_position; } }); } public void show_mute () { this.mute.icon_name = "audio-volume-muted"; } public void show_unmute () { this.mute.icon_name = "audio-volume-high"; } [GtkCallback] private void on_sidebar_row_activated (Gtk.ListBoxRow row) { if (row == this.sidebar_setup) { this.stack.set_visible_child_name("setup"); } else if (row == this.sidebar_play_queue) { this.stack.set_visible_child_name("play_queue"); } } [GtkCallback] private string format_timestamp (int64 ns) { int64 ms = ns / 1000000; int s = (int) (ms / 1000); return "%02d:%02d".printf (s/60, s%60); } // same timeout logic as https://code.videolan.org/videolan/npapi-vlc/blob/6eae0ffb9cbaf8f6e04423de2ff38daabdf7cae3/npapi/vlcplugin_gtk.cpp#L312 private uint seek_timeout_id = 0; [GtkCallback] private bool on_play_position_seek (Gtk.Range range, Gtk.ScrollType scroll_type, double value) { if (this.seek_timeout_id == 0) { this.seek_timeout_id = Timeout.add (500, () => { playbin.seek.begin ((int64) range.adjustment.value, (obj, res) => { this.seek_timeout_id = 0; }); return false; }); } return false; } [GtkCallback] private void on_play_pause_clicked () { if (this.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 void on_skip_forward_clicked () { this.play_queue.skip_forward (); } [GtkCallback] private void on_skip_backward_clicked () { this.play_queue.skip_backward (); } }