[rygel/wip/didl-s: 34/35] renderer: Fix controller concurrency



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]