renderer-gst: Fix two criticals on startup
[profile/ivi/rygel.git] / src / librygel-renderer-gst / rygel-playbin-player.vala
1 /*
2  * Copyright (C) 2008 OpenedHand Ltd.
3  * Copyright (C) 2009,2010,2011,2012 Nokia Corporation.
4  * Copyright (C) 2012 Openismus GmbH
5  * Copyright (C) 2012,2013 Intel Corporation.
6  * Copyright (C) 2013  Cable Television Laboratories, Inc.
7  *
8  * Author: Jorn Baayen <jorn@openedhand.com>
9  *         Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
10  *                               <zeeshan.ali@nokia.com>
11  *         Jens Georg <jensg@openismus.com>
12  *         Neha Shanbhag <N.Shanbhag@cablelabs.com>
13  *         Sivakumar Mani <siva@orexel.com>
14  *
15  * Rygel is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU Lesser General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * Rygel is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU Lesser General Public License for more details.
24  *
25  * You should have received a copy of the GNU Lesser General Public License
26  * along with this program; if not, write to the Free Software Foundation,
27  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28  */
29
30 using Gst;
31 using GUPnP;
32
33 /**
34  * Implementation of RygelMediaPlayer for GStreamer.
35  *
36  * This class is useful only when implementing Rygel plugins.
37  */
38 public class Rygel.Playbin.Player : GLib.Object, Rygel.MediaPlayer {
39     private const string TRANSFER_MODE_STREAMING = "Streaming";
40     private const string TRANSFER_MODE_INTERACTIVE = "Interactive";
41     private const string PROTOCOL_INFO_TEMPLATE = "http-get:%s:*:%s";
42
43     private const string[] protocols = { "http-get", "rtsp" };
44     private const string[] mime_types = {
45                                         "audio/mpeg",
46                                         "application/ogg",
47                                         "audio/x-vorbis",
48                                         "audio/x-vorbis+ogg",
49                                         "audio/ogg",
50                                         "audio/x-ms-wma",
51                                         "audio/x-ms-asf",
52                                         "audio/x-flac",
53                                         "audio/x-flac+ogg",
54                                         "audio/flac",
55                                         "audio/mp4",
56                                         "audio/3gpp",
57                                         "audio/vnd.dlna.adts",
58                                         "audio/x-mod",
59                                         "audio/x-wav",
60                                         "audio/x-ac3",
61                                         "audio/x-m4a",
62                                         "audio/l16;rate=44100;channels=2",
63                                         "audio/l16;rate=44100;channels=1",
64                                         "audio/l16;channels=2;rate=44100",
65                                         "audio/l16;channels=1;rate=44100",
66                                         "audio/l16;rate=44100",
67                                         "image/jpeg",
68                                         "image/png",
69                                         "video/x-theora",
70                                         "video/x-theora+ogg",
71                                         "video/x-oggm",
72                                         "video/ogg",
73                                         "video/x-dirac",
74                                         "video/x-wmv",
75                                         "video/x-wma",
76                                         "video/x-msvideo",
77                                         "video/x-3ivx",
78                                         "video/x-3ivx",
79                                         "video/x-matroska",
80                                         "video/x-mkv",
81                                         "video/mpeg",
82                                         "video/mp4",
83                                         "application/x-shockwave-flash",
84                                         "video/x-ms-asf",
85                                         "video/x-xvid",
86                                         "video/x-ms-wmv" };
87     private static Player player;
88
89     public dynamic Element playbin { get; private set; }
90
91     private string _playback_state = "NO_MEDIA_PRESENT";
92     public string playback_state {
93         owned get {
94             return this._playback_state;
95         }
96
97         set {
98             Gst.State state, pending;
99
100             this.playbin.get_state (out state, out pending, Gst.MSECOND);
101
102             debug ("Changing playback state to %s.", value);
103
104             switch (value) {
105                 case "STOPPED":
106                     if (state != State.NULL || pending != State.VOID_PENDING) {
107                         this._playback_state = "TRANSITIONING";
108                         this.playbin.set_state (State.NULL);
109                     } else {
110                         this._playback_state = value;
111                     }
112                 break;
113                 case "PAUSED_PLAYBACK":
114                     if (state != State.PAUSED || pending != State.VOID_PENDING) {
115                         this._playback_state = "TRANSITIONING";
116                         this.playbin.set_state (State.PAUSED);
117                     } else {
118                         this._playback_state = value;
119                     }
120                 break;
121                 case "PLAYING":
122                     if (this._new_playback_speed != this._playback_speed &&
123                         (state == State.PLAYING || state == State.PAUSED) &&
124                         pending == State.VOID_PENDING) {
125                         /* already playing, but play speed has changed */
126                         this._playback_state = "TRANSITIONING";
127                         this.seek (this.position);
128                     } else if (state != State.PLAYING ||
129                                pending != State.VOID_PENDING) {
130                         // This needs a check if GStreamer and DLNA agree on
131                         // the "liveness" of the source (s0/sn increase in
132                         // protocol info)
133                         this._playback_state = "TRANSITIONING";
134                         this.playbin.set_state (State.PLAYING);
135                     } else {
136                         this._playback_state = value;
137                     }
138                 break;
139                 case "EOS":
140                     this._playback_state = value;
141                 break;
142                 default:
143                 break;
144             }
145         }
146     }
147
148     private string[] _allowed_playback_speeds = {
149         "1/16", "1/8", "1/4", "1/2", "1", "2", "4", "8", "16", "32", "64"
150     };
151     public string[] allowed_playback_speeds {
152         owned get {
153             return this._allowed_playback_speeds;
154         }
155     }
156
157     /**
158      * Actual _playback_speed is updated when playbin seek succeeds.
159      * Until that point, the playback speed set via api is stored in
160      * _new_playback_speed.
161      **/
162     private string _new_playback_speed = "1";
163
164     private string _playback_speed = "1";
165     public string playback_speed {
166         owned get {
167             return this._playback_speed;
168         }
169
170         set {
171             this._new_playback_speed = value;
172             /* theoretically we should trigger a seek here if we were
173              * playing already, but playback state does get changed
174              * after this when "Play" is invoked... */
175         }
176     }
177
178     private string transfer_mode = null;
179
180     private bool uri_update_hint = false;
181     private string? _uri = null;
182     public string? uri {
183         owned get {
184             return _uri;
185         }
186
187         set {
188             this._uri = value;
189             this.playbin.set_state (State.READY);
190             this.playbin.uri = value;
191             if (value != "") {
192                 switch (this._playback_state) {
193                     case "NO_MEDIA_PRESENT":
194                         this._playback_state = "STOPPED";
195                         this.notify_property ("playback-state");
196                         break;
197                     case "STOPPED":
198                         break;
199                     case "PAUSED_PLAYBACK":
200                         this.playbin.set_state (State.PAUSED);
201                         break;
202                     case "EOS":
203                     case "PLAYING":
204                         this.playbin.set_state (State.PLAYING);
205                         break;
206                     default:
207                         break;
208                 }
209             } else {
210                 this._playback_state = "NO_MEDIA_PRESENT";
211                 this.notify_property ("playback-state");
212             }
213             debug ("URI set to %s.", value);
214         }
215     }
216
217     private string _mime_type = "";
218     public string? mime_type {
219         owned get {
220             return this._mime_type;
221         }
222
223         set {
224             this._mime_type = value;
225         }
226     }
227
228     private string _metadata = "";
229     public string? metadata {
230         owned get {
231             return this._metadata;
232         }
233
234         set {
235             this._metadata = value;
236         }
237     }
238
239     public bool can_seek {
240         get {
241             return this.transfer_mode != TRANSFER_MODE_INTERACTIVE &&
242                    ! this.mime_type.has_prefix ("image/");
243         }
244     }
245
246     public bool can_seek_bytes {
247         get {
248             return this.transfer_mode != TRANSFER_MODE_INTERACTIVE &&
249                    ! this.mime_type.has_prefix ("image/");
250         }
251     }
252
253     private string _content_features = "";
254     private ProtocolInfo protocol_info;
255     public string? content_features {
256         owned get {
257             return this._content_features;
258         }
259
260         set {
261             var pi_string = PROTOCOL_INFO_TEMPLATE.printf (this.mime_type,
262                                                            value);
263             try {
264                 this.protocol_info = new ProtocolInfo.from_string (pi_string);
265                 var flags = this.protocol_info.dlna_flags;
266                 if (DLNAFlags.INTERACTIVE_TRANSFER_MODE in flags) {
267                     this.transfer_mode = TRANSFER_MODE_INTERACTIVE;
268                 } else if (DLNAFlags.STREAMING_TRANSFER_MODE in flags) {
269                     this.transfer_mode = TRANSFER_MODE_STREAMING;
270                 } else {
271                     this.transfer_mode = null;
272                 }
273             } catch (Error error) {
274                 this.protocol_info = null;
275                 this.transfer_mode = null;
276             }
277             this._content_features = value;
278         }
279     }
280
281     public double volume {
282         get {
283             return this.playbin.volume;
284         }
285
286         set {
287             this.playbin.volume = value;
288             debug ("volume set to %f.", value);
289         }
290     }
291
292     public int64 duration {
293         get {
294             int64 dur=0;
295
296             if (this.playbin.source != null &&
297                 this.playbin.source.query_duration (Format.TIME, out dur)) {
298                 return dur / Gst.USECOND;
299             } else {
300                 return 0;
301             }
302         }
303     }
304
305     public int64 size {
306         get {
307             int64 dur = 0;
308
309             if (this.playbin.source != null &&
310                 this.playbin.source.query_duration (Format.BYTES, out dur)) {
311                 return dur;
312             } else {
313                 return 0;
314             }
315         }
316     }
317
318     public int64 position {
319         get {
320             int64 pos;
321
322             if (this.playbin.source.query_position (Format.TIME, out pos)) {
323                 return pos / Gst.USECOND;
324             } else {
325                 return 0;
326             }
327         }
328     }
329
330     public int64 byte_position {
331        get {
332             int64 pos;
333
334             if (this.playbin.source.query_position (Format.BYTES, out pos)) {
335                 return pos;
336             } else {
337                 return 0;
338             }
339         }
340     }
341
342     private Player () {
343         this.playbin = ElementFactory.make ("playbin", null);
344         this.setup_playbin ();
345     }
346
347     [Deprecated (since="0.21.5")]
348     public Player.wrap (Gst.Element playbin)
349                         requires (playbin.get_type ().name () == "GstPlayBin") {
350         this.playbin = playbin;
351         this.setup_playbin ();
352     }
353
354     public static Player get_default () {
355         if (player == null) {
356             player = new Player ();
357         }
358
359         return player;
360     }
361
362     private bool seek_with_format (Format format, int64 target) {
363         bool seeked;
364
365         var speed = this.play_speed_to_double (this._new_playback_speed);
366         if (speed > 0) {
367             seeked = this.playbin.seek (speed,
368                                         format,
369                                         SeekFlags.FLUSH | SeekFlags.SKIP | SeekFlags.ACCURATE,
370                                         Gst.SeekType.SET,
371                                         target,
372                                         Gst.SeekType.NONE,
373                                         -1);
374         } else {
375             seeked = this.playbin.seek (speed,
376                                         format,
377                                         SeekFlags.FLUSH | SeekFlags.SKIP | SeekFlags.ACCURATE,
378                                         Gst.SeekType.SET,
379                                         0,
380                                         Gst.SeekType.SET,
381                                         target);
382         }
383         if (seeked) {
384             this._playback_speed = this._new_playback_speed;
385         }
386
387         return seeked;
388     }
389
390     public bool seek (int64 time) {
391         debug ("Seeking %lld usec, play speed %s", time, this._new_playback_speed);
392
393         // Playbin doesn't return false when seeking beyond the end of the
394         // file
395         if (time > this.duration) {
396             return false;
397         }
398
399         return this.seek_with_format (Format.TIME, time * Gst.USECOND);
400     }
401
402     public bool seek_bytes (int64 bytes) {
403         debug ("Seeking %lld bytes, play speed %s", bytes, this._new_playback_speed);
404
405         int64 size = this.size;
406         if (size > 0 && bytes > size) {
407             return false;
408         }
409
410         return this.seek_with_format (Format.BYTES, bytes);
411     }
412
413     public string[] get_protocols () {
414         return protocols;
415     }
416
417     public string[] get_mime_types () {
418         return mime_types;
419     }
420
421     private GLib.List<DLNAProfile> _supported_profiles;
422     public unowned GLib.List<DLNAProfile> supported_profiles {
423         get {
424             if (_supported_profiles == null) {
425                 // FIXME: Check available decoders in registry and register
426                 // profiles after that
427                 _supported_profiles = new GLib.List<DLNAProfile> ();
428
429                 // Image
430                 _supported_profiles.prepend (new DLNAProfile ("JPEG_SM",
431                                                               "image/jpeg"));
432                 _supported_profiles.prepend (new DLNAProfile ("JPEG_MED",
433                                                               "image/jpeg"));
434                 _supported_profiles.prepend (new DLNAProfile ("JPEG_LRG",
435                                                               "image/jpeg"));
436                 _supported_profiles.prepend (new DLNAProfile ("PNG_LRG",
437                                                               "image/png"));
438
439                 // Audio
440                 _supported_profiles.prepend (new DLNAProfile ("MP3",
441                                                               "audio/mpeg"));
442                 _supported_profiles.prepend (new DLNAProfile ("MP3X",
443                                                               "audio/mpeg"));
444                 _supported_profiles.prepend (new DLNAProfile
445                                         ("AAC_ADTS_320",
446                                          "audio/vnd.dlna.adts"));
447                 _supported_profiles.prepend (new DLNAProfile ("AAC_ISO_320",
448                                                               "audio/mp4"));
449                 _supported_profiles.prepend (new DLNAProfile ("AAC_ISO_320",
450                                                               "audio/3gpp"));
451                 _supported_profiles.prepend (new DLNAProfile
452                                         ("LPCM",
453                                          "audio/l16;rate=44100;channels=2"));
454                 _supported_profiles.prepend (new DLNAProfile
455                                         ("LPCM",
456                                          "audio/l16;rate=44100;channels=1"));
457                 _supported_profiles.prepend (new DLNAProfile ("WMABASE",
458                                                               "audio/x-ms-wma"));
459                 _supported_profiles.prepend (new DLNAProfile ("WMAFULL",
460                                                               "audio/x-ms-wma"));
461                 _supported_profiles.prepend (new DLNAProfile ("WMAPRO",
462                                                               "audio/x-ms-wma"));
463
464                 // Video
465                 _supported_profiles.prepend (new DLNAProfile
466                                         ("MPEG_TS_SD_EU_ISO",
467                                          "video/mpeg"));
468                 _supported_profiles.prepend (new DLNAProfile
469                                         ("MPEG_TS_SD_NA_ISO",
470                                          "video/mpeg"));
471                 _supported_profiles.prepend (new DLNAProfile
472                                         ("MPEG_TS_HD_NA_ISO",
473                                          "video/mpeg"));
474                 _supported_profiles.prepend (new DLNAProfile
475                                         ("AVC_MP4_BL_CIF15_AAC_520",
476                                          "video/mp4"));
477             }
478
479             return _supported_profiles;
480         }
481     }
482
483     private bool is_rendering_image () {
484         dynamic Element typefind;
485
486         typefind = (this.playbin as Gst.Bin).get_by_name ("typefind");
487         Caps caps = typefind.caps;
488         unowned Structure structure = caps.get_structure (0);
489
490         return structure.get_name () == "image/jpeg" ||
491                structure.get_name () == "image/png";
492     }
493
494     private void bus_handler (Gst.Bus bus,
495                               Message message) {
496         switch (message.type) {
497         case MessageType.DURATION_CHANGED:
498             if (this.playbin.query_duration (Format.TIME, null)) {
499                 this.notify_property ("duration");
500             }
501         break;
502         case MessageType.STATE_CHANGED:
503             if (message.src == this.playbin) {
504                 State old_state, new_state, pending;
505
506                 message.parse_state_changed (out old_state,
507                                              out new_state,
508                                              out pending);
509                 if (old_state == State.READY && new_state == State.PAUSED) {
510                     if (this.uri_update_hint) {
511                         this.uri_update_hint = false;
512                         string uri = this.playbin.current_uri;
513                         if (this._uri != uri && uri != "") {
514                             // uri changed externally
515                             this._uri = this.playbin.uri;
516                             this.notify_property ("uri");
517                             this.metadata = this.generate_basic_didl ();
518                         }
519                     }
520                 }
521
522                 if (pending == State.VOID_PENDING) {
523                     switch (new_state) {
524                         case State.PAUSED:
525                             this.playback_state = "PAUSED_PLAYBACK";
526                             break;
527                         case State.NULL:
528                             this.playback_state = "STOPPED";
529                             break;
530                         case State.PLAYING:
531                             this.playback_state = "PLAYING";
532                             break;
533                         default:
534                             break;
535                     }
536                 }
537
538                 if (old_state == State.PAUSED && new_state == State.PLAYING) {
539                     this.playback_state = "PLAYING";
540                 }
541             }
542             break;
543         case MessageType.EOS:
544             if (!this.is_rendering_image ()) {
545                 debug ("EOS");
546                 this.playback_state = "EOS";
547             } else {
548                 debug ("Content is image, ignoring EOS");
549             }
550
551             break;
552         case MessageType.ERROR:
553             Error error;
554             string debug_message;
555
556             message.parse_error (out error, out debug_message);
557
558             warning ("Error from GStreamer element %s: %s (%s)",
559                      this.playbin.name,
560                      error.message,
561                      debug_message);
562             warning ("Going to STOPPED state");
563
564             this.playback_state = "STOPPED";
565
566             break;
567         }
568     }
569
570     private void on_source_setup (Element pipeline, dynamic Element source) {
571         if (source.get_type ().name () == "GstSoupHTTPSrc" &&
572             this.transfer_mode != null) {
573             debug ("Setting transfer mode to %s", this.transfer_mode);
574
575             var structure = new Structure.empty ("HTTPHeaders");
576             structure.set_value ("transferMode.dlna.org", this.transfer_mode);
577
578             source.extra_headers = structure;
579         }
580     }
581
582     private void on_uri_notify (ParamSpec pspec) {
583         this.uri_update_hint = true;
584     }
585
586     /**
587      * Generate basic DIDLLite information.
588      *
589      * This is used when the URI gets changed externally. DLNA requires that a
590      * minimum DIDLLite is always present if the URI is not empty.
591      */
592     private string generate_basic_didl () {
593         var writer = new DIDLLiteWriter (null);
594         var item = writer.add_item ();
595         item.id = "1";
596         item.parent_id = "-1";
597         item.upnp_class = "object.item";
598         var resource = item.add_resource ();
599         resource.uri = this._uri;
600         var file = File.new_for_uri (this.uri);
601         item.title = file.get_basename ();
602
603         return writer.get_string ();
604     }
605
606     private void setup_playbin () {
607         // Needed to get "Stop" events from the playbin.
608         // We can do this because we have a bus watch
609         this.playbin.auto_flush_bus = false;
610         assert (this.playbin != null);
611
612         this.playbin.source_setup.connect (this.on_source_setup);
613         this.playbin.notify["uri"].connect (this.on_uri_notify);
614
615         // Bus handler
616         var bus = this.playbin.get_bus ();
617         bus.add_signal_watch ();
618         bus.message.connect (this.bus_handler);
619     }
620 }