[rygel] server: Add support for encrypted content
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rygel] server: Add support for encrypted content
- Date: Tue, 17 Feb 2015 00:06:46 +0000 (UTC)
commit 68329a27c7be10869c71f650f50bdeab3f3f77c2
Author: Jens Georg <mail jensge org>
Date: Mon Feb 16 23:02:44 2015 +0100
server: Add support for encrypted content
Code based on Cablelabs*s CVP-2 implementation
Signed-off-by: Jens Georg <mail jensge org>
src/librygel-server/filelist.am | 4 +-
.../rygel-dtcp-cleartext-request.vala | 145 +++++++++++++++++++
.../rygel-dtcp-cleartext-response.vala | 97 +++++++++++++
src/librygel-server/rygel-http-get.vala | 24 +++-
.../rygel-http-resource-handler.vala | 9 +-
src/librygel-server/rygel-http-server.vala | 14 ++
src/librygel-server/rygel-media-object.vala | 28 ++++-
src/librygel-server/rygel-media-resource.vala | 150 +++++++++++++-------
8 files changed, 411 insertions(+), 60 deletions(-)
---
diff --git a/src/librygel-server/filelist.am b/src/librygel-server/filelist.am
index e2ed3b2..f46a0c2 100644
--- a/src/librygel-server/filelist.am
+++ b/src/librygel-server/filelist.am
@@ -84,4 +84,6 @@ LIBRYGEL_SERVER_NONVAPI_SOURCE_FILES = \
rygel-data-sink.vala \
rygel-playspeed.vala \
rygel-playspeed-request.vala \
- rygel-playspeed-response.vala
+ rygel-playspeed-response.vala \
+ rygel-dtcp-cleartext-request.vala \
+ rygel-dtcp-cleartext-response.vala
diff --git a/src/librygel-server/rygel-dtcp-cleartext-request.vala
b/src/librygel-server/rygel-dtcp-cleartext-request.vala
new file mode 100644
index 0000000..c116c7f
--- /dev/null
+++ b/src/librygel-server/rygel-dtcp-cleartext-request.vala
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+using GUPnP;
+
+public class Rygel.DTCPCleartextRequest : Rygel.HTTPSeekRequest {
+ public static const string DTCP_RANGE_HEADER = "Range.dtcp.com";
+
+ /**
+ * The start of the cleartext range in bytes
+ */
+ public int64 start_byte { get; private set; }
+
+ /**
+ * The end of the cleartext range in bytes (inclusive). May be HTTPSeekRequest.UNSPECIFIED
+ */
+ public int64 end_byte { get; private set; }
+
+ /**
+ * The length of the cleartext range in bytes. May be HTTPSeekRequest.UNSPECIFIED
+ */
+ public int64 range_length { get; private set; }
+
+ /**
+ * The length of the cleartext resource in bytes. May be HTTPSeekRequest.UNSPECIFIED
+ */
+ public int64 total_size { get; private set; }
+
+ public DTCPCleartextRequest (HTTPGet request) throws HTTPSeekRequestError,
+ HTTPRequestError {
+ base ();
+
+ int64 start, end, total_size;
+
+ // It's only possible to get the cleartext size from a MediaResource
+ // (and only if it is link protected)
+ if (request.handler is HTTPMediaResourceHandler) {
+ MediaResource resource = (request.handler as HTTPMediaResourceHandler)
+ .media_resource;
+ total_size = resource.cleartext_size;
+ if (total_size <= 0) {
+ // Even if it's a resource and the content is link-protected, it may have an
+ // unknown cleartext size (e.g. if it's live/in-progress content). This doesn't
+ // mean the request is invalid, it just means the total size is non-static
+ total_size = UNSPECIFIED;
+ }
+ } else {
+ total_size = UNSPECIFIED;
+ }
+
+ unowned string range = request.msg.request_headers.get_one (DTCP_RANGE_HEADER);
+
+ if (range == null) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "%s request header not present",
+ DTCP_RANGE_HEADER );
+ }
+
+ if (!range.has_prefix ("bytes")) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "Invalid %s value (missing bytes field): '%s'",
+ DTCP_RANGE_HEADER, range );
+ }
+
+ var range_tokens = range.substring (6).split ("-", 2); // skip "bytes="
+ if (range_tokens[0].length == 0) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "No range start specified: '%s'",
+ range );
+ }
+
+ if (!int64.try_parse (range_tokens[0], out start) || (start < 0)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "Invalid %s range start: '%s'",
+ DTCP_RANGE_HEADER, range );
+ }
+ // valid range start specified
+
+ // Look for a range end...
+ if (range_tokens[1].length == 0) {
+ end = UNSPECIFIED;
+ } else {
+ if (!int64.try_parse (range_tokens[1], out end) || (end <= 0)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "Invalid %s range end: '%s'",
+ DTCP_RANGE_HEADER, range );
+ }
+ // valid end range specified
+ }
+
+ if ((end != UNSPECIFIED) && (start > end)) {
+ throw new HTTPSeekRequestError.INVALID_RANGE ( "Invalid %s range - start > end: '%s'",
+ DTCP_RANGE_HEADER, range );
+ }
+
+ if ((total_size != UNSPECIFIED) && (start > total_size-1)) {
+ throw new HTTPSeekRequestError.OUT_OF_RANGE ( "Invalid %s range - start > length: '%s'",
+ DTCP_RANGE_HEADER, range );
+ }
+
+ if ((total_size != UNSPECIFIED) && (end > total_size-1)) {
+ // It's not clear from the DLNA link protection spec if the range end can be beyond
+ // the total length. We'll assume RFC 2616 14.35.1 semantics. But note that having
+ // an end with an unspecified size will be normal for live/in-progress content
+ end = total_size-1;
+ }
+
+ this.start_byte = start;
+ this.end_byte = end;
+ this.range_length = (end == UNSPECIFIED) ? UNSPECIFIED
+ : end-start+1; // +1, since range is inclusive
+ this.total_size = total_size;
+ }
+
+ public static bool supported (HTTPGet request) {
+ return (request.handler is HTTPMediaResourceHandler)
+ && (request.handler as HTTPMediaResourceHandler)
+ .media_resource.is_cleartext_range_support_enabled ();
+ }
+
+ public static bool requested (HTTPGet request) {
+ return (request.msg.request_headers.get_one (DTCP_RANGE_HEADER) != null);
+ }
+}
diff --git a/src/librygel-server/rygel-dtcp-cleartext-response.vala
b/src/librygel-server/rygel-dtcp-cleartext-response.vala
new file mode 100644
index 0000000..91d055c
--- /dev/null
+++ b/src/librygel-server/rygel-dtcp-cleartext-response.vala
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+using GUPnP;
+
+public class Rygel.DTCPCleartextResponse : Rygel.HTTPResponseElement {
+ public static const string DTCP_CONTENT_RANGE_HEADER = "Content-Range.dtcp.com";
+
+ /**
+ * The start of the response 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 length of the range in bytes
+ */
+ public int64 range_length { get; private set; }
+
+ /**
+ * The length of the resource in bytes. May be HTTPSeekRequest.UNSPECIFIED
+ */
+ public int64 total_size { get; private set; }
+
+ /**
+ * The encrypted length of the response
+ */
+ public int64 encrypted_length { get; public set;}
+
+ public DTCPCleartextResponse (int64 start_byte, int64 end_byte, int64 total_size,
+ int64 encrypted_length = UNSPECIFIED) {
+ 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;
+ this.encrypted_length = encrypted_length;
+ }
+
+ public DTCPCleartextResponse.from_request (DTCPCleartextRequest request,
+ int64 encrypted_length = UNSPECIFIED) {
+ this.start_byte = request.start_byte;
+ this.end_byte = request.end_byte;
+ this.range_length = request.range_length;
+ this.total_size = request.total_size;
+ this.encrypted_length = encrypted_length;
+ }
+
+ public override void add_response_headers (Rygel.HTTPRequest request) {
+ // Content-Range.dtcp.com: bytes START_BYTE-END_BYTE/TOTAL_LENGTH (or "*")
+ if (this.start_byte != UNSPECIFIED) {
+ string response = "bytes " + this.start_byte.to_string ()
+ + "-" + this.end_byte.to_string () + "/"
+ + ( (this.total_size == UNSPECIFIED) ? "*"
+ : this.total_size.to_string () );
+
+ request.msg.response_headers.append (DTCP_CONTENT_RANGE_HEADER, response);
+ }
+ if (this.encrypted_length != UNSPECIFIED) {
+ request.msg.response_headers.set_content_length (this.encrypted_length);
+ }
+ }
+
+ public override string to_string () {
+ return ("DTCPCleartextResponse(bytes=%lld-%lld/%lld, enc_len=%lld)"
+ .printf (this.start_byte, this.end_byte, this.total_size, this.encrypted_length));
+ }
+}
diff --git a/src/librygel-server/rygel-http-get.vala b/src/librygel-server/rygel-http-get.vala
index 1b759ec..3a49155 100644
--- a/src/librygel-server/rygel-http-get.vala
+++ b/src/librygel-server/rygel-http-get.vala
@@ -118,8 +118,21 @@ public class Rygel.HTTPGet : HTTPRequest {
var requested_time_seek = HTTPTimeSeekRequest.requested (this);
var supports_byte_seek = HTTPByteSeekRequest.supported (this);
var requested_byte_seek = HTTPByteSeekRequest.requested (this);
+ var supports_cleartext_seek = DTCPCleartextRequest.supported (this);
+ var requested_cleartext_seek = DTCPCleartextRequest.requested (this);
- if (requested_byte_seek) {
+ // Order is significant here when the request has more than one seek header
+ if (requested_cleartext_seek) {
+ if (!supports_cleartext_seek) {
+ throw new HTTPRequestError.UNACCEPTABLE ( "Cleartext seek not supported for "
+ + this.uri.to_string () );
+ }
+ if (requested_byte_seek) {
+ // Per DLNA Link Protection 7.6.4.3.3.9
+ throw new HTTPRequestError.UNACCEPTABLE ( "Both Cleartext and Range seek requested "
+ + this.uri.to_string ());
+ }
+ } else if (requested_byte_seek) {
if (!supports_byte_seek) {
throw new HTTPRequestError.UNACCEPTABLE ( "Byte seek not supported for "
+ this.uri.to_string () );
@@ -137,7 +150,7 @@ public class Rygel.HTTPGet : HTTPRequest {
// Note: We need to check the speed first since direction factors into validating
// the time-seek request
try {
- if ( !requested_byte_seek
+ if ( !(requested_byte_seek || requested_cleartext_seek)
&& PlaySpeedRequest.requested (this) ) {
this.speed_request = new PlaySpeedRequest.from_request (this);
debug ("Processing playspeed %s", speed_request.speed.to_string ());
@@ -164,7 +177,12 @@ public class Rygel.HTTPGet : HTTPRequest {
}
try {
// Order is intentional here
- if (supports_byte_seek && requested_byte_seek) {
+ if (supports_cleartext_seek && requested_cleartext_seek) {
+ var cleartext_seek = new DTCPCleartextRequest (this);
+ debug ("Processing DTCP cleartext byte range request (bytes %lld to %lld)",
+ cleartext_seek.start_byte, cleartext_seek.end_byte);
+ this.seek = cleartext_seek;
+ } else 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);
diff --git a/src/librygel-server/rygel-http-resource-handler.vala
b/src/librygel-server/rygel-http-resource-handler.vala
index 2b0b78e..1c8009c 100644
--- a/src/librygel-server/rygel-http-resource-handler.vala
+++ b/src/librygel-server/rygel-http-resource-handler.vala
@@ -46,11 +46,14 @@ internal class Rygel.HTTPMediaResourceHandler : HTTPGetHandler {
public override void add_response_headers (HTTPGet request)
throws HTTPRequestError {
request.http_server.set_resource_delivery_options (this.media_resource);
- request.msg.response_headers.append ("Content-Type",
- this.media_resource.mime_type);
+ var replacements = request.http_server.get_replacements ();
+ var mime_type = MediaObject.apply_replacements
+ (replacements,
+ this.media_resource.mime_type);
+ request.msg.response_headers.append ("Content-Type", mime_type);
// Add contentFeatures.dlna.org
- var protocol_info = this.media_resource.get_protocol_info ();
+ var protocol_info = media_resource.get_protocol_info (replacements);
if (protocol_info != null) {
var pi_fields = protocol_info.to_string ().split (":", 4);
if (pi_fields[3] != null) {
diff --git a/src/librygel-server/rygel-http-server.vala b/src/librygel-server/rygel-http-server.vala
index f72c4d8..8f12b7a 100644
--- a/src/librygel-server/rygel-http-server.vala
+++ b/src/librygel-server/rygel-http-server.vala
@@ -6,6 +6,7 @@
* Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
* <zeeshan ali nokia com>
* Jens Georg <jensg openismus com>
+ * Doug Galligan <doug sentosatech com>
* Craig Pratt <craig ecaspia com>
*
* This file is part of Rygel.
@@ -36,6 +37,7 @@ public class Rygel.HTTPServer : GLib.Object, Rygel.StateMachine {
public GUPnP.Context context;
private ArrayList<HTTPRequest> requests;
private bool locally_hosted;
+ public HashTable<string, string> replacements;
public Cancellable cancellable { get; set; }
@@ -53,6 +55,14 @@ public class Rygel.HTTPServer : GLib.Object, Rygel.StateMachine {
this.context.host_ip == "127.0.0.1";
this.path_root = "/" + name;
+ this.replacements = new HashTable <string, string> (str_hash, str_equal);
+ this.replacements.insert ("@SERVICE_ADDRESS@",
+ this.context.host_ip);
+ this.replacements.insert ("@SERVICE_INTERFACE@",
+ this.context.interface);
+ this.replacements.insert ("@SERVICE_PORT@",
+ this.context.port.to_string ());
+ this.replacements.insert ("@HOSTNAME@", Environment.get_host_name ());
}
public async void run () {
@@ -111,6 +121,10 @@ public class Rygel.HTTPServer : GLib.Object, Rygel.StateMachine {
return new ArrayList<ProtocolInfo>();
}
+ public HashTable<string, string> get_replacements () {
+ return this.replacements;
+ }
+
public bool is_local () {
return this.locally_hosted;
}
diff --git a/src/librygel-server/rygel-media-object.vala b/src/librygel-server/rygel-media-object.vala
index 2eb9279..8523370 100644
--- a/src/librygel-server/rygel-media-object.vala
+++ b/src/librygel-server/rygel-media-object.vala
@@ -252,7 +252,8 @@ public abstract class Rygel.MediaObject : GLib.Object {
public void serialize_resource_list (DIDLLiteObject didl_object,
HTTPServer http_server)
throws Error {
- foreach (var res in this.get_resource_list ()) {
+ var replacements = http_server.get_replacements ();
+ foreach (var res in get_resource_list ()) {
if (res.uri == null || res.uri == "") {
var uri = http_server.create_uri_for_object (this,
-1,
@@ -266,7 +267,7 @@ public abstract class Rygel.MediaObject : GLib.Object {
}
var didl_resource = didl_object.add_resource ();
http_server.set_resource_delivery_options (res);
- res.serialize (didl_resource);
+ res.serialize (didl_resource, replacements);
res.uri = null;
res.import_uri = null;
} else {
@@ -275,7 +276,7 @@ public abstract class Rygel.MediaObject : GLib.Object {
if (protocol != "internal" || http_server.is_local ()) {
// Exclude internal resources when request is non-local
var didl_resource = didl_object.add_resource ();
- res.serialize (didl_resource);
+ res.serialize (didl_resource, replacements);
}
} catch (Error e) {
warning (_("Could not determine protocol for %s"), res.uri);
@@ -285,6 +286,27 @@ public abstract class Rygel.MediaObject : GLib.Object {
}
/**
+ * Replace each key in replacement_pairs with its corresponding
+ * value in the source_string and return the result.
+ *
+ * If source_string is null, null is returned.
+ */
+ public static string ? apply_replacements
+ (HashTable<string, string> replacement_pairs,
+ string source_string) {
+ if (source_string == null) {
+ return null;
+ }
+ var replaced_string = source_string;
+ replacement_pairs.foreach ((search_string, replacement)
+ => {
+ replaced_string
+ = replaced_string.replace (search_string, replacement);
+ } );
+ return replaced_string;
+ }
+
+ /**
* Create a stream source for the given resource
*/
public abstract DataSource? create_stream_source_for_resource
diff --git a/src/librygel-server/rygel-media-resource.vala b/src/librygel-server/rygel-media-resource.vala
index 178412b..3b3494d 100644
--- a/src/librygel-server/rygel-media-resource.vala
+++ b/src/librygel-server/rygel-media-resource.vala
@@ -62,9 +62,10 @@ public class Rygel.MediaResource : GLib.Object {
public DLNAOperation dlna_operation { get; set; default = DLNAOperation.NONE; }
// I know gupnp-av DIDLLiteResource and ProtocolInfo structures have the above fields.
- // But both proved to be problematic in their current form. This class can be
- // refactored if/when these classes are made more more flexible. For now, this class
- // needs to serve the needs of Rygel first and foremost...
+ // But both proved to be problematic in their current form when used in a variety
+ // of containers (appears to be issues with reference management causing (incomplete)
+ // copies of DIDLLiteResource/ProtocolInfo to be made). This class can be refactored
+ // if/when these classes are made more flexible.
public MediaResource (string name) {
this.name = name;
@@ -100,44 +101,11 @@ public class Rygel.MediaResource : GLib.Object {
this.dlna_operation = that.dlna_operation;
}
- public static string []? copy_speeds (string? [] src) {
- if (src == null) {
- return null;
- }
- var new_speeds = new string[src.length];
- int speed_index = 0;
- foreach (var speed in src) {
- new_speeds[speed_index++] = speed;
- }
-
- return new_speeds;
- }
-
- public MediaResource dup () {
- return new MediaResource.from_resource (this.get_name (), this);
- }
-
- public string get_name () {
- return this.name;
- }
-
- private HashMap<string,string> property_table = new HashMap<string,string> ();
-
- public void set_custom_property (string ? name, string ? value) {
- property_table.set (name,value);
- }
-
- public string get_custom_property (string ? name) {
- return property_table.get (name);
- }
-
- public Set get_custom_property_names () {
- return property_table.keys;
- }
-
- public void apply_didl_lite (DIDLLiteResource didl_resource) {
- // Populate the MediaResource from the given DIDLLiteResource
+ public MediaResource.from_didl_lite_resource (string name, DIDLLiteResource didl_resource) {
+ // Create a MediaResource from the given DIDLLiteResource
// Note: For a DIDLLiteResource, a value of -1/null also signals "not set"
+ this.name = name;
+ // res block
this.uri = didl_resource.uri;
this.size = didl_resource.size64;
this.cleartext_size = didl_resource.cleartext_size;
@@ -149,6 +117,7 @@ public class Rygel.MediaResource : GLib.Object {
this.height = didl_resource.height;
this.audio_channels = didl_resource.audio_channels;
this.sample_freq = didl_resource.sample_freq;
+ // protocol info
if (didl_resource.protocol_info != null) {
this.protocol = didl_resource.protocol_info.protocol;
this.mime_type = didl_resource.protocol_info.mime_type;
@@ -161,10 +130,37 @@ public class Rygel.MediaResource : GLib.Object {
}
}
- public DIDLLiteResource serialize (DIDLLiteResource didl_resource) {
- // Note: For a DIDLLiteResource, a value of -1/null also signals "not set"
- didl_resource.uri = this.uri;
- didl_resource.import_uri = this.import_uri;
+ public MediaResource dup () {
+ return new MediaResource.from_resource (this.get_name (), this);
+ }
+
+ public static string []? copy_speeds (string? [] src) {
+ if (src == null) {
+ return null;
+ }
+ var new_speeds = new string[src.length];
+ int speed_index = 0;
+ foreach (var speed in src) {
+ new_speeds[speed_index++] = speed;
+ }
+
+ return new_speeds;
+ }
+
+ public string get_name () {
+ return this.name;
+ }
+
+ public DIDLLiteResource serialize
+ (DIDLLiteResource didl_resource,
+ HashTable<string, string> ? replacements) {
+ // Note: For a DIDLLiteResource, a values -1/null also signal "not set"
+ if (replacements == null) {
+ didl_resource.uri = this.uri;
+ } else {
+ didl_resource.uri = MediaObject.apply_replacements (replacements,
+ this.uri);
+ }
didl_resource.size64 = this.size;
didl_resource.cleartext_size = this.cleartext_size;
didl_resource.duration = this.duration;
@@ -191,12 +187,18 @@ public class Rygel.MediaResource : GLib.Object {
this.play_speeds = copy_speeds (pi.play_speeds);
}
- public ProtocolInfo get_protocol_info () {
+ public ProtocolInfo get_protocol_info
+ (HashTable<string, string> ? replacements = null) {
var new_pi = new ProtocolInfo ();
new_pi.protocol = this.protocol;
new_pi.network = this.network;
- new_pi.mime_type = this.mime_type;
+ if (replacements == null) {
+ new_pi.mime_type = this.mime_type;
+ } else {
+ new_pi.mime_type = MediaObject.apply_replacements (replacements,
+ this.mime_type);
+ }
new_pi.dlna_profile = this.dlna_profile;
new_pi.dlna_conversion = this.dlna_conversion;
new_pi.dlna_operation = this.dlna_operation;
@@ -289,8 +291,8 @@ public class Rygel.MediaResource : GLib.Object {
}
public string to_string () {
- var strbuf = new StringBuilder ();
- strbuf.append (name).append_unichar ('(');
+ var strbuf = new StringBuilder (name);
+ strbuf.append_unichar ('(');
if (this.size >= 0) {
strbuf.append ("size ").append (this.size.to_string ())
.append_unichar (',');
@@ -343,12 +345,60 @@ public class Rygel.MediaResource : GLib.Object {
strbuf.append ("dlna_profile ")
.append (this.dlna_profile == null ? "null" : this.dlna_profile)
.append_unichar (',');
- strbuf.append_printf ("dlna_flags %.8X,", this.dlna_flags);
+ strbuf.append_printf ("dlna_flags %.8X [", this.dlna_flags);
+ if (is_dlna_protocol_flag_set (DLNAFlags.SENDER_PACED)) {
+ strbuf.append ("sp-flag ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.TIME_BASED_SEEK)) {
+ strbuf.append ("lop-time ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.BYTE_BASED_SEEK)) {
+ strbuf.append ("lop-byte ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.S0_INCREASE)) {
+ strbuf.append ("s0-increase ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.SN_INCREASE)) {
+ strbuf.append ("sn-increase ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.STREAMING_TRANSFER_MODE)) {
+ strbuf.append ("streaming ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.INTERACTIVE_TRANSFER_MODE)) {
+ strbuf.append ("interactive ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.BACKGROUND_TRANSFER_MODE)) {
+ strbuf.append ("background ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.CONNECTION_STALL)) {
+ strbuf.append ("stall ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.DLNA_V15)) {
+ strbuf.append ("v1.5 ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.LINK_PROTECTED_CONTENT)) {
+ strbuf.append ("link-protected ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.CLEARTEXT_BYTESEEK_FULL)) {
+ strbuf.append ("cleartext-full ");
+ }
+ if (is_dlna_protocol_flag_set (DLNAFlags.LOP_CLEARTEXT_BYTESEEK)) {
+ strbuf.append ("cleartext-lop ");
+ }
+ strbuf.overwrite (strbuf.len-1,"],"); // Replace space
+
if (this.dlna_conversion != DLNAConversion.NONE) {
strbuf.append_printf ("dlna_conversion %1d,", this.dlna_conversion);
}
if (this.dlna_operation != DLNAOperation.NONE) {
- strbuf.append_printf ("dlna_operation %.2X,", this.dlna_operation);
+ strbuf.append_printf ("dlna_operation %.2X [", this.dlna_operation);
+ if (is_dlna_operation_mode_set (DLNAOperation.RANGE)) {
+ strbuf.append ("byte-seek ");
+ }
+ if (is_dlna_operation_mode_set (DLNAOperation.TIMESEEK)) {
+ strbuf.append ("time-seek ");
+ }
+ strbuf.overwrite (strbuf.len-1,"],"); // Replace space
}
if (this.play_speeds != null) {
strbuf.append ("play_speeds [");
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]