[rygel] server,media-engines: Add support for playspeed requests



commit 41e57590992aa93a69844a4bef78528287d71dbf
Author: Jens Georg <mail jensge org>
Date:   Fri Feb 13 14:49:33 2015 +0100

    server,media-engines: Add support for playspeed requests
    
    Code based on Cablelabs's CVP-2 implementation
    
    Signed-off-by: Jens Georg <mail jensge org>

 src/librygel-server/filelist.am                    |    5 +-
 src/librygel-server/rygel-data-source.vala         |   13 ++-
 src/librygel-server/rygel-http-get-handler.vala    |    8 ++
 src/librygel-server/rygel-http-get.vala            |   56 +++++++++-
 .../rygel-http-resource-handler.vala               |    4 +
 src/librygel-server/rygel-http-response.vala       |    4 +-
 .../rygel-http-time-seek-request.vala              |   56 +++++++++-
 src/librygel-server/rygel-media-container.vala     |    9 ++-
 src/librygel-server/rygel-playspeed-request.vala   |  115 ++++++++++++++++++++
 src/librygel-server/rygel-playspeed-response.vala  |   90 +++++++++++++++
 src/librygel-server/rygel-playspeed.vala           |  100 +++++++++++++++++
 .../gstreamer/rygel-gst-data-source.vala           |    9 ++-
 .../simple/rygel-simple-data-source.vala           |   10 ++-
 13 files changed, 457 insertions(+), 22 deletions(-)
---
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index ef4d309..e2ed3b2 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -81,4 +81,7 @@ LIBRYGEL_SERVER_NONVAPI_SOURCE_FILES = \
        rygel-wmp-hacks.vala \
        rygel-xbmc-hacks.vala \
        rygel-xbox-hacks.vala \
-       rygel-data-sink.vala
+       rygel-data-sink.vala \
+       rygel-playspeed.vala \
+       rygel-playspeed-request.vala \
+       rygel-playspeed-response.vala
diff --git a/src/librygel-server/rygel-data-source.vala b/src/librygel-server/rygel-data-source.vala
index d663ff8..8937fc4 100644
--- a/src/librygel-server/rygel-data-source.vala
+++ b/src/librygel-server/rygel-data-source.vala
@@ -25,6 +25,7 @@
 public errordomain Rygel.DataSourceError {
     GENERAL,
     SEEK_FAILED,
+    PLAYSPEED_FAILED
 }
 
 /**
@@ -61,20 +62,24 @@ public errordomain Rygel.DataSourceError {
  */
 public interface Rygel.DataSource : GLib.Object {
     /**
-     * Preroll the data with the given seek
+     * Preroll the data with the given seek and playspeed.
      *
      * @param seek    optional seek/range specifier
-    *
+     * @param playspeed optional playback rate specifier. This will only be provided
+     *                  when a scaled rate is requested (the speed will not be 0.0 or 1.0)
+     *
      * @return List of HTTPResponseElements appropriate for the content request and
-     *         optional seek (e.g. Content-Range, TimeSeekRange.dlna.org,
+     *         optional seek/playspeed (e.g. Content-Range, TimeSeekRange.dlna.org,
      *         etc) or null/empty list if none are appropriate. Note: the list will
      *         be processed in-order by the caller.
      *
      * @throws Error if anything goes wrong while prerolling the stream.
      *         Throws DataSourceError.SEEK_FAILED if a seek method is not supported or the
      *         range is not fulfillable.
+     *         Throws PLAYSPEED_FAILED if the rate is not supported or fulfillable.
      */
-    public abstract Gee.List<HTTPResponseElement> ? preroll (HTTPSeekRequest? seek)
+    public abstract Gee.List<HTTPResponseElement> ? preroll (HTTPSeekRequest? seek,
+                                                             PlaySpeedRequest? playspeed)
        throws Error;
 
     /**
diff --git a/src/librygel-server/rygel-http-get-handler.vala b/src/librygel-server/rygel-http-get-handler.vala
index 20dee38..9c68d4d 100644
--- a/src/librygel-server/rygel-http-get-handler.vala
+++ b/src/librygel-server/rygel-http-get-handler.vala
@@ -99,6 +99,14 @@ public abstract class Rygel.HTTPGetHandler: GLib.Object {
     public virtual bool supports_time_seek () {
         return false;
     }
+
+    /**
+     * Returns true if the handler supports any play speed requests.
+     */
+    public virtual bool supports_playspeed () {
+        return false;
+    }
+
     /**
      * Create an HTTPResponse object that will render the body.
      */
diff --git a/src/librygel-server/rygel-http-get.vala b/src/librygel-server/rygel-http-get.vala
index 6230f38..1b759ec 100644
--- a/src/librygel-server/rygel-http-get.vala
+++ b/src/librygel-server/rygel-http-get.vala
@@ -35,6 +35,7 @@ public class Rygel.HTTPGet : HTTPRequest {
     private const string TRANSFER_MODE_HEADER = "transferMode.dlna.org";
 
     public HTTPSeekRequest seek;
+    public PlaySpeedRequest speed_request;
 
     public HTTPGetHandler handler;
 
@@ -130,6 +131,37 @@ public class Rygel.HTTPGet : HTTPRequest {
             }
         }
 
+        // Check for DLNA PlaySpeed request only if Range or Range.dtcp.com is not
+        // in the request. DLNA 7.5.4.3.3.19.2, DLNA Link Protection : 7.6.4.4.2.12
+        // (is 7.5.4.3.3.19.2 compatible with the use case in 7.5.4.3.2.24.5?)
+        // Note: We need to check the speed first since direction factors into validating
+        //       the time-seek request
+        try {
+            if ( !requested_byte_seek
+                 && PlaySpeedRequest.requested (this) ) {
+                this.speed_request = new PlaySpeedRequest.from_request (this);
+                debug ("Processing playspeed %s", speed_request.speed.to_string ());
+                if (this.speed_request.speed.is_normal_rate ()) {
+                    // This is not a scaled-rate request. Treat it as if it wasn't even there
+                    this.speed_request = null;
+                }
+            } else {
+                this.speed_request = null;
+            }
+        } catch (PlaySpeedError error) {
+            this.server.unpause_message (this.msg);
+            if (error is PlaySpeedError.INVALID_SPEED_FORMAT) {
+                this.end (Soup.Status.BAD_REQUEST, error.message);
+                // Per DLNA 7.5.4.3.3.16.3
+            } else if (error is PlaySpeedError.SPEED_NOT_PRESENT) {
+                this.end (Soup.Status.NOT_ACCEPTABLE, error.message);
+                 // Per DLNA 7.5.4.3.3.16.5
+            } else {
+                throw error;
+            }
+            debug ("Error processing PlaySpeed: %s", error.message);
+            return;
+        }
         try {
             // Order is intentional here
             if (supports_byte_seek && requested_byte_seek) {
@@ -139,7 +171,8 @@ public class Rygel.HTTPGet : HTTPRequest {
                 this.seek = byte_seek;
             } else if (supports_time_seek && requested_time_seek) {
                 // Assert: speed_request has been checked/processed
-                var time_seek = new HTTPTimeSeekRequest (this);
+                var time_seek = new HTTPTimeSeekRequest (this, ((this.speed_request == null) ? null
+                                                                : this.speed_request.speed) );
                 debug ("Processing " + time_seek.to_string ());
                 this.seek = time_seek;
             } else {
@@ -201,7 +234,13 @@ public class Rygel.HTTPGet : HTTPRequest {
         {
             Soup.Encoding response_body_encoding;
             // See DLNA 7.5.4.3.2.15 for requirements
-            if (response_size > 0) {
+            if ((this.speed_request != null)
+                && (this.msg.get_http_version () != Soup HTTPVersion  1_0) ) {
+                // We'll want the option to insert PlaySpeed position information
+                //  whether or not we know the length (see DLNA 7.5.4.3.3.17)
+                response_body_encoding = Soup.Encoding.CHUNKED;
+                debug ("Response encoding set to CHUNKED");
+            } else if (response_size > 0) {
                 // TODO: Incorporate ChunkEncodingMode.dlna.org request into this block
                 response_body_encoding = Soup.Encoding.CONTENT_LENGTH;
                 debug ("Response encoding set to CONTENT-LENGTH");
@@ -220,9 +259,10 @@ public class Rygel.HTTPGet : HTTPRequest {
 
         // Determine the Vary header (if not HTTP 1.0)
         {
-            // Per DLNA 7.5.4.3.2.35.4, the Vary header needs to include the timeseek
-            // header if it is supported for the resource/uri
-            if (supports_time_seek) {
+            // Per DLNA 7.5.4.3.2.35.4, the Vary header needs to include the timeseek and/or
+            //  playspeed header if both/either are supported for the resource/uri
+            bool supports_playspeed = PlaySpeedRequest.supported (this);
+            if (supports_time_seek || supports_playspeed) {
                 if (this.msg.get_http_version () != Soup HTTPVersion  1_0) {
                     var vary_header = new StringBuilder
                                              (this.msg.response_headers.get_list ("Vary"));
@@ -232,6 +272,12 @@ public class Rygel.HTTPGet : HTTPRequest {
                         }
                         vary_header.append (HTTPTimeSeekRequest.TIMESEEKRANGE_HEADER);
                     }
+                    if (supports_playspeed) {
+                        if (vary_header.len > 0) {
+                            vary_header.append (",");
+                        }
+                        vary_header.append (PlaySpeedRequest.PLAYSPEED_HEADER);
+                    }
                     this.msg.response_headers.replace ("Vary", vary_header.str);
                 }
             }
diff --git a/src/librygel-server/rygel-http-resource-handler.vala 
b/src/librygel-server/rygel-http-resource-handler.vala
index 51a49a4..2b0b78e 100644
--- a/src/librygel-server/rygel-http-resource-handler.vala
+++ b/src/librygel-server/rygel-http-resource-handler.vala
@@ -107,4 +107,8 @@ internal class Rygel.HTTPMediaResourceHandler : HTTPGetHandler {
         return media_resource.supports_arbitrary_time_seek ()
                || media_resource.supports_limited_time_seek ();
     }
+
+    public override bool supports_playspeed () {
+        return media_resource.supports_playspeed ();
+    }
 }
diff --git a/src/librygel-server/rygel-http-response.vala b/src/librygel-server/rygel-http-response.vala
index 3845689..ddc6546 100644
--- a/src/librygel-server/rygel-http-response.vala
+++ b/src/librygel-server/rygel-http-response.vala
@@ -34,6 +34,7 @@ public class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
     public Cancellable cancellable { get; set; }
 
     public HTTPSeekRequest seek;
+    public PlaySpeedRequest speed;
 
     private SourceFunc run_continue;
     private int _priority = -1;
@@ -71,6 +72,7 @@ public class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
         this.msg = request.msg;
         this.cancellable = request_handler.cancellable;
         this.seek = request.seek;
+        this.speed = request.speed_request;
         this.src = src;
         this.sink = new DataSink (this.src, this.server, this.msg, this.seek);
         this.src.done.connect ( () => {
@@ -102,7 +104,7 @@ public class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
     }
 
     public Gee.List<HTTPResponseElement> ? preroll () throws Error {
-        return this.src.preroll (this.seek);
+        return this.src.preroll (this.seek, this.speed);
     }
 
     public async void run () {
diff --git a/src/librygel-server/rygel-http-time-seek-request.vala 
b/src/librygel-server/rygel-http-time-seek-request.vala
index 70fceb0..008c77d 100644
--- a/src/librygel-server/rygel-http-time-seek-request.vala
+++ b/src/librygel-server/rygel-http-time-seek-request.vala
@@ -59,13 +59,21 @@ public class Rygel.HTTPTimeSeekRequest : Rygel.HTTPSeekRequest {
      * Note: This constructor will check the syntax of the request (per DLNA 7.5.4.3.2.24.3)
      *       as well as perform some range validation. If the provided request is associated
      *       with a handler that can provide content duration, the start and end time will
-     *       be checked for out-of-bounds conditions.
+     *       be checked for out-of-bounds conditions. Additionally, the start and end will
+     *       be checked according to playspeed direction (with rate +1.0 assumed when speed
+     *       is not provided). When speed is provided, the range end parameter check is
+     *       relaxed when the rate is not +1.0 (per DLNA 7.5.4.3.2.24.4).
+     *
      * @param request The HTTP GET/HEAD request
+     * @speed speed An associated speed request
      */
-    internal HTTPTimeSeekRequest (HTTPGet request)
+    internal HTTPTimeSeekRequest (HTTPGet request, PlaySpeed ? speed)
             throws HTTPSeekRequestError {
         base ();
 
+        bool positive_rate = (speed == null) || speed.is_positive ();
+        bool trick_mode = (speed != null) && !speed.is_normal_rate ();
+
         this.total_duration = request.handler.get_resource_duration ();
         if (this.total_duration <= 0) {
             this.total_duration = UNSPECIFIED;
@@ -100,15 +108,37 @@ public class Rygel.HTTPTimeSeekRequest : Rygel.HTTPSeekRequest {
                           TIMESEEKRANGE_HEADER, range);
         }
 
-        this.start_time = start;
+        // Check for out-of-bounds range start and clamp it in if in trick/scan mode
+        if ((this.total_duration != UNSPECIFIED) && (start > this.total_duration)) {
+            if (trick_mode && !positive_rate) { // Per DLNA 7.5.4.3.2.24.4
+                this.start_time = this.total_duration;
+            } else { // See DLNA 7.5.4.3.2.24.8
+                throw new HTTPSeekRequestError.OUT_OF_RANGE
+                              ("Invalid %s start time %lldns is beyond the content duration of %lldns",
+                               TIMESEEKRANGE_HEADER, start, this.total_duration);
+            }
+        } else { // Nothing to check it against - just store it
+            this.start_time = start;
+        }
 
         // Look for an end time
         int64 end = UNSPECIFIED;
         if (parse_npt_time (range_tokens[1], ref end)) {
             // The end time was specified in the npt ("start-end")
             // Check for valid range
-            {
-                this.end_time = end;
+            if (positive_rate) {
+                // Check for out-of-bounds range end or fence it in
+                if ((this.total_duration != UNSPECIFIED) && (end > this.total_duration)) {
+                    if (trick_mode) { // Per DLNA 7.5.4.3.2.24.4
+                        this.end_time = this.total_duration;
+                    } else { // Per DLNA 7.5.4.3.2.24.8
+                        throw new HTTPSeekRequestError.OUT_OF_RANGE
+                                      ("Invalid %s end time %lldns is beyond the content duration of %lldns",
+                                       TIMESEEKRANGE_HEADER, end,this.total_duration);
+                    }
+                } else {
+                    this.end_time = end;
+                }
 
                 this.range_duration =  this.end_time - this.start_time;
                 // At positive rate, start < end
@@ -117,6 +147,16 @@ public class Rygel.HTTPTimeSeekRequest : Rygel.HTTPSeekRequest {
                                   ("Invalid %s value (start time after end time - forward scan): '%s'",
                                    TIMESEEKRANGE_HEADER, range);
                 }
+            } else { // Negative rate
+                // Note: start_time has already been checked/clamped
+                this.end_time = end;
+                this.range_duration = this.start_time - this.end_time;
+                // At negative rate, start > end
+                if (this.range_duration <= 0) { // See DLNA 7.5.4.3.2.24.12
+                    throw new HTTPSeekRequestError.INVALID_RANGE
+                                 ("Invalid %s value (start time before end time - reverse scan): '%s'",
+                                  TIMESEEKRANGE_HEADER, range);
+                }
             }
         } else { // End time not specified in the npt field ("start-")
             // See DLNA 7.5.4.3.2.24.4
@@ -124,7 +164,11 @@ public class Rygel.HTTPTimeSeekRequest : Rygel.HTTPSeekRequest {
             if (this.total_duration == UNSPECIFIED) {
                 this.range_duration = UNSPECIFIED;
             } else {
-                this.range_duration = this.total_duration - this.start_time;
+                if (positive_rate) {
+                    this.range_duration = this.total_duration - this.start_time;
+                } else {
+                    this.range_duration = this.start_time; // Going backward from start to 0
+                }
             }
         }
     }
diff --git a/src/librygel-server/rygel-media-container.vala b/src/librygel-server/rygel-media-container.vala
index 46ff5fd..d6e25f7 100644
--- a/src/librygel-server/rygel-media-container.vala
+++ b/src/librygel-server/rygel-media-container.vala
@@ -58,17 +58,22 @@ internal class Rygel.PlaylistDatasource : Rygel.DataSource, Object {
 
     public signal void data_ready ();
 
-    public Gee.List<HTTPResponseElement> ? preroll ( HTTPSeekRequest? seek_request)
+    public Gee.List<HTTPResponseElement> ? preroll ( HTTPSeekRequest? seek_request,
+                                                     PlaySpeedRequest? playspeed_request)
        throws Error {
         if (seek_request != null) {
             throw new DataSourceError.SEEK_FAILED
                                         (_("Seeking not supported"));
         }
 
+        if (playspeed_request != null) {
+            throw new DataSourceError.PLAYSPEED_FAILED
+                                    (_("Speed not supported"));
+        }
+
         return null;
     }
 
-
     public void start () throws Error {
         if (this.data == null) {
             this.data_ready.connect ( () => {
diff --git a/src/librygel-server/rygel-playspeed-request.vala 
b/src/librygel-server/rygel-playspeed-request.vala
new file mode 100644
index 0000000..4e8ee91
--- /dev/null
+++ b/src/librygel-server/rygel-playspeed-request.vala
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013  Cable Television Laboratories, Inc.
+ *
+ * Author: Craig Pratt <craig ecaspia com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CABLE TELEVISION LABORATORIES
+ * INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+public errordomain Rygel.PlaySpeedError {
+    INVALID_SPEED_FORMAT,
+    SPEED_NOT_PRESENT
+}
+
+/**
+ * This class represents a DLNA PlaySpeed request (PlaySpeed.dlna.org)
+ */
+public class Rygel.PlaySpeedRequest : GLib.Object {
+    public static const string PLAYSPEED_HEADER = "PlaySpeed.dlna.org";
+    public PlaySpeed speed { get; private set; }
+
+    /**
+     * Return true if playspeed is supported
+     *
+     * This method utilizes elements associated with the request to determine if a
+     * PlaySpeed request is supported for the given request/resource.
+     */
+    public static bool supported (HTTPGet request) {
+        return request.handler.supports_playspeed ();
+    }
+
+    internal static bool requested (HTTPGet request) {
+        return request.msg.request_headers.get_one (PLAYSPEED_HEADER) != null;
+    }
+
+    public PlaySpeedRequest (int numerator, uint denominator) {
+        base ();
+        this.speed = new PlaySpeed (numerator, denominator);
+    }
+
+    public PlaySpeedRequest.from_string (string speed) throws PlaySpeedError {
+        base ();
+        this.speed = new PlaySpeed.from_string (speed);
+    }
+
+    internal PlaySpeedRequest.from_request (Rygel.HTTPGet request) throws PlaySpeedError {
+        base ();
+        // Format: PlaySpeed.dlna.org: speed=<rate>
+        string speed_string = request.msg.request_headers.get_one (PLAYSPEED_HEADER);
+
+        if (speed_string == null) {
+            throw new PlaySpeedError.SPEED_NOT_PRESENT ("Could not find %s",
+                                                        PLAYSPEED_HEADER);
+        }
+
+        var elements = speed_string.split ("=");
+
+        if ((elements.length != 2) || (elements[0] != "speed")) {
+            throw new PlaySpeedError.INVALID_SPEED_FORMAT ("ill-formed value for "
+                                                           + PLAYSPEED_HEADER + ": "
+                                                           + speed_string );
+        }
+
+        speed = new PlaySpeed.from_string (elements[1]);
+
+        // Normal rate is always valid. Just check for valid scaled rate
+        if (!speed.is_normal_rate ()) {
+            // Validate if playspeed is listed in the protocolInfo
+            if (request.handler is HTTPMediaResourceHandler) {
+                MediaResource resource = (request.handler as HTTPMediaResourceHandler)
+                                         .media_resource;
+                var speeds = resource.play_speeds;
+                bool found_speed = false;
+                foreach (var speed in speeds) {
+                    var cur_speed = new PlaySpeedRequest.from_string (speed);
+                    if (this.equals (cur_speed)) {
+                        found_speed = true;
+                        break;
+                    }
+                }
+                if (!found_speed) {
+                    throw new PlaySpeedError
+                              .SPEED_NOT_PRESENT ("Unknown playspeed requested (%s)",
+                                                  speed_string);
+                }
+            }
+        }
+    }
+
+    public bool equals (PlaySpeedRequest that) {
+        if (that == null) return false;
+
+        return (this.speed.equals (that.speed));
+    }
+}
diff --git a/src/librygel-server/rygel-playspeed-response.vala 
b/src/librygel-server/rygel-playspeed-response.vala
new file mode 100644
index 0000000..82057ce
--- /dev/null
+++ b/src/librygel-server/rygel-playspeed-response.vala
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013  Cable Television Laboratories, Inc.
+ *
+ * Author: Craig Pratt <craig ecaspia com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CABLE TELEVISION LABORATORIES
+ * INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * This class represents a DLNA PlaySpeed response (PlaySpeed.dlna.org)
+ */
+public class Rygel.PlaySpeedResponse : Rygel.HTTPResponseElement {
+    public static const string FRAMERATE_HEADER = "FrameRateInTrickMode.dlna.org";
+
+    PlaySpeed speed;
+    public static const int NO_FRAMERATE = -1;
+
+    /**
+     * The framerate supported for the given rate, in frames per second
+     */
+    public int framerate;
+
+    public PlaySpeedResponse (int numerator, uint denominator, int framerate) {
+        base ();
+        this.speed = new PlaySpeed (numerator, denominator);
+        this.framerate = NO_FRAMERATE;
+    }
+
+    public PlaySpeedResponse.from_speed (PlaySpeed speed, int framerate)
+       throws PlaySpeedError {
+        base ();
+        this.speed = speed;
+        this.framerate = framerate;
+    }
+
+    public PlaySpeedResponse.from_string (string speed, int framerate)
+       throws PlaySpeedError {
+        base ();
+        this.speed = new PlaySpeed.from_string (speed);
+        this.framerate = NO_FRAMERATE;
+    }
+
+    public bool equals (PlaySpeedRequest that) {
+        if (that == null) return false;
+
+        return (this.speed.equals (that.speed));
+    }
+
+    public override void add_response_headers (Rygel.HTTPRequest request) {
+        if (!this.speed.is_normal_rate ()) {
+            // Format: PlaySpeed.dlna.org: speed=<rate>
+            request.msg.response_headers.append (PlaySpeedRequest.PLAYSPEED_HEADER,
+                                                 "speed=" + this.speed.to_string ());
+            if (this.framerate > 0) {
+                // Format: FrameRateInTrickMode.dlna.org: rate=<2-digit framerate>
+                var framerate_val = "rate=%02d".printf(this.framerate);
+                request.msg.response_headers.append (FRAMERATE_HEADER, framerate_val);
+            }
+            if (request.msg.get_http_version () == Soup HTTPVersion  1_0) {
+                request.msg.response_headers.replace ("Pragma","no-cache");
+            }
+        }
+    }
+
+    public override string to_string () {
+        return ("PlaySpeedResponse(speed=%s, framerate=%d)"
+                .printf (this.speed.to_string (), this.framerate));
+    }
+}
diff --git a/src/librygel-server/rygel-playspeed.vala b/src/librygel-server/rygel-playspeed.vala
new file mode 100644
index 0000000..ad96c6e
--- /dev/null
+++ b/src/librygel-server/rygel-playspeed.vala
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013  Cable Television Laboratories, Inc.
+ *
+ * Author: Craig Pratt <craig ecaspia com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CABLE TELEVISION LABORATORIES
+ * INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * This is a container for a PlaySpeed value.
+ *
+ * A Playspeed can be positive or negative whole numbers or fractions.
+ * e.g. "2". "1/2", "-1/4"
+ */
+public class Rygel.PlaySpeed {
+    public int numerator; // Sign of the speed will be attached to the numerator
+    public uint denominator;
+
+    public PlaySpeed (int numerator, uint denominator) {
+        this.numerator = numerator;
+        this.denominator = denominator;
+    }
+
+    public PlaySpeed.from_string (string speed) throws PlaySpeedError {
+        parse (speed);
+    }
+
+    public bool equals (PlaySpeed that) {
+        if (that == null) return false;
+
+        return ( (this.numerator == that.numerator)
+                 && (this.denominator == that.denominator) );
+    }
+
+    public bool is_positive () {
+        return (this.numerator > 0);
+    }
+
+    public bool is_normal_rate () {
+        return (this.numerator == 1) && (this.denominator == 1);
+    }
+
+    public string to_string () {
+        if (this.denominator == 1) {
+            return numerator.to_string ();
+        } else {
+            return this.numerator.to_string () + "/" + this.denominator.to_string ();
+        }
+    }
+
+    public float to_float () {
+        return (float)numerator/denominator;
+    }
+
+    public double to_double () {
+        return (double)numerator/denominator;
+    }
+
+    private void parse (string speed) throws PlaySpeedError {
+        if (! ("/" in speed)) {
+            this.numerator = int.parse (speed);
+            this.denominator = 1;
+        } else {
+            var elements = speed.split ("/");
+            if (elements.length != 2) {
+                throw new PlaySpeedError.INVALID_SPEED_FORMAT ("Missing/extra numerator/denominator");
+            }
+            this.numerator = int.parse (elements[0]);
+            this.denominator = int.parse (elements[1]);
+        }
+        // "0" isn't a valid numerator or denominator (and parse returns "0" on parse error)
+        if (this.numerator == 0) {
+            throw new PlaySpeedError.INVALID_SPEED_FORMAT ("Invalid numerator in speed: " + speed);
+        }
+        if (this.denominator <= 0) {
+            throw new PlaySpeedError.INVALID_SPEED_FORMAT ("Invalid denominator in speed: " + speed);
+        }
+    }
+}
diff --git a/src/media-engines/gstreamer/rygel-gst-data-source.vala 
b/src/media-engines/gstreamer/rygel-gst-data-source.vala
index f49f0ce..fb08a4f 100644
--- a/src/media-engines/gstreamer/rygel-gst-data-source.vala
+++ b/src/media-engines/gstreamer/rygel-gst-data-source.vala
@@ -61,10 +61,17 @@ internal class Rygel.GstDataSource : Rygel.DataSource, GLib.Object {
         this.src = element;
     }
 
-    public Gee.List<HTTPResponseElement> ? preroll ( HTTPSeekRequest? seek_request)
+    public Gee.List<HTTPResponseElement> ? preroll ( HTTPSeekRequest? seek_request,
+                                                     PlaySpeedRequest? playspeed_request)
        throws Error {
         var response_list = new Gee.ArrayList<HTTPResponseElement>();
 
+        if (playspeed_request != null) {
+            throw new DataSourceError.PLAYSPEED_FAILED
+                                    (_("Playspeed not supported"));
+        }
+
+
         if (seek_request == null) {
             debug("No seek requested - sending entire binary");
         } else if (seek_request is HTTPByteSeekRequest) {
diff --git a/src/media-engines/simple/rygel-simple-data-source.vala 
b/src/media-engines/simple/rygel-simple-data-source.vala
index 1700eff..1ed0577 100644
--- a/src/media-engines/simple/rygel-simple-data-source.vala
+++ b/src/media-engines/simple/rygel-simple-data-source.vala
@@ -50,9 +50,10 @@ internal class Rygel.SimpleDataSource : DataSource, Object {
         this.stop ();
     }
 
-    public Gee.List<HTTPResponseElement> ? preroll (HTTPSeekRequest? seek_request)
+    public Gee.List<HTTPResponseElement> ? preroll (HTTPSeekRequest? seek_request,
+                                                    PlaySpeedRequest? playspeed_request)
        throws Error {
-        var response_list = new Gee.ArrayList<HTTPResponseElement> ();
+         var response_list = new Gee.ArrayList<HTTPResponseElement> ();
 
         if (seek_request != null) {
             if (!(seek_request is HTTPByteSeekRequest)) {
@@ -73,6 +74,11 @@ internal class Rygel.SimpleDataSource : DataSource, Object {
             this.last_byte = 0; // Indicates the entire file
         }
 
+        if (playspeed_request != null) {
+            throw new DataSourceError.PLAYSPEED_FAILED
+                                    (_("Playspeed not supported"));
+        }
+
         return response_list;
     }
     public void start () throws Error {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]