audrey/src/playbin.vala

208 lines
7.5 KiB
Vala
Raw Normal View History

enum PlaybinState {
STOPPED,
PAUSED,
PLAYING,
}
class Playbin : GLib.Object {
2024-10-17 10:05:20 +00:00
private Mpv.Handle mpv = new Mpv.Handle ();
2024-10-12 12:28:05 +00:00
2024-10-17 10:05:20 +00:00
public PlaybinState state { get; private set; default = PlaybinState.STOPPED; }
private int _volume = 100;
public int volume {
get { return _volume; }
2024-10-12 12:28:05 +00:00
set {
2024-10-18 19:54:12 +00:00
var ret = mpv.set_property_int64 ("volume", value);
if (ret >= 0) {
_volume = value;
} else {
warning ("failed to set volume: %s", ret.to_string ());
}
2024-10-12 12:28:05 +00:00
}
}
2024-10-17 10:05:20 +00:00
public bool _mute = false;
2024-10-12 12:28:05 +00:00
public bool mute {
2024-10-17 10:05:20 +00:00
get { return _mute; }
set {
2024-10-18 19:54:12 +00:00
var ret = mpv.set_property_flag ("mute", value);
if (ret >= 0) {
_mute = value;
} else {
warning ("failed to set mute status: %s", ret.to_string ());
}
2024-10-17 10:05:20 +00:00
}
2024-10-12 12:28:05 +00:00
}
2024-10-17 10:05:20 +00:00
public uint play_queue_position { get; private set; }
public Subsonic.Song? song { get; private set; }
private bool notify_next_playing;
2024-10-17 10:05:20 +00:00
public signal void now_playing ();
2024-10-16 10:37:39 +00:00
2024-10-17 10:05:20 +00:00
public double position { get; private set; default = 0.0; }
public double duration { get; private set; default = 0.0; }
2024-10-13 17:00:47 +00:00
2024-10-16 11:02:32 +00:00
public Subsonic.Client api { get; set; default = null; }
2024-10-15 20:45:16 +00:00
private ListModel _play_queue = null;
public ListModel play_queue {
get { return _play_queue; }
set {
2024-10-17 10:05:20 +00:00
assert (_play_queue == null); // only set this once
2024-10-15 20:45:16 +00:00
_play_queue = value;
2024-10-17 10:05:20 +00:00
value.items_changed.connect (on_play_queue_items_changed);
2024-10-15 20:45:16 +00:00
}
}
2024-10-17 10:05:20 +00:00
private void on_play_queue_items_changed (ListModel play_queue, uint position, uint removed, uint added) {
2024-10-17 20:32:51 +00:00
for (uint i = 0; i < removed; i += 1) {
assert (this.mpv.command ({
"playlist-remove",
position.to_string (),
}) >= 0);
}
for (uint i = 0; i < added; i += 1) {
assert (this.mpv.command ({
"loadfile",
this.api.stream_uri (((Subsonic.Song) play_queue.get_item (position+i)).id),
"insert-at-play",
(position+i).to_string (),
}) >= 0);
2024-10-17 10:05:20 +00:00
}
}
2024-10-17 06:06:09 +00:00
2024-10-17 10:05:20 +00:00
public Playbin () {
assert (this.mpv.initialize () >= 0);
assert (this.mpv.set_property_string ("user-agent", Audrey.Const.user_agent) >= 0);
assert (this.mpv.set_property_string ("video", "no") >= 0);
assert (this.mpv.set_property_string ("prefetch-playlist", "yes") >= 0);
assert (this.mpv.set_property_string ("gapless-audio", "yes") >= 0);
assert (this.mpv.observe_property (0, "time-pos", Mpv.Format.DOUBLE) >= 0);
assert (this.mpv.observe_property (1, "duration", Mpv.Format.DOUBLE) >= 0);
assert (this.mpv.observe_property (2, "playlist-pos", Mpv.Format.INT64) >= 0);
2024-10-17 10:05:20 +00:00
this.mpv.wakeup_callback = () => {
Idle.add (() => {
while (true) {
var event = this.mpv.wait_event (0.0);
if (event.event_id == Mpv.EventId.NONE) break;
switch (event.event_id) {
case Mpv.EventId.PROPERTY_CHANGE:
2024-10-17 20:32:51 +00:00
var data = event.parse_property ();
2024-10-17 10:05:20 +00:00
switch (event.reply_userdata) {
case 0:
assert (data.name == "time-pos");
if (data.format == Mpv.Format.NONE) {
this.position = 0.0;
} else {
2024-10-17 20:32:51 +00:00
this.position = data.parse_double ();
2024-10-17 10:05:20 +00:00
}
break;
case 1:
assert (data.name == "duration");
if (data.format == Mpv.Format.NONE) {
this.duration = 0.0;
} else {
2024-10-17 20:32:51 +00:00
this.duration = data.parse_double ();
2024-10-17 10:05:20 +00:00
}
break;
case 2:
assert (data.name == "playlist-pos");
if (data.format == Mpv.Format.NONE) {
this.play_queue_position = 0;
} else {
2024-10-17 20:32:51 +00:00
this.play_queue_position = (uint) data.parse_int64 ();
2024-10-17 10:05:20 +00:00
}
break;
default:
assert (false);
break;
}
break;
case Mpv.EventId.FILE_LOADED:
if (this.notify_next_playing) {
this.song = (Subsonic.Song) this.play_queue.get_item (this.play_queue_position);
this.now_playing ();
} else {
assert (this.song == (Subsonic.Song) this.play_queue.get_item (this.play_queue_position));
this.notify_next_playing = true;
}
2024-10-17 10:05:20 +00:00
break;
2024-10-18 20:13:28 +00:00
case Mpv.EventId.END_FILE:
var data = event.parse_end_file ();
if (data.error < 0) {
warning ("playback of track aborted: %s", data.error.to_string ());
}
break;
2024-10-17 10:05:20 +00:00
default:
2024-10-17 20:32:51 +00:00
// ignore by default
2024-10-17 10:05:20 +00:00
break;
}
}
return false;
});
};
}
public void seek (double position) {
this.position = position;
assert (this.mpv.command ({"seek", position.to_string (), "absolute"}) >= 0);
2024-10-12 12:28:05 +00:00
}
// manually changes which track in the play queue to play
public void select_track (uint position)
requires (position < this.play_queue.get_n_items ())
{
assert (this.mpv.command ({"playlist-play-index", position.to_string ()}) >= 0);
this.state = PlaybinState.PLAYING;
this.play_queue_position = position;
this.song = (Subsonic.Song) this.play_queue.get_item (position);
this.now_playing ();
this.notify_next_playing = false;
2024-10-12 12:28:05 +00:00
}
2024-10-12 13:36:47 +00:00
public void pause () {
assert (this.state != PlaybinState.STOPPED);
this.state = PlaybinState.PAUSED;
2024-10-18 08:57:16 +00:00
GLib.debug ("setting state to paused");
// TODO: abstract away this handling around mpv api a bit for auto debug printing
var ret = this.mpv.set_property_flag("pause", true);
if (ret != 0) {
2024-10-18 19:54:12 +00:00
GLib.debug ("failed to set state to paused (%d): %s", ret, ret.to_string());
2024-10-18 08:57:16 +00:00
}
2024-10-12 13:36:47 +00:00
}
public void play () {
assert (this.state != PlaybinState.STOPPED);
this.state = PlaybinState.PLAYING;
2024-10-18 08:57:16 +00:00
GLib.debug ("setting state to playing");
var ret = this.mpv.set_property_flag("pause", false);
if (ret != 0) {
2024-10-18 19:54:12 +00:00
GLib.debug ("failed to set state to playing (%d): %s", ret, ret.to_string());
2024-10-18 08:57:16 +00:00
}
}
2024-10-17 10:05:20 +00:00
public void next_track () {
assert (this.state != PlaybinState.STOPPED);
this.state = PlaybinState.PLAYING;
assert (this.mpv.command ({"playlist-next-playlist"}) >= 0);
2024-10-17 10:05:20 +00:00
}
public void prev_track () {
assert (this.state != PlaybinState.STOPPED);
this.state = PlaybinState.PLAYING;
assert (this.mpv.command ({"playlist-prev-playlist"}) >= 0);
}
2024-10-12 12:28:05 +00:00
}