rygel r415 - in trunk/src: plugins/test rygel
- From: zeeshanak svn gnome org
- To: svn-commits-list gnome org
- Subject: rygel r415 - in trunk/src: plugins/test rygel
- Date: Wed, 7 Jan 2009 12:56:08 +0000 (UTC)
Author: zeeshanak
Date: Wed Jan 7 12:56:08 2009
New Revision: 415
URL: http://svn.gnome.org/viewvc/rygel?rev=415&view=rev
Log:
Implement partial download (Range header).
Seems to work pretty well against PS3 but I did observe stream hanging
once.
Modified:
trunk/src/plugins/test/rygel-test-item.vala
trunk/src/rygel/rygel-gst-stream.vala
trunk/src/rygel/rygel-streamer.vala
Modified: trunk/src/plugins/test/rygel-test-item.vala
==============================================================================
--- trunk/src/plugins/test/rygel-test-item.vala (original)
+++ trunk/src/plugins/test/rygel-test-item.vala Wed Jan 7 12:56:08 2009
@@ -72,7 +72,7 @@
Element src = this.create_gst_source ();
// Ask streamer to handle the stream for us but use our source in
// the pipeline.
- streamer.stream_from_gst_source (src, stream);
+ streamer.stream_from_gst_source (src, stream, null);
} catch (Error error) {
critical ("Error in attempting to start streaming %s: %s",
path,
Modified: trunk/src/rygel/rygel-gst-stream.vala
==============================================================================
--- trunk/src/rygel/rygel-gst-stream.vala (original)
+++ trunk/src/rygel/rygel-gst-stream.vala Wed Jan 7 12:56:08 2009
@@ -41,14 +41,18 @@
private AsyncQueue<Buffer> buffers;
+ private Event seek_event;
+
public GstStream (Stream stream,
string name,
- Element src) throws Error {
+ Element src,
+ Event? seek_event) throws Error {
this.stream = stream;
this.name = name;
+ this.seek_event = seek_event;
this.buffers = new AsyncQueue<Buffer> ();
- this.stream.accept ();
+ this.stream.accept (seek != null);
this.prepare_pipeline (src);
}
@@ -197,6 +201,16 @@
if (message.type == MessageType.EOS) {
ret = false;
+ } else if (message.type == MessageType.STATE_CHANGED &&
+ this.seek_event != null) {
+ State new_state;
+
+ message.parse_state_changed (null, out new_state, null);
+ if (new_state == State.PAUSED || new_state == State.PLAYING) {
+ // Time to shove-in the pending seek event
+ this.send_event (this.seek_event);
+ this.seek_event = null;
+ }
} else {
GLib.Error err;
string err_msg;
Modified: trunk/src/rygel/rygel-streamer.vala
==============================================================================
--- trunk/src/rygel/rygel-streamer.vala (original)
+++ trunk/src/rygel/rygel-streamer.vala Wed Jan 7 12:56:08 2009
@@ -1,8 +1,10 @@
/*
- * Copyright (C) 2008 Nokia Corporation, all rights reserved.
+ * Copyright (C) 2008, 2009 Nokia Corporation, all rights reserved.
+ * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
*
* Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
* <zeeshan ali nokia com>
+ * Jorn Baayen <jorn baayen gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +27,12 @@
using Gee;
using Gst;
+using GUPnP;
+
+public errordomain Rygel.StreamerError {
+ INVALID_RANGE = Soup.KnownStatusCode.BAD_REQUEST,
+ OUT_OF_RANGE = Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE
+}
public class Rygel.Streamer : GLib.Object {
private const string SERVER_PATH_PREFIX = "/RygelStreamer";
@@ -62,10 +70,12 @@
}
public void stream_from_gst_source (Element# src,
- Stream stream) throws Error {
- GstStream gst_stream;
-
- gst_stream = new GstStream (stream, "RygelGstStream", src);
+ Stream stream,
+ Event? seek_event) throws Error {
+ GstStream gst_stream = new GstStream (stream,
+ "RygelGstStream",
+ src,
+ seek_event);
gst_stream.set_state (State.PLAYING);
stream.eos += on_eos;
@@ -128,8 +138,26 @@
return;
}
+ size_t offset = 0;
+ size_t length = item.res.size;
+ bool got_range;
+
+ try {
+ got_range = this.parse_range (msg, out offset, out length);
+ } catch (StreamerError err) {
+ warning ("%s", err.message);
+ msg.set_status (err.code);
+ return;
+ }
+
+ if (item.res.size == -1 && got_range) {
+ warning ("Partial download not applicable for item %s", item_id);
+ msg.set_status (Soup.KnownStatusCode.NOT_ACCEPTABLE);
+ return;
+ }
+
// Add headers
- this.add_item_headers (msg, item);
+ this.add_item_headers (msg, item, got_range, offset, length);
if (msg.method == "HEAD") {
// Only headers requested, no need to stream contents
@@ -138,9 +166,9 @@
}
if (item.upnp_class == MediaItem.IMAGE_CLASS) {
- this.handle_interactive_item (msg, item);
+ this.handle_interactive_item (msg, item, got_range, offset, length);
} else {
- this.handle_streaming_item (msg, item);
+ this.handle_streaming_item (msg, item, got_range, offset, length);
}
}
@@ -165,7 +193,10 @@
}
private void add_item_headers (Soup.Message msg,
- MediaItem item) {
+ MediaItem item,
+ bool partial_content,
+ size_t offset,
+ size_t length) {
if (item.res.mime_type != null) {
msg.response_headers.append ("Content-Type", item.res.mime_type);
}
@@ -174,10 +205,27 @@
msg.response_headers.append ("Content-Length",
item.res.size.to_string ());
}
+
+ if (DLNAOperation.RANGE in item.res.dlna_operation) {
+ msg.response_headers.append ("Accept-Ranges", "bytes");
+ }
+
+ if (partial_content) {
+ // Content-Range: bytes OFFSET-LENGTH/TOTAL_LENGTH
+ var content_range = "bytes " +
+ offset.to_string () + "-" +
+ (length - 1).to_string () + "/" +
+ item.res.size.to_string ();
+
+ msg.response_headers.append ("Content-Range", content_range);
+ }
}
private void handle_streaming_item (Soup.Message msg,
- MediaItem item) {
+ MediaItem item,
+ bool partial_content,
+ size_t offset,
+ size_t length) {
string uri = item.res.uri;
// Create to Gst source that can handle the URI
@@ -195,8 +243,21 @@
// create a stream for it
var stream = new Stream (this.context.server, msg);
try {
+ // Create the seek event if needed
+ Event seek_event = null;
+
+ if (partial_content) {
+ seek_event = new Event.seek (1.0,
+ Format.BYTES,
+ SeekFlags.FLUSH,
+ Gst.SeekType.SET,
+ offset,
+ Gst.SeekType.SET,
+ length);
+ }
+
// Then attach the gst source to stream we are good to go
- this.stream_from_gst_source (src, stream);
+ this.stream_from_gst_source (src, stream, seek_event);
} catch (Error error) {
critical ("Error in attempting to start streaming %s: %s",
uri,
@@ -205,17 +266,20 @@
}
private void handle_interactive_item (Soup.Message msg,
- MediaItem item) {
+ MediaItem item,
+ bool partial_content,
+ size_t offset,
+ size_t length) {
string uri = item.res.uri;
File file = File.new_for_uri (uri);
string contents;
- size_t length;
+ size_t file_length;
try {
file.load_contents (null,
out contents,
- out length,
+ out file_length,
null);
} catch (Error error) {
warning ("Failed to load contents from URI: %s: %s\n",
@@ -225,10 +289,82 @@
return;
}
- msg.set_status (Soup.KnownStatusCode.OK);
+ assert (offset <= file_length);
+ assert (length <= file_length);
+
+ if (partial_content) {
+ msg.set_status (Soup.KnownStatusCode.PARTIAL_CONTENT);
+ } else {
+ msg.set_status (Soup.KnownStatusCode.OK);
+ }
+
msg.response_body.append (Soup.MemoryUse.COPY,
- contents,
+ contents.offset ((long) offset),
length);
}
+
+ /* Parses the HTTP Range header on @message and sets:
+ *
+ * @offset to the requested offset (left unchanged if none specified),
+ * @length to the requested length (left unchanged if none specified).
+ *
+ * Both @offset and @length are expected to be initialised to their default
+ * values. Throws a #StreamerError in case of error.
+ *
+ * Returns %true a range header was found, false otherwise. */
+ private bool parse_range (Soup.Message message,
+ out size_t offset,
+ out size_t length)
+ throws StreamerError {
+ string range;
+ string[] range_tokens;
+
+ range = message.request_headers.get ("Range");
+ if (range == null) {
+ return false;
+ }
+
+ // We have a Range header. Parse.
+ if (!range.has_prefix ("bytes=")) {
+ throw new StreamerError.INVALID_RANGE ("Invalid Range '%s'",
+ range);
+ }
+
+ range_tokens = range.offset (6).split ("-", 2);
+
+ if (range_tokens[0] == null || range_tokens[1] == null) {
+ throw new StreamerError.INVALID_RANGE ("Invalid Range '%s'",
+ range);
+ }
+
+ // Get first byte position
+ string first_byte = range_tokens[0];
+ if (first_byte[0].isdigit ()) {
+ offset = first_byte.to_long ();
+ } else if (first_byte != "") {
+ throw new StreamerError.INVALID_RANGE ("Invalid Range '%s'",
+ range);
+ }
+
+ // Save the actual length
+ size_t actual_length = length;
+
+ // Get last byte position if specified
+ string last_byte = range_tokens[1];
+ if (last_byte[0].isdigit ()) {
+ length = last_byte.to_long ();
+ } else if (last_byte != "") {
+ throw new StreamerError.INVALID_RANGE ("Invalid Range '%s'",
+ range);
+ }
+
+ // Offset shouldn't go beyond actual length of media
+ if (offset > actual_length || length > actual_length) {
+ throw new StreamerError.OUT_OF_RANGE (
+ "Range '%s' not setsifiable", range);
+ }
+
+ return true;
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]