summaryrefslogtreecommitdiff
path: root/src/librygel-server/rygel-http-response.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/librygel-server/rygel-http-response.vala')
-rw-r--r--src/librygel-server/rygel-http-response.vala294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/librygel-server/rygel-http-response.vala b/src/librygel-server/rygel-http-response.vala
new file mode 100644
index 00000000..bbcf6550
--- /dev/null
+++ b/src/librygel-server/rygel-http-response.vala
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ * <zeeshan.ali@nokia.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 Gst;
+using Soup;
+
+internal 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;
+
+ private SourceFunc run_continue;
+ private int _priority = -1;
+ public int priority {
+ get {
+ if (this._priority != -1) {
+ return this._priority;
+ }
+
+ var mode = this.msg.request_headers.get_one
+ ("transferMode.dlna.org");
+
+ if (mode == null || mode == "Interactive") {
+ this._priority = Priority.DEFAULT;
+ } else if (mode == "Streaming") {
+ this._priority = Priority.HIGH;
+ } else if (mode == "Background") {
+ this._priority = Priority.LOW;
+ } else {
+ this._priority = Priority.DEFAULT;
+ }
+
+ return _priority;
+ }
+ }
+
+ private Pipeline pipeline;
+ private uint bus_watch_id;
+ private bool unref_soup_server;
+
+ public HTTPResponse (HTTPGet request,
+ HTTPGetHandler request_handler,
+ Element src) throws Error {
+ this.server = request.server;
+ this.msg = request.msg;
+ this.cancellable = request_handler.cancellable;
+ this.seek = request.seek;
+
+ if (this.cancellable != null) {
+ this.cancellable.cancelled.connect (this.on_cancelled);
+ }
+
+ this.msg.response_body.set_accumulate (false);
+
+ this.prepare_pipeline ("RygelHTTPGstResponse", src);
+ this.server.weak_ref (this.on_server_weak_ref);
+ this.unref_soup_server = true;
+ }
+
+ ~HTTPResponse () {
+ if (this.unref_soup_server) {
+ this.server.weak_unref (this.on_server_weak_ref);
+ }
+ }
+
+ public async void run () {
+ // Only bother attempting to seek if the offset is greater than zero.
+ if (this.seek != null && this.seek.start > 0) {
+ this.pipeline.set_state (State.PAUSED);
+ } else {
+ this.pipeline.set_state (State.PLAYING);
+ }
+
+ this.run_continue = run.callback;
+
+ yield;
+ }
+
+ public void push_data (uint8[] data) {
+ this.msg.response_body.append (Soup.MemoryUse.COPY, data);
+
+ this.server.unpause_message (this.msg);
+ }
+
+ public virtual void end (bool aborted, uint status) {
+ var sink = this.pipeline.get_by_name (HTTPGstSink.NAME) as HTTPGstSink;
+ sink.cancellable.cancel ();
+
+ this.pipeline.set_state (State.NULL);
+ Source.remove (this.bus_watch_id);
+
+ var encoding = this.msg.response_headers.get_encoding ();
+
+ if (!aborted && encoding != Encoding.CONTENT_LENGTH) {
+ this.msg.response_body.complete ();
+ this.server.unpause_message (this.msg);
+ }
+
+ if (this.run_continue != null) {
+ this.run_continue ();
+ }
+
+ if (status != Soup.KnownStatusCode.NONE) {
+ this.msg.set_status (status);
+ }
+
+ this.completed ();
+ }
+
+ private void on_cancelled (Cancellable cancellable) {
+ this.end (true, Soup.KnownStatusCode.CANCELLED);
+ }
+
+ private void on_server_weak_ref (GLib.Object object) {
+ this.unref_soup_server = false;
+ this.cancellable.cancel ();
+ }
+
+ private void prepare_pipeline (string name, Element src) throws Error {
+ var sink = new HTTPGstSink (this);
+
+ this.pipeline = new Pipeline (name);
+ assert (this.pipeline != null);
+
+ this.pipeline.add_many (src, sink);
+
+ if (src.numsrcpads == 0) {
+ // Seems source uses dynamic pads, link when pad available
+ src.pad_added.connect (this.src_pad_added);
+ } else {
+ // static pads? easy!
+ if (!src.link (sink)) {
+ throw new GstError.LINK (_("Failed to link %s to %s"),
+ src.name,
+ sink.name);
+ }
+ }
+
+ // Bus handler
+ var bus = this.pipeline.get_bus ();
+ this.bus_watch_id = bus.add_watch (this.bus_handler);
+ }
+
+ private void src_pad_added (Element src, Pad src_pad) {
+ var caps = src_pad.get_caps_reffed ();
+
+ var sink = this.pipeline.get_by_name (HTTPGstSink.NAME);
+ Pad sink_pad;
+
+ dynamic Element depay = GstUtils.get_rtp_depayloader (caps);
+ if (depay != null) {
+ this.pipeline.add (depay);
+ if (!depay.link (sink)) {
+ critical (_("Failed to link %s to %s"),
+ depay.name,
+ sink.name);
+
+ this.end (false, KnownStatusCode.NONE);
+
+ return;
+ }
+
+ sink_pad = depay.get_compatible_pad (src_pad, caps);
+ } else {
+ sink_pad = sink.get_compatible_pad (src_pad, caps);
+ }
+
+ if (src_pad.link (sink_pad) != PadLinkReturn.OK) {
+ critical (_("Failed to link pad %s to %s"),
+ src_pad.name,
+ sink_pad.name);
+ this.end (false, KnownStatusCode.NONE);
+
+ return;
+ }
+
+ if (depay != null) {
+ depay.sync_state_with_parent ();
+ }
+ }
+
+ private bool bus_handler (Gst.Bus bus, Gst.Message message) {
+ bool ret = true;
+
+ if (message.type == MessageType.EOS) {
+ ret = false;
+ } else if (message.type == MessageType.STATE_CHANGED) {
+ if (message.src != this.pipeline) {
+ return true;
+ }
+
+ if (this.seek != null && this.seek.start > 0) {
+ State old_state;
+ State new_state;
+
+ message.parse_state_changed (out old_state,
+ out new_state,
+ null);
+
+ if (old_state == State.READY && new_state == State.PAUSED) {
+ if (this.perform_seek ()) {
+ this.pipeline.set_state (State.PLAYING);
+ }
+ }
+ }
+ } else {
+ GLib.Error err;
+ string err_msg;
+
+ if (message.type == MessageType.ERROR) {
+ message.parse_error (out err, out err_msg);
+ critical (_("Error from pipeline %s: %s"),
+ this.pipeline.name,
+ err_msg);
+
+ ret = false;
+ } else if (message.type == MessageType.WARNING) {
+ message.parse_warning (out err, out err_msg);
+ warning (_("Warning from pipeline %s: %s"),
+ this.pipeline.name,
+ err_msg);
+ }
+ }
+
+ // If pipeline state didn't change due to the request being cancelled,
+ // end this request. Otherwise it was already ended.
+ if (!ret) {
+ Idle.add_full (this.priority, () => {
+ if (!this.cancellable.is_cancelled ()) {
+ this.end (false, KnownStatusCode.NONE);
+ }
+
+ return false;
+ });
+ }
+
+ return ret;
+ }
+
+ private bool perform_seek () {
+ var stop_type = Gst.SeekType.NONE;
+ Format format;
+
+ if (this.seek is HTTPTimeSeek) {
+ format = Format.TIME;
+
+ } else {
+ format = Format.BYTES;
+ }
+
+ if (this.seek.stop > 0) {
+ stop_type = Gst.SeekType.SET;
+ }
+
+ if (!this.pipeline.seek (1.0,
+ format,
+ SeekFlags.FLUSH | SeekFlags.ACCURATE,
+ Gst.SeekType.SET,
+ this.seek.start,
+ stop_type,
+ this.seek.stop + 1)) {
+ warning (_("Failed to seek to offset %lld"), this.seek.start);
+
+ this.end (false, KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE);
+
+ return false;
+ }
+
+ return true;
+ }
+}