diff --git a/src/playbin.vala b/src/playbin.vala index 6db65a1..afb1e87 100644 --- a/src/playbin.vala +++ b/src/playbin.vala @@ -57,6 +57,9 @@ 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 = {}; @@ -67,9 +70,6 @@ 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,153 +83,123 @@ 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); - 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)"); - } + this.mpv.wakeup_callback = () => { + Idle.add (() => { + if (this.is_handling_event) return false; + this.is_handling_event = true; - IOChannel wakeup_read = new IOChannel.unix_new (wakeup_fds[0]); - IOChannel wakeup_write = new IOChannel.unix_new (wakeup_fds[1]); + while (true) { + var event = this.mpv.wait_event (0.0); + if (event.event_id == Mpv.EventId.NONE) break; - wakeup_read.set_close_on_unref (true); - wakeup_write.set_close_on_unref (true); + 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; - - 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 (); + default: + assert (false); + break; } break; - 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 (); - } + 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 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); + 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 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"); - } + case Mpv.EventId.COMMAND_REPLY: + unowned CommandCallback *cc = (CommandCallback *) event.reply_userdata; + cc.error = event.error; + cc.callback (); break; - + default: - assert (false); + // ignore by default 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; } - } - return true; - })); + this.is_handling_event = false; + return false; + }); + }; } public void seek (double position) { diff --git a/src/vapi/mpv.vapi b/src/vapi/mpv.vapi index 9167113..b0fca22 100644 --- a/src/vapi/mpv.vapi +++ b/src/vapi/mpv.vapi @@ -43,7 +43,7 @@ namespace Mpv { [CCode (cname = "mpv_wait_event")] public unowned Event? wait_event (double timeout); - public unowned WakeupCallback wakeup_callback { + public WakeupCallback wakeup_callback { [CCode (cname = "mpv_set_wakeup_callback")] set; }