summaryrefslogtreecommitdiff
path: root/src/librygel-server
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2015-02-12 22:32:34 +0100
committerJens Georg <mail@jensge.org>2015-02-17 01:05:14 +0100
commit65bab73cd2cad36156e4b9f609031e47b5c76c44 (patch)
tree119c740f48c35b9dac11576a677e1a45079014f8 /src/librygel-server
parenta58008ef4e7b8afdfe088273ac51d05782615567 (diff)
downloadrygel-65bab73cd2cad36156e4b9f609031e47b5c76c44.tar.gz
server,media-engines: Refactor seek handling
Code based on Cablelabs's CVP-2 implementation.
Diffstat (limited to 'src/librygel-server')
-rw-r--r--src/librygel-server/filelist.am8
-rw-r--r--src/librygel-server/rygel-data-sink.vala12
-rw-r--r--src/librygel-server/rygel-data-source.vala31
-rw-r--r--src/librygel-server/rygel-http-byte-seek-request.vala148
-rw-r--r--src/librygel-server/rygel-http-byte-seek-response.vala77
-rw-r--r--src/librygel-server/rygel-http-byte-seek.vala98
-rw-r--r--src/librygel-server/rygel-http-get-handler.vala29
-rw-r--r--src/librygel-server/rygel-http-get.vala121
-rw-r--r--src/librygel-server/rygel-http-identity-handler.vala87
-rw-r--r--src/librygel-server/rygel-http-request.vala10
-rw-r--r--src/librygel-server/rygel-http-resource-handler.vala14
-rw-r--r--src/librygel-server/rygel-http-response-element.vala44
-rw-r--r--src/librygel-server/rygel-http-response.vala12
-rw-r--r--src/librygel-server/rygel-http-seek.vala93
-rw-r--r--src/librygel-server/rygel-http-subtitle-handler.vala4
-rw-r--r--src/librygel-server/rygel-http-thumbnail-handler.vala4
-rw-r--r--src/librygel-server/rygel-http-time-seek-request.vala211
-rw-r--r--src/librygel-server/rygel-http-time-seek-response.vala224
-rw-r--r--src/librygel-server/rygel-http-time-seek.vala179
-rw-r--r--src/librygel-server/rygel-media-container.vala12
20 files changed, 911 insertions, 507 deletions
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index 9d85d41d..ef4d3093 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -37,19 +37,21 @@ LIBRYGEL_SERVER_NONVAPI_SOURCE_FILES = \
rygel-content-directory.vala \
rygel-dbus-thumbnailer.vala \
rygel-engine-loader.vala \
+ rygel-http-byte-seek-request.vala \
+ rygel-http-byte-seek-response.vala \
rygel-free-desktop-interfaces.vala \
- rygel-http-byte-seek.vala \
rygel-http-get-handler.vala \
rygel-http-get.vala \
rygel-http-thumbnail-handler.vala \
rygel-http-subtitle-handler.vala \
- rygel-http-identity-handler.vala \
rygel-http-item-uri.vala \
rygel-http-post.vala \
rygel-http-request.vala \
rygel-http-response.vala \
+ rygel-http-response-element.vala \
rygel-http-server.vala \
- rygel-http-time-seek.vala \
+ rygel-http-time-seek-request.vala \
+ rygel-http-time-seek-response.vala \
rygel-http-resource-handler.vala \
rygel-import-resource.vala \
rygel-object-creator.vala \
diff --git a/src/librygel-server/rygel-data-sink.vala b/src/librygel-server/rygel-data-sink.vala
index 9ff7d51a..eb206ed7 100644
--- a/src/librygel-server/rygel-data-sink.vala
+++ b/src/librygel-server/rygel-data-sink.vala
@@ -1,7 +1,9 @@
/*
* Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
*
* Author: Jens Georg <jensg@openismus.com>
+ * Craig Pratt <craig@ecaspia.com>
*
* This file is part of Rygel.
*
@@ -40,7 +42,7 @@ internal class Rygel.DataSink : Object {
public DataSink (DataSource source,
Server server,
Message message,
- HTTPSeek? offsets) {
+ HTTPSeekRequest? offsets) {
this.source = source;
this.server = server;
this.message = message;
@@ -49,10 +51,12 @@ internal class Rygel.DataSink : Object {
this.bytes_sent = 0;
this.max_bytes = int64.MAX;
if (offsets != null &&
- offsets is HTTPByteSeek) {
- this.max_bytes = offsets.length;
+ offsets is HTTPByteSeekRequest &&
+ ((offsets as HTTPByteSeekRequest).range_length != HTTPSeekRequest.UNSPECIFIED)) {
+ this.max_bytes = (offsets as HTTPByteSeekRequest).range_length;
}
-
+ debug ("Setting max_bytes to %s", (this.max_bytes == int64.MAX)
+ ? "MAX" : this.max_bytes.to_string());
this.source.data_available.connect (this.on_data_available);
this.message.wrote_chunk.connect (this.on_wrote_chunk);
}
diff --git a/src/librygel-server/rygel-data-source.vala b/src/librygel-server/rygel-data-source.vala
index 437f5509..d663ff85 100644
--- a/src/librygel-server/rygel-data-source.vala
+++ b/src/librygel-server/rygel-data-source.vala
@@ -1,7 +1,9 @@
/*
* Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
*
* Author: Jens Georg <jensg@openismus.com>
+ * Craig Pratt <craig@ecaspia.com>
*
* This file is part of Rygel.
*
@@ -22,7 +24,7 @@
public errordomain Rygel.DataSourceError {
GENERAL,
- SEEK_FAILED
+ SEEK_FAILED,
}
/**
@@ -36,7 +38,8 @@ public errordomain Rygel.DataSourceError {
* to Rygel which adds them to the response it sends to the original HTTP
* request received from the client.
*
- * The data source is responsible for providing the streamable byte-stream
+ * The data source is responsible for providing response header information
+ * describing the content being produced and a streamable byte-stream
* via its data_available signal. End-of-stream is signalled by the
* done signal, while errors are signalled by the error signal.
*
@@ -58,14 +61,28 @@ public errordomain Rygel.DataSourceError {
*/
public interface Rygel.DataSource : GLib.Object {
/**
+ * Preroll the data with the given seek
+ *
+ * @param seek optional seek/range specifier
+ *
+ * @return List of HTTPResponseElements appropriate for the content request and
+ * optional seek (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.
+ */
+ public abstract Gee.List<HTTPResponseElement> ? preroll (HTTPSeekRequest? seek)
+ throws Error;
+
+ /**
* Start producing the data.
*
- * @param offsets optional limits of the stream for partial streaming
- * @throws Error if anything goes wrong while starting the stream. Throws
- * DataSourceError.SEEK_FAILED if a seek method is not supported or the
- * range is not fulfillable.
+ * @throws Error if anything goes wrong while starting the stream.
*/
- public abstract void start (HTTPSeek? offsets) throws Error;
+ public abstract void start () throws Error;
/**
* Temporarily stop data generation.
diff --git a/src/librygel-server/rygel-http-byte-seek-request.vala b/src/librygel-server/rygel-http-byte-seek-request.vala
new file mode 100644
index 00000000..38969f99
--- /dev/null
+++ b/src/librygel-server/rygel-http-byte-seek-request.vala
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ * <zeeshan.ali@nokia.com>
+ * Jens Georg <jensg@openismus.com>
+ * 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.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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.
+ */
+
+using GUPnP;
+
+public class Rygel.HTTPByteSeekRequest : Rygel.HTTPSeekRequest {
+ /**
+ * The start of the range in bytes
+ */
+ public int64 start_byte { get; set; }
+
+ /**
+ * The end of the range in bytes (inclusive)
+ */
+ public int64 end_byte { get; set; }
+
+ /**
+ * The length of the range in bytes
+ */
+ public int64 range_length { get; private set; }
+
+ /**
+ * The length of the resource in bytes
+ */
+ public int64 total_size { get; set; }
+
+
+ public HTTPByteSeekRequest (HTTPGet request) throws HTTPSeekRequestError,
+ HTTPRequestError {
+ base ();
+ unowned string range = request.msg.request_headers.get_one ("Range");
+ if (range == null) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ("Range header not present");
+ }
+
+ int64 start_byte, end_byte, total_size;
+
+ // The size (entity body size) may not be known up-front (especially for live sources)
+ total_size = request.handler.get_resource_size ();
+ if (total_size < 0) {
+ total_size = UNSPECIFIED;
+ }
+
+ // Note: DLNA restricts the syntax on the Range header (see DLNA 7.5.4.3.2.22.3)
+ // And we need to retain the concept of an "open range" ("bytes=DIGITS-")
+ // since the interpretation/legality varies based on the context
+ // (e.g. DLNA 7.5.4.3.2.19.2, 7.5.4.3.2.20.1, 7.5.4.3.2.20.3)
+ if (!range.has_prefix ("bytes=")) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid Range value (missing 'bytes=' field): '%s'", range);
+ }
+
+ var parsed_range = range.substring (6);
+ if (!parsed_range.contains ("-")) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid Range request with no '-': '%s'", range);
+ }
+
+ var range_tokens = parsed_range.split ("-", 2);
+
+ if (!int64.try_parse (strip_leading_zeros(range_tokens[0]), out start_byte)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid Range start value: '%s'", range);
+ }
+
+ if ((total_size != UNSPECIFIED) && (start_byte >= total_size)) {
+ throw new HTTPSeekRequestError.OUT_OF_RANGE
+ ("Range start value %lld is larger than content size %lld: '%s'",
+ start_byte, total_size, range);
+ }
+
+ if (range_tokens[1] == null || (range_tokens[1].length == 0)) {
+ end_byte = UNSPECIFIED;
+ range_length = UNSPECIFIED;
+ } else {
+ if (!int64.try_parse (strip_leading_zeros(range_tokens[1]), out end_byte)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid Range end value: '%s'", range);
+ }
+ if (end_byte < start_byte) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Range end value %lld is smaller than range start value %lld: '%s'",
+ end_byte, start_byte, range);
+ }
+ if ((total_size != UNSPECIFIED) && (end_byte >= total_size)) {
+ end_byte = total_size - 1;
+ }
+ range_length = end_byte - start_byte + 1; // range is inclusive
+ }
+ this.start_byte = start_byte;
+ this.end_byte = end_byte;
+ this.total_size = total_size;
+ }
+
+ public static bool supported (HTTPGet request) {
+ bool force_seek = false;
+
+ try {
+ var hack = ClientHacks.create (request.msg);
+ force_seek = hack.force_seek ();
+ } catch (Error error) { }
+
+ return force_seek || request.handler.supports_byte_seek ();
+ }
+
+ public static bool requested (HTTPGet request) {
+ return (request.msg.request_headers.get_one ("Range") != null);
+ }
+
+ // Leading "0"s cause try_parse() to assume the value is octal (see Vala bug 656691)
+ // So we strip them off before passing to int64.try_parse()
+ private static string strip_leading_zeros (string number_string) {
+ int i=0;
+ while ((number_string[i] == '0') && (i < number_string.length)) {
+ i++;
+ }
+ if (i == 0) {
+ return number_string;
+ } else {
+ return number_string[i:number_string.length];
+ }
+ }
+
+}
diff --git a/src/librygel-server/rygel-http-byte-seek-response.vala b/src/librygel-server/rygel-http-byte-seek-response.vala
new file mode 100644
index 00000000..459b2d94
--- /dev/null
+++ b/src/librygel-server/rygel-http-byte-seek-response.vala
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ * <zeeshan.ali@nokia.com>
+ * Jens Georg <jensg@openismus.com>
+ * 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.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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.
+ */
+
+using GUPnP;
+
+public class Rygel.HTTPByteSeekResponse : Rygel.HTTPResponseElement {
+ /**
+ * The start of the range in bytes
+ */
+ public int64 start_byte { get; set; }
+
+ /**
+ * The end of the range in bytes (inclusive)
+ */
+ public int64 end_byte { get; set; }
+
+ /**
+ * The length of the range in bytes
+ */
+ public int64 range_length { get; private set; }
+
+ /**
+ * The length of the resource in bytes
+ */
+ public int64 total_size { get; set; }
+
+ public HTTPByteSeekResponse (int64 start_byte, int64 end_byte, int64 total_size) {
+ this.start_byte = start_byte;
+ this.end_byte = end_byte;
+ this.range_length = end_byte-start_byte+1; // +1, since range is inclusive
+ this.total_size = total_size;
+ }
+
+ public HTTPByteSeekResponse.from_request (HTTPByteSeekRequest request) {
+ this.start_byte = request.start_byte;
+ this.end_byte = request.end_byte;
+ this.range_length = request.range_length;
+ this.total_size = request.total_size;
+ }
+
+ public override void add_response_headers (Rygel.HTTPRequest request) {
+ // Content-Range: bytes START_BYTE-END_BYTE/TOTAL_LENGTH (or "*")
+ request.msg.response_headers.set_content_range ( this.start_byte, this.end_byte,
+ this.total_size );
+ request.msg.response_headers.append ("Accept-Ranges", "bytes");
+ request.msg.response_headers.set_content_length (range_length);
+ }
+
+ public override string to_string () {
+ return ("HTTPByteSeekResponse(bytes=%lld-%lld/%lld (%lld bytes))"
+ .printf (this.start_byte, this.end_byte, this.total_size, this.total_size));
+ }
+}
diff --git a/src/librygel-server/rygel-http-byte-seek.vala b/src/librygel-server/rygel-http-byte-seek.vala
deleted file mode 100644
index df1f2cc2..00000000
--- a/src/librygel-server/rygel-http-byte-seek.vala
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2012 Intel Corporation.
- *
- * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * <zeeshan.ali@nokia.com>
- * Jens Georg <jensg@openismus.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.
- *
- * Rygel is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * 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.
- */
-
-internal class Rygel.HTTPByteSeek : Rygel.HTTPSeek {
- public HTTPByteSeek (HTTPGet request) throws HTTPSeekError {
- Soup.Range[] ranges;
- int64 start = 0, total_length;
- unowned string range = request.msg.request_headers.get_one ("Range");
-
- if (request.thumbnail != null) {
- total_length = request.thumbnail.size;
- } else if (request.subtitle != null) {
- total_length = request.subtitle.size;
- } else {
- total_length = (request.object as MediaFileItem).size;
- }
- var stop = total_length - 1;
-
- if (range != null) {
- if (request.msg.request_headers.get_ranges (total_length,
- out ranges)) {
- // TODO: Somehow deal with multipart/byterange properly
- start = ranges[0].start;
- stop = ranges[0].end;
- } else {
- // Range header was present but invalid
- throw new HTTPSeekError.INVALID_RANGE (_("Invalid Range '%s'"),
- range);
- }
-
- if (start > stop) {
- throw new HTTPSeekError.INVALID_RANGE (_("Invalid Range '%s'"),
- range);
- }
- }
-
- base (request.msg, start, stop, 1, total_length);
- this.seek_type = HTTPSeekType.BYTE;
- }
-
- public static bool needed (HTTPGet request) {
- bool force_seek = false;
-
- try {
- var hack = ClientHacks.create (request.msg);
- force_seek = hack.force_seek ();
- } catch (Error error) { }
-
- return force_seek || (!(request.object is MediaContainer) &&
- ((request.object as MediaFileItem).size > 0 &&
- request.handler is HTTPIdentityHandler) ||
- (request.handler is HTTPThumbnailHandler) ||
- (request.handler is HTTPSubtitleHandler));
- }
-
- public static bool requested (HTTPGet request) {
- return request.msg.request_headers.get_one ("Range") != null;
- }
-
- public override void add_response_headers () {
- // Content-Range: bytes START_BYTE-STOP_BYTE/TOTAL_LENGTH
- var range = "bytes ";
- unowned Soup.MessageHeaders headers = this.msg.response_headers;
-
- if (this.msg.request_headers.get_one ("Range") != null) {
- headers.append ("Accept-Ranges", "bytes");
-
- range += this.start.to_string () + "-" +
- this.stop.to_string () + "/" +
- this.total_length.to_string ();
- headers.append ("Content-Range", range);
- }
-
- headers.set_content_length (this.length);
- }
-}
diff --git a/src/librygel-server/rygel-http-get-handler.vala b/src/librygel-server/rygel-http-get-handler.vala
index 37e1aa0c..20dee38c 100644
--- a/src/librygel-server/rygel-http-get-handler.vala
+++ b/src/librygel-server/rygel-http-get-handler.vala
@@ -1,9 +1,11 @@
/*
* Copyright (C) 2008-2010 Nokia Corporation.
* Copyright (C) 2010 Andreas Henriksson <andreas@fatal.se>
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
*
* Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
* <zeeshan.ali@nokia.com>
+ * Craig Pratt <craig@ecaspia.com>
*
* This file is part of Rygel.
*
@@ -27,7 +29,7 @@ using GUPnP;
/**
* HTTP GET request handler interface.
*/
-internal abstract class Rygel.HTTPGetHandler: GLib.Object {
+public abstract class Rygel.HTTPGetHandler: GLib.Object {
protected const string TRANSFER_MODE_HEADER = "transferMode.dlna.org";
protected const string TRANSFER_MODE_STREAMING = "Streaming";
@@ -36,7 +38,6 @@ internal abstract class Rygel.HTTPGetHandler: GLib.Object {
public Cancellable cancellable { get; set; }
- // Add response headers.
/**
* Invokes the handler to add response headers to/for the given HTTP request
*/
@@ -78,7 +79,29 @@ internal abstract class Rygel.HTTPGetHandler: GLib.Object {
*/
public abstract int64 get_resource_size ();
- // Create an HTTPResponse object that will render the body.
+ /**
+ * Returns the resource duration (in microseconds) or -1 if not known.
+ */
+ public virtual int64 get_resource_duration () {
+ return -1;
+ }
+
+ /**
+ * Returns true if the handler supports full random-access byte seek.
+ */
+ public virtual bool supports_byte_seek () {
+ return false;
+ }
+
+ /**
+ * Returns true if the handler supports full random-access time seek.
+ */
+ public virtual bool supports_time_seek () {
+ return false;
+ }
+ /**
+ * Create an HTTPResponse object that will render the body.
+ */
public abstract HTTPResponse render_body (HTTPGet request)
throws HTTPRequestError;
diff --git a/src/librygel-server/rygel-http-get.vala b/src/librygel-server/rygel-http-get.vala
index dbde59cc..79adfbb8 100644
--- a/src/librygel-server/rygel-http-get.vala
+++ b/src/librygel-server/rygel-http-get.vala
@@ -31,12 +31,12 @@
/**
* Responsible for handling HTTP GET & HEAD client requests.
*/
-internal class Rygel.HTTPGet : HTTPRequest {
+public class Rygel.HTTPGet : HTTPRequest {
private const string TRANSFER_MODE_HEADER = "transferMode.dlna.org";
+ public HTTPSeekRequest seek;
public Thumbnail thumbnail;
public Subtitle subtitle;
- public HTTPSeek seek;
private int thumbnail_index;
private int subtitle_index;
@@ -82,10 +82,6 @@ internal class Rygel.HTTPGet : HTTPRequest {
this.cancellable);
}
- if (this.handler == null) {
- this.handler = new HTTPIdentityHandler (this.cancellable);
- }
-
{ // Check the transfer mode
var transfer_mode = this.msg.request_headers.get_one (TRANSFER_MODE_HEADER);
@@ -157,39 +153,67 @@ internal class Rygel.HTTPGet : HTTPRequest {
}
private async void handle_item_request () throws Error {
- var need_time_seek = HTTPTimeSeek.needed (this);
- var requested_time_seek = HTTPTimeSeek.requested (this);
- var need_byte_seek = HTTPByteSeek.needed (this);
- var requested_byte_seek = HTTPByteSeek.requested (this);
-
- if ((requested_time_seek && !need_time_seek) ||
- (requested_byte_seek && !need_byte_seek)) {
- throw new HTTPRequestError.UNACCEPTABLE ("Invalid seek request");
+ var supports_time_seek = HTTPTimeSeekRequest.supported (this);
+ var requested_time_seek = HTTPTimeSeekRequest.requested (this);
+ var supports_byte_seek = HTTPByteSeekRequest.supported (this);
+ var requested_byte_seek = HTTPByteSeekRequest.requested (this);
+
+ if (requested_byte_seek) {
+ if (!supports_byte_seek) {
+ throw new HTTPRequestError.UNACCEPTABLE ( "Byte seek not supported for "
+ + this.uri.to_string () );
+ }
+ } else if (requested_time_seek) {
+ if (!supports_time_seek) {
+ throw new HTTPRequestError.UNACCEPTABLE ( "Time seek not supported for "
+ + this.uri.to_string () );
+ }
}
try {
- if (need_time_seek && requested_time_seek) {
- this.seek = new HTTPTimeSeek (this);
- } else if (need_byte_seek && requested_byte_seek) {
- this.seek = new HTTPByteSeek (this);
- }
- } catch (HTTPSeekError error) {
- this.server.unpause_message (this.msg);
-
- if (error is HTTPSeekError.INVALID_RANGE) {
- this.end (Soup.Status.BAD_REQUEST);
- } else if (error is HTTPSeekError.OUT_OF_RANGE) {
- this.end (Soup.Status.REQUESTED_RANGE_NOT_SATISFIABLE);
+ // Order is intentional here
+ if (supports_byte_seek && requested_byte_seek) {
+ var byte_seek = new HTTPByteSeekRequest (this);
+ debug ("Processing byte range request (bytes %lld to %lld)",
+ byte_seek.start_byte, byte_seek.end_byte);
+ 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);
+ debug ("Processing " + time_seek.to_string ());
+ this.seek = time_seek;
} else {
- throw error;
+ this.seek = null;
}
-
+ } catch (HTTPSeekRequestError error) {
+ warning ("Caught HTTPSeekRequestError: " + error.message);
+ this.server.unpause_message (this.msg);
+ this.end (error.code, error.message); // All seek error codes are Soup.Status codes
return;
- }
+ }
// Add headers
this.handler.add_response_headers (this);
+ var response = this.handler.render_body (this);
+
+ // Have the response process the seek/speed request
+ try {
+ var responses = response.preroll ();
+
+ // Incorporate the prerolled responses
+ if (responses != null) {
+ foreach (var response_elem in responses) {
+ response_elem.add_response_headers (this);
+ }
+ }
+ } catch (HTTPSeekRequestError error) {
+ warning ("Caught HTTPSeekRequestError on preroll: " + error.message);
+ this.server.unpause_message (this.msg);
+ this.end (error.code, error.message); // All seek error codes are Soup.Status codes
+ return;
+ }
+
// Determine the size value
int64 response_size;
{
@@ -213,13 +237,6 @@ internal class Rygel.HTTPGet : HTTPRequest {
// size will factor into other logic below...
}
- // Add general headers
- if (this.msg.request_headers.get_one ("Range") != null) {
- this.msg.set_status (Soup.Status.PARTIAL_CONTENT);
- } else {
- this.msg.set_status (Soup.Status.OK);
- }
-
// Determine the transfer mode encoding
{
Soup.Encoding response_body_encoding;
@@ -241,6 +258,36 @@ internal class Rygel.HTTPGet : HTTPRequest {
this.msg.response_headers.set_encoding (response_body_encoding);
}
+ // 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) {
+ if (this.msg.get_http_version () != Soup.HTTPVersion.@1_0) {
+ var vary_header = new StringBuilder
+ (this.msg.response_headers.get_list ("Vary"));
+ if (supports_time_seek) {
+ if (vary_header.len > 0) {
+ vary_header.append (",");
+ }
+ vary_header.append (HTTPTimeSeekRequest.TIMESEEKRANGE_HEADER);
+ }
+ this.msg.response_headers.replace ("Vary", vary_header.str);
+ }
+ }
+ }
+
+ // Determine the status code
+ {
+ int response_code;
+ if (this.msg.response_headers.get_one ("Content-Range") != null) {
+ response_code = Soup.Status.PARTIAL_CONTENT;
+ } else {
+ response_code = Soup.Status.OK;
+ }
+ this.msg.set_status (response_code);
+ }
+
if (msg.get_http_version () == Soup.HTTPVersion.@1_0) {
// Set the response version to HTTP 1.1 (see DLNA 7.5.4.3.2.7.2)
msg.set_http_version (Soup.HTTPVersion.@1_1);
@@ -259,8 +306,6 @@ internal class Rygel.HTTPGet : HTTPRequest {
return;
}
- var response = this.handler.render_body (this);
-
yield response.run ();
this.end (Soup.Status.NONE);
diff --git a/src/librygel-server/rygel-http-identity-handler.vala b/src/librygel-server/rygel-http-identity-handler.vala
deleted file mode 100644
index 19e8a218..00000000
--- a/src/librygel-server/rygel-http-identity-handler.vala
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2008, 2009 Nokia Corporation.
- * Copyright (C) 2012 Intel Corporation.
- *
- * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * <zeeshan.ali@nokia.com>
- * Jens Georg <jensg@openismus.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.
- *
- * Rygel is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * 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.
- */
-
-using GUPnP;
-
-// An HTTP request handler that passes the item content through as is.
-internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
-
- public HTTPIdentityHandler (Cancellable? cancellable) {
- this.cancellable = cancellable;
- }
-
- public override void add_response_headers (HTTPGet request)
- throws HTTPRequestError {
- {
- request.msg.response_headers.append ("Content-Type",
- (request.object as MediaFileItem).mime_type);
- }
-
- if (request.seek != null) {
- request.seek.add_response_headers ();
- } else {
- var size = this.get_size (request);
-
- if (size > 0) {
- request.msg.response_headers.set_content_length (size);
- }
- }
-
- // Chain-up
- base.add_response_headers (request);
- }
-
- public override HTTPResponse render_body (HTTPGet request)
- throws HTTPRequestError {
- try {
- return this.render_body_real (request);
- } catch (Error err) {
- throw new HTTPRequestError.NOT_FOUND (err.message);
- }
- }
-
- public override bool supports_transfer_mode (string mode) {
- return true;
- }
-
- public override int64 get_resource_size () {
- return -1;
- }
-
- private HTTPResponse render_body_real (HTTPGet request) throws Error {
- var src = (request.object as MediaFileItem).create_stream_source
- (request.http_server.context.host_ip);
-
- if (src == null) {
- throw new HTTPRequestError.NOT_FOUND (_("Not found"));
- }
-
- return new HTTPResponse (request, this, src);
- }
-
- private int64 get_size (HTTPGet request) {
- return (request.object as MediaFileItem).size;
- }
-}
diff --git a/src/librygel-server/rygel-http-request.vala b/src/librygel-server/rygel-http-request.vala
index 389d3c09..243705c1 100644
--- a/src/librygel-server/rygel-http-request.vala
+++ b/src/librygel-server/rygel-http-request.vala
@@ -110,12 +110,16 @@ public abstract class Rygel.HTTPRequest : GLib.Object, Rygel.StateMachine {
status = Soup.Status.NOT_FOUND;
}
- this.end (status);
+ this.end (status, error.message);
}
- protected void end (uint status) {
+ protected void end (uint status, string ? reason = null) {
if (status != Soup.Status.NONE) {
- this.msg.set_status (status);
+ if (reason == null) {
+ this.msg.set_status (status);
+ } else {
+ this.msg.set_status_full (status, reason);
+ }
}
this.completed ();
diff --git a/src/librygel-server/rygel-http-resource-handler.vala b/src/librygel-server/rygel-http-resource-handler.vala
index 965ab648..51a49a47 100644
--- a/src/librygel-server/rygel-http-resource-handler.vala
+++ b/src/librygel-server/rygel-http-resource-handler.vala
@@ -93,4 +93,18 @@ internal class Rygel.HTTPMediaResourceHandler : HTTPGetHandler {
public override int64 get_resource_size () {
return media_resource.size;
}
+
+ public override int64 get_resource_duration () {
+ return media_resource.duration * TimeSpan.SECOND;
+ }
+
+ public override bool supports_byte_seek () {
+ return media_resource.supports_arbitrary_byte_seek ()
+ || media_resource.supports_limited_byte_seek ();
+ }
+
+ public override bool supports_time_seek () {
+ return media_resource.supports_arbitrary_time_seek ()
+ || media_resource.supports_limited_time_seek ();
+ }
}
diff --git a/src/librygel-server/rygel-http-response-element.vala b/src/librygel-server/rygel-http-response-element.vala
new file mode 100644
index 00000000..0ac57cf8
--- /dev/null
+++ b/src/librygel-server/rygel-http-response-element.vala
@@ -0,0 +1,44 @@
+/*
+ * 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 abstract class represents an entity that can contribute response headers to a
+ * HTTP request.
+ */
+public abstract class Rygel.HTTPResponseElement : GLib.Object {
+ // For designating fields that are unset
+ public static const int64 UNSPECIFIED = -1;
+
+ /**
+ * Set the type-appropriate headers on the associated HTTP Message
+ */
+ public abstract void add_response_headers (Rygel.HTTPRequest request);
+
+ public abstract string to_string ();
+}
diff --git a/src/librygel-server/rygel-http-response.vala b/src/librygel-server/rygel-http-response.vala
index 7cfa7bf9..38456893 100644
--- a/src/librygel-server/rygel-http-response.vala
+++ b/src/librygel-server/rygel-http-response.vala
@@ -1,10 +1,12 @@
/*
* Copyright (C) 2008-2012 Nokia Corporation.
* Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
*
* Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
* <zeeshan.ali@nokia.com>
* Jens Georg <jensg@openismus.com>
+ * Craig Pratt <craig@ecaspia.com>
*
* This file is part of Rygel.
*
@@ -25,13 +27,13 @@
using Soup;
-internal class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
+public class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
public unowned Soup.Server server { get; private set; }
public Soup.Message msg;
public Cancellable cancellable { get; set; }
- public HTTPSeek seek;
+ public HTTPSeekRequest seek;
private SourceFunc run_continue;
private int _priority = -1;
@@ -99,10 +101,14 @@ internal class Rygel.HTTPResponse : GLib.Object, Rygel.StateMachine {
}
}
+ public Gee.List<HTTPResponseElement> ? preroll () throws Error {
+ return this.src.preroll (this.seek);
+ }
+
public async void run () {
this.run_continue = run.callback;
try {
- this.src.start (this.seek);
+ this.src.start ();
} catch (Error error) {
Idle.add (() => {
this.end (false, Status.NONE);
diff --git a/src/librygel-server/rygel-http-seek.vala b/src/librygel-server/rygel-http-seek.vala
index a7c66ea0..95860f39 100644
--- a/src/librygel-server/rygel-http-seek.vala
+++ b/src/librygel-server/rygel-http-seek.vala
@@ -1,10 +1,12 @@
/*
* Copyright (C) 2008-2009 Nokia Corporation.
* Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
*
* Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
* <zeeshan.ali@nokia.com>
* Jens Georg <jensg@openismus.com>
+ * Craig Pratt <craig@ecaspia.com>
*
* This file is part of Rygel.
*
@@ -23,89 +25,22 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-public errordomain Rygel.HTTPSeekError {
+/**
+ * Various errors that can be thrown when attempting to seek into a stream.
+ *
+ * Note: All codes must be set to Soup.Status codes
+ */
+public errordomain Rygel.HTTPSeekRequestError {
INVALID_RANGE = Soup.Status.BAD_REQUEST,
+ BAD_REQUEST = Soup.Status.BAD_REQUEST,
OUT_OF_RANGE = Soup.Status.REQUESTED_RANGE_NOT_SATISFIABLE,
}
-public enum Rygel.HTTPSeekType {
- BYTE,
- TIME
-}
-
/**
- * HTTPSeek is an abstract representation of a ranged HTTP request.
- *
- * It can be one of:
- *
- * - The classic Range request (seek_type == HTTPSeekType.BYTE), with start and stop in bytes.
- * - The DLNA-Specific "TimeSeekRange.dlna.org" request (seek_type == HTTPSeekType.TIME) with start and stop in microseconds.
+ * HTTPSeekRequest is an abstract base for a variety of seek request types.
*/
-public abstract class Rygel.HTTPSeek : GLib.Object {
-
- /**
- * Identifies whether this is a class Range request or a DLNA-specific
- * "TimeSeekRange.dlna.org" request.
- */
- public HTTPSeekType seek_type { get; protected set; }
- public Soup.Message msg { get; private set; }
-
- /**
- * The start of the range as a number of bytes (classic) or as microseconds
- * (DLNA-specific). See seek_type.
- */
- public int64 start { get; private set; }
-
- /**
- * The end of the range as a number of bytes (classic) or as microseconds
- * (DLNA-specific). See seek_type.
- */
- public int64 stop { get; private set; }
-
- /**
- * Either 1 byte (classic) or as 1000 G_TIME_SPAN_MILLISECOND microseconds
- * (DLNA-specific). See seek_type.
- */
- public int64 step { get; private set; }
-
- /**
- * The length of the range as a number of bytes (classic) or as microseconds
- * (DLNA-specific). See seek_type.
- */
- public int64 length { get; private set; }
-
- /**
- * The length of the media file as a number of bytes (classic) or as microseconds
- * (DLNA-specific). See seek_type.
- */
- public int64 total_length { get; private set; }
-
- public HTTPSeek (Soup.Message msg,
- int64 start,
- int64 stop,
- int64 step,
- int64 total_length) throws HTTPSeekError {
- this.msg = msg;
- this.start = start;
- this.stop = stop;
- this.length = length;
- this.total_length = total_length;
-
- if (start < 0 || start >= total_length) {
- throw new HTTPSeekError.OUT_OF_RANGE (_("Out Of Range Start '%ld'"),
- start);
- }
- if (stop < 0 || stop >= total_length) {
- throw new HTTPSeekError.OUT_OF_RANGE (_("Out Of Range Stop '%ld'"),
- stop);
- }
-
- if (length > 0) {
- this.stop = stop.clamp (start + 1, length - 1);
- }
-
- this.length = stop + step - start;
- }
-
- public abstract void add_response_headers ();
+public abstract class Rygel.HTTPSeekRequest : GLib.Object {
+ // For designating fields that are unset
+ public static const int64 UNSPECIFIED = -1;
+ // Note: -1 is significant in that libsoup also uses it to designate an "unknown" value
}
diff --git a/src/librygel-server/rygel-http-subtitle-handler.vala b/src/librygel-server/rygel-http-subtitle-handler.vala
index d011587b..cb27d73a 100644
--- a/src/librygel-server/rygel-http-subtitle-handler.vala
+++ b/src/librygel-server/rygel-http-subtitle-handler.vala
@@ -92,4 +92,8 @@ internal class Rygel.HTTPSubtitleHandler : Rygel.HTTPGetHandler {
public override int64 get_resource_size () {
return subtitle.size;
}
+
+ public override bool supports_byte_seek () {
+ return true;
+ }
}
diff --git a/src/librygel-server/rygel-http-thumbnail-handler.vala b/src/librygel-server/rygel-http-thumbnail-handler.vala
index 9a4975f6..cc23e715 100644
--- a/src/librygel-server/rygel-http-thumbnail-handler.vala
+++ b/src/librygel-server/rygel-http-thumbnail-handler.vala
@@ -93,4 +93,8 @@ internal class Rygel.HTTPThumbnailHandler : Rygel.HTTPGetHandler {
public override int64 get_resource_size () {
return thumbnail.size;
}
+
+ public override bool supports_byte_seek () {
+ return true;
+ }
}
diff --git a/src/librygel-server/rygel-http-time-seek-request.vala b/src/librygel-server/rygel-http-time-seek-request.vala
new file mode 100644
index 00000000..70fceb02
--- /dev/null
+++ b/src/librygel-server/rygel-http-time-seek-request.vala
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2009 Nokia Corporation.
+ * Copyright (C) 2012 Intel Corporation.
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ * <zeeshan.ali@nokia.com>
+ * Jens Georg <jensg@openismus.com>
+ * 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.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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 class represents a DLNA TimeSeekRange request.
+ *
+ * A TimeSeekRange request can only have a time range ("npt=start-end").
+ */
+public class Rygel.HTTPTimeSeekRequest : Rygel.HTTPSeekRequest {
+ public static const string TIMESEEKRANGE_HEADER = "TimeSeekRange.dlna.org";
+ /**
+ * Requested range start time, in microseconds
+ */
+ public int64 start_time;
+
+ /**
+ * Requested range end time, in microseconds
+ */
+ public int64 end_time;
+
+ /**
+ * Requested range duration, in microseconds
+ */
+ public int64 range_duration;
+
+ /**
+ * The total duration of the resource, in microseconds
+ */
+ public int64 total_duration;
+
+ /**
+ * Create a HTTPTimeSeekRequest corresponding with a HTTPGet that contains a
+ * TimeSeekRange.dlna.org header value.
+ *
+ * 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.
+ * @param request The HTTP GET/HEAD request
+ */
+ internal HTTPTimeSeekRequest (HTTPGet request)
+ throws HTTPSeekRequestError {
+ base ();
+
+ this.total_duration = request.handler.get_resource_duration ();
+ if (this.total_duration <= 0) {
+ this.total_duration = UNSPECIFIED;
+ }
+
+ var range = request.msg.request_headers.get_one (TIMESEEKRANGE_HEADER);
+
+ if (range == null) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ("%s not present",
+ TIMESEEKRANGE_HEADER);
+ }
+
+ if (!range.has_prefix ("npt=")) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid %s value (missing npt field): '%s'",
+ TIMESEEKRANGE_HEADER, range);
+ }
+
+ var parsed_range = range.substring (4);
+ if (!parsed_range.contains ("-")) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid %s request with no '-': '%s'",
+ TIMESEEKRANGE_HEADER, range);
+ }
+
+ var range_tokens = parsed_range.split ("-", 2);
+
+ int64 start = UNSPECIFIED;
+ if (!parse_npt_time (range_tokens[0], ref start)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE
+ ("Invalid %s value (no start): '%s'",
+ TIMESEEKRANGE_HEADER, range);
+ }
+
+ 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;
+
+ this.range_duration = this.end_time - this.start_time;
+ // At positive 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 after end time - forward scan): '%s'",
+ TIMESEEKRANGE_HEADER, range);
+ }
+ }
+ } else { // End time not specified in the npt field ("start-")
+ // See DLNA 7.5.4.3.2.24.4
+ this.end_time = UNSPECIFIED; // Will indicate "end/beginning of binary"
+ if (this.total_duration == UNSPECIFIED) {
+ this.range_duration = UNSPECIFIED;
+ } else {
+ this.range_duration = this.total_duration - this.start_time;
+ }
+ }
+ }
+
+ public string to_string () {
+ return ("HTTPTimeSeekRequest (npt=%lld-%s)".printf (this.start_time,
+ (this.end_time != UNSPECIFIED
+ ? this.end_time.to_string()
+ : "*") ) );
+ }
+
+ /**
+ * Return true if time-seek is supported.
+ *
+ * This method utilizes elements associated with the request to determine if a
+ * TimeSeekRange request is supported for the given request/resource.
+ */
+ public static bool supported (HTTPGet request) {
+ bool force_seek = false;
+
+ try {
+ var hack = ClientHacks.create (request.msg);
+ force_seek = hack.force_seek ();
+ } catch (Error error) { }
+
+ return force_seek || request.handler.supports_time_seek ();
+ }
+
+ /**
+ * Return true of the HTTPGet contains a TimeSeekRange request.
+ */
+ public static bool requested (HTTPGet request) {
+ return (request.msg.request_headers.get_one (TIMESEEKRANGE_HEADER) != null);
+ }
+
+ // Parses npt times in the format of '417.33' and returns the time in microseconds
+ private static bool parse_npt_seconds (string range_token,
+ ref int64 value) {
+ if (range_token[0].isdigit ()) {
+ value = (int64) (double.parse (range_token) * TimeSpan.SECOND);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ // Parses npt times in the format of '10:19:25.7' and returns the time in microseconds
+ private static bool parse_npt_time (string? range_token,
+ ref int64 value) {
+ if (range_token == null) {
+ return false;
+ }
+
+ if (range_token.index_of (":") == -1) {
+ return parse_npt_seconds (range_token, ref value);
+ }
+ // parse_seconds has a ':' in it...
+ int64 seconds_sum = 0;
+ int time_factor = 0;
+ string[] time_tokens;
+
+ seconds_sum = 0;
+ time_factor = 3600;
+
+ time_tokens = range_token.split (":", 3);
+ if (time_tokens[0] == null ||
+ time_tokens[1] == null ||
+ time_tokens[2] == null) {
+ return false;
+ }
+
+ foreach (string time in time_tokens) {
+ if (time[0].isdigit ()) {
+ seconds_sum += (int64) ((double.parse (time) * TimeSpan.SECOND) * time_factor);
+ } else {
+ return false;
+ }
+ time_factor /= 60;
+ }
+ value = seconds_sum;
+
+ return true;
+ }
+}
diff --git a/src/librygel-server/rygel-http-time-seek-response.vala b/src/librygel-server/rygel-http-time-seek-response.vala
new file mode 100644
index 00000000..3e26e67f
--- /dev/null
+++ b/src/librygel-server/rygel-http-time-seek-response.vala
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2013 Cable Television Laboratories, Inc.
+ * Contact: http://www.cablelabs.com/
+ *
+ * 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 class Rygel.HTTPTimeSeekResponse : Rygel.HTTPResponseElement {
+ /**
+ * Effective range start time, in microseconds
+ */
+ public int64 start_time { get; private set; }
+
+ /**
+ * Effective range end time, in microseconds
+ */
+ public int64 end_time { get; private set; }
+
+ /**
+ * Effective range duration, in microseconds
+ */
+ public int64 range_duration { get; private set; }
+
+ /**
+ * The total duration of the resource, in microseconds
+ */
+ public int64 total_duration { get; private set; }
+
+ /**
+ * The start of the range in bytes
+ */
+ public int64 start_byte { get; private set; }
+
+ /**
+ * The end of the range in bytes (inclusive)
+ */
+ public int64 end_byte { get; private set; }
+
+ /**
+ * The response length in bytes
+ */
+ public int64 response_length { get; private set; }
+
+ /**
+ * The length of the resource in bytes
+ */
+ public int64 total_size { get; private set; }
+
+ /**
+ * Construct a HTTPTimeSeekResponse with time and byte range
+ *
+ * start_time and start_byte must be specified.
+ *
+ * if total_duration and total_size are UNSPECIFIED, then the content duration/size
+ * will be signaled as unknown ("*")
+ *
+ * if end_time is UNSPECIFIED, then the time range end will be omitted from the
+ * response. If the end_byte is UNSPECIFIED, the entire byte range response will be
+ * omitted. (see DLNA 7.5.4.3.2.24.3)
+ */
+ public HTTPTimeSeekResponse (int64 start_time, int64 end_time, int64 total_duration,
+ int64 start_byte, int64 end_byte, int64 total_size) {
+ base ();
+ this.start_time = start_time;
+ this.end_time = end_time;
+ this.total_duration = total_duration;
+
+ this.start_byte = start_byte;
+ this.end_byte = end_byte;
+ this.response_length = (end_byte == UNSPECIFIED) ? UNSPECIFIED
+ : (end_byte - start_byte + 1);
+ this.total_size = total_size;
+ }
+
+ /**
+ * Create a HTTPTimeSeekResponse only containing a time range
+ *
+ * Note: This form is only valid when byte-seek is not supported, according to the
+ * associated resource's ProtocolInfo (see DLNA 7.5.4.3.2.24.5)
+ */
+ public HTTPTimeSeekResponse.time_only (int64 start_time, int64 end_time, int64 total_duration) {
+ base ();
+ this.start_time = start_time;
+ this.end_time = end_time;
+ this.total_duration = total_duration;
+
+ this.start_byte = UNSPECIFIED;
+ this.end_byte = UNSPECIFIED;
+ this.response_length = UNSPECIFIED;
+ this.total_size = UNSPECIFIED;
+ }
+
+ /**
+ * Construct a HTTPTimeSeekResponse with time and byte range and allowing for a
+ * response length override. This is useful when the response body is larger than the
+ * specified byte range from the original content binary.
+ *
+ * start_time and start_byte must be specified.
+ *
+ * If total_duration and total_size are UNSPECIFIED, then the content duration/size
+ * will be signaled as unknown ("*")
+ *
+ * if end_time is UNSPECIFIED, then the time range end will be omitted from the
+ * response. If the end_byte is UNSPECIFIED, the entire byte range response will be
+ * omitted. (see DLNA 7.5.4.3.2.24.3)
+ */
+ public HTTPTimeSeekResponse.with_length (int64 start_time, int64 end_time,
+ int64 total_duration,
+ int64 start_byte, int64 end_byte,
+ int64 total_size,
+ int64 response_length) {
+ base ();
+ this.start_time = start_time;
+ this.end_time = end_time;
+ this.total_duration = total_duration;
+
+ this.start_byte = start_byte;
+ this.end_byte = end_byte;
+ this.response_length = response_length;
+ this.total_size = total_size;
+ }
+
+ /**
+ * Create a HTTPTimeSeekResponse from a HTTPTimeSeekRequest
+ *
+ * Note: This form is only valid when byte-seek is not supported, according to the
+ * associated resource's ProtocolInfo (see DLNA 7.5.4.3.2.24.5)
+ */
+ public HTTPTimeSeekResponse.from_request ( HTTPTimeSeekRequest time_seek_request,
+ int64 total_duration ) {
+ HTTPTimeSeekResponse.time_only ( time_seek_request.start_time,
+ time_seek_request.end_time,
+ total_duration );
+ }
+
+ public override void add_response_headers (Rygel.HTTPRequest request) {
+ var response = get_response_string ();
+ if (response != null) {
+ request.msg.response_headers.append (HTTPTimeSeekRequest.TIMESEEKRANGE_HEADER,
+ response);
+ if (this.response_length != UNSPECIFIED) {
+ // Note: Don't use set_content_range () here - we don't want a "Content-range" header
+ request.msg.response_headers.set_content_length (this.response_length);
+ }
+ if (request.msg.get_http_version () == Soup.HTTPVersion.@1_0) {
+ request.msg.response_headers.replace ("Pragma","no-cache");
+ }
+ }
+ }
+
+ private string? get_response_string () {
+ if (start_time == UNSPECIFIED) {
+ return null;
+ }
+
+ // The response form of TimeSeekRange:
+ //
+ // TimeSeekRange.dlna.org: npt=START_TIME-END_TIME/DURATION bytes=START_BYTE-END_BYTE/LENGTH
+ //
+ // The "bytes=" field can be ommitted in some cases. (e.g. ORG_OP a-val==1, b-val==0)
+ // The DURATION can be "*" in some cases (e.g. for limited-operation mode)
+ // The LENGTH can be "*" in some cases (e.g. for limited-operation mode)
+ // And the entire response header can be ommitted for HEAD requests (see DLNA 7.5.4.3.2.24.2)
+
+ // It's not our job at this level to enforce all the semantics of the TimeSeekRange
+ // response, as we don't have enough context. Setting up the correct HTTPTimeSeekRequest
+ // object is the responsibility of the object owner. To form the response, we just
+ // use what is set.
+
+ var response = new StringBuilder ();
+ response.append ("npt=");
+ response.append_printf ("%.3f-", (double) this.start_time / TimeSpan.SECOND);
+ if (this.end_time != UNSPECIFIED) {
+ response.append_printf ("%.3f", (double) this.end_time / TimeSpan.SECOND);
+ }
+ if (this.total_duration != UNSPECIFIED) {
+ response.append_printf ("/%.3f", (double) this.total_duration / TimeSpan.SECOND);
+ } else {
+ response.append ("/*");
+ }
+
+ if ((this.start_byte != UNSPECIFIED) && (this.end_byte != UNSPECIFIED)) {
+ response.append (" bytes=");
+ response.append (this.start_byte.to_string ());
+ response.append ("-");
+ response.append (this.end_byte.to_string ());
+ response.append ("/");
+ if (this.total_size != UNSPECIFIED) {
+ response.append (this.total_size.to_string ());
+ } else {
+ response.append ("*");
+ }
+ }
+
+ return response.str;
+ }
+
+ public override string to_string () {
+ return ("HTTPTimeSeekResponse (%s)".printf (get_response_string ()));
+ }
+}
diff --git a/src/librygel-server/rygel-http-time-seek.vala b/src/librygel-server/rygel-http-time-seek.vala
deleted file mode 100644
index 397d8315..00000000
--- a/src/librygel-server/rygel-http-time-seek.vala
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2009 Nokia Corporation.
- * Copyright (C) 2012 Intel Corporation.
- *
- * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
- * <zeeshan.ali@nokia.com>
- * Jens Georg <jensg@openismus.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.
- *
- * Rygel is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * 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.
- */
-
-using GUPnP;
-
-internal class Rygel.HTTPTimeSeek : Rygel.HTTPSeek {
- public HTTPTimeSeek (HTTPGet request) throws HTTPSeekError {
- string range;
- string[] range_tokens;
- int64 start = 0;
- int64 duration = (request.object as AudioItem).duration * TimeSpan.SECOND;
- int64 stop = duration - TimeSpan.MILLISECOND;
- int64 parsed_value = 0;
- bool parsing_start = true;
-
- range = request.msg.request_headers.get_one ("TimeSeekRange.dlna.org");
-
- if (range != null) {
- if (!range.has_prefix ("npt=")) {
- throw new HTTPSeekError.INVALID_RANGE ("Invalid Range '%s'",
- range);
- }
-
- range_tokens = range.substring (4).split ("-", 2);
- if (range_tokens[0] == null ||
- // Start token of the range must be provided
- range_tokens[0] == "" ||
- range_tokens[1] == null) {
- throw new HTTPSeekError.INVALID_RANGE (_("Invalid Range '%s'"),
- range);
- }
-
- foreach (string range_token in range_tokens) {
- if (range_token == "") {
- continue;
- }
-
- if (range_token.index_of (":") == -1) {
- if (!parse_seconds (range_token, ref parsed_value)) {
- throw new HTTPSeekError.INVALID_RANGE
- (_("Invalid Range '%s'"),
- range);
- }
- } else {
- if (!parse_time (range_token,
- ref parsed_value)) {
- throw new HTTPSeekError.INVALID_RANGE
- (_("Invalid Range '%s'"),
- range);
- }
- }
-
- if (parsing_start) {
- parsing_start = false;
- start = parsed_value;
- } else {
- stop = parsed_value;
- }
- }
-
- if (start > stop) {
- throw new HTTPSeekError.INVALID_RANGE
- (_("Invalid Range '%s'"),
- range);
- }
- }
-
- base (request.msg, start, stop - 1, TimeSpan.MILLISECOND, duration);
- this.seek_type = HTTPSeekType.TIME;
- }
-
- private static bool is_transcoder (HTTPGetHandler handler) {
- return (handler is HTTPMediaResourceHandler) &&
- ((handler as HTTPMediaResourceHandler).media_resource.dlna_conversion ==
- DLNAConversion.TRANSCODED);
- }
-
- public static bool needed (HTTPGet request) {
- bool force_seek = false;
-
- try {
- var hack = ClientHacks.create (request.msg);
- force_seek = hack.force_seek ();
- } catch (Error error) { }
-
- return force_seek || (request.object is AudioItem &&
- (request.object as AudioItem).duration > 0 &&
- (is_transcoder (request.handler) ||
- (!(request.handler is HTTPThumbnailHandler) &&
- !(request.handler is HTTPSubtitleHandler) &&
- (request.object as MediaFileItem).is_live_stream ())));
- }
-
- public static bool requested (HTTPGet request) {
- return request.msg.request_headers.get_one ("TimeSeekRange.dlna.org") !=
- null;
- }
-
- public override void add_response_headers () {
- // TimeSeekRange.dlna.org: npt=START_TIME-END_TIME/DURATION
- double start = (double) this.start / TimeSpan.SECOND;
- double stop = (double) this.stop / TimeSpan.SECOND;
- double total = (double) this.total_length / TimeSpan.SECOND;
-
- var start_str = new char[double.DTOSTR_BUF_SIZE];
- var stop_str = new char[double.DTOSTR_BUF_SIZE];
- var total_str = new char[double.DTOSTR_BUF_SIZE];
-
- var range = "npt=" + start.format (start_str, "%.3f") + "-" +
- stop.format (stop_str, "%.3f") + "/" +
- total.format (total_str, "%.3f");
-
- this.msg.response_headers.append ("TimeSeekRange.dlna.org", range);
- }
-
- // Parses npt times in the format of '417.33'
- private static bool parse_seconds (string range_token,
- ref int64 value) {
- if (range_token[0].isdigit ()) {
- value = (int64) (double.parse (range_token) * TimeSpan.SECOND);
- } else {
- return false;
- }
- return true;
- }
-
- // Parses npt times in the format of '10:19:25.7'
- private static bool parse_time (string range_token,
- ref int64 value) {
- int64 seconds_sum = 0;
- int time_factor = 0;
- string[] time_tokens;
-
- seconds_sum = 0;
- time_factor = 3600;
-
- time_tokens = range_token.split (":", 3);
- if (time_tokens[0] == null ||
- time_tokens[1] == null ||
- time_tokens[2] == null) {
- return false;
- }
-
- foreach (string time in time_tokens) {
- if (time[0].isdigit ()) {
- seconds_sum += (int64) ((double.parse (time) *
- TimeSpan.SECOND) * time_factor);
- } else {
- return false;
- }
- time_factor /= 60;
- }
- value = seconds_sum;
-
- return true;
- }
-}
diff --git a/src/librygel-server/rygel-media-container.vala b/src/librygel-server/rygel-media-container.vala
index e739ad39..46ff5fd3 100644
--- a/src/librygel-server/rygel-media-container.vala
+++ b/src/librygel-server/rygel-media-container.vala
@@ -58,16 +58,22 @@ internal class Rygel.PlaylistDatasource : Rygel.DataSource, Object {
public signal void data_ready ();
- public void start (HTTPSeek? offsets) throws Error {
- if (offsets != null) {
+ public Gee.List<HTTPResponseElement> ? preroll ( HTTPSeekRequest? seek_request)
+ throws Error {
+ if (seek_request != null) {
throw new DataSourceError.SEEK_FAILED
(_("Seeking not supported"));
}
+ return null;
+ }
+
+
+ public void start () throws Error {
if (this.data == null) {
this.data_ready.connect ( () => {
try {
- this.start (offsets);
+ this.start ();
} catch (Error error) { }
});