audrey/src/playbin.vala

167 lines
5.1 KiB
Vala

class Playbin : Object {
// dynamic: undocumented vala feature
// lets us access the about-to-finish signal
private dynamic Gst.Element playbin;
// used to prevent stale seeks
private uint64 stream_counter = 0;
// cubic: recommended for media player volume sliders?
public double volume {
get {
return ((Gst.Audio.StreamVolume) this.playbin).get_volume (Gst.Audio.StreamVolumeFormat.CUBIC);
}
set {
((Gst.Audio.StreamVolume) this.playbin).set_volume (Gst.Audio.StreamVolumeFormat.CUBIC, value);
}
}
public bool mute {
get { return this.playbin.mute; }
set { this.playbin.mute = value; }
}
private bool seeking = false;
public int64 position { get; private set; }
public int64 duration { get; private set; }
public signal void stream_started (string uri);
public signal void stream_over ();
private void source_setup (Gst.Element playbin, dynamic Gst.Element source) {
source.user_agent = "audrey/linux";
}
construct {
this.playbin = Gst.ElementFactory.make ("playbin3", null);
assert (this.playbin != null);
this.playbin.source_setup.connect (this.source_setup);
this.playbin.about_to_finish.connect (this.about_to_finish);
// regularly update position
Timeout.add (500, () => {
if (!this.seeking) {
int64 new_position;
if (this.playbin.query_position (Gst.Format.TIME, out new_position)) {
this.position = new_position < this.duration ? new_position : this.duration;
} else {
this.position = 0;
}
}
// keep rerunning
return true;
});
var bus = this.playbin.get_bus ();
bus.add_signal_watch ();
bus.message["error"].connect ((message) => {
Error err;
string? debug;
message.parse_error (out err, out debug);
error ("gst playbin bus error: %s", err.message);
});
bus.message["warning"].connect ((message) => {
Error err;
string? debug;
message.parse_error (out err, out debug);
warning ("gst playbin bus warning: %s", err.message);
});
bus.message["state-changed"].connect ((message) => {
if (message.src != this.playbin) return;
Gst.State old_state;
Gst.State new_state;
message.parse_state_changed (out old_state, out new_state, null);
switch (new_state) {
case Gst.State.NULL:
break;
case Gst.State.READY:
break;
case Gst.State.PAUSED:
break;
case Gst.State.PLAYING:
if (this.queued_seek_position < 0) {
this.seeking = false;
} else if (this.queued_seek_counter == this.stream_counter) {
assert (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH, this.queued_seek_position));
}
this.queued_seek_position = -1;
break;
}
});
bus.message["stream-start"].connect ((message) => {
this.stream_counter += 1;
int64 new_duration;
assert (this.playbin.query_duration (Gst.Format.TIME, out new_duration));
this.duration = new_duration;
this.stream_started ((string) this.playbin.current_uri);
});
bus.message["eos"].connect ((message) => {
this.stream_over ();
});
}
private uint64 queued_seek_counter;
private int64 queued_seek_position = -1;
public void seek (int64 position) {
if (this.playbin.seek_simple (Gst.Format.TIME, Gst.SeekFlags.FLUSH/* | Gst.SeekFlags.KEY_UNIT*/, position)) {
this.seeking = true;
this.position = position;
} else {
// queue this seek
this.queued_seek_counter = this.stream_counter;
this.queued_seek_position = position;
}
}
public void play_now (string uri) {
this.playbin.set_state (Gst.State.READY);
this.playbin.uri = uri;
this.playbin.set_state (Gst.State.PLAYING);
this.set_next_uri (uri);
}
Mutex next_uri_lock;
string? next_uri;
public void set_next_uri (string? next_uri) {
this.next_uri_lock.lock ();
this.next_uri = next_uri;
this.next_uri_lock.unlock ();
}
// called when uri can be switched for gapless playback
// need async queue because this might be called from a gstreamer thread
private void about_to_finish (dynamic Gst.Element playbin) {
this.next_uri_lock.lock ();
string? next_uri = this.next_uri;
this.next_uri_lock.unlock ();
if (next_uri != null) {
playbin.uri = next_uri;
}
}
public void pause () {
this.playbin.set_state (Gst.State.PAUSED);
}
public void play () {
this.playbin.set_state (Gst.State.PLAYING);
}
}