use pipe for mpv wakeup callback
hopefully works better with the gtk main loop but idk lol
This commit is contained in:
parent
2b2ace0f5c
commit
72d4e63249
2 changed files with 127 additions and 100 deletions
225
src/playbin.vala
225
src/playbin.vala
|
@ -57,9 +57,6 @@ public class Playbin : GLib.Object {
|
|||
public ListStore _play_queue;
|
||||
public ListModel play_queue { get { return this._play_queue; } }
|
||||
|
||||
// try to prevent wait_event to be called twice
|
||||
private bool is_handling_event = false;
|
||||
|
||||
private async Mpv.Error mpv_command_async (string[] args) {
|
||||
CommandCallback cc = {};
|
||||
|
||||
|
@ -70,6 +67,9 @@ public class Playbin : GLib.Object {
|
|||
return cc.error;
|
||||
}
|
||||
|
||||
// should be Mpv.WakeupCallback, but i think there's a vala bug here
|
||||
private SourceOnceFunc wakeup_callback; // anchor reference here, mpv won't remind us
|
||||
|
||||
public Playbin () {
|
||||
this._play_queue = new ListStore (typeof (Subsonic.Song));
|
||||
|
||||
|
@ -83,123 +83,150 @@ public class Playbin : GLib.Object {
|
|||
assert (this.mpv.observe_property (2, "playlist-pos", Mpv.Format.INT64) >= 0);
|
||||
assert (this.mpv.observe_property (3, "pause", Mpv.Format.FLAG) >= 0);
|
||||
|
||||
this.mpv.wakeup_callback = () => {
|
||||
Idle.add (() => {
|
||||
if (this.is_handling_event) return false;
|
||||
this.is_handling_event = true;
|
||||
int wakeup_fds[2];
|
||||
try {
|
||||
assert (Unix.open_pipe (wakeup_fds, 0));
|
||||
} catch (Error e) {
|
||||
error (@"could not open pipe for mpv wakeup: $(e.message)");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var event = this.mpv.wait_event (0.0);
|
||||
if (event.event_id == Mpv.EventId.NONE) break;
|
||||
IOChannel wakeup_read = new IOChannel.unix_new (wakeup_fds[0]);
|
||||
IOChannel wakeup_write = new IOChannel.unix_new (wakeup_fds[1]);
|
||||
|
||||
switch (event.event_id) {
|
||||
case Mpv.EventId.PROPERTY_CHANGE:
|
||||
var data = event.parse_property ();
|
||||
switch (event.reply_userdata) {
|
||||
case 0:
|
||||
assert (data.name == "time-pos");
|
||||
if (data.format == Mpv.Format.NONE) {
|
||||
this.position = 0.0;
|
||||
} else {
|
||||
this.position = data.parse_double ();
|
||||
}
|
||||
break;
|
||||
try {
|
||||
wakeup_read.set_encoding (null);
|
||||
wakeup_write.set_encoding (null);
|
||||
wakeup_write.set_buffered (false);
|
||||
} catch (Error e) {
|
||||
error (@"could not set up pipes for mpv wakeup: $(e.message)");
|
||||
}
|
||||
|
||||
case 1:
|
||||
assert (data.name == "duration");
|
||||
if (data.format == Mpv.Format.NONE) {
|
||||
// this.duration = 0.0; i think this prevents the fallback below from working
|
||||
} else {
|
||||
this.duration = data.parse_double ();
|
||||
}
|
||||
break;
|
||||
this.wakeup_callback = () => {
|
||||
try {
|
||||
wakeup_write.write_chars ({0}, null);
|
||||
} catch (Error e) {
|
||||
error (@"could not write to mpv wakeup pipe: $(e.message)");
|
||||
}
|
||||
};
|
||||
this.mpv.wakeup_callback = this.wakeup_callback;
|
||||
|
||||
case 2:
|
||||
// here as a sanity check
|
||||
// should always match our own play_queu_position/state
|
||||
assert (data.name == "playlist-pos");
|
||||
int64 playlist_pos = data.parse_int64 ();
|
||||
if (playlist_pos < 0) {
|
||||
if (this.state != PlaybinState.STOPPED) {
|
||||
error ("mpv has no current playlist entry, but we think it's index %u", this.play_queue_position);
|
||||
}
|
||||
assert (this.play_queue_position == this.play_queue.get_n_items ());
|
||||
} else {
|
||||
if (this.state == PlaybinState.STOPPED) {
|
||||
error ("mpv is at playlist entry %u, but we're stopped", (uint) playlist_pos);
|
||||
}
|
||||
if (this.play_queue_position != (uint) playlist_pos) {
|
||||
error ("mpv is at playlist entry %u, but we think it's %u", (uint) playlist_pos, this.play_queue_position);
|
||||
}
|
||||
}
|
||||
break;
|
||||
assert (0 < wakeup_read.add_watch (IOCondition.IN, (source, condition) => {
|
||||
try {
|
||||
wakeup_read.read_chars ({0}, null);
|
||||
} catch (Error e) {
|
||||
error (@"could not read from mpv wakeup pipe: $(e.message)");
|
||||
}
|
||||
|
||||
case 3:
|
||||
// also here as a sanity check
|
||||
// should always match our own state
|
||||
assert (data.name == "pause");
|
||||
bool pause = data.parse_flag ();
|
||||
if (pause && this.state != PlaybinState.PAUSED) {
|
||||
error (@"mpv is paused, but we are @(this.state)");
|
||||
}
|
||||
if (!pause && this.state == PlaybinState.PAUSED) {
|
||||
error ("mpv is not paused, but we are paused");
|
||||
}
|
||||
break;
|
||||
while (true) {
|
||||
var event = this.mpv.wait_event (0.0);
|
||||
if (event.event_id == Mpv.EventId.NONE) break;
|
||||
|
||||
default:
|
||||
assert (false);
|
||||
break;
|
||||
switch (event.event_id) {
|
||||
case Mpv.EventId.PROPERTY_CHANGE:
|
||||
var data = event.parse_property ();
|
||||
switch (event.reply_userdata) {
|
||||
case 0:
|
||||
assert (data.name == "time-pos");
|
||||
if (data.format == Mpv.Format.NONE) {
|
||||
this.position = 0.0;
|
||||
} else {
|
||||
this.position = data.parse_double ();
|
||||
}
|
||||
break;
|
||||
|
||||
case Mpv.EventId.START_FILE:
|
||||
debug ("START_FILE received");
|
||||
|
||||
// estimate duration from api data
|
||||
// while mpv doesn't know it
|
||||
this.duration = ((Subsonic.Song) this._play_queue.get_item (this.play_queue_position)).duration;
|
||||
|
||||
this.new_track ();
|
||||
case 1:
|
||||
assert (data.name == "duration");
|
||||
if (data.format == Mpv.Format.NONE) {
|
||||
// this.duration = 0.0; i think this prevents the fallback below from working
|
||||
} else {
|
||||
this.duration = data.parse_double ();
|
||||
}
|
||||
break;
|
||||
|
||||
case Mpv.EventId.END_FILE:
|
||||
var data = event.parse_end_file ();
|
||||
debug (@"END_FILE received (reason: $(data.reason))");
|
||||
|
||||
if (data.error < 0) {
|
||||
warning ("playback of track aborted: %s", data.error.to_string ());
|
||||
}
|
||||
|
||||
if (data.reason == Mpv.EndFileReason.EOF) {
|
||||
// assume this is a proper transition
|
||||
this.play_queue_position += 1;
|
||||
|
||||
if (this.play_queue_position == this._play_queue.get_n_items ()) {
|
||||
// reached the end (?)
|
||||
this.state = PlaybinState.STOPPED;
|
||||
this.stopped ();
|
||||
case 2:
|
||||
// here as a sanity check
|
||||
// should always match our own play_queu_position/state
|
||||
assert (data.name == "playlist-pos");
|
||||
int64 playlist_pos = data.parse_int64 ();
|
||||
if (playlist_pos < 0) {
|
||||
if (this.state != PlaybinState.STOPPED) {
|
||||
error ("mpv has no current playlist entry, but we think it's index %u", this.play_queue_position);
|
||||
}
|
||||
assert (this.play_queue_position == this.play_queue.get_n_items ());
|
||||
} else {
|
||||
if (this.state == PlaybinState.STOPPED) {
|
||||
error ("mpv is at playlist entry %u, but we're stopped", (uint) playlist_pos);
|
||||
}
|
||||
if (this.play_queue_position != (uint) playlist_pos) {
|
||||
error ("mpv is at playlist entry %u, but we think it's %u", (uint) playlist_pos, this.play_queue_position);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Mpv.EventId.COMMAND_REPLY:
|
||||
unowned CommandCallback *cc = (CommandCallback *) event.reply_userdata;
|
||||
cc.error = event.error;
|
||||
cc.callback ();
|
||||
case 3:
|
||||
// also here as a sanity check
|
||||
// should always match our own state
|
||||
assert (data.name == "pause");
|
||||
bool pause = data.parse_flag ();
|
||||
if (pause && this.state != PlaybinState.PAUSED) {
|
||||
error (@"mpv is paused, but we are @(this.state)");
|
||||
}
|
||||
if (!pause && this.state == PlaybinState.PAUSED) {
|
||||
error ("mpv is not paused, but we are paused");
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// ignore by default
|
||||
assert (false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Mpv.EventId.START_FILE:
|
||||
debug ("START_FILE received");
|
||||
|
||||
// estimate duration from api data
|
||||
// while mpv doesn't know it
|
||||
this.duration = ((Subsonic.Song) this._play_queue.get_item (this.play_queue_position)).duration;
|
||||
|
||||
this.new_track ();
|
||||
break;
|
||||
|
||||
case Mpv.EventId.END_FILE:
|
||||
var data = event.parse_end_file ();
|
||||
debug (@"END_FILE received (reason: $(data.reason))");
|
||||
|
||||
if (data.error < 0) {
|
||||
warning ("playback of track aborted: %s", data.error.to_string ());
|
||||
}
|
||||
|
||||
if (data.reason == Mpv.EndFileReason.EOF) {
|
||||
// assume this is a proper transition
|
||||
this.play_queue_position += 1;
|
||||
|
||||
if (this.play_queue_position == this._play_queue.get_n_items ()) {
|
||||
// reached the end (?)
|
||||
this.state = PlaybinState.STOPPED;
|
||||
this.stopped ();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Mpv.EventId.COMMAND_REPLY:
|
||||
unowned CommandCallback *cc = (CommandCallback *) event.reply_userdata;
|
||||
cc.error = event.error;
|
||||
cc.callback ();
|
||||
break;
|
||||
|
||||
default:
|
||||
// ignore by default
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.is_handling_event = false;
|
||||
return false;
|
||||
});
|
||||
};
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
public void seek (double position) {
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Mpv {
|
|||
[CCode (cname = "mpv_wait_event")]
|
||||
public unowned Event? wait_event (double timeout);
|
||||
|
||||
public WakeupCallback wakeup_callback {
|
||||
public unowned WakeupCallback wakeup_callback {
|
||||
[CCode (cname = "mpv_set_wakeup_callback")] set;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue