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.
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>
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.
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.
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.
34 * Implementation of RygelMediaPlayer for GStreamer.
36 * This class is useful only when implementing Rygel plugins.
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";
43 private const string[] protocols = { "http-get", "rtsp" };
44 private const string[] mime_types = {
57 "audio/vnd.dlna.adts",
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",
83 "application/x-shockwave-flash",
87 private static Player player;
89 public dynamic Element playbin { get; private set; }
91 private string _playback_state = "NO_MEDIA_PRESENT";
92 public string playback_state {
94 return this._playback_state;
98 Gst.State state, pending;
100 this.playbin.get_state (out state, out pending, Gst.MSECOND);
102 debug ("Changing playback state to %s.", value);
106 if (state != State.NULL || pending != State.VOID_PENDING) {
107 this._playback_state = "TRANSITIONING";
108 this.playbin.set_state (State.NULL);
110 this._playback_state = value;
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);
118 this._playback_state = value;
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
133 this._playback_state = "TRANSITIONING";
134 this.playbin.set_state (State.PLAYING);
136 this._playback_state = value;
140 this._playback_state = value;
148 private string[] _allowed_playback_speeds = {
149 "1/16", "1/8", "1/4", "1/2", "1", "2", "4", "8", "16", "32", "64"
151 public string[] allowed_playback_speeds {
153 return this._allowed_playback_speeds;
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.
162 private string _new_playback_speed = "1";
164 private string _playback_speed = "1";
165 public string playback_speed {
167 return this._playback_speed;
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... */
178 private string transfer_mode = null;
180 private bool uri_update_hint = false;
181 private string? _uri = null;
189 this.playbin.set_state (State.READY);
190 this.playbin.uri = value;
192 switch (this._playback_state) {
193 case "NO_MEDIA_PRESENT":
194 this._playback_state = "STOPPED";
195 this.notify_property ("playback-state");
199 case "PAUSED_PLAYBACK":
200 this.playbin.set_state (State.PAUSED);
204 this.playbin.set_state (State.PLAYING);
210 this._playback_state = "NO_MEDIA_PRESENT";
211 this.notify_property ("playback-state");
213 debug ("URI set to %s.", value);
217 private string _mime_type = "";
218 public string? mime_type {
220 return this._mime_type;
224 this._mime_type = value;
228 private string _metadata = "";
229 public string? metadata {
231 return this._metadata;
235 this._metadata = value;
239 public bool can_seek {
241 return this.transfer_mode != TRANSFER_MODE_INTERACTIVE &&
242 ! this.mime_type.has_prefix ("image/");
246 public bool can_seek_bytes {
248 return this.transfer_mode != TRANSFER_MODE_INTERACTIVE &&
249 ! this.mime_type.has_prefix ("image/");
253 private string _content_features = "";
254 private ProtocolInfo protocol_info;
255 public string? content_features {
257 return this._content_features;
261 var pi_string = PROTOCOL_INFO_TEMPLATE.printf (this.mime_type,
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;
271 this.transfer_mode = null;
273 } catch (Error error) {
274 this.protocol_info = null;
275 this.transfer_mode = null;
277 this._content_features = value;
281 public double volume {
283 return this.playbin.volume;
287 this.playbin.volume = value;
288 debug ("volume set to %f.", value);
292 public int64 duration {
296 if (this.playbin.source != null &&
297 this.playbin.source.query_duration (Format.TIME, out dur)) {
298 return dur / Gst.USECOND;
309 if (this.playbin.source != null &&
310 this.playbin.source.query_duration (Format.BYTES, out dur)) {
318 public int64 position {
322 if (this.playbin.source.query_position (Format.TIME, out pos)) {
323 return pos / Gst.USECOND;
330 public int64 byte_position {
334 if (this.playbin.source.query_position (Format.BYTES, out pos)) {
343 this.playbin = ElementFactory.make ("playbin", null);
344 this.setup_playbin ();
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 ();
354 public static Player get_default () {
355 if (player == null) {
356 player = new Player ();
362 private bool seek_with_format (Format format, int64 target) {
365 var speed = this.play_speed_to_double (this._new_playback_speed);
367 seeked = this.playbin.seek (speed,
369 SeekFlags.FLUSH | SeekFlags.SKIP | SeekFlags.ACCURATE,
375 seeked = this.playbin.seek (speed,
377 SeekFlags.FLUSH | SeekFlags.SKIP | SeekFlags.ACCURATE,
384 this._playback_speed = this._new_playback_speed;
390 public bool seek (int64 time) {
391 debug ("Seeking %lld usec, play speed %s", time, this._new_playback_speed);
393 // Playbin doesn't return false when seeking beyond the end of the
395 if (time > this.duration) {
399 return this.seek_with_format (Format.TIME, time * Gst.USECOND);
402 public bool seek_bytes (int64 bytes) {
403 debug ("Seeking %lld bytes, play speed %s", bytes, this._new_playback_speed);
405 int64 size = this.size;
406 if (size > 0 && bytes > size) {
410 return this.seek_with_format (Format.BYTES, bytes);
413 public string[] get_protocols () {
417 public string[] get_mime_types () {
421 private GLib.List<DLNAProfile> _supported_profiles;
422 public unowned GLib.List<DLNAProfile> supported_profiles {
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> ();
430 _supported_profiles.prepend (new DLNAProfile ("JPEG_SM",
432 _supported_profiles.prepend (new DLNAProfile ("JPEG_MED",
434 _supported_profiles.prepend (new DLNAProfile ("JPEG_LRG",
436 _supported_profiles.prepend (new DLNAProfile ("PNG_LRG",
440 _supported_profiles.prepend (new DLNAProfile ("MP3",
442 _supported_profiles.prepend (new DLNAProfile ("MP3X",
444 _supported_profiles.prepend (new DLNAProfile
446 "audio/vnd.dlna.adts"));
447 _supported_profiles.prepend (new DLNAProfile ("AAC_ISO_320",
449 _supported_profiles.prepend (new DLNAProfile ("AAC_ISO_320",
451 _supported_profiles.prepend (new DLNAProfile
453 "audio/l16;rate=44100;channels=2"));
454 _supported_profiles.prepend (new DLNAProfile
456 "audio/l16;rate=44100;channels=1"));
457 _supported_profiles.prepend (new DLNAProfile ("WMABASE",
459 _supported_profiles.prepend (new DLNAProfile ("WMAFULL",
461 _supported_profiles.prepend (new DLNAProfile ("WMAPRO",
465 _supported_profiles.prepend (new DLNAProfile
466 ("MPEG_TS_SD_EU_ISO",
468 _supported_profiles.prepend (new DLNAProfile
469 ("MPEG_TS_SD_NA_ISO",
471 _supported_profiles.prepend (new DLNAProfile
472 ("MPEG_TS_HD_NA_ISO",
474 _supported_profiles.prepend (new DLNAProfile
475 ("AVC_MP4_BL_CIF15_AAC_520",
479 return _supported_profiles;
483 private bool is_rendering_image () {
484 dynamic Element typefind;
486 typefind = (this.playbin as Gst.Bin).get_by_name ("typefind");
487 Caps caps = typefind.caps;
488 unowned Structure structure = caps.get_structure (0);
490 return structure.get_name () == "image/jpeg" ||
491 structure.get_name () == "image/png";
494 private void bus_handler (Gst.Bus bus,
496 switch (message.type) {
497 case MessageType.DURATION_CHANGED:
498 if (this.playbin.query_duration (Format.TIME, null)) {
499 this.notify_property ("duration");
502 case MessageType.STATE_CHANGED:
503 if (message.src == this.playbin) {
504 State old_state, new_state, pending;
506 message.parse_state_changed (out old_state,
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 ();
522 if (pending == State.VOID_PENDING) {
525 this.playback_state = "PAUSED_PLAYBACK";
528 this.playback_state = "STOPPED";
531 this.playback_state = "PLAYING";
538 if (old_state == State.PAUSED && new_state == State.PLAYING) {
539 this.playback_state = "PLAYING";
543 case MessageType.EOS:
544 if (!this.is_rendering_image ()) {
546 this.playback_state = "EOS";
548 debug ("Content is image, ignoring EOS");
552 case MessageType.ERROR:
554 string debug_message;
556 message.parse_error (out error, out debug_message);
558 warning ("Error from GStreamer element %s: %s (%s)",
562 warning ("Going to STOPPED state");
564 this.playback_state = "STOPPED";
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);
575 var structure = new Structure.empty ("HTTPHeaders");
576 structure.set_value ("transferMode.dlna.org", this.transfer_mode);
578 source.extra_headers = structure;
582 private void on_uri_notify (ParamSpec pspec) {
583 this.uri_update_hint = true;
587 * Generate basic DIDLLite information.
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.
592 private string generate_basic_didl () {
593 var writer = new DIDLLiteWriter (null);
594 var item = writer.add_item ();
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 ();
603 return writer.get_string ();
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);
612 this.playbin.source_setup.connect (this.on_source_setup);
613 this.playbin.notify["uri"].connect (this.on_uri_notify);
616 var bus = this.playbin.get_bus ();
617 bus.add_signal_watch ();
618 bus.message.connect (this.bus_handler);