[rygel/wip/didl-s: 34/35] renderer: Fix controller concurrency
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rygel/wip/didl-s: 34/35] renderer: Fix controller concurrency
- Date: Wed, 21 Nov 2012 16:58:07 +0000 (UTC)
commit 49c16545ba225de1f16c42d0cf4f122c03cb69a0
Author: Jens Georg <jensg openismus com>
Date: Wed Nov 21 17:15:30 2012 +0100
renderer: Fix controller concurrency
Each instance of AVTransport was fighting in the EOS case, causing crashes.
This patch introduces a PlayerController which holds some of the global states
and does the playlist handling.
src/librygel-renderer/filelist.am | 2 +-
src/librygel-renderer/rygel-av-transport.vala | 126 +++++-----------
.../rygel-media-renderer-plugin.vala | 8 +
src/librygel-renderer/rygel-player-controller.vala | 166 ++++++++++++++++++++
src/librygel-renderer/rygel-playlist-handler.vala | 136 ----------------
5 files changed, 213 insertions(+), 225 deletions(-)
---
diff --git a/src/librygel-renderer/filelist.am b/src/librygel-renderer/filelist.am
index 352b8ce..b1f7888 100644
--- a/src/librygel-renderer/filelist.am
+++ b/src/librygel-renderer/filelist.am
@@ -5,7 +5,7 @@ LIBRYGEL_RENDERER_VAPI_SOURCE_FILES = \
LIBRYGEL_RENDERER_NONVAPI_SOURCE_FILES = \
rygel-av-transport.vala \
- rygel-playlist-handler.vala \
+ rygel-player-controller.vala \
rygel-rendering-control.vala \
rygel-sink-connection-manager.vala \
rygel-time-utils.vala \
diff --git a/src/librygel-renderer/rygel-av-transport.vala b/src/librygel-renderer/rygel-av-transport.vala
index 61cffeb..b6b65ce 100644
--- a/src/librygel-renderer/rygel-av-transport.vala
+++ b/src/librygel-renderer/rygel-av-transport.vala
@@ -35,36 +35,8 @@ internal class Rygel.AVTransport : Service {
"urn:schemas-upnp-org:metadata-1-0/AVT/";
private Session session;
- private PlaylistHandler playlist_handler;
private string protocol_info;
- // The setters below update the LastChange message
- private uint _n_tracks = 0;
- public uint n_tracks {
- get {
- return this._n_tracks;
- }
-
- set {
- this._n_tracks = value;
-
- this.changelog.log ("NumberOfTracks", this._n_tracks.to_string ());
- }
- }
-
- private uint _track = 0;
- public uint track {
- get {
- return this._track;
- }
-
- set {
- this._track = value;
-
- this.changelog.log ("CurrentTrack", this._track.to_string ());
- }
- }
-
private string _metadata = "";
public string metadata {
owned get {
@@ -175,10 +147,14 @@ internal class Rygel.AVTransport : Service {
private ChangeLog changelog;
private MediaPlayer player;
+ private PlayerController controller;
public override void constructed () {
+ var plugin = this.root_device.resource_factory as MediaRendererPlugin;
+
this.changelog = new ChangeLog (this, LAST_CHANGE_NS);
this.player = this.get_player ();
+ this.controller = plugin.get_controller ();
query_variable["LastChange"].connect (this.query_last_change_cb);
@@ -199,13 +175,14 @@ internal class Rygel.AVTransport : Service {
action_invoked["Next"].connect (this.next_cb);
action_invoked["Previous"].connect (this.previous_cb);
- this.player.notify["playback-state"].connect (this.notify_state_cb);
+ this.controller.notify["playback-state"].connect (this.notify_state_cb);
+ this.controller.notify["n-tracks"].connect (this.notify_n_tracks_cb);
+ this.controller.notify["track"].connect (this.notify_track_cb);
this.player.notify["duration"].connect (this.notify_duration_cb);
this.player.notify["uri"].connect (this.notify_uri_cb);
this.player.notify["metadata"].connect (this.notify_meta_data_cb);
this.session = new SessionAsync ();
- var plugin = this.root_device.resource_factory as MediaRendererPlugin;
this.protocol_info = plugin.get_protocol_info ();
}
@@ -232,8 +209,8 @@ internal class Rygel.AVTransport : Service {
log.log ("RecordMediumWriteStatus", "NOT_IMPLEMENTED");
log.log ("CurrentRecordQualityMode", "NOT_IMPLEMENTED");
log.log ("PossibleRecordQualityMode", "NOT_IMPLEMENTED");
- log.log ("NumberOfTracks", this.n_tracks.to_string ());
- log.log ("CurrentTrack", this.track.to_string ());
+ log.log ("NumberOfTracks", this.controller.n_tracks.to_string ());
+ log.log ("CurrentTrack", this.controller.track.to_string ());
log.log ("CurrentTrackDuration", this.player.duration_as_str);
log.log ("CurrentMediaDuration", this.player.duration_as_str);
log.log ("CurrentTrackMetaData",
@@ -286,6 +263,8 @@ internal class Rygel.AVTransport : Service {
typeof (string),
out _metadata);
+ // remove current playlist handler
+ this.controller.set_playlist (null);
if (_uri.has_prefix ("http://") || _uri.has_prefix ("https://")) {
var message = new Message ("HEAD", _uri);
message.request_headers.append ("getContentFeatures.dlna.org",
@@ -334,8 +313,8 @@ internal class Rygel.AVTransport : Service {
// Track == Media
this.track_metadata = _metadata;
this.track_uri = _uri;
- this.n_tracks = 1;
- this.track = 1;
+ this.controller.n_tracks = 1;
+ this.controller.track = 1;
action.return ();
}
@@ -346,11 +325,11 @@ internal class Rygel.AVTransport : Service {
this.metadata = _metadata;
this.uri = _uri;
if (_uri == "") {
- this.n_tracks = 0;
- this.track = 0;
+ this.controller.n_tracks = 0;
+ this.controller.track = 0;
} else {
- this.n_tracks = 1;
- this.track = 1;
+ this.controller.n_tracks = 1;
+ this.controller.track = 1;
}
action.return ();
@@ -364,7 +343,7 @@ internal class Rygel.AVTransport : Service {
}
string media_duration;
- if (this.playlist_handler != null) {
+ if (this.controller.n_tracks > 1) {
// We don't know the size of the playlist. Might need change if we
// support playlists whose size we know in advance
media_duration = "0:00:00";
@@ -374,7 +353,7 @@ internal class Rygel.AVTransport : Service {
action.set ("NrTracks",
typeof (uint),
- this.n_tracks,
+ this.controller.n_tracks,
"MediaDuration",
typeof (string),
media_duration,
@@ -410,7 +389,7 @@ internal class Rygel.AVTransport : Service {
}
string media_duration;
- if (this.playlist_handler != null) {
+ if (this.controller.n_tracks > 1) {
// We don't know the size of the playlist. Might need change if we
// support playlists whose size we know in advance
media_duration = "0:00:00";
@@ -423,7 +402,7 @@ internal class Rygel.AVTransport : Service {
"NO_MEDIA",
"NrTracks",
typeof (uint),
- this.n_tracks,
+ this.controller.n_tracks,
"MediaDuration",
typeof (string),
media_duration,
@@ -480,7 +459,7 @@ internal class Rygel.AVTransport : Service {
action.set ("Track",
typeof (uint),
- this.track,
+ this.controller.track,
"TrackDuration",
typeof (string),
this.player.duration_as_str,
@@ -616,21 +595,15 @@ internal class Rygel.AVTransport : Service {
return;
case "TRACK_NR":
debug ("Setting track to %s.", target);
- if (this.playlist_handler == null) {
- action.return_error (710, _("Seek mode not supported"));
-
- return;
- }
-
var track = int.parse (target);
- if (track < 1 || track > this.n_tracks) {
+ if (track < 1 || track > this.controller.n_tracks) {
action.return_error (711, _("Illegal seek target"));
return;
}
- this.playlist_handler.current_track = track;
- this.playlist_handler.set_track ();
+
+ this.controller.go_to (track);
action.return();
@@ -643,10 +616,7 @@ internal class Rygel.AVTransport : Service {
}
private void next_cb (Service service, ServiceAction action) {
- // DLNA leaves us two options here, bail out with error 711 or just
- // ignore it on EOL. We chose the latter.
- if (this.playlist_handler != null) {
- this.playlist_handler.next ();
+ if (this.controller.next ()) {
action.return ();
} else {
action.return_error (711, _("Illegal seek target"));
@@ -654,10 +624,7 @@ internal class Rygel.AVTransport : Service {
}
private void previous_cb (Service service, ServiceAction action) {
- // DLNA leaves us two options here, bail out with error 711 or just
- // ignore it on BOL. We chose the latter.
- if (this.playlist_handler != null) {
- this.playlist_handler.previous ();
+ if (this.controller.previous ()) {
action.return ();
} else {
action.return_error (711, _("Illegal seek target"));
@@ -666,30 +633,20 @@ internal class Rygel.AVTransport : Service {
private void notify_state_cb (Object player, ParamSpec p) {
var state = this.player.playback_state;
- if (state == "EOS") {
- if (this.playlist_handler == null) {
- // Just move to stop
- Idle.add (() => {
- this.player.playback_state = "STOPPED";
+ this.changelog.log ("TransportState", state);
+ }
- return false;
- });
+ private void notify_n_tracks_cb (Object player, ParamSpec p) {
+ this.changelog.log ("NumberOfTracks",
+ this.controller.n_tracks.to_string ());
+ }
- return;
- } else {
- // Set next playlist item
- if (!this.playlist_handler.next ()) {
- // We were at the end of the list; as per DLNA, move to
- // STOPPED and let current track be 1.
- this.playlist_handler.reset ();
- }
- }
- } else {
- this.changelog.log ("TransportState", state);
- }
+ private void notify_track_cb (Object player, ParamSpec p) {
+ this.changelog.log ("CurrentTrack",
+ this.controller.track.to_string ());
}
- private void notify_duration_cb (Object player, ParamSpec p) {
+ private void notify_duration_cb (Object player, ParamSpec p) {
this.changelog.log ("CurrentTrackDuration",
this.player.duration_as_str);
this.changelog.log ("CurrentMediaDuration",
@@ -707,10 +664,6 @@ internal class Rygel.AVTransport : Service {
}
private async void handle_playlist (ServiceAction action) {
- debug ("Trying to download playlist");
- Idle.add ( () => { handle_playlist.callback (); return false; });
- yield;
-
var message = new Message ("GET", this.uri);
this.session.queue_message (message, () => {
handle_playlist.callback ();
@@ -735,10 +688,7 @@ internal class Rygel.AVTransport : Service {
return;
}
- this.playlist_handler = new PlaylistHandler (collection,
- this,
- this.player,
- this.protocol_info);
+ this.controller.set_playlist (collection);
action.return ();
}
diff --git a/src/librygel-renderer/rygel-media-renderer-plugin.vala b/src/librygel-renderer/rygel-media-renderer-plugin.vala
index d40f6a4..923acf6 100644
--- a/src/librygel-renderer/rygel-media-renderer-plugin.vala
+++ b/src/librygel-renderer/rygel-media-renderer-plugin.vala
@@ -36,6 +36,7 @@ public class Rygel.MediaRendererPlugin : Rygel.Plugin {
"/xml/MediaRenderer2.xml";
private string sink_protocol_info;
+ private PlayerController controller;
/**
* Create an instance of the plugin.
@@ -68,12 +69,19 @@ public class Rygel.MediaRendererPlugin : Rygel.Plugin {
RenderingControl.DESCRIPTION_PATH,
typeof (RenderingControl));
this.add_resource (resource);
+
+ this.controller = new PlayerController (this.get_player (),
+ this.get_protocol_info ());
}
public virtual MediaPlayer? get_player () {
return null;
}
+ internal PlayerController get_controller () {
+ return this.controller;
+ }
+
public string get_protocol_info () {
var player = this.get_player ();
if (player == null) {
diff --git a/src/librygel-renderer/rygel-player-controller.vala b/src/librygel-renderer/rygel-player-controller.vala
new file mode 100644
index 0000000..60a7a22
--- /dev/null
+++ b/src/librygel-renderer/rygel-player-controller.vala
@@ -0,0 +1,166 @@
+using GUPnP;
+
+internal class Rygel.PlayerController : Object {
+ private const int DEFAULT_IMAGE_TIMEOUT = 15;
+ private const string CONFIG_SECTION = "Renderer";
+ private const string TIMEOUT_KEY = "image-timeout";
+ private const string DIDL_FRAME_TEMPLATE = "<DIDL-Lite " +
+ "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " +
+ "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" " +
+ "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\" " +
+ "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">" +
+ "%s</DIDL-Lite>";
+
+ public string playback_state { get; set; default = "NO_MEDIA_PRESENT"; }
+ public uint n_tracks { get; set; default = 0; }
+ public uint track { get; set; default = 0; }
+
+ private MediaPlayer player;
+ private MediaCollection collection;
+ private List<DIDLLiteItem> collection_items;
+ private string protocol_info;
+ private uint timeout_id;
+ private uint default_image_timeout;
+ private Configuration config;
+
+ public PlayerController (MediaPlayer player, string protocol_info) {
+ this.player = player;
+ this.protocol_info = protocol_info;
+ this.player.notify["playback-state"].connect (this.notify_state_cb);
+
+ this.config = MetaConfig.get_default ();
+ this.config.setting_changed.connect (this.on_setting_changed);
+ this.default_image_timeout = DEFAULT_IMAGE_TIMEOUT;
+ this.on_setting_changed (CONFIG_SECTION, TIMEOUT_KEY);
+ }
+
+ public bool next () {
+ if (this.track + 1 > this.n_tracks) {
+ return false;
+ }
+
+ this.track++;
+ this.apply_track ();
+
+ return true;
+ }
+
+ public bool previous () {
+ if (this.track <= 1) {
+ return false;
+ }
+
+ this.track--;
+ this.apply_track ();
+
+ return true;
+ }
+
+ public void go_to (uint track) {
+ // check happens in AV_TRANSPORT
+ this.track = track;
+ this.apply_track ();
+ }
+
+ public void set_playlist (MediaCollection? collection) {
+ this.collection = collection;
+ if (this.timeout_id != 0) {
+ this.timeout_id = 0;
+ Source.remove (this.timeout_id);
+ }
+
+ if (this.collection != null) {
+ this.collection_items = collection.get_items ();
+ this.n_tracks = this.collection_items.length ();
+ this.track = 1;
+ this.apply_track ();
+ }
+ }
+
+ private void notify_state_cb (Object player, ParamSpec p) {
+ var state = this.player.playback_state;
+ if (state == "EOS") {
+ if (this.collection == null) {
+ // Just move to stop
+ Idle.add (() => {
+ this.player.playback_state = "STOPPED";
+
+ return false;
+ });
+
+ return;
+ } else {
+ // Set next playlist item
+ if (!this.next ()) {
+ // We were at the end of the list; as per DLNA, move to
+ // STOPPED and let current track be 1.
+ this.reset ();
+ }
+ }
+ } else {
+ // just forward
+ this.playback_state = state;
+ }
+ }
+
+ private void apply_track () {
+ var item = this.collection_items.nth (this.track - 1).data;
+
+ var res = item.get_compat_resource (this.protocol_info, true);
+ this.player.metadata = DIDL_FRAME_TEMPLATE.printf
+ (item.get_xml_string ());
+ this.player.uri = res.get_uri ();
+ if (item.upnp_class.has_prefix ("object.item.image") &&
+ this.collection != null) {
+ this.setup_image_timeouts (item.lifetime);
+ }
+ }
+
+ private void reset () {
+ this.player.playback_state = "STOPPED";
+ this.track = 1;
+ this.apply_track ();
+ }
+
+ private void setup_image_timeouts (long lifetime) {
+ // For images, we handle the timeout here. Either the item carries a
+ // dlna:lifetime tag, then we use that or we use a default timeout of
+ // 5 minutes.
+ var timeout = this.default_image_timeout;
+ if (lifetime > 0) {
+ timeout = (uint) lifetime;
+ }
+
+ debug ("Item is image, setup timer: %ld", timeout);
+
+ if (this.timeout_id != 0) {
+ Source.remove (this.timeout_id);
+ }
+
+ this.timeout_id = Timeout.add_seconds ((uint) timeout, () => {
+ this.timeout_id = 0;
+ if (!this.next ()) {
+ this.reset ();
+ }
+
+ return false;
+ });
+ }
+
+ private void on_setting_changed (string section, string key) {
+ if (section != CONFIG_SECTION && key != TIMEOUT_KEY) {
+ return;
+ }
+
+ try {
+ this.default_image_timeout = config.get_int (CONFIG_SECTION,
+ TIMEOUT_KEY,
+ 0,
+ int.MAX);
+ } catch (Error error) {
+ this.default_image_timeout = DEFAULT_IMAGE_TIMEOUT;
+ }
+
+ debug ("New image timeout: %lu", this.default_image_timeout);
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]