summaryrefslogtreecommitdiff
path: root/src/media-engines/gstreamer/rygel-gst-data-source.vala
blob: 198ada545cc8ad4618c37a5df0a4ecb24067f398 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * Copyright (C) 2012 Intel Corporation.
 * Copyright (C) 2013 Cable Television Laboratories, Inc.
 *
 * Author: Jens Georg <jensg@openismus.com>
 *         Prasanna Modem <prasanna@ecaspia.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.1 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using Gst;

internal errordomain Rygel.GstDataSourceError {
    NOT_COMPATIBLE
}

internal class Rygel.GstDataSource : Rygel.DataSource, GLib.Object {
    internal dynamic Element src;
    internal MediaResource res;
    private Pipeline pipeline;
    private HTTPSeekRequest seek = null;
    private GstSink sink;
    private uint bus_watch_id;

    public GstDataSource (string uri, MediaResource ? resource) throws Error {
        this.res = resource;
        this.src = GstUtils.create_source_for_uri (uri);
        if (this.src == null) {
            var msg = _("Could not create GstElement for URI %s");

            throw new GstDataSourceError.NOT_COMPATIBLE (msg, uri);
        }
    }

    ~GstDataSource () {
        if (this.sink != null) {
            this.sink.cancellable.cancel ();
        }

        if (this.pipeline != null) {
            this.pipeline.set_state (State.NULL);
        }
    }

    public GstDataSource.from_element (Element element) {
        this.src = element;
    }

    public Gee.List<HTTPResponseElement>? preroll
                                        (HTTPSeekRequest? seek_request,
                                         PlaySpeedRequest? playspeed_request)
                                         throws Error {
        var response_list = new Gee.ArrayList<HTTPResponseElement> ();

        if (playspeed_request != null) {
            throw new DataSourceError.PLAYSPEED_FAILED
                                    (_("Playspeed not supported"));
        }

        if (seek_request == null) {
            debug("No seek requested - sending entire binary");
        } else if (seek_request is HTTPByteSeekRequest) {
            // Supported - and no reponse values required...
            var seek_response = new HTTPByteSeekResponse.from_request
                                        (seek_request as HTTPByteSeekRequest);
            debug ("Processing byte seek request for bytes %lld-%lld",
                   seek_response.start_byte,
                   seek_response.end_byte);
            response_list.add (seek_response);
        } else if (seek_request is HTTPTimeSeekRequest) {
            var time_seek = seek_request as HTTPTimeSeekRequest;
            // Set the effective TimeSeekRange response range to the requested
            // range
            // TODO: Align this with actual time range being returned, might
            // not be possible as we would need to seek before handling the
            // response
            var seek_response = new HTTPTimeSeekResponse.from_request
                                        (time_seek,
                                         res.duration * TimeSpan.SECOND);
            debug ("Processing time seek request for %lldms-%lldms",
                   seek_response.start_time,
                   seek_response.end_time);
            response_list.add (seek_response);
        } else {
            // Unknown/unsupported seek type
            throw new DataSourceError.SEEK_FAILED
                                    (_("HTTPSeekRequest type %s unsupported"),
                                     seek_request.get_type (). name ());
        }

        this.seek = seek_request;

        return response_list;
    }

    public void start () throws Error {
        this.prepare_pipeline ("RygelGstDataSource", this.src);
        if (this.seek != null) {
            this.pipeline.set_state (State.PAUSED);
        } else {
            this.pipeline.set_state (State.PLAYING);
        }
    }

    public void freeze () {
        this.sink.freeze ();
    }

    public void thaw () {
        this.sink.thaw ();
    }

    public void stop () {
        // Unlock eventually frozen sink
        this.sink.cancellable.cancel ();
        this.pipeline.set_state (State.NULL);
        if (this.bus_watch_id != 0) {
            Source.remove (this.bus_watch_id);
            this.bus_watch_id = 0;
        }
        Idle.add ( () => { this.done (); return false; });
    }

    private void prepare_pipeline (string   name,
                                   Element  src) throws Error {
        this.sink = new GstSink (this, this.seek);

        this.pipeline = new Pipeline (name);
        if (pipeline == null) {
            throw new DataSourceError.GENERAL
                                        (_("Failed to create pipeline"));
        }

        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 (Priority.DEFAULT, this.bus_handler);
    }

    private void src_pad_added (Element src, Pad src_pad) {
        var caps = src_pad.query_caps (null);

        var sink = this.pipeline.get_by_name (GstSink.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.done ();

                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.done ();

            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;
            }

            State old_state;
            State new_state;

            message.parse_state_changed (out old_state,
                                         out new_state,
                                         null);

            if (old_state == State.NULL && new_state == State.READY) {
                dynamic Element element = this.pipeline.get_by_name ("muxer");
                if (element != null) {
                    var name = element.get_factory ().get_name ();
                    // Awesome gross hack, really.
                    if (name == "mp4mux") {
                        element.streamable = true;
                        element.fragment_duration = 1000;
                    }
                }
            }

            var filename = "rygel_media_engine_%d_%d".printf (old_state,
                                                               new_state);
            Debug.bin_to_dot_file_with_ts (this.pipeline,
                                           DebugGraphDetails.ALL,
                                           filename);

            if (this.seek != 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) {
                Debug.bin_to_dot_file_with_ts (this.pipeline,
                                               DebugGraphDetails.ALL,
                                               "rygel_media_engine_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 (!ret) {
            Idle.add_full (Priority.DEFAULT, () => {
                this.done ();

                return false;
            });

            this.bus_watch_id = 0;
        }

        return ret;
    }

    private bool perform_seek () {
        var stop_type = Gst.SeekType.NONE;
        Format format;
        var flags = SeekFlags.FLUSH;
        int64 start, stop;

        if (this.seek is HTTPTimeSeekRequest) {
            var time_seek = this.seek as HTTPTimeSeekRequest;
            format = Format.TIME;
            flags |= SeekFlags.KEY_UNIT;
            start = time_seek.start_time * Gst.USECOND;
            // Work-around for https://bugzilla.gnome.org/show_bug.cgi?id=762787
            if (this.src.name == "dvdreadsrc" && start == 0) {
                start += 1 * Gst.SECOND;
            }
            stop = time_seek.end_time * Gst.USECOND;
            debug ("Performing time-range seek: %lldns to %lldns", start, stop);
        } else if (this.seek is HTTPByteSeekRequest) {
            var byte_seek = this.seek as HTTPByteSeekRequest;
            if (byte_seek.range_length >= byte_seek.total_size) {
                // Can happen on (invalid) seeks on resources with unspecified
                // size
                return true;
            }

            format = Format.BYTES;
            flags |= SeekFlags.ACCURATE;
            start = byte_seek.start_byte;
            stop = byte_seek.end_byte;
            debug ("Performing byte-range seek: bytes %lld to %lld",
                   start,
                   stop);
        } else {
            var result = new DataSourceError.SEEK_FAILED
                                        (_("Unsupported seek type"));
            this.error (result);

            return false;
        }

        if (stop > 0) {
            stop_type = Gst.SeekType.SET;
        }

        if (!this.pipeline.seek (1.0,
                                 format,
                                 flags,
                                 Gst.SeekType.SET,
                                 start,
                                 stop_type,
                                 stop + 1)) {
            warning (_("Failed to seek to offsets %lld:%lld"), start, stop);

            this.error (new DataSourceError.SEEK_FAILED (_("Failed to seek")));

            return false;
        }

        return true;
    }
}