1 /*
2  * Copyright (C) 2003-2007 the GStreamer project
3  *      Julien Moutte <julien@moutte.net>
4  *      Ronald Bultje <rbultje@ronald.bitfreak.net>
5  * Copyright (C) 2003-2014 Bastien Nocera <hadess@hadess.net>
6  * Copyright (C) 2005-2008 Tim-Philipp Müller <tim centricular net>
7  * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
8  * Copyright © 2009 Christian Persch
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
23  *
24  * The Totem project hereby grant permission for non-gpl compatible GStreamer
25  * plugins to be used and distributed together with GStreamer and Totem. This
26  * permission is above and beyond the permissions granted by the GPL license
27  * Totem is covered by.
28  *
29  * Monday 7th February 2005: Christian Schaller: Add exception clause.
30  * See license_change file for details.
31  *
32  */
33 
34 /**
35  * SECTION:bacon-video-widget
36  * @short_description: video playing widget and abstraction
37  * @stability: Unstable
38  * @include: bacon-video-widget.h
39  *
40  * #BaconVideoWidget is a widget to play audio or video streams, with support for visualisations for audio-only streams. It has a GStreamer
41  * backend, and abstracts away the differences to provide a simple interface to the functionality required by Totem. It handles all the low-level
42  * audio and video work for Totem (or passes the work off to the backend).
43  **/
44 
45 #include <config.h>
46 
47 #define GST_USE_UNSTABLE_API 1
48 
49 #include <gst/gst.h>
50 
51 /* GStreamer Interfaces */
52 #include <gst/video/navigation.h>
53 #include <gst/video/colorbalance.h>
54 /* for detecting sources of errors */
55 #include <gst/video/gstvideosink.h>
56 #include <gst/video/video.h>
57 #include <gst/audio/audio.h>
58 #include <gst/audio/streamvolume.h>
59 
60 /* for missing decoder/demuxer detection */
61 #include <gst/pbutils/pbutils.h>
62 
63 /* for the cover metadata info */
64 #include <gst/tag/tag.h>
65 
66 #include <clutter-gst/clutter-gst.h>
67 #include "totem-aspect-frame.h"
68 
69 /* system */
70 #include <unistd.h>
71 #include <time.h>
72 #include <string.h>
73 #include <stdio.h>
74 #include <math.h>
75 
76 /* gtk+/gnome */
77 #include <gtk/gtk.h>
78 #include <glib/gi18n-lib.h>
79 #include <gio/gio.h>
80 #include <gdesktop-enums.h>
81 
82 #include "totem-gst-helpers.h"
83 #include "totem-gst-pixbuf-helpers.h"
84 #include "bacon-video-widget.h"
85 #include "bacon-video-widget-gst-missing-plugins.h"
86 #include "bacon-video-controls-actor.h"
87 #include "bacon-video-spinner-actor.h"
88 #include "bacon-video-widget-enums.h"
89 
90 #define DEFAULT_USER_AGENT "Videos/"VERSION
91 
92 #define DEFAULT_CONTROLS_WIDTH 600             /* In pixels */
93 #define LOGO_SIZE 256                          /* Maximum size of the logo */
94 #define REWIND_OR_PREVIOUS 4000
95 #define POPUP_HIDING_TIMEOUT 2
96 
97 #define MAX_NETWORK_SPEED 10752
98 #define BUFFERING_LEFT_RATIO 1.1
99 
100 /* Helper constants */
101 #define NANOSECS_IN_SEC 1000000000
102 #define SEEK_TIMEOUT NANOSECS_IN_SEC / 10
103 #define FORWARD_RATE 1.0
104 #define REVERSE_RATE -1.0
105 #define DIRECTION_STR (forward == FALSE ? "reverse" : "forward")
106 
107 #define is_error(e, d, c) \
108   (e->domain == GST_##d##_ERROR && \
109    e->code == GST_##d##_ERROR_##c)
110 
111 #define I_(string) (g_intern_static_string (string))
112 
113 static void bacon_video_widget_initable_iface_init (GInitableIface *iface);
114 
115 /* Signals */
116 enum
117 {
118   SIGNAL_ERROR,
119   SIGNAL_EOS,
120   SIGNAL_REDIRECT,
121   SIGNAL_CHANNELS_CHANGE,
122   SIGNAL_TICK,
123   SIGNAL_GOT_METADATA,
124   SIGNAL_BUFFERING,
125   SIGNAL_MISSING_PLUGINS,
126   SIGNAL_DOWNLOAD_BUFFERING,
127   SIGNAL_SEEK_REQUESTED,
128   SIGNAL_TRACK_SKIP_REQUESTED,
129   SIGNAL_VOLUME_CHANGE_REQUESTED,
130   LAST_SIGNAL
131 };
132 
133 /* Properties */
134 enum
135 {
136   PROP_0,
137   PROP_LOGO_MODE,
138   PROP_POSITION,
139   PROP_CURRENT_TIME,
140   PROP_STREAM_LENGTH,
141   PROP_PLAYING,
142   PROP_REFERRER,
143   PROP_SEEKABLE,
144   PROP_USER_AGENT,
145   PROP_VOLUME,
146   PROP_DOWNLOAD_FILENAME,
147   PROP_DEINTERLACING,
148   PROP_BRIGHTNESS,
149   PROP_CONTRAST,
150   PROP_SATURATION,
151   PROP_HUE,
152   PROP_AUDIO_OUTPUT_TYPE,
153   PROP_AV_OFFSET,
154   PROP_REVEAL_CONTROLS
155 };
156 
157 static const gchar *video_props_str[4] = {
158   "brightness",
159   "contrast",
160   "saturation",
161   "hue"
162 };
163 
164 struct _BaconVideoWidget
165 {
166   GtkClutterEmbed              parent;
167 
168   char                        *user_agent;
169 
170   char                        *referrer;
171   char                        *mrl;
172   char                        *subtitle_uri;
173   BvwAspectRatio               ratio_type;
174 
175   GstElement                  *play;
176   GstElement                  *video_sink;
177   GstNavigation               *navigation;
178 
179   guint                        update_id;
180   guint                        fill_id;
181 
182   GdkPixbuf                   *logo_pixbuf;
183   GdkPixbuf                   *cover_pixbuf; /* stream-specific image */
184 
185   gboolean                     media_has_video;
186   gboolean                     media_has_audio;
187   gint                         seekable; /* -1 = don't know, FALSE = no */
188   gint64                       stream_length;
189   gint64                       current_time;
190   gdouble                      current_position;
191   gboolean                     is_live;
192 
193   GstTagList                  *tagcache;
194   GstTagList                  *audiotags;
195   GstTagList                  *videotags;
196 
197   GAsyncQueue                 *tag_update_queue;
198   guint                        tag_update_id;
199 
200   gboolean                     got_redirect;
201 
202   ClutterActor                *stage;
203   ClutterActor                *texture;
204   ClutterActor                *frame;
205   ClutterActor                *header_controls;
206   ClutterActor                *controls;
207   ClutterActor                *spinner;
208 
209   ClutterActor                *logo_frame;
210   ClutterContent              *logo;
211 
212   GdkCursor                   *cursor;
213 
214   /* Controls */
215   gboolean                     reveal_controls;
216   guint                        transition_timeout_id;
217   GHashTable                  *busy_popup_ht; /* key=reason string, value=gboolean */
218 
219   /* Visual effects */
220   GstElement                  *audio_capsfilter;
221   GstElement                  *audio_pitchcontrol;
222 
223   /* Other stuff */
224   gboolean                     logo_mode;
225   gboolean                     cursor_shown;
226   gboolean                     uses_audio_fakesink;
227   gdouble                      volume;
228   gboolean                     is_menu;
229   gboolean                     has_angles;
230   GList                       *chapters;
231 
232   BvwRotation                  rotation;
233 
234   gint                         video_width; /* Movie width */
235   gint                         video_height; /* Movie height */
236   gint                         movie_par_n; /* Movie pixel aspect ratio numerator */
237   gint                         movie_par_d; /* Movie pixel aspect ratio denominator */
238   gint                         video_width_pixels; /* Scaled movie width */
239   gint                         video_height_pixels; /* Scaled movie height */
240   gint                         video_fps_n;
241   gint                         video_fps_d;
242 
243   BvwAudioOutputType           speakersetup;
244 
245   GstBus                      *bus;
246   gulong                       sig_bus_async;
247 
248   gint                         eos_id;
249 
250   /* When seeking, queue up the seeks if they happen before
251    * the previous one finished */
252   GMutex                       seek_mutex;
253   GstClock                    *clock;
254   GstClockTime                 seek_req_time;
255   gint64                       seek_time;
256   /* state we want to be in, as opposed to actual pipeline state
257    * which may change asynchronously or during buffering */
258   GstState                     target_state;
259   gboolean                     buffering;
260   gboolean                     download_buffering;
261   char                        *download_filename;
262   /* used to compute when the download buffer has gone far
263    * enough to start playback, not "amount of buffering time left
264    * to reach 100% fill-level" */
265   gint64                       buffering_left;
266 
267   /* for easy codec installation */
268   GList                       *missing_plugins;   /* GList of GstMessages */
269   gboolean                     plugin_install_in_progress;
270   GCancellable                *missing_plugins_cancellable;
271 
272   /* for mounting locations if necessary */
273   GCancellable                *mount_cancellable;
274   gboolean                     mount_in_progress;
275 
276   /* for auth */
277   GMountOperation             *auth_dialog;
278   GMountOperationResult        auth_last_result;
279   char                        *user_id, *user_pw;
280 
281   /* for stepping */
282   float                        rate;
283 };
284 
285 G_DEFINE_TYPE_WITH_CODE (BaconVideoWidget, bacon_video_widget, GTK_CLUTTER_TYPE_EMBED,
286 			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
287 						bacon_video_widget_initable_iface_init))
288 
289 static void bacon_video_widget_set_property (GObject * object,
290                                              guint property_id,
291                                              const GValue * value,
292                                              GParamSpec * pspec);
293 static void bacon_video_widget_get_property (GObject * object,
294                                              guint property_id,
295                                              GValue * value,
296                                              GParamSpec * pspec);
297 
298 static void bacon_video_widget_finalize (GObject * object);
299 
300 static void bvw_reconfigure_fill_timeout (BaconVideoWidget *bvw, guint msecs);
301 static void size_changed_cb (GdkScreen *screen, BaconVideoWidget *bvw);
302 static void bvw_stop_play_pipeline (BaconVideoWidget * bvw);
303 static GError* bvw_error_from_gst_error (BaconVideoWidget *bvw, GstMessage *m);
304 static gboolean bvw_check_for_cover_pixbuf (BaconVideoWidget * bvw);
305 static const GdkPixbuf * bvw_get_logo_pixbuf (BaconVideoWidget * bvw);
306 static gboolean bvw_set_playback_direction (BaconVideoWidget *bvw, gboolean forward);
307 static gboolean bacon_video_widget_seek_time_no_lock (BaconVideoWidget *bvw,
308 						      gint64 _time,
309 						      GstSeekFlags flag,
310 						      GError **error);
311 static void set_controls_visibility (BaconVideoWidget *bvw,
312 				     gboolean          visible,
313 				     gboolean          animate);
314 
315 typedef struct {
316   GstTagList *tags;
317   const gchar *type;
318 } UpdateTagsDelayedData;
319 
320 static void update_tags_delayed_data_destroy (UpdateTagsDelayedData *data);
321 
322 static GtkWidgetClass *parent_class = NULL;
323 
324 static int bvw_signals[LAST_SIGNAL] = { 0 };
325 
326 GST_DEBUG_CATEGORY (_totem_gst_debug_cat);
327 #define GST_CAT_DEFAULT _totem_gst_debug_cat
328 
329 typedef gchar * (* MsgToStrFunc) (GstMessage * msg);
330 
331 static const gchar *
get_type_name(GType class_type,int type)332 get_type_name (GType class_type, int type)
333 {
334   GEnumClass *eclass;
335   GEnumValue *value;
336 
337   eclass = G_ENUM_CLASS (g_type_class_peek (class_type));
338   value = g_enum_get_value (eclass, type);
339 
340   if (value == NULL)
341     return "unknown";
342 
343   return value->value_nick;
344 }
345 
346 static gchar **
bvw_get_missing_plugins_foo(const GList * missing_plugins,MsgToStrFunc func)347 bvw_get_missing_plugins_foo (const GList * missing_plugins, MsgToStrFunc func)
348 {
349   GPtrArray *arr = g_ptr_array_new ();
350   GHashTable *ht;
351 
352   ht = g_hash_table_new (g_str_hash, g_str_equal);
353   while (missing_plugins != NULL) {
354     char *tmp;
355     tmp = func (GST_MESSAGE (missing_plugins->data));
356     if (!g_hash_table_lookup (ht, tmp)) {
357       g_ptr_array_add (arr, tmp);
358       g_hash_table_insert (ht, tmp, GINT_TO_POINTER (1));
359     } else {
360       g_free (tmp);
361     }
362     missing_plugins = missing_plugins->next;
363   }
364   g_ptr_array_add (arr, NULL);
365   g_hash_table_destroy (ht);
366   return (gchar **) g_ptr_array_free (arr, FALSE);
367 }
368 
369 static gchar **
bvw_get_missing_plugins_details(const GList * missing_plugins)370 bvw_get_missing_plugins_details (const GList * missing_plugins)
371 {
372   return bvw_get_missing_plugins_foo (missing_plugins,
373       gst_missing_plugin_message_get_installer_detail);
374 }
375 
376 static gchar **
bvw_get_missing_plugins_descriptions(const GList * missing_plugins)377 bvw_get_missing_plugins_descriptions (const GList * missing_plugins)
378 {
379   return bvw_get_missing_plugins_foo (missing_plugins,
380       gst_missing_plugin_message_get_description);
381 }
382 
383 static void
bvw_clear_missing_plugins_messages(BaconVideoWidget * bvw)384 bvw_clear_missing_plugins_messages (BaconVideoWidget * bvw)
385 {
386   g_list_free_full (bvw->missing_plugins, (GDestroyNotify) gst_mini_object_unref);
387   bvw->missing_plugins = NULL;
388 }
389 
390 static void
bvw_check_if_video_decoder_is_missing(BaconVideoWidget * bvw)391 bvw_check_if_video_decoder_is_missing (BaconVideoWidget * bvw)
392 {
393   GList *l;
394 
395   if (bvw->media_has_video || bvw->missing_plugins == NULL)
396     return;
397 
398   for (l = bvw->missing_plugins; l != NULL; l = l->next) {
399     GstMessage *msg = GST_MESSAGE (l->data);
400     gchar *d, *f;
401 
402     if ((d = gst_missing_plugin_message_get_installer_detail (msg))) {
403       if ((f = strstr (d, "|decoder-")) && strstr (f, "video")) {
404         GError *err;
405 
406         /* create a fake GStreamer error so we get a nice warning message */
407         err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, "x");
408         msg = gst_message_new_error (GST_OBJECT (bvw->play), err, NULL);
409         g_error_free (err);
410         err = bvw_error_from_gst_error (bvw, msg);
411         gst_message_unref (msg);
412         g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0, err->message, FALSE);
413         g_error_free (err);
414         g_free (d);
415         break;
416       }
417       g_free (d);
418     }
419   }
420 }
421 
422 static void
set_display_pixel_aspect_ratio(GdkMonitor * monitor,GValue * value)423 set_display_pixel_aspect_ratio (GdkMonitor *monitor,
424 				GValue    *value)
425 {
426   static const gint par[][2] = {
427     {1, 1},                     /* regular screen */
428     {16, 15},                   /* PAL TV */
429     {11, 10},                   /* 525 line Rec.601 video */
430     {54, 59},                   /* 625 line Rec.601 video */
431     {64, 45},                   /* 1280x1024 on 16:9 display */
432     {5, 3},                     /* 1280x1024 on 4:3 display */
433     {4, 3}                      /* 800x600 on 16:9 display */
434   };
435   guint i;
436   gint par_index;
437   gdouble ratio;
438   gdouble delta;
439   GdkRectangle rect;
440 
441 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
442 
443   /* first calculate the "real" ratio based on the X values;
444    * which is the "physical" w/h divided by the w/h in pixels of the display */
445   gdk_monitor_get_geometry (monitor, &rect);
446 
447   ratio = (gdouble) (gdk_monitor_get_width_mm (monitor) * rect.height) /
448     (gdk_monitor_get_height_mm (monitor) * rect.width);
449 
450   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
451   /* now find the one from par[][2] with the lowest delta to the real one */
452   delta = DELTA (0);
453   par_index = 0;
454 
455   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
456     gdouble this_delta = DELTA (i);
457 
458     if (this_delta < delta) {
459       par_index = i;
460       delta = this_delta;
461     }
462   }
463 
464   GST_DEBUG ("Decided on index %d (%d/%d)", par_index,
465 	     par[par_index][0], par[par_index][1]);
466   gst_value_set_fraction (value, par[par_index][0], par[par_index][1]);
467 }
468 
469 static void
get_media_size(BaconVideoWidget * bvw,gint * width,gint * height)470 get_media_size (BaconVideoWidget *bvw, gint *width, gint *height)
471 {
472   if (bvw->logo_mode) {
473     const GdkPixbuf *pixbuf;
474 
475     pixbuf = bvw_get_logo_pixbuf (bvw);
476     if (pixbuf) {
477       *width = gdk_pixbuf_get_width (pixbuf);
478       *height = gdk_pixbuf_get_height (pixbuf);
479       if (*width == *height) {
480 	/* The icons will be square, so lie so we get a 16:9
481 	 * ratio */
482 	*width = (int) ((float) *height / 9. * 16.);
483       }
484     } else {
485       *width = 0;
486       *height = 0;
487     }
488   } else {
489     if (bvw->media_has_video) {
490       GValue disp_par = {0, };
491       guint movie_par_n, movie_par_d, disp_par_n, disp_par_d, num, den;
492 
493       /* Create and init the fraction value */
494       g_value_init (&disp_par, GST_TYPE_FRACTION);
495 
496       /* Square pixel is our default */
497       gst_value_set_fraction (&disp_par, 1, 1);
498 
499       /* Now try getting display's pixel aspect ratio */
500       if (gtk_widget_get_realized (GTK_WIDGET (bvw))) {
501 	GdkDisplay *display;
502 	GdkWindow *window;
503 	GdkMonitor *monitor;
504 
505 	display = gtk_widget_get_display (GTK_WIDGET (bvw));
506 	window = gtk_widget_get_window (GTK_WIDGET (bvw));
507 	if (window)
508 	  monitor = gdk_display_get_monitor_at_window (display, window);
509 	else
510 	  monitor = gdk_display_get_primary_monitor (display);
511 	set_display_pixel_aspect_ratio (monitor, &disp_par);
512       }
513 
514       disp_par_n = gst_value_get_fraction_numerator (&disp_par);
515       disp_par_d = gst_value_get_fraction_denominator (&disp_par);
516 
517       GST_DEBUG ("display PAR is %d/%d", disp_par_n, disp_par_d);
518 
519       /* If movie pixel aspect ratio is enforced, use that */
520       if (bvw->ratio_type != BVW_RATIO_AUTO) {
521         switch (bvw->ratio_type) {
522           case BVW_RATIO_SQUARE:
523             movie_par_n = 1;
524             movie_par_d = 1;
525             break;
526           case BVW_RATIO_FOURBYTHREE:
527             movie_par_n = 4 * bvw->video_height;
528             movie_par_d = 3 * bvw->video_width;
529             break;
530           case BVW_RATIO_ANAMORPHIC:
531             movie_par_n = 16 * bvw->video_height;
532             movie_par_d = 9 * bvw->video_width;
533             break;
534           case BVW_RATIO_DVB:
535             movie_par_n = 20 * bvw->video_height;
536             movie_par_d = 9 * bvw->video_width;
537             break;
538           /* handle these to avoid compiler warnings */
539           case BVW_RATIO_AUTO:
540           default:
541             movie_par_n = 0;
542             movie_par_d = 0;
543             g_assert_not_reached ();
544         }
545       } else {
546         /* Use the movie pixel aspect ratio if any */
547         movie_par_n = bvw->movie_par_n;
548         movie_par_d = bvw->movie_par_d;
549       }
550 
551       GST_DEBUG ("movie PAR is %d/%d", movie_par_n, movie_par_d);
552 
553       if (bvw->video_width == 0 || bvw->video_height == 0) {
554         GST_DEBUG ("width and/or height 0, assuming 1/1 ratio");
555         num = 1;
556         den = 1;
557       } else if (!gst_video_calculate_display_ratio (&num, &den,
558           bvw->video_width, bvw->video_height,
559           movie_par_n, movie_par_d, disp_par_n, disp_par_d)) {
560         GST_WARNING ("overflow calculating display aspect ratio!");
561         num = 1;   /* FIXME: what values to use here? */
562         den = 1;
563       }
564 
565       GST_DEBUG ("calculated scaling ratio %d/%d for video %dx%d", num, den,
566           bvw->video_width, bvw->video_height);
567 
568       /* now find a width x height that respects this display ratio.
569        * prefer those that have one of w/h the same as the incoming video
570        * using wd / hd = num / den */
571 
572       /* start with same height, because of interlaced video */
573       /* check hd / den is an integer scale factor, and scale wd with the PAR */
574       if (bvw->video_height % den == 0) {
575         GST_DEBUG ("keeping video height");
576         bvw->video_width_pixels =
577             (guint) gst_util_uint64_scale (bvw->video_height, num, den);
578         bvw->video_height_pixels = bvw->video_height;
579       } else if (bvw->video_width % num == 0) {
580         GST_DEBUG ("keeping video width");
581         bvw->video_width_pixels = bvw->video_width;
582         bvw->video_height_pixels =
583             (guint) gst_util_uint64_scale (bvw->video_width, den, num);
584       } else {
585         GST_DEBUG ("approximating while keeping video height");
586         bvw->video_width_pixels =
587             (guint) gst_util_uint64_scale (bvw->video_height, num, den);
588         bvw->video_height_pixels = bvw->video_height;
589       }
590       GST_DEBUG ("scaling to %dx%d", bvw->video_width_pixels,
591           bvw->video_height_pixels);
592 
593       *width = bvw->video_width_pixels;
594       *height = bvw->video_height_pixels;
595 
596       /* Free the PAR fraction */
597       g_value_unset (&disp_par);
598     }
599     else {
600       *width = 0;
601       *height = 0;
602     }
603   }
604 }
605 
606 static gboolean
leave_notify_cb(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)607 leave_notify_cb (GtkWidget        *widget,
608 		 GdkEventCrossing *event,
609 		 gpointer          user_data)
610 {
611   gboolean res = GDK_EVENT_PROPAGATE;
612   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (user_data);
613   GdkDevice *device;
614 
615   if (event->detail != GDK_NOTIFY_NONLINEAR &&
616       event->detail != GDK_NOTIFY_NONLINEAR_VIRTUAL)
617     return res;
618 
619   device = gdk_event_get_source_device ((GdkEvent *) event);
620   if (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
621     return res;
622 
623   if (bvw->reveal_controls) {
624     gboolean not_busy;
625 
626     not_busy = g_hash_table_size (bvw->busy_popup_ht) == 0;
627     if (not_busy) {
628       GST_DEBUG ("will hide because we're not busy and cursor left");
629       set_controls_visibility (bvw, FALSE, TRUE);
630     }
631   }
632 
633   return res;
634 }
635 
636 static void
bacon_video_widget_realize(GtkWidget * widget)637 bacon_video_widget_realize (GtkWidget * widget)
638 {
639   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
640   GtkWidget *toplevel;
641 
642   GTK_WIDGET_CLASS (parent_class)->realize (widget);
643 
644   gtk_widget_set_realized (widget, TRUE);
645 
646   /* get screen size changes */
647   g_signal_connect (G_OBJECT (gtk_widget_get_screen (widget)),
648 		    "size-changed", G_CALLBACK (size_changed_cb), bvw);
649 
650   /* setup the toplevel, ready to be resized */
651   toplevel = gtk_widget_get_toplevel (widget);
652   gtk_window_set_geometry_hints (GTK_WINDOW (toplevel), widget, NULL, 0);
653   g_signal_connect (G_OBJECT (toplevel), "leave-notify-event",
654 		    G_CALLBACK (leave_notify_cb), bvw);
655 
656   bvw->missing_plugins_cancellable = g_cancellable_new ();
657   g_object_set_data_full (G_OBJECT (bvw), "missing-plugins-cancellable",
658 			  bvw->missing_plugins_cancellable, g_object_unref);
659   bacon_video_widget_gst_missing_plugins_setup (bvw);
660 }
661 
662 static void
bacon_video_widget_unrealize(GtkWidget * widget)663 bacon_video_widget_unrealize (GtkWidget *widget)
664 {
665   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
666   GtkWidget *toplevel;
667 
668   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
669 
670   gtk_widget_set_realized (widget, FALSE);
671 
672   g_signal_handlers_disconnect_by_func (G_OBJECT (gtk_widget_get_screen (widget)),
673 					size_changed_cb, bvw);
674   toplevel = gtk_widget_get_toplevel (widget);
675   g_signal_handlers_disconnect_by_func (G_OBJECT (toplevel),
676 					leave_notify_cb, bvw);
677 
678   g_cancellable_cancel (bvw->missing_plugins_cancellable);
679   bvw->missing_plugins_cancellable = NULL;
680   g_object_set_data (G_OBJECT (bvw), "missing-plugins-cancellable", NULL);
681 }
682 
683 static void
size_changed_cb(GdkScreen * screen,BaconVideoWidget * bvw)684 size_changed_cb (GdkScreen *screen, BaconVideoWidget *bvw)
685 {
686   bvw_check_for_cover_pixbuf (bvw);
687 }
688 
689 static void
set_current_actor(BaconVideoWidget * bvw)690 set_current_actor (BaconVideoWidget *bvw)
691 {
692   gboolean draw_logo;
693 
694   if (bvw->stage == NULL)
695     return;
696 
697   /* If there's only audio and no visualisation, draw the logo as well.
698    * If we have a cover image to display, we display it regardless of whether we're
699    * doing visualisations. */
700   draw_logo = bvw->media_has_audio && !bvw->media_has_video;
701 
702   if (bvw->logo_mode || draw_logo) {
703     const GdkPixbuf *pixbuf;
704 
705     pixbuf = bvw_get_logo_pixbuf (bvw);
706     if (pixbuf != NULL) {
707       gboolean ret;
708       GError *err = NULL;
709 
710       ret = clutter_image_set_data (CLUTTER_IMAGE (bvw->logo),
711 				    gdk_pixbuf_get_pixels (pixbuf),
712 				    gdk_pixbuf_get_has_alpha (pixbuf) ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
713 				    gdk_pixbuf_get_width (pixbuf),
714 				    gdk_pixbuf_get_height (pixbuf),
715 				    gdk_pixbuf_get_rowstride (pixbuf),
716 				    &err);
717       if (ret == FALSE) {
718 	g_warning ("clutter_image_set_data() failed %s", err->message);
719 	g_error_free (err);
720       } else {
721 	clutter_actor_show (CLUTTER_ACTOR (bvw->logo_frame));
722 	clutter_actor_hide (CLUTTER_ACTOR (bvw->frame));
723 	return;
724       }
725     }
726   }
727 
728   clutter_actor_show (CLUTTER_ACTOR (bvw->frame));
729   clutter_actor_hide (CLUTTER_ACTOR (bvw->logo_frame));
730 }
731 
732 static void
unschedule_hiding_popup(BaconVideoWidget * bvw)733 unschedule_hiding_popup (BaconVideoWidget *bvw)
734 {
735   if (bvw->transition_timeout_id > 0)
736     g_source_remove (bvw->transition_timeout_id);
737   bvw->transition_timeout_id = 0;
738 }
739 
740 static gboolean
hide_popup_timeout_cb(BaconVideoWidget * bvw)741 hide_popup_timeout_cb (BaconVideoWidget *bvw)
742 {
743   set_controls_visibility (bvw, FALSE, TRUE);
744   unschedule_hiding_popup (bvw);
745   return G_SOURCE_REMOVE;
746 }
747 
748 static void
schedule_hiding_popup(BaconVideoWidget * bvw)749 schedule_hiding_popup (BaconVideoWidget *bvw)
750 {
751   unschedule_hiding_popup (bvw);
752   bvw->transition_timeout_id = g_timeout_add_seconds (POPUP_HIDING_TIMEOUT, (GSourceFunc) hide_popup_timeout_cb, bvw);
753   g_source_set_name_by_id (bvw->transition_timeout_id, "[totem] hide_popup_timeout_cb");
754 }
755 
756 static void
set_show_cursor(BaconVideoWidget * bvw,gboolean show_cursor)757 set_show_cursor (BaconVideoWidget *bvw,
758 		 gboolean show_cursor)
759 {
760   GdkWindow *window;
761 
762   bvw->cursor_shown = show_cursor;
763   window = gtk_widget_get_window (GTK_WIDGET (bvw));
764 
765   if (!window)
766     return;
767 
768   if (show_cursor == FALSE) {
769     GdkCursor *cursor;
770     GdkDisplay *display;
771 
772     display = gdk_window_get_display (window);
773     cursor = gdk_cursor_new_for_display (display, GDK_BLANK_CURSOR);
774     gdk_window_set_cursor (window, cursor);
775     g_object_unref (cursor);
776   } else {
777     gdk_window_set_cursor (window, bvw->cursor);
778   }
779 }
780 
781 static void
set_controls_visibility(BaconVideoWidget * bvw,gboolean visible,gboolean animate)782 set_controls_visibility (BaconVideoWidget *bvw,
783 			 gboolean          visible,
784 			 gboolean          animate)
785 {
786   guint8 opacity = visible ? OVERLAY_OPACITY : 0;
787   gint header_controls_height;
788   gfloat header_controls_y;
789   guint duration;
790 
791   gtk_widget_get_preferred_height (gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (bvw->header_controls)),
792                                    NULL,
793                                    &header_controls_height);
794   header_controls_y = visible ? 0 : -header_controls_height;
795 
796   duration = animate ? 250 : 0;
797 
798   /* FIXME:
799    * Using a show/hide seems to not trigger the
800    * controls to redraw, so let's change the opacity instead */
801   clutter_actor_set_easing_duration (bvw->controls, duration);
802   clutter_actor_set_easing_duration (bvw->header_controls, duration);
803   clutter_actor_set_opacity (bvw->controls, opacity);
804   clutter_actor_set_y (bvw->header_controls, header_controls_y);
805 
806   set_show_cursor (bvw, visible);
807   if (visible && animate)
808     schedule_hiding_popup (bvw);
809 
810   bvw->reveal_controls = visible;
811   g_object_notify (G_OBJECT (bvw), "reveal-controls");
812 }
813 
814 static void
translate_coords(GtkWidget * widget,GdkWindow * window,int x,int y,int * out_x,int * out_y)815 translate_coords (GtkWidget   *widget,
816 		  GdkWindow   *window,
817 		  int          x,
818 		  int          y,
819 		  int         *out_x,
820 		  int         *out_y)
821 {
822   GtkWidget *src;
823 
824   gdk_window_get_user_data (window, (gpointer *)&src);
825   if (src && src != widget) {
826     gtk_widget_translate_coordinates (src, widget, x, y, out_x, out_y);
827   } else {
828     *out_x = x;
829     *out_y = y;
830   }
831 }
832 
833 static gboolean
ignore_event(BaconVideoWidget * bvw,int x,int y)834 ignore_event (BaconVideoWidget *bvw,
835 	      int               x,
836 	      int               y)
837 {
838   ClutterActor *actor;
839 
840   actor = clutter_stage_get_actor_at_pos (CLUTTER_STAGE (bvw->stage), CLUTTER_PICK_REACTIVE, x, y);
841 
842   /* Eat the GTK+ event if we're not clicking on the video itself */
843   if (actor == bvw->controls)
844     return TRUE;
845 
846   return FALSE;
847 }
848 
849 /* need to use gstnavigation interface for these vmethods, to allow for the sink
850    to map screen coordinates to video coordinates in the presence of e.g.
851    hardware scaling */
852 
853 static gboolean
bacon_video_widget_motion_notify(GtkWidget * widget,GdkEventMotion * event)854 bacon_video_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event)
855 {
856   gboolean res = GDK_EVENT_PROPAGATE;
857   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
858   GdkDevice *device;
859   int x, y;
860 
861   g_return_val_if_fail (bvw->play != NULL, FALSE);
862 
863   if (bvw->navigation && !bvw->logo_mode)
864     gst_navigation_send_mouse_event (bvw->navigation, "mouse-move", 0, event->x, event->y);
865 
866   if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
867     res |= GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
868 
869   device = gdk_event_get_source_device ((GdkEvent *) event);
870   if (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
871     return res;
872 
873   if (!bvw->reveal_controls)
874     set_controls_visibility (bvw, TRUE, TRUE);
875 
876   translate_coords (widget, event->window, event->x, event->y, &x, &y);
877   if (ignore_event (bvw, x, y)) {
878     /* Is the mouse on the popups? */
879     unschedule_hiding_popup (bvw);
880   } else {
881     schedule_hiding_popup (bvw);
882   }
883 
884   return res;
885 }
886 
887 static gboolean
bacon_video_widget_button_press_or_release(GtkWidget * widget,GdkEventButton * event)888 bacon_video_widget_button_press_or_release (GtkWidget *widget, GdkEventButton *event)
889 {
890   gboolean res = FALSE;
891   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (widget);
892   int x, y;
893   GdkDevice *device;
894 
895   device = gdk_event_get_source_device ((GdkEvent *) event);
896   if (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
897     return FALSE;
898 
899   g_return_val_if_fail (bvw->play != NULL, FALSE);
900 
901   translate_coords (widget, event->window, event->x, event->y, &x, &y);
902   if (ignore_event (bvw, x, y))
903     return GDK_EVENT_STOP;
904 
905   if (event->type != GDK_BUTTON_PRESS &&
906       event->type != GDK_BUTTON_RELEASE)
907     goto bail;
908 
909   if (bvw->navigation &&
910       !bvw->logo_mode &&
911       event->button == 1 &&
912       bvw->is_menu != FALSE) {
913     const char *event_str;
914     event_str = (event->type == GDK_BUTTON_PRESS) ? "mouse-button-press" : "mouse-button-release";
915     gst_navigation_send_mouse_event (bvw->navigation,
916 				     event_str, event->button, x, y);
917 
918     /* FIXME need to check whether the backend will have handled
919      * the button press
920      res = TRUE; */
921   }
922 
923 bail:
924   if (event->type == GDK_BUTTON_PRESS && GTK_WIDGET_CLASS (parent_class)->button_press_event)
925     res |= GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
926   if (event->type == GDK_BUTTON_RELEASE && GTK_WIDGET_CLASS (parent_class)->button_release_event)
927     res |= GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
928 
929   return res;
930 }
931 
932 static gboolean
bacon_video_widget_tap(ClutterTapAction * action,ClutterActor * actor,BaconVideoWidget * bvw)933 bacon_video_widget_tap (ClutterTapAction *action,
934 			ClutterActor     *actor,
935 			BaconVideoWidget *bvw)
936 {
937   ClutterInputDevice *device;
938   const ClutterEvent *event;
939   gboolean value;
940 
941   GST_DEBUG ("Tap event received");
942 
943   event = clutter_gesture_action_get_last_event (CLUTTER_GESTURE_ACTION (action), 0);
944   if (!event)
945     return CLUTTER_EVENT_PROPAGATE;
946 
947   device = clutter_event_get_source_device (event);
948   if (device == NULL ||
949       clutter_input_device_get_device_type (device) != CLUTTER_TOUCHSCREEN_DEVICE)
950     return CLUTTER_EVENT_PROPAGATE;
951 
952   value = (clutter_actor_get_opacity (bvw->controls) == 0);
953   set_controls_visibility (bvw, value, FALSE);
954   return CLUTTER_EVENT_STOP;
955 }
956 
957 static gboolean
bacon_video_widget_swipe(ClutterSwipeAction * action,ClutterActor * actor,ClutterSwipeDirection direction,BaconVideoWidget * bvw)958 bacon_video_widget_swipe (ClutterSwipeAction    *action,
959 			  ClutterActor          *actor,
960 			  ClutterSwipeDirection  direction,
961 			  BaconVideoWidget      *bvw)
962 {
963   GST_DEBUG ("Swipe event received");
964 
965   if ((direction & CLUTTER_SWIPE_DIRECTION_UP) ||
966       (direction & CLUTTER_SWIPE_DIRECTION_DOWN)) {
967     if ((direction & CLUTTER_SWIPE_DIRECTION_LEFT) ||
968         (direction & CLUTTER_SWIPE_DIRECTION_RIGHT)) {
969       GST_DEBUG ("Ignoring diagonal swipe 0x%X", direction);
970       return CLUTTER_EVENT_PROPAGATE;
971     }
972   }
973 
974   if (direction & CLUTTER_SWIPE_DIRECTION_LEFT)
975     g_signal_emit (G_OBJECT (bvw), bvw_signals[SIGNAL_SEEK_REQUESTED], 0,
976 		   gtk_widget_get_direction (GTK_WIDGET (bvw)) == GTK_TEXT_DIR_RTL);
977   if (direction & CLUTTER_SWIPE_DIRECTION_RIGHT)
978     g_signal_emit (G_OBJECT (bvw), bvw_signals[SIGNAL_SEEK_REQUESTED], 0,
979 		   gtk_widget_get_direction (GTK_WIDGET (bvw)) == GTK_TEXT_DIR_LTR);
980 
981   return CLUTTER_EVENT_STOP;
982 }
983 
984 static gboolean
bacon_video_widget_handle_scroll(GtkWidget * widget,GdkEventScroll * event,BaconVideoWidget * bvw)985 bacon_video_widget_handle_scroll (GtkWidget        *widget,
986 				  GdkEventScroll   *event,
987 				  BaconVideoWidget *bvw)
988 {
989   int x, y;
990   gboolean forward;
991   gdouble delta_y;
992 
993   g_return_val_if_fail (bvw->play != NULL, FALSE);
994 
995   if (event->direction != GDK_SCROLL_SMOOTH)
996     return GDK_EVENT_PROPAGATE;
997 
998   if (widget == (gpointer) bvw) {
999     translate_coords (widget, event->window, event->x, event->y, &x, &y);
1000     if (ignore_event (bvw, x, y))
1001       return GDK_EVENT_STOP;
1002   }
1003 
1004   gdk_event_get_scroll_deltas ((GdkEvent *) event, NULL, &delta_y);
1005   if (delta_y == 0.0)
1006     return GDK_EVENT_PROPAGATE;
1007   forward = delta_y >= 0.0 ? FALSE : TRUE;
1008 
1009   if (widget == (gpointer) bvw ||
1010       widget == g_object_get_data (G_OBJECT (bvw->controls), "seek_scale")) {
1011     if (bvw->seekable > 0)
1012       g_signal_emit (G_OBJECT (bvw), bvw_signals[SIGNAL_SEEK_REQUESTED], 0, forward);
1013   } else if (widget == g_object_get_data (G_OBJECT (bvw->controls), "volume_button")) {
1014     if (bacon_video_widget_can_set_volume (bvw))
1015       g_signal_emit (G_OBJECT (bvw), bvw_signals[SIGNAL_VOLUME_CHANGE_REQUESTED], 0, forward);
1016   }
1017 
1018   return GDK_EVENT_STOP;
1019 }
1020 
1021 static gboolean
bacon_video_widget_scroll(GtkWidget * widget,GdkEventScroll * event)1022 bacon_video_widget_scroll (GtkWidget *widget, GdkEventScroll *event)
1023 {
1024   return bacon_video_widget_handle_scroll (widget, event, BACON_VIDEO_WIDGET (widget));
1025 }
1026 
1027 static void
bacon_video_widget_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1028 bacon_video_widget_get_preferred_width (GtkWidget *widget,
1029                                         gint      *minimum,
1030                                         gint      *natural)
1031 {
1032   /* We could also make the actor a minimum width, based on its contents */
1033   *minimum = *natural = DEFAULT_CONTROLS_WIDTH;
1034 }
1035 
1036 static void
bacon_video_widget_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1037 bacon_video_widget_get_preferred_height (GtkWidget *widget,
1038                                          gint      *minimum,
1039                                          gint      *natural)
1040 {
1041   *minimum = *natural = DEFAULT_CONTROLS_WIDTH / 16 * 9;
1042 }
1043 
1044 static gboolean
bvw_boolean_handled_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer foobar)1045 bvw_boolean_handled_accumulator (GSignalInvocationHint * ihint,
1046     GValue * return_accu, const GValue * handler_return, gpointer foobar)
1047 {
1048   gboolean continue_emission;
1049   gboolean signal_handled;
1050 
1051   signal_handled = g_value_get_boolean (handler_return);
1052   g_value_set_boolean (return_accu, signal_handled);
1053   continue_emission = !signal_handled;
1054 
1055   return continue_emission;
1056 }
1057 
1058 static void
disable_vaapi(void)1059 disable_vaapi (void)
1060 {
1061   GstRegistry *registry;
1062   GstPlugin *plugin;
1063 
1064   registry = gst_registry_get ();
1065   plugin = gst_registry_find_plugin (registry, "vaapi");
1066   if (!plugin)
1067     return;
1068   gst_registry_remove_plugin (registry, plugin);
1069 }
1070 
1071 static void
bacon_video_widget_class_init(BaconVideoWidgetClass * klass)1072 bacon_video_widget_class_init (BaconVideoWidgetClass * klass)
1073 {
1074   GObjectClass *object_class;
1075   GtkWidgetClass *widget_class;
1076 
1077   disable_vaapi ();
1078   clutter_gst_init (NULL, NULL);
1079 
1080   object_class = (GObjectClass *) klass;
1081   widget_class = (GtkWidgetClass *) klass;
1082 
1083   parent_class = g_type_class_peek_parent (klass);
1084 
1085   /* GtkWidget */
1086   widget_class->get_preferred_width = bacon_video_widget_get_preferred_width;
1087   widget_class->get_preferred_height = bacon_video_widget_get_preferred_height;
1088   widget_class->realize = bacon_video_widget_realize;
1089   widget_class->unrealize = bacon_video_widget_unrealize;
1090 
1091   widget_class->motion_notify_event = bacon_video_widget_motion_notify;
1092   widget_class->button_press_event = bacon_video_widget_button_press_or_release;
1093   widget_class->button_release_event = bacon_video_widget_button_press_or_release;
1094   widget_class->scroll_event = bacon_video_widget_scroll;
1095 
1096   /* GObject */
1097   object_class->set_property = bacon_video_widget_set_property;
1098   object_class->get_property = bacon_video_widget_get_property;
1099   object_class->finalize = bacon_video_widget_finalize;
1100 
1101   /* Properties */
1102   /**
1103    * BaconVideoWidget:logo-mode:
1104    *
1105    * Whether the logo should be displayed when no stream is loaded, or the widget
1106    * should take up no space.
1107    **/
1108   g_object_class_install_property (object_class, PROP_LOGO_MODE,
1109                                    g_param_spec_boolean ("logo-mode", "Logo mode?",
1110                                                          "Whether the logo should be displayed when no stream is loaded.", FALSE,
1111                                                          G_PARAM_READWRITE |
1112                                                          G_PARAM_STATIC_STRINGS));
1113 
1114   /**
1115    * BaconVideoWidget:position:
1116    *
1117    * The current position in the stream, as a percentage between <code class="literal">0</code> and <code class="literal">1</code>.
1118    **/
1119   g_object_class_install_property (object_class, PROP_POSITION,
1120                                    g_param_spec_double ("position", "Position", "The current position in the stream.",
1121 							0, 1.0, 0,
1122 							G_PARAM_READABLE |
1123                                                         G_PARAM_STATIC_STRINGS));
1124 
1125   /**
1126    * BaconVideoWidget:stream-length:
1127    *
1128    * The length of the current stream, in milliseconds.
1129    **/
1130   g_object_class_install_property (object_class, PROP_STREAM_LENGTH,
1131 	                           g_param_spec_int64 ("stream-length", "Stream length",
1132                                                      "The length of the current stream, in milliseconds.", 0, G_MAXINT64, 0,
1133                                                      G_PARAM_READABLE |
1134                                                      G_PARAM_STATIC_STRINGS));
1135 
1136   /**
1137    * BaconVideoWidget:playing:
1138    *
1139    * Whether a stream is currently playing.
1140    **/
1141   g_object_class_install_property (object_class, PROP_PLAYING,
1142                                    g_param_spec_boolean ("playing", "Playing?",
1143                                                          "Whether a stream is currently playing.", FALSE,
1144                                                          G_PARAM_READABLE |
1145                                                          G_PARAM_STATIC_STRINGS));
1146 
1147   /**
1148    * BaconVideoWidget:seekable:
1149    *
1150    * Whether the current stream can be seeked.
1151    **/
1152   g_object_class_install_property (object_class, PROP_SEEKABLE,
1153                                    g_param_spec_boolean ("seekable", "Seekable?",
1154                                                          "Whether the current stream can be seeked.", FALSE,
1155                                                          G_PARAM_READABLE |
1156                                                          G_PARAM_STATIC_STRINGS));
1157 
1158   /**
1159    * BaconVideoWidget:volume:
1160    *
1161    * The current volume level, as a percentage between <code class="literal">0</code> and <code class="literal">1</code>.
1162    **/
1163   g_object_class_install_property (object_class, PROP_VOLUME,
1164 	                           g_param_spec_double ("volume", "Volume", "The current volume level.",
1165 	                                                0.0, 1.0, 0.0,
1166 	                                                G_PARAM_READWRITE |
1167                                                         G_PARAM_STATIC_STRINGS));
1168 
1169   /**
1170    * BaconVideoWidget:referrer:
1171    *
1172    * The HTTP referrer URI.
1173    **/
1174   g_object_class_install_property (object_class, PROP_REFERRER,
1175                                    g_param_spec_string ("referrer", "Referrer URI", "The HTTP referrer URI.",
1176                                                         NULL,
1177                                                         G_PARAM_READWRITE |
1178                                                         G_PARAM_STATIC_STRINGS));
1179 
1180   /**
1181    * BaconVideoWidget:user-agent:
1182    *
1183    * The HTTP user agent string to use.
1184    **/
1185   g_object_class_install_property (object_class, PROP_USER_AGENT,
1186                                    g_param_spec_string ("user-agent", "User agent", "The HTTP user agent string to use.",
1187                                                         NULL,
1188                                                         G_PARAM_READWRITE |
1189                                                         G_PARAM_STATIC_STRINGS));
1190 
1191   /**
1192    * BaconVideoWidget:download-filename:
1193    *
1194    * The filename of the fully downloaded stream when using
1195    * download buffering.
1196    **/
1197   g_object_class_install_property (object_class, PROP_DOWNLOAD_FILENAME,
1198                                    g_param_spec_string ("download-filename", "Download filename.", "The filename of the fully downloaded stream.",
1199                                                         NULL,
1200                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1201 
1202   /**
1203    * BaconVideoWidget:deinterlacing:
1204    *
1205    * Whether to automatically deinterlace videos.
1206    **/
1207   g_object_class_install_property (object_class, PROP_DEINTERLACING,
1208                                    g_param_spec_boolean ("deinterlacing", "Deinterlacing?",
1209                                                          "Whether to automatically deinterlace videos.", FALSE,
1210                                                          G_PARAM_READWRITE |
1211                                                          G_PARAM_STATIC_STRINGS));
1212 
1213   /**
1214    * BaconVideoWidget:brightness:
1215    *
1216    * The brightness of the video display.
1217    **/
1218   g_object_class_install_property (object_class, PROP_BRIGHTNESS,
1219                                    g_param_spec_int ("brightness", "Brightness",
1220                                                       "The brightness of the video display.", 0, 65535, 32768,
1221                                                       G_PARAM_READWRITE |
1222                                                       G_PARAM_STATIC_STRINGS));
1223 
1224   /**
1225    * BaconVideoWidget:contrast:
1226    *
1227    * The contrast of the video display.
1228    **/
1229   g_object_class_install_property (object_class, PROP_CONTRAST,
1230                                    g_param_spec_int ("contrast", "Contrast",
1231                                                       "The contrast of the video display.", 0, 65535, 32768,
1232                                                       G_PARAM_READWRITE |
1233                                                       G_PARAM_STATIC_STRINGS));
1234 
1235   /**
1236    * BaconVideoWidget:saturation:
1237    *
1238    * The saturation of the video display.
1239    **/
1240   g_object_class_install_property (object_class, PROP_SATURATION,
1241                                    g_param_spec_int ("saturation", "Saturation",
1242                                                       "The saturation of the video display.", 0, 65535, 32768,
1243                                                       G_PARAM_READWRITE |
1244                                                       G_PARAM_STATIC_STRINGS));
1245 
1246   /**
1247    * BaconVideoWidget:hue:
1248    *
1249    * The hue of the video display.
1250    **/
1251   g_object_class_install_property (object_class, PROP_HUE,
1252                                    g_param_spec_int ("hue", "Hue",
1253                                                       "The hue of the video display.", 0, 65535, 32768,
1254                                                       G_PARAM_READWRITE |
1255                                                       G_PARAM_STATIC_STRINGS));
1256 
1257   /**
1258    * BaconVideoWidget:audio-output-type:
1259    *
1260    * The type of audio output to use (e.g. the number of channels).
1261    **/
1262   g_object_class_install_property (object_class, PROP_AUDIO_OUTPUT_TYPE,
1263                                    g_param_spec_enum ("audio-output-type", "Audio output type",
1264                                                       "The type of audio output to use.", BVW_TYPE_AUDIO_OUTPUT_TYPE,
1265                                                       BVW_AUDIO_SOUND_STEREO,
1266                                                       G_PARAM_READWRITE |
1267                                                       G_PARAM_STATIC_STRINGS));
1268 
1269   /**
1270    * BaconVideoWidget:av-offset:
1271    *
1272    * Control the synchronisation offset between the audio and video streams.
1273    * Positive values make the audio ahead of the video and negative values
1274    * make the audio go behind the video.
1275    **/
1276   g_object_class_install_property (object_class, PROP_AV_OFFSET,
1277 				   g_param_spec_int64 ("av-offset", "Audio/Video offset",
1278 						       "The synchronisation offset between audio and video in nanoseconds.",
1279 						       G_MININT64, G_MAXINT64,
1280 						       0, G_PARAM_READWRITE |
1281 						       G_PARAM_STATIC_STRINGS));
1282 
1283   /**
1284    * BaconVideoWidget:reveal-controls:
1285    *
1286    * Whether to show or hide the controls.
1287    **/
1288   g_object_class_install_property (object_class, PROP_REVEAL_CONTROLS,
1289                                    g_param_spec_boolean ("reveal-controls", "Reveal controls",
1290                                                          "Whether to show or hide the controls.", FALSE,
1291                                                          G_PARAM_READABLE |
1292                                                          G_PARAM_STATIC_STRINGS));
1293 
1294   /* Signals */
1295   /**
1296    * BaconVideoWidget::error:
1297    * @bvw: the #BaconVideoWidget which received the signal
1298    * @message: the error message
1299    * @playback_stopped: %TRUE if playback has stopped due to the error, %FALSE otherwise
1300    * @fatal: %TRUE if the error was fatal to playback, %FALSE otherwise
1301    *
1302    * Emitted when the backend wishes to asynchronously report an error. If @fatal is %TRUE,
1303    * playback of this stream cannot be restarted.
1304    **/
1305   bvw_signals[SIGNAL_ERROR] =
1306     g_signal_new (I_("error"),
1307                   G_TYPE_FROM_CLASS (object_class),
1308                   G_SIGNAL_RUN_LAST,
1309                   0,
1310                   NULL, NULL,
1311                   g_cclosure_marshal_generic,
1312                   G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);
1313 
1314   /**
1315    * BaconVideoWidget::eos:
1316    * @bvw: the #BaconVideoWidget which received the signal
1317    *
1318    * Emitted when the end of the current stream is reached.
1319    **/
1320   bvw_signals[SIGNAL_EOS] =
1321     g_signal_new (I_("eos"),
1322                   G_TYPE_FROM_CLASS (object_class),
1323                   G_SIGNAL_RUN_LAST,
1324                   0,
1325                   NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1326 
1327   /**
1328    * BaconVideoWidget::got-metadata:
1329    * @bvw: the #BaconVideoWidget which received the signal
1330    *
1331    * Emitted when the widget has updated the metadata of the current stream. This
1332    * will typically happen just after opening a stream.
1333    *
1334    * Call bacon_video_widget_get_metadata() to query the updated metadata.
1335    **/
1336   bvw_signals[SIGNAL_GOT_METADATA] =
1337     g_signal_new (I_("got-metadata"),
1338                   G_TYPE_FROM_CLASS (object_class),
1339                   G_SIGNAL_RUN_LAST,
1340                   0,
1341                   NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1342 
1343   /**
1344    * BaconVideoWidget::got-redirect:
1345    * @bvw: the #BaconVideoWidget which received the signal
1346    * @new_mrl: the new MRL
1347    *
1348    * Emitted when a redirect response is received from a stream's server.
1349    **/
1350   bvw_signals[SIGNAL_REDIRECT] =
1351     g_signal_new (I_("got-redirect"),
1352                   G_TYPE_FROM_CLASS (object_class),
1353                   G_SIGNAL_RUN_LAST,
1354                   0,
1355                   NULL, NULL, g_cclosure_marshal_VOID__STRING,
1356                   G_TYPE_NONE, 1, G_TYPE_STRING);
1357 
1358   /**
1359    * BaconVideoWidget::channels-change:
1360    * @bvw: the #BaconVideoWidget which received the signal
1361    *
1362    * Emitted when the number of audio languages available changes, or when the
1363    * selected audio language is changed.
1364    *
1365    * Query the new list of audio languages with bacon_video_widget_get_languages().
1366    **/
1367   bvw_signals[SIGNAL_CHANNELS_CHANGE] =
1368     g_signal_new (I_("channels-change"),
1369                   G_TYPE_FROM_CLASS (object_class),
1370                   G_SIGNAL_RUN_LAST,
1371                   0,
1372                   NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
1373 
1374   /**
1375    * BaconVideoWidget::tick:
1376    * @bvw: the #BaconVideoWidget which received the signal
1377    * @current_time: the current position in the stream, in milliseconds since the beginning of the stream
1378    * @stream_length: the length of the stream, in milliseconds
1379    * @current_position: the current position in the stream, as a percentage between <code class="literal">0</code> and <code class="literal">1</code>
1380    * @seekable: %TRUE if the stream can be seeked, %FALSE otherwise
1381    *
1382    * Emitted every time an important time event happens, or at regular intervals when playing a stream.
1383    **/
1384   bvw_signals[SIGNAL_TICK] =
1385     g_signal_new (I_("tick"),
1386                   G_TYPE_FROM_CLASS (object_class),
1387                   G_SIGNAL_RUN_LAST,
1388                   0,
1389                   NULL, NULL,
1390                   g_cclosure_marshal_generic,
1391                   G_TYPE_NONE, 4, G_TYPE_INT64, G_TYPE_INT64, G_TYPE_DOUBLE,
1392                   G_TYPE_BOOLEAN);
1393 
1394   /**
1395    * BaconVideoWidget::buffering:
1396    * @bvw: the #BaconVideoWidget which received the signal
1397    * @percentage: the percentage of buffering completed, between <code class="literal">0</code> and <code class="literal">1</code>
1398    *
1399    * Emitted regularly when a network stream is being buffered, to provide status updates on the buffering
1400    * progress.
1401    **/
1402   bvw_signals[SIGNAL_BUFFERING] =
1403     g_signal_new (I_("buffering"),
1404                   G_TYPE_FROM_CLASS (object_class),
1405                   G_SIGNAL_RUN_LAST,
1406                   0,
1407                   NULL, NULL,
1408                   g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE);
1409 
1410   /**
1411    * BaconVideoWidget::missing-plugins:
1412    * @bvw: the #BaconVideoWidget which received the signal
1413    * @details: a %NULL-terminated array of missing plugin details for use when installing the plugins with libgimme-codec
1414    * @descriptions: a %NULL-terminated array of missing plugin descriptions for display to the user
1415    * @playing: %TRUE if the stream could be played even without these plugins, %FALSE otherwise
1416    *
1417    * Emitted when plugins required to play the current stream are not found. This allows the application
1418    * to request the user install them before proceeding to try and play the stream again.
1419    *
1420    * Note that this signal is only available for the GStreamer backend.
1421    *
1422    * Return value: %TRUE if the signal was handled and some action was taken, %FALSE otherwise
1423    **/
1424   bvw_signals[SIGNAL_MISSING_PLUGINS] =
1425     g_signal_new (I_("missing-plugins"),
1426                   G_TYPE_FROM_CLASS (object_class),
1427                   G_SIGNAL_RUN_LAST,
1428                   0,
1429                   bvw_boolean_handled_accumulator, NULL,
1430                   g_cclosure_marshal_generic,
1431                   G_TYPE_BOOLEAN, 3, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_BOOLEAN);
1432 
1433   /**
1434    * BaconVideoWidget::download-buffering:
1435    * @bvw: the #BaconVideoWidget which received the signal
1436    * @percentage: the percentage of download buffering completed, between <code class="literal">0</code> and <code class="literal">1</code>
1437    *
1438    * Emitted regularly when a network stream is being cached on disk, to provide status
1439    *  updates on the buffering level of the stream.
1440    **/
1441   bvw_signals[SIGNAL_DOWNLOAD_BUFFERING] =
1442     g_signal_new ("download-buffering",
1443                   G_TYPE_FROM_CLASS (object_class),
1444                   G_SIGNAL_RUN_LAST,
1445                   0,
1446                   NULL, NULL,
1447                   g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE);
1448 
1449   /**
1450    * BaconVideoWidget::seek-requested:
1451    * @bvw: the #BaconVideoWidget which received the signal
1452    * @forward: whether the seek requested is a forward or backward seek.
1453    *
1454    * Emitted when a gesture, our mouse movement that should seek is made.
1455    **/
1456   bvw_signals[SIGNAL_SEEK_REQUESTED] =
1457     g_signal_new ("seek-requested",
1458                   G_TYPE_FROM_CLASS (object_class),
1459                   G_SIGNAL_RUN_LAST,
1460                   0,
1461                   NULL, NULL,
1462                   g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
1463 
1464   /**
1465    * BaconVideoWidget::track-skip-requested:
1466    * @bvw: the #BaconVideoWidget which received the signal
1467    * @forward: whether the track change requested is a forward or backward skip.
1468    *
1469    * Emitted when a gesture, our mouse movement that should seek is made.
1470    **/
1471   bvw_signals[SIGNAL_TRACK_SKIP_REQUESTED] =
1472     g_signal_new ("track-skip-requested",
1473                   G_TYPE_FROM_CLASS (object_class),
1474                   G_SIGNAL_RUN_LAST,
1475                   0,
1476                   NULL, NULL,
1477                   g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
1478 
1479   /**
1480    * BaconVideoWidget::volume-change-requested:
1481    * @bvw: the #BaconVideoWidget which received the signal
1482    * @increase: whether the volume change requested is an increase or decrease.
1483    *
1484    * Emitted when a gesture, our mouse movement that should change the volume
1485    * is emitted.
1486    **/
1487   bvw_signals[SIGNAL_VOLUME_CHANGE_REQUESTED] =
1488     g_signal_new ("volume-change-requested",
1489                   G_TYPE_FROM_CLASS (object_class),
1490                   G_SIGNAL_RUN_LAST,
1491                   0,
1492                   NULL, NULL,
1493                   g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
1494 }
1495 
1496 static void
bacon_video_widget_init(BaconVideoWidget * bvw)1497 bacon_video_widget_init (BaconVideoWidget * bvw)
1498 {
1499   gtk_widget_set_can_focus (GTK_WIDGET (bvw), TRUE);
1500 
1501   g_type_class_ref (BVW_TYPE_METADATA_TYPE);
1502   g_type_class_ref (BVW_TYPE_DVD_EVENT);
1503   g_type_class_ref (BVW_TYPE_ROTATION);
1504 
1505   g_object_set (G_OBJECT (bvw), "use-layout-size", TRUE, NULL);
1506 
1507   bvw->update_id = 0;
1508   bvw->tagcache = NULL;
1509   bvw->audiotags = NULL;
1510   bvw->videotags = NULL;
1511   bvw->volume = -1.0;
1512   bvw->movie_par_n = bvw->movie_par_d = 1;
1513   bvw->rate = FORWARD_RATE;
1514 
1515   bvw->tag_update_queue = g_async_queue_new_full ((GDestroyNotify) update_tags_delayed_data_destroy);
1516   bvw->tag_update_id = 0;
1517 
1518   g_mutex_init (&bvw->seek_mutex);
1519   bvw->clock = gst_system_clock_obtain ();
1520   bvw->seek_req_time = GST_CLOCK_TIME_NONE;
1521   bvw->seek_time = -1;
1522 
1523   bvw->missing_plugins = NULL;
1524   bvw->plugin_install_in_progress = FALSE;
1525 
1526   bvw->mount_cancellable = NULL;
1527   bvw->mount_in_progress = FALSE;
1528   bvw->auth_last_result = G_MOUNT_OPERATION_HANDLED;
1529   bvw->auth_dialog = NULL;
1530 
1531   bvw->busy_popup_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1532 
1533   bacon_video_widget_gst_missing_plugins_block ();
1534 }
1535 
1536 static gboolean bvw_query_timeout (BaconVideoWidget *bvw);
1537 static gboolean bvw_query_buffering_timeout (BaconVideoWidget *bvw);
1538 static void parse_stream_info (BaconVideoWidget *bvw);
1539 
1540 static void
bvw_update_stream_info(BaconVideoWidget * bvw)1541 bvw_update_stream_info (BaconVideoWidget *bvw)
1542 {
1543   parse_stream_info (bvw);
1544 
1545   g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
1546   g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
1547 }
1548 
1549 static void
bvw_handle_application_message(BaconVideoWidget * bvw,GstMessage * msg)1550 bvw_handle_application_message (BaconVideoWidget *bvw, GstMessage *msg)
1551 {
1552   const GstStructure *structure;
1553   const gchar *msg_name;
1554 
1555   structure = gst_message_get_structure (msg);
1556   msg_name = gst_structure_get_name (structure);
1557   g_return_if_fail (msg_name != NULL);
1558 
1559   GST_DEBUG ("Handling application message: %" GST_PTR_FORMAT, structure);
1560 
1561   if (strcmp (msg_name, "stream-changed") == 0) {
1562     bvw_update_stream_info (bvw);
1563   }
1564   else if (strcmp (msg_name, "video-size") == 0) {
1565     int w, h;
1566 
1567     g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
1568 
1569     /* This is necessary for the pixel-aspect-ratio of the
1570      * display to be taken into account. */
1571     get_media_size (bvw, &w, &h);
1572     clutter_actor_set_size (bvw->texture, w, h);
1573 
1574     set_current_actor (bvw);
1575   } else {
1576     g_debug ("Unhandled application message %s", msg_name);
1577   }
1578 }
1579 
1580 static gboolean
bvw_do_navigation_query(BaconVideoWidget * bvw,GstQuery * query)1581 bvw_do_navigation_query (BaconVideoWidget * bvw, GstQuery *query)
1582 {
1583   if (!bvw->navigation)
1584     return FALSE;
1585 
1586   return gst_element_query (GST_ELEMENT_CAST (bvw->navigation), query);
1587 }
1588 
1589 static void
mount_cb(GObject * obj,GAsyncResult * res,gpointer user_data)1590 mount_cb (GObject *obj, GAsyncResult *res, gpointer user_data)
1591 {
1592   BaconVideoWidget * bvw = user_data;
1593   gboolean ret;
1594   gchar *uri;
1595   GError *error = NULL;
1596   GError *err = NULL;
1597   GstMessage *msg;
1598 
1599   ret = g_file_mount_enclosing_volume_finish (G_FILE (obj), res, &error);
1600   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1601     return;
1602 
1603   g_clear_object (&bvw->mount_cancellable);
1604   bvw->mount_in_progress = FALSE;
1605 
1606   uri = g_strdup (bvw->mrl);
1607 
1608   if (ret) {
1609     GstState target_state;
1610 
1611     GST_DEBUG ("Mounting location '%s' successful", GST_STR_NULL (uri));
1612     /* Save the expected pipeline state */
1613     target_state = bvw->target_state;
1614     bacon_video_widget_open (bvw, uri);
1615     if (target_state == GST_STATE_PLAYING)
1616       bacon_video_widget_play (bvw, NULL);
1617     g_free (uri);
1618     return;
1619   }
1620 
1621   if (!ret)
1622     GST_DEBUG ("Mounting location '%s' failed: %s", GST_STR_NULL (uri), error->message);
1623   else
1624     GST_DEBUG ("Failed to set '%s' back to playing: %s", GST_STR_NULL (uri), error->message);
1625 
1626   /* create a fake GStreamer error so we get a nice warning message */
1627   err = g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_OPEN_READ, error->message);
1628   msg = gst_message_new_error (GST_OBJECT (bvw->play), err, error->message);
1629   g_error_free (err);
1630   g_error_free (error);
1631   err = bvw_error_from_gst_error (bvw, msg);
1632   gst_message_unref (msg);
1633   g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0, err->message, FALSE);
1634   g_error_free (err);
1635 
1636   g_free (uri);
1637 }
1638 
1639 static void
bvw_handle_element_message(BaconVideoWidget * bvw,GstMessage * msg)1640 bvw_handle_element_message (BaconVideoWidget *bvw, GstMessage *msg)
1641 {
1642   const GstStructure *structure;
1643   const gchar *type_name = NULL;
1644   gchar *src_name;
1645 
1646   src_name = gst_object_get_name (msg->src);
1647 
1648   structure = gst_message_get_structure (msg);
1649   if (structure)
1650     type_name = gst_structure_get_name (structure);
1651 
1652   GST_DEBUG ("from %s: %" GST_PTR_FORMAT, src_name, structure);
1653 
1654   if (type_name == NULL)
1655     goto unhandled;
1656 
1657   if (strcmp (type_name, "redirect") == 0) {
1658     const gchar *new_location;
1659 
1660     new_location = gst_structure_get_string (structure, "new-location");
1661     GST_DEBUG ("Got redirect to '%s'", GST_STR_NULL (new_location));
1662 
1663     if (new_location && *new_location) {
1664       g_signal_emit (bvw, bvw_signals[SIGNAL_REDIRECT], 0, new_location);
1665       goto done;
1666     }
1667   } else if (strcmp (type_name, "progress") == 0) {
1668     /* this is similar to buffering messages, but shouldn't affect pipeline
1669      * state; qtdemux emits those when headers are after movie data and
1670      * it is in streaming mode and has to receive all the movie data first */
1671     if (!bvw->buffering) {
1672       gint percent = 0;
1673 
1674       if (gst_structure_get_int (structure, "percent", &percent)) {
1675 	gdouble fraction = (gdouble) percent / 100.0;
1676         g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, fraction);
1677       }
1678     }
1679     goto done;
1680   } else if (gst_is_missing_plugin_message (msg)) {
1681     bvw->missing_plugins =
1682       g_list_prepend (bvw->missing_plugins, gst_message_ref (msg));
1683     goto done;
1684   } else if (strcmp (type_name, "not-mounted") == 0) {
1685     const GValue *val;
1686     GFile *file;
1687     GMountOperation *mount_op;
1688     GtkWidget *toplevel;
1689     GstState target_state;
1690     const char *uri;
1691 
1692     val = gst_structure_get_value (structure, "uri");
1693     uri = g_value_get_string (val);
1694 
1695     if (bvw->mount_in_progress) {
1696       g_cancellable_cancel (bvw->mount_cancellable);
1697       g_clear_object (&bvw->mount_cancellable);
1698       bvw->mount_in_progress = FALSE;
1699     }
1700 
1701     GST_DEBUG ("Trying to mount location '%s'", GST_STR_NULL (uri));
1702 
1703     toplevel = gtk_widget_get_toplevel (GTK_WIDGET (bvw));
1704     if (toplevel == GTK_WIDGET (bvw) || !GTK_IS_WINDOW (toplevel))
1705       toplevel = NULL;
1706 
1707     val = gst_structure_get_value (structure, "file");
1708     if (val == NULL)
1709       goto done;
1710 
1711     file = G_FILE (g_value_get_object (val));
1712     if (file == NULL)
1713       goto done;
1714 
1715     /* Save and restore the expected pipeline state */
1716     target_state = bvw->target_state;
1717     bacon_video_widget_stop (bvw);
1718     bvw->target_state = target_state;
1719 
1720     mount_op = gtk_mount_operation_new (toplevel ? GTK_WINDOW (toplevel) : NULL);
1721     bvw->mount_in_progress = TRUE;
1722     bvw->mount_cancellable = g_cancellable_new ();
1723     g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE,
1724         mount_op, bvw->mount_cancellable, mount_cb, bvw);
1725 
1726     g_object_unref (mount_op);
1727     goto done;
1728   } else if (strcmp (type_name, "GstCacheDownloadComplete") == 0) {
1729     const gchar *location;
1730 
1731     /* do query for the last time */
1732     bvw_query_buffering_timeout (bvw);
1733     /* Finished buffering the whole file, so don't run the timeout anymore */
1734     bvw_reconfigure_fill_timeout (bvw, 0);
1735 
1736     /* Tell the front-end about the downloaded file */
1737     g_object_notify (G_OBJECT (bvw), "download-filename");
1738 
1739     location = gst_structure_get_string (structure, "location");
1740     GST_DEBUG ("Finished download of '%s'", GST_STR_NULL (location));
1741     goto done;
1742   } else {
1743     GstNavigationMessageType nav_msg_type =
1744         gst_navigation_message_get_type (msg);
1745 
1746     switch (nav_msg_type) {
1747       case GST_NAVIGATION_MESSAGE_MOUSE_OVER: {
1748         gint active;
1749         GdkWindow *window;
1750         GdkDisplay *display;
1751         if (!gst_navigation_message_parse_mouse_over (msg, &active))
1752           break;
1753         window = gtk_widget_get_window (GTK_WIDGET (bvw));
1754         if (active) {
1755           if (bvw->cursor == NULL) {
1756             display = gdk_window_get_display (window);
1757             bvw->cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
1758           }
1759         } else {
1760 	  g_clear_object (&bvw->cursor);
1761         }
1762         gdk_window_set_cursor (window, bvw->cursor);
1763         goto done;
1764       }
1765       case GST_NAVIGATION_MESSAGE_COMMANDS_CHANGED: {
1766         GstQuery *cmds_q = gst_navigation_query_new_commands();
1767         gboolean res = bvw_do_navigation_query (bvw, cmds_q);
1768 
1769         if (res) {
1770           gboolean is_menu = FALSE;
1771 	  gboolean has_angles = FALSE;
1772           guint i, n;
1773 
1774           if (gst_navigation_query_parse_commands_length (cmds_q, &n)) {
1775             for (i = 0; i < n; i++) {
1776               GstNavigationCommand cmd;
1777               if (!gst_navigation_query_parse_commands_nth (cmds_q, i, &cmd))
1778                 break;
1779               is_menu |= (cmd == GST_NAVIGATION_COMMAND_ACTIVATE);
1780               is_menu |= (cmd == GST_NAVIGATION_COMMAND_LEFT);
1781               is_menu |= (cmd == GST_NAVIGATION_COMMAND_RIGHT);
1782               is_menu |= (cmd == GST_NAVIGATION_COMMAND_UP);
1783               is_menu |= (cmd == GST_NAVIGATION_COMMAND_DOWN);
1784 
1785 	      has_angles |= (cmd == GST_NAVIGATION_COMMAND_PREV_ANGLE);
1786 	      has_angles |= (cmd == GST_NAVIGATION_COMMAND_NEXT_ANGLE);
1787             }
1788           }
1789 	  /* Are we in a menu now? */
1790 	  if (bvw->is_menu != is_menu) {
1791 	    bvw->is_menu = is_menu;
1792 	    g_object_notify (G_OBJECT (bvw), "seekable");
1793 	  }
1794 	  /* Do we have angle switching now? */
1795 	  if (bvw->has_angles != has_angles) {
1796 	    bvw->has_angles = has_angles;
1797 	    g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
1798 	  }
1799         }
1800 
1801         gst_query_unref (cmds_q);
1802         goto done;
1803       }
1804       case GST_NAVIGATION_MESSAGE_ANGLES_CHANGED:
1805       case GST_NAVIGATION_MESSAGE_INVALID:
1806         goto unhandled;
1807       default:
1808         break;
1809     }
1810   }
1811 
1812 unhandled:
1813   GST_WARNING ("Unhandled element message %s from %s: %" GST_PTR_FORMAT,
1814       GST_STR_NULL (type_name), GST_STR_NULL (src_name), msg);
1815 
1816 done:
1817   g_free (src_name);
1818 }
1819 
1820 /* This is a hack to avoid doing poll_for_state_change() indirectly
1821  * from the bus message callback (via EOS => totem => close => wait for READY)
1822  * and deadlocking there. We need something like a
1823  * gst_bus_set_auto_flushing(bus, FALSE) ... */
1824 static gboolean
bvw_signal_eos_delayed(gpointer user_data)1825 bvw_signal_eos_delayed (gpointer user_data)
1826 {
1827   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (user_data);
1828 
1829   g_signal_emit (bvw, bvw_signals[SIGNAL_EOS], 0, NULL);
1830   bvw->eos_id = 0;
1831   return FALSE;
1832 }
1833 
1834 static void
bvw_reconfigure_tick_timeout(BaconVideoWidget * bvw,guint msecs)1835 bvw_reconfigure_tick_timeout (BaconVideoWidget *bvw, guint msecs)
1836 {
1837   if (bvw->update_id != 0) {
1838     GST_DEBUG ("removing tick timeout");
1839     g_source_remove (bvw->update_id);
1840     bvw->update_id = 0;
1841   }
1842   if (msecs > 0) {
1843     GST_DEBUG ("adding tick timeout (at %ums)", msecs);
1844     bvw->update_id =
1845       g_timeout_add (msecs, (GSourceFunc) bvw_query_timeout, bvw);
1846     g_source_set_name_by_id (bvw->update_id, "[totem] bvw_query_timeout");
1847   }
1848 }
1849 
1850 static void
bvw_reconfigure_fill_timeout(BaconVideoWidget * bvw,guint msecs)1851 bvw_reconfigure_fill_timeout (BaconVideoWidget *bvw, guint msecs)
1852 {
1853   if (bvw->fill_id != 0) {
1854     GST_DEBUG ("removing fill timeout");
1855     g_source_remove (bvw->fill_id);
1856     bvw->fill_id = 0;
1857   }
1858   if (msecs > 0) {
1859     GST_DEBUG ("adding fill timeout (at %ums)", msecs);
1860     bvw->fill_id =
1861       g_timeout_add (msecs, (GSourceFunc) bvw_query_buffering_timeout, bvw);
1862     g_source_set_name_by_id (bvw->fill_id, "[totem] bvw_query_buffering_timeout");
1863   }
1864 }
1865 
1866 /* returns TRUE if the error/signal has been handled and should be ignored */
1867 static gboolean
bvw_emit_missing_plugins_signal(BaconVideoWidget * bvw,gboolean prerolled)1868 bvw_emit_missing_plugins_signal (BaconVideoWidget * bvw, gboolean prerolled)
1869 {
1870   gboolean handled = FALSE;
1871   gchar **descriptions, **details;
1872 
1873   details = bvw_get_missing_plugins_details (bvw->missing_plugins);
1874   descriptions = bvw_get_missing_plugins_descriptions (bvw->missing_plugins);
1875 
1876   GST_LOG ("emitting missing-plugins signal (prerolled=%d)", prerolled);
1877 
1878   g_signal_emit (bvw, bvw_signals[SIGNAL_MISSING_PLUGINS], 0,
1879       details, descriptions, prerolled, &handled);
1880   GST_DEBUG ("missing-plugins signal was %shandled", (handled) ? "" : "not ");
1881 
1882   g_strfreev (descriptions);
1883   g_strfreev (details);
1884 
1885   if (handled) {
1886     bvw->plugin_install_in_progress = TRUE;
1887     bvw_clear_missing_plugins_messages (bvw);
1888   }
1889 
1890   /* if it wasn't handled, we might need the list of missing messages again
1891    * later to create a proper error message with details of what's missing */
1892 
1893   return handled;
1894 }
1895 
1896 static void
bvw_auth_reply_cb(GMountOperation * op,GMountOperationResult result,BaconVideoWidget * bvw)1897 bvw_auth_reply_cb (GMountOperation      *op,
1898 		   GMountOperationResult result,
1899 		   BaconVideoWidget     *bvw)
1900 {
1901   GST_DEBUG ("Got authentication reply %d", result);
1902   bvw->auth_last_result = result;
1903 
1904   if (result == G_MOUNT_OPERATION_HANDLED) {
1905     bvw->user_id = g_strdup (g_mount_operation_get_username (op));
1906     bvw->user_pw = g_strdup (g_mount_operation_get_password (op));
1907   }
1908 
1909   g_clear_object (&bvw->auth_dialog);
1910 
1911   if (bvw->target_state == GST_STATE_PLAYING) {
1912     GST_DEBUG ("Starting deferred playback after authentication");
1913     bacon_video_widget_play (bvw, NULL);
1914   }
1915 }
1916 
1917 static int
bvw_get_http_error_code(GstMessage * err_msg)1918 bvw_get_http_error_code (GstMessage *err_msg)
1919 {
1920   GError *err = NULL;
1921   gchar *dbg = NULL;
1922   int code = -1;
1923 
1924   if (g_strcmp0 ("GstRTSPSrc", G_OBJECT_TYPE_NAME (err_msg->src)) != 0 &&
1925       g_strcmp0 ("GstSoupHTTPSrc", G_OBJECT_TYPE_NAME (err_msg->src)) != 0)
1926     return code;
1927 
1928   gst_message_parse_error (err_msg, &err, &dbg);
1929 
1930   /* Urgh! Check whether this is an auth error */
1931   if (err == NULL || dbg == NULL)
1932     goto done;
1933   if (!is_error (err, RESOURCE, READ) &&
1934       !is_error (err, RESOURCE, OPEN_READ))
1935     goto done;
1936 
1937   /* FIXME: Need to find a better way than parsing the plain text */
1938   /* Keep in sync with bvw_error_from_gst_error() */
1939   if (strstr (dbg, "401") != NULL)
1940     code = 401;
1941   else if (strstr (dbg, "404") != NULL)
1942     code = 404;
1943   else if (strstr (dbg, "403") != NULL)
1944     code = 403;
1945   else if (strstr (dbg, "install glib-networking") != NULL)
1946     code = 495;
1947 
1948 done:
1949   if (err != NULL)
1950     g_error_free (err);
1951   g_free (dbg);
1952   return code;
1953 }
1954 
1955 /* returns TRUE if the error should be ignored */
1956 static gboolean
bvw_check_missing_auth(BaconVideoWidget * bvw,GstMessage * err_msg)1957 bvw_check_missing_auth (BaconVideoWidget * bvw, GstMessage * err_msg)
1958 {
1959   GtkWidget *toplevel;
1960   GMountOperationClass *klass;
1961   int code;
1962 
1963   if (gtk_widget_get_realized (GTK_WIDGET (bvw)) == FALSE)
1964     return FALSE;
1965 
1966   /* The user already tried, and we aborted */
1967   if (bvw->auth_last_result == G_MOUNT_OPERATION_ABORTED) {
1968     GST_DEBUG ("Not authenticating, the user aborted the last auth attempt");
1969     return FALSE;
1970   }
1971   /* There's already an auth on-going, ignore */
1972   if (bvw->auth_dialog != NULL) {
1973     GST_DEBUG ("Ignoring error, we're doing authentication");
1974     return TRUE;
1975   }
1976 
1977   /* RTSP or HTTP source with user-id property ? */
1978   code = bvw_get_http_error_code (err_msg);
1979   if (code != 401)
1980     return FALSE;
1981 
1982   if (g_object_class_find_property (G_OBJECT_GET_CLASS (err_msg->src), "user-id") == NULL) {
1983     GST_DEBUG ("HTTP error is 401, but don't have \"user-id\" property, exiting");
1984     return FALSE;
1985   }
1986 
1987   GST_DEBUG ("Trying to get auth for location '%s'", GST_STR_NULL (bvw->mrl));
1988 
1989   if (bvw->auth_dialog == NULL) {
1990     toplevel = gtk_widget_get_toplevel (GTK_WIDGET (bvw));
1991     bvw->auth_dialog = gtk_mount_operation_new (GTK_WINDOW (toplevel));
1992     g_signal_connect (G_OBJECT (bvw->auth_dialog), "reply",
1993 		      G_CALLBACK (bvw_auth_reply_cb), bvw);
1994   }
1995 
1996   /* And popup the dialogue! */
1997   klass = (GMountOperationClass *) G_OBJECT_GET_CLASS (bvw->auth_dialog);
1998   klass->ask_password (bvw->auth_dialog,
1999 		       _("Password requested for RTSP server"),
2000 		       g_get_user_name (),
2001 		       NULL,
2002 		       G_ASK_PASSWORD_NEED_PASSWORD | G_ASK_PASSWORD_NEED_USERNAME);
2003   return TRUE;
2004 }
2005 
2006 /* returns TRUE if the error has been handled and should be ignored */
2007 static gboolean
bvw_check_missing_plugins_error(BaconVideoWidget * bvw,GstMessage * err_msg)2008 bvw_check_missing_plugins_error (BaconVideoWidget * bvw, GstMessage * err_msg)
2009 {
2010   gboolean error_src_is_playbin;
2011   gboolean ret = FALSE;
2012   GError *err = NULL;
2013 
2014   if (bvw->missing_plugins == NULL) {
2015     GST_DEBUG ("no missing-plugin messages");
2016     return FALSE;
2017   }
2018 
2019   gst_message_parse_error (err_msg, &err, NULL);
2020 
2021   error_src_is_playbin = (err_msg->src == GST_OBJECT_CAST (bvw->play));
2022 
2023   /* If we get a WRONG_TYPE error from playbin itself it's most likely because
2024    * there is a subtitle stream we can decode, but no video stream to overlay
2025    * it on. Since there were missing-plugins messages, we'll assume this is
2026    * because we cannot decode the video stream (this should probably be fixed
2027    * in playbin, but for now we'll work around it here) */
2028   if (is_error (err, CORE, MISSING_PLUGIN) ||
2029       is_error (err, STREAM, CODEC_NOT_FOUND) ||
2030       (is_error (err, STREAM, WRONG_TYPE) && error_src_is_playbin)) {
2031     ret = bvw_emit_missing_plugins_signal (bvw, FALSE);
2032     if (ret) {
2033       /* If it was handled, stop playback to make sure we're not processing any
2034        * other error messages that might also be on the bus */
2035       bacon_video_widget_stop (bvw);
2036     }
2037   } else {
2038     GST_DEBUG ("not an error code we are looking for, doing nothing");
2039   }
2040 
2041   g_error_free (err);
2042   return ret;
2043 }
2044 
2045 static gboolean
bvw_check_mpeg_eos(BaconVideoWidget * bvw,GstMessage * err_msg)2046 bvw_check_mpeg_eos (BaconVideoWidget *bvw, GstMessage *err_msg)
2047 {
2048   gboolean ret = FALSE;
2049   g_autoptr(GError) err = NULL;
2050   g_autofree char *dbg = NULL;
2051 
2052   gst_message_parse_error (err_msg, &err, &dbg);
2053 
2054   /* Error from gst-libs/gst/video/gstvideodecoder.c
2055    * thrown by mpeg2dec */
2056 
2057   if (err != NULL &&
2058       dbg != NULL &&
2059       is_error (err, STREAM, DECODE) &&
2060       strstr (dbg, "no valid frames found")) {
2061     if (bvw->eos_id == 0) {
2062       bvw->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
2063       g_source_set_name_by_id (bvw->eos_id, "[totem] bvw_signal_eos_delayed");
2064       GST_DEBUG ("Throwing EOS instead of an error when seeking to the end of an MPEG file");
2065     } else {
2066       GST_DEBUG ("Not throwing EOS instead of an error when seeking to the end of an MPEG file, EOS already planned");
2067     }
2068     ret = TRUE;
2069   }
2070 
2071   return ret;
2072 }
2073 
2074 /* returns TRUE if the error/signal has been handled and should be ignored */
2075 static gboolean
bvw_check_missing_plugins_on_preroll(BaconVideoWidget * bvw)2076 bvw_check_missing_plugins_on_preroll (BaconVideoWidget * bvw)
2077 {
2078   if (bvw->missing_plugins == NULL) {
2079     GST_DEBUG ("no missing-plugin messages");
2080     return FALSE;
2081   }
2082 
2083   return bvw_emit_missing_plugins_signal (bvw, TRUE);
2084 }
2085 
2086 static void
update_orientation_from_video(BaconVideoWidget * bvw)2087 update_orientation_from_video (BaconVideoWidget *bvw)
2088 {
2089   BvwRotation rotation = BVW_ROTATION_R_ZERO;
2090   char *orientation_str = NULL;
2091   gboolean ret;
2092   gdouble angle;
2093 
2094   /* Don't change the rotation if explicitely set */
2095   if (bvw->rotation != BVW_ROTATION_R_ZERO)
2096     return;
2097 
2098   ret = gst_tag_list_get_string_index (bvw->tagcache,
2099 				       GST_TAG_IMAGE_ORIENTATION, 0, &orientation_str);
2100   if (!ret || !orientation_str || g_str_equal (orientation_str, "rotate-0"))
2101     rotation = BVW_ROTATION_R_ZERO;
2102   else if (g_str_equal (orientation_str, "rotate-90"))
2103     rotation = BVW_ROTATION_R_90R;
2104   else if (g_str_equal (orientation_str, "rotate-180"))
2105     rotation = BVW_ROTATION_R_180;
2106   else if (g_str_equal (orientation_str, "rotate-270"))
2107     rotation = BVW_ROTATION_R_90L;
2108   else
2109     g_warning ("Unhandled orientation value: '%s'", orientation_str);
2110 
2111   g_free (orientation_str);
2112 
2113   angle = rotation * 90.0;
2114   totem_aspect_frame_set_rotation (TOTEM_ASPECT_FRAME (bvw->frame), angle);
2115 }
2116 
2117 static void
bvw_update_tags(BaconVideoWidget * bvw,GstTagList * tag_list,const gchar * type)2118 bvw_update_tags (BaconVideoWidget * bvw, GstTagList *tag_list, const gchar *type)
2119 {
2120   GstTagList **cache = NULL;
2121   GstTagList *result;
2122 
2123   /* all tags (replace previous tags, title/artist/etc. might change
2124    * in the middle of a stream, e.g. with radio streams) */
2125   result = gst_tag_list_merge (bvw->tagcache, tag_list,
2126                                    GST_TAG_MERGE_REPLACE);
2127   if (bvw->tagcache &&
2128       result &&
2129       gst_tag_list_is_equal (result, bvw->tagcache)) {
2130     gst_tag_list_unref (result);
2131     GST_WARNING ("Pipeline sent %s tags update with no changes", type);
2132     return;
2133   }
2134   g_clear_pointer (&bvw->tagcache, gst_tag_list_unref);
2135   bvw->tagcache = result;
2136   GST_DEBUG ("Tags: %" GST_PTR_FORMAT, tag_list);
2137 
2138   /* media-type-specific tags */
2139   if (!strcmp (type, "video")) {
2140     cache = &bvw->videotags;
2141   } else if (!strcmp (type, "audio")) {
2142     cache = &bvw->audiotags;
2143   }
2144 
2145   if (cache) {
2146     result = gst_tag_list_merge (*cache, tag_list, GST_TAG_MERGE_REPLACE);
2147     if (*cache)
2148       gst_tag_list_unref (*cache);
2149     *cache = result;
2150   }
2151 
2152   /* clean up */
2153   if (tag_list)
2154     gst_tag_list_unref (tag_list);
2155 
2156   bvw_check_for_cover_pixbuf (bvw);
2157 
2158   g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0);
2159 
2160   update_orientation_from_video (bvw);
2161 
2162   set_current_actor (bvw);
2163 }
2164 
2165 static void
update_tags_delayed_data_destroy(UpdateTagsDelayedData * data)2166 update_tags_delayed_data_destroy (UpdateTagsDelayedData *data)
2167 {
2168   g_slice_free (UpdateTagsDelayedData, data);
2169 }
2170 
2171 static gboolean
bvw_update_tags_dispatcher(BaconVideoWidget * bvw)2172 bvw_update_tags_dispatcher (BaconVideoWidget *bvw)
2173 {
2174   UpdateTagsDelayedData *data;
2175 
2176   /* If we take the queue's lock for the entire function call, we can use it to protect tag_update_id too */
2177   g_async_queue_lock (bvw->tag_update_queue);
2178 
2179   while ((data = g_async_queue_try_pop_unlocked (bvw->tag_update_queue)) != NULL) {
2180     bvw_update_tags (bvw, data->tags, data->type);
2181     update_tags_delayed_data_destroy (data);
2182   }
2183 
2184   bvw->tag_update_id = 0;
2185   g_async_queue_unlock (bvw->tag_update_queue);
2186 
2187   return FALSE;
2188 }
2189 
2190 /* Marshal the changed tags to the main thread for updating the GUI
2191  * and sending the BVW signals */
2192 static void
bvw_update_tags_delayed(BaconVideoWidget * bvw,GstTagList * tags,const gchar * type)2193 bvw_update_tags_delayed (BaconVideoWidget *bvw, GstTagList *tags, const gchar *type) {
2194   UpdateTagsDelayedData *data = g_slice_new0 (UpdateTagsDelayedData);
2195 
2196   data->tags = tags;
2197   data->type = type;
2198 
2199   g_async_queue_lock (bvw->tag_update_queue);
2200   g_async_queue_push_unlocked (bvw->tag_update_queue, data);
2201 
2202   if (bvw->tag_update_id == 0) {
2203     bvw->tag_update_id = g_idle_add ((GSourceFunc) bvw_update_tags_dispatcher, bvw);
2204     g_source_set_name_by_id (bvw->tag_update_id, "[totem] bvw_update_tags_dispatcher");
2205   }
2206 
2207   g_async_queue_unlock (bvw->tag_update_queue);
2208 }
2209 
2210 static void
video_tags_changed_cb(GstElement * playbin2,gint stream_id,gpointer user_data)2211 video_tags_changed_cb (GstElement *playbin2, gint stream_id, gpointer user_data)
2212 {
2213   BaconVideoWidget *bvw = (BaconVideoWidget *) user_data;
2214   GstTagList *tags = NULL;
2215   gint current_stream_id = 0;
2216 
2217   g_object_get (G_OBJECT (bvw->play), "current-video", &current_stream_id, NULL);
2218 
2219   /* Only get the updated tags if it's for our current stream id */
2220   if (current_stream_id != stream_id)
2221     return;
2222 
2223   g_signal_emit_by_name (G_OBJECT (bvw->play), "get-video-tags", stream_id, &tags);
2224 
2225   if (tags)
2226     bvw_update_tags_delayed (bvw, tags, "video");
2227 }
2228 
2229 static void
audio_tags_changed_cb(GstElement * playbin2,gint stream_id,gpointer user_data)2230 audio_tags_changed_cb (GstElement *playbin2, gint stream_id, gpointer user_data)
2231 {
2232   BaconVideoWidget *bvw = (BaconVideoWidget *) user_data;
2233   GstTagList *tags = NULL;
2234   gint current_stream_id = 0;
2235 
2236   g_object_get (G_OBJECT (bvw->play), "current-audio", &current_stream_id, NULL);
2237 
2238   /* Only get the updated tags if it's for our current stream id */
2239   if (current_stream_id != stream_id)
2240     return;
2241 
2242   g_signal_emit_by_name (G_OBJECT (bvw->play), "get-audio-tags", stream_id, &tags);
2243 
2244   if (tags)
2245     bvw_update_tags_delayed (bvw, tags, "audio");
2246 }
2247 
2248 static void
text_tags_changed_cb(GstElement * playbin2,gint stream_id,gpointer user_data)2249 text_tags_changed_cb (GstElement *playbin2, gint stream_id, gpointer user_data)
2250 {
2251   BaconVideoWidget *bvw = (BaconVideoWidget *) user_data;
2252   GstTagList *tags = NULL;
2253   gint current_stream_id = 0;
2254 
2255   g_object_get (G_OBJECT (bvw->play), "current-text", &current_stream_id, NULL);
2256 
2257   /* Only get the updated tags if it's for our current stream id */
2258   if (current_stream_id != stream_id)
2259     return;
2260 
2261   g_signal_emit_by_name (G_OBJECT (bvw->play), "get-text-tags", stream_id, &tags);
2262 
2263   if (tags)
2264     bvw_update_tags_delayed (bvw, tags, "text");
2265 }
2266 
2267 static gboolean
bvw_download_buffering_done(BaconVideoWidget * bvw)2268 bvw_download_buffering_done (BaconVideoWidget *bvw)
2269 {
2270   /* When we set buffering left to 0, that means it's ready to play */
2271   if (bvw->buffering_left == 0) {
2272     GST_DEBUG ("Buffering left is 0, so buffering done");
2273     return TRUE;
2274   }
2275   if (bvw->stream_length <= 0)
2276     return FALSE;
2277   /* When queue2 doesn't implement buffering-left, always think
2278    * it's ready to go */
2279   if (bvw->buffering_left < 0) {
2280     GST_DEBUG ("Buffering left not implemented, so buffering done");
2281     return TRUE;
2282   }
2283 
2284   if (bvw->buffering_left * BUFFERING_LEFT_RATIO < bvw->stream_length - bvw->current_time) {
2285     GST_DEBUG ("Buffering left: %" G_GINT64_FORMAT " * %f, = %f < %" G_GUINT64_FORMAT,
2286 	       bvw->buffering_left, BUFFERING_LEFT_RATIO,
2287 	       bvw->buffering_left * BUFFERING_LEFT_RATIO,
2288 	       bvw->stream_length - bvw->current_time);
2289     return TRUE;
2290   }
2291   return FALSE;
2292 }
2293 
2294 static void
bvw_handle_buffering_message(GstMessage * message,BaconVideoWidget * bvw)2295 bvw_handle_buffering_message (GstMessage * message, BaconVideoWidget *bvw)
2296 {
2297   GstBufferingMode mode;
2298   gint percent = 0;
2299 
2300    gst_message_parse_buffering_stats (message, &mode, NULL, NULL, NULL);
2301    if (mode == GST_BUFFERING_DOWNLOAD) {
2302      if (bvw->download_buffering == FALSE) {
2303        bvw->download_buffering = TRUE;
2304 
2305        /* We're not ready to play yet, so pause the stream */
2306        GST_DEBUG ("Pausing because we're not ready to play the buffer yet");
2307        gst_element_set_state (GST_ELEMENT (bvw->play), GST_STATE_PAUSED);
2308 
2309        bvw_reconfigure_fill_timeout (bvw, 200);
2310      }
2311 
2312      return;
2313    }
2314 
2315    /* We switched from download mode to normal buffering */
2316    if (bvw->download_buffering != FALSE) {
2317      bvw_reconfigure_fill_timeout (bvw, 0);
2318      bvw->download_buffering = FALSE;
2319      g_clear_pointer (&bvw->download_filename, g_free);
2320    }
2321 
2322    /* Live, timeshift and stream buffering modes */
2323   gst_message_parse_buffering (message, &percent);
2324   g_signal_emit (bvw, bvw_signals[SIGNAL_BUFFERING], 0, (gdouble) percent / 100.0);
2325 
2326   if (percent >= 100) {
2327     clutter_actor_hide (bvw->spinner);
2328     /* Reset */
2329     g_object_set (G_OBJECT (bvw->spinner), "percent", 0.0, NULL);
2330   } else {
2331     clutter_actor_show (bvw->spinner);
2332     g_object_set (G_OBJECT (bvw->spinner), "percent", (float) percent, NULL);
2333   }
2334 
2335   if (percent >= 100) {
2336     /* a 100% message means buffering is done */
2337     bvw->buffering = FALSE;
2338     /* if the desired state is playing, go back */
2339     if (bvw->target_state == GST_STATE_PLAYING) {
2340       GST_DEBUG ("Buffering done, setting pipeline back to PLAYING");
2341       bacon_video_widget_play (bvw, NULL);
2342     } else {
2343       GST_DEBUG ("Buffering done, keeping pipeline PAUSED");
2344     }
2345   } else if (bvw->target_state == GST_STATE_PLAYING) {
2346     GstState cur_state;
2347 
2348     gst_element_get_state (bvw->play, &cur_state, NULL, 0);
2349     if (cur_state != GST_STATE_PAUSED) {
2350       GST_DEBUG ("Buffering ... temporarily pausing playback %d%%", percent);
2351       gst_element_set_state (bvw->play, GST_STATE_PAUSED);
2352     } else {
2353       GST_LOG ("Buffering (already paused) ... %d%%", percent);
2354     }
2355     bvw->buffering = TRUE;
2356   } else {
2357     GST_LOG ("Buffering ... %d", percent);
2358     bvw->buffering = TRUE;
2359   }
2360 }
2361 
2362 static inline void
bvw_get_navigation_if_available(BaconVideoWidget * bvw)2363 bvw_get_navigation_if_available (BaconVideoWidget *bvw)
2364 {
2365   GstElement * nav;
2366   nav = gst_bin_get_by_interface (GST_BIN (bvw->play),
2367                                         GST_TYPE_NAVIGATION);
2368   g_clear_pointer (&bvw->navigation, gst_object_unref);
2369 
2370   if (nav)
2371     bvw->navigation = GST_NAVIGATION (nav);
2372 }
2373 
2374 static void
bvw_handle_toc_message(GstMessage * message,BaconVideoWidget * bvw)2375 bvw_handle_toc_message (GstMessage       *message,
2376 			BaconVideoWidget *bvw)
2377 {
2378   GstToc *toc;
2379   GList *entries, *l;
2380   guint i;
2381 
2382   gst_message_parse_toc (message, &toc, NULL);
2383   if (gst_toc_get_scope (toc) != GST_TOC_SCOPE_GLOBAL)
2384     goto out;
2385 
2386   entries = gst_toc_get_entries (toc);
2387 
2388 parse:
2389   if (entries == NULL)
2390     goto out;
2391   if (gst_toc_entry_get_entry_type (entries->data) != GST_TOC_ENTRY_TYPE_CHAPTER) {
2392     if (g_list_length (entries) == 1) {
2393       entries = gst_toc_entry_get_sub_entries (entries->data);
2394       goto parse;
2395     }
2396     goto out;
2397   }
2398 
2399   GST_DEBUG ("Found %d chapters", g_list_length (entries));
2400 
2401   if (bvw->chapters)
2402     g_list_free_full (bvw->chapters, (GDestroyNotify) gst_mini_object_unref);
2403 
2404   for (l = entries, i = 0; l != NULL; l = l->next, i++) {
2405     GstTocEntry *entry = l->data;
2406     gint64 start, stop;
2407 
2408     if (!gst_toc_entry_get_start_stop_times (entry, &start, &stop)) {
2409       GST_DEBUG ("Chapter #%d (couldn't get times)", i);
2410     } else {
2411       GST_DEBUG ("Chapter #%d (start: %" G_GINT64_FORMAT " stop: %" G_GINT64_FORMAT ")", i, start, stop);
2412     }
2413   }
2414 
2415   bvw->chapters = g_list_copy_deep (entries, (GCopyFunc) gst_mini_object_ref, NULL);
2416 
2417 out:
2418   gst_toc_unref (toc);
2419 }
2420 
2421 static void
bvw_bus_message_cb(GstBus * bus,GstMessage * message,BaconVideoWidget * bvw)2422 bvw_bus_message_cb (GstBus * bus, GstMessage * message, BaconVideoWidget *bvw)
2423 {
2424   GstMessageType msg_type;
2425 
2426   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
2427 
2428   msg_type = GST_MESSAGE_TYPE (message);
2429 
2430   if (msg_type != GST_MESSAGE_STATE_CHANGED) {
2431     gchar *src_name = gst_object_get_name (message->src);
2432     GST_LOG ("Handling %s message from element %s",
2433         gst_message_type_get_name (msg_type), src_name);
2434     g_free (src_name);
2435   }
2436 
2437   switch (msg_type) {
2438     case GST_MESSAGE_ERROR: {
2439       totem_gst_message_print (message, bvw->play, "totem-error");
2440 
2441       if (!bvw_check_missing_plugins_error (bvw, message) &&
2442 	  !bvw_check_missing_auth (bvw, message) &&
2443 	  !bvw_check_mpeg_eos (bvw, message)) {
2444         GError *error;
2445 
2446         error = bvw_error_from_gst_error (bvw, message);
2447 
2448         bvw->target_state = GST_STATE_NULL;
2449         if (bvw->play)
2450           gst_element_set_state (bvw->play, GST_STATE_NULL);
2451 
2452         bvw->buffering = FALSE;
2453 
2454         g_signal_emit (bvw, bvw_signals[SIGNAL_ERROR], 0,
2455                        error->message, TRUE);
2456 
2457         g_error_free (error);
2458       }
2459       break;
2460     }
2461     case GST_MESSAGE_WARNING: {
2462       GST_WARNING ("Warning message: %" GST_PTR_FORMAT, message);
2463       break;
2464     }
2465     case GST_MESSAGE_TAG:
2466       /* Ignore TAG messages, we get updated tags from the
2467        * {audio,video,text}-tags-changed signals of playbin2
2468        */
2469       break;
2470     case GST_MESSAGE_EOS:
2471       GST_DEBUG ("EOS message");
2472       /* update slider one last time */
2473       bvw_query_timeout (bvw);
2474       if (bvw->eos_id == 0) {
2475         bvw->eos_id = g_idle_add (bvw_signal_eos_delayed, bvw);
2476         g_source_set_name_by_id (bvw->eos_id, "[totem] bvw_signal_eos_delayed");
2477       }
2478       break;
2479     case GST_MESSAGE_BUFFERING:
2480       bvw_handle_buffering_message (message, bvw);
2481       break;
2482     case GST_MESSAGE_APPLICATION: {
2483       bvw_handle_application_message (bvw, message);
2484       break;
2485     }
2486     case GST_MESSAGE_STATE_CHANGED: {
2487       GstState old_state, new_state;
2488       gchar *src_name;
2489 
2490       gst_message_parse_state_changed (message, &old_state, &new_state, NULL);
2491 
2492       if (old_state == new_state)
2493         break;
2494 
2495       /* we only care about playbin (pipeline) state changes */
2496       if (GST_MESSAGE_SRC (message) != GST_OBJECT (bvw->play))
2497         break;
2498 
2499       src_name = gst_object_get_name (message->src);
2500       GST_DEBUG ("%s changed state from %s to %s", src_name,
2501           gst_element_state_get_name (old_state),
2502           gst_element_state_get_name (new_state));
2503       g_free (src_name);
2504 
2505       if (new_state <= GST_STATE_READY) {
2506         if (bvw->navigation)
2507           g_clear_object (&bvw->navigation);
2508       }
2509 
2510       /* now do stuff */
2511       if (new_state <= GST_STATE_PAUSED) {
2512         bvw_query_timeout (bvw);
2513         bvw_reconfigure_tick_timeout (bvw, 0);
2514       } else if (new_state > GST_STATE_PAUSED) {
2515         bvw_reconfigure_tick_timeout (bvw, 200);
2516       }
2517 
2518       if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
2519         GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN_CAST (bvw->play),
2520             GST_DEBUG_GRAPH_SHOW_ALL ^ GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS,
2521             "totem-prerolled");
2522 	bacon_video_widget_get_stream_length (bvw);
2523         bvw_update_stream_info (bvw);
2524         if (!bvw_check_missing_plugins_on_preroll (bvw)) {
2525           /* show a non-fatal warning message if we can't decode the video */
2526           bvw_check_if_video_decoder_is_missing (bvw);
2527         }
2528 	/* Now that we have the length, check whether we wanted
2529 	 * to pause or to stop the pipeline */
2530         if (bvw->target_state == GST_STATE_PAUSED)
2531 	  bacon_video_widget_pause (bvw);
2532       } else if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY) {
2533         bvw->media_has_video = FALSE;
2534         bvw->media_has_audio = FALSE;
2535 
2536         /* clean metadata cache */
2537 	g_clear_pointer (&bvw->tagcache, gst_tag_list_unref);
2538 	g_clear_pointer (&bvw->audiotags, gst_tag_list_unref);
2539 	g_clear_pointer (&bvw->videotags, gst_tag_list_unref);
2540 
2541         bvw->video_width = 0;
2542         bvw->video_height = 0;
2543       }
2544       break;
2545     }
2546     case GST_MESSAGE_ELEMENT: {
2547       bvw_handle_element_message (bvw, message);
2548       break;
2549     }
2550 
2551     case GST_MESSAGE_DURATION_CHANGED: {
2552       gint64 len = -1;
2553       if (gst_element_query_duration (bvw->play, GST_FORMAT_TIME, &len) && len != -1) {
2554         bvw->stream_length = len / GST_MSECOND;
2555 	GST_DEBUG ("got new stream length (through duration message) %" G_GINT64_FORMAT, bvw->stream_length);
2556       }
2557       break;
2558     }
2559 
2560     case GST_MESSAGE_ASYNC_DONE: {
2561 	gint64 _time;
2562 	/* When a seek has finished, set the playing state again */
2563 	g_mutex_lock (&bvw->seek_mutex);
2564 
2565 	bvw->seek_req_time = gst_clock_get_internal_time (bvw->clock);
2566 	_time = bvw->seek_time;
2567 	bvw->seek_time = -1;
2568 
2569 	g_mutex_unlock (&bvw->seek_mutex);
2570 
2571 	if (_time >= 0) {
2572 	  GST_DEBUG ("Have an old seek to schedule, doing it now");
2573 	  bacon_video_widget_seek_time_no_lock (bvw, _time, 0, NULL);
2574 	} else if (bvw->target_state == GST_STATE_PLAYING) {
2575 	  GST_DEBUG ("Maybe starting deferred playback after seek");
2576 	  bacon_video_widget_play (bvw, NULL);
2577 	}
2578 	bvw_get_navigation_if_available (bvw);
2579 	bacon_video_widget_get_stream_length (bvw);
2580 	bacon_video_widget_is_seekable (bvw);
2581       break;
2582     }
2583 
2584     case GST_MESSAGE_TOC: {
2585 	bvw_handle_toc_message (message, bvw);
2586 	break;
2587     }
2588 
2589     /* FIXME: at some point we might want to handle CLOCK_LOST and set the
2590      * pipeline back to PAUSED and then PLAYING again to select a different
2591      * clock (this seems to trip up rtspsrc though so has to wait until
2592      * rtspsrc gets fixed) */
2593     case GST_MESSAGE_CLOCK_PROVIDE:
2594     case GST_MESSAGE_CLOCK_LOST:
2595     case GST_MESSAGE_NEW_CLOCK:
2596     case GST_MESSAGE_STATE_DIRTY:
2597     case GST_MESSAGE_STREAM_STATUS:
2598       break;
2599 
2600     case GST_MESSAGE_UNKNOWN:
2601     case GST_MESSAGE_INFO:
2602     case GST_MESSAGE_STEP_DONE:
2603     case GST_MESSAGE_STRUCTURE_CHANGE:
2604     case GST_MESSAGE_SEGMENT_START:
2605     case GST_MESSAGE_SEGMENT_DONE:
2606     case GST_MESSAGE_LATENCY:
2607     case GST_MESSAGE_ASYNC_START:
2608     case GST_MESSAGE_REQUEST_STATE:
2609     case GST_MESSAGE_STEP_START:
2610     case GST_MESSAGE_QOS:
2611     case GST_MESSAGE_PROGRESS:
2612     case GST_MESSAGE_ANY:
2613     case GST_MESSAGE_RESET_TIME:
2614     case GST_MESSAGE_STREAM_START:
2615     case GST_MESSAGE_NEED_CONTEXT:
2616     case GST_MESSAGE_HAVE_CONTEXT:
2617     default:
2618       GST_LOG ("Unhandled message: %" GST_PTR_FORMAT, message);
2619       break;
2620   }
2621 }
2622 
2623 static void
got_time_tick(GstElement * play,gint64 time_nanos,BaconVideoWidget * bvw)2624 got_time_tick (GstElement * play, gint64 time_nanos, BaconVideoWidget * bvw)
2625 {
2626   gboolean seekable;
2627 
2628   bvw->current_time = (gint64) time_nanos / GST_MSECOND;
2629 
2630   if (bvw->stream_length == 0) {
2631     bvw->current_position = 0;
2632   } else {
2633     bvw->current_position =
2634       (gdouble) bvw->current_time / bvw->stream_length;
2635   }
2636 
2637   if (bvw->stream_length == 0) {
2638     seekable = bacon_video_widget_is_seekable (bvw);
2639   } else {
2640     if (bvw->seekable == -1)
2641       g_object_notify (G_OBJECT (bvw), "seekable");
2642     seekable = TRUE;
2643   }
2644 
2645   bvw->is_live = (bvw->stream_length == 0);
2646 
2647 /*
2648   GST_DEBUG ("current time: %" GST_TIME_FORMAT ", stream length: %" GST_TIME_FORMAT ", seekable: %s",
2649       GST_TIME_ARGS (bvw->current_time * GST_MSECOND),
2650       GST_TIME_ARGS (bvw->stream_length * GST_MSECOND),
2651       (seekable) ? "TRUE" : "FALSE");
2652 */
2653 
2654   g_signal_emit (bvw, bvw_signals[SIGNAL_TICK], 0,
2655                  bvw->current_time, bvw->stream_length,
2656                  bvw->current_position,
2657                  seekable);
2658 }
2659 
2660 static void
bvw_set_user_agent_on_element(BaconVideoWidget * bvw,GstElement * element)2661 bvw_set_user_agent_on_element (BaconVideoWidget * bvw, GstElement * element)
2662 {
2663   const char *user_agent;
2664 
2665   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "user-agent") == NULL)
2666     return;
2667 
2668   user_agent = bvw->user_agent ? bvw->user_agent : DEFAULT_USER_AGENT;
2669   GST_DEBUG ("Setting HTTP user-agent to '%s'", user_agent);
2670   g_object_set (element, "user-agent", user_agent, NULL);
2671 }
2672 
2673 static void
bvw_set_auth_on_element(BaconVideoWidget * bvw,GstElement * element)2674 bvw_set_auth_on_element (BaconVideoWidget * bvw, GstElement * element)
2675 {
2676   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "user-id") == NULL)
2677     return;
2678   if (bvw->auth_last_result != G_MOUNT_OPERATION_HANDLED)
2679     return;
2680   if (bvw->user_id == NULL || bvw->user_pw == NULL)
2681     return;
2682 
2683   GST_DEBUG ("Setting username and password");
2684   g_object_set (element,
2685 		"user-id", bvw->user_id,
2686 		"user-pw", bvw->user_pw,
2687 		NULL);
2688 
2689   g_clear_pointer (&bvw->user_id, g_free);
2690   g_clear_pointer (&bvw->user_pw, g_free);
2691 }
2692 
2693 static void
bvw_set_http_proxy_on_element(BaconVideoWidget * bvw,GstElement * element,const char * uri_str)2694 bvw_set_http_proxy_on_element (BaconVideoWidget *bvw,
2695 			       GstElement       *element,
2696 			       const char       *uri_str)
2697 {
2698   GstUri *uri;
2699   char *protocol, *proxy_url;
2700   const char *host, *userinfo;
2701   guint port;
2702   char **user_strv;
2703   g_autofree char *user = NULL;
2704   g_autofree char *password = NULL;
2705 
2706   uri = gst_uri_from_string (uri_str);
2707   if (!uri) {
2708     GST_DEBUG ("Failed to parse URI '%s'", uri_str);
2709     return;
2710   }
2711 
2712   protocol = gst_uri_get_protocol (uri_str);
2713   host = gst_uri_get_host (uri);
2714   port = gst_uri_get_port (uri);
2715 
2716   proxy_url = g_strdup_printf ("%s://%s:%d", protocol, host, port);
2717   g_object_set (element, "proxy", proxy_url, NULL);
2718   g_free (proxy_url);
2719 
2720   /* https doesn't handle authentication yet */
2721   if (gst_uri_has_protocol (uri_str, "https"))
2722     goto finish;
2723 
2724   userinfo = gst_uri_get_userinfo (uri);
2725   if (userinfo == NULL)
2726     goto finish;
2727 
2728   user_strv = g_strsplit (userinfo, ":", 2);
2729   user = g_uri_unescape_string (user_strv[0], NULL);
2730   password = g_uri_unescape_string (user_strv[1], NULL);
2731 
2732   g_object_set (element,
2733 		"proxy-id", user,
2734 		"proxy-pw", password,
2735 		NULL);
2736   g_strfreev (user_strv);
2737 
2738 finish:
2739   gst_uri_unref (uri);
2740 }
2741 
2742 static void
bvw_set_proxy_on_element(BaconVideoWidget * bvw,GstElement * element)2743 bvw_set_proxy_on_element (BaconVideoWidget * bvw, GstElement * element)
2744 {
2745   GError *error = NULL;
2746   char **uris;
2747 
2748   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "proxy") == NULL)
2749     return;
2750 
2751   uris = g_proxy_resolver_lookup (g_proxy_resolver_get_default (),
2752 				  bvw->mrl,
2753 				  NULL,
2754 				  &error);
2755   if (!uris) {
2756     if (error != NULL) {
2757       GST_DEBUG ("Failed to look up proxy for MRL '%s': %s",
2758                  bvw->mrl,
2759                  error->message);
2760       g_clear_error (&error);
2761     }
2762     return;
2763   }
2764 
2765   if (!g_str_equal (uris[0], "direct://"))
2766     bvw_set_http_proxy_on_element (bvw, element, uris[0]);
2767   g_strfreev (uris);
2768 }
2769 
2770 static void
bvw_set_referrer_on_element(BaconVideoWidget * bvw,GstElement * element)2771 bvw_set_referrer_on_element (BaconVideoWidget * bvw, GstElement * element)
2772 {
2773   GstStructure *extra_headers = NULL;
2774 
2775   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "extra-headers") == NULL)
2776     return;
2777 
2778   GST_DEBUG ("Setting HTTP referrer to '%s'", bvw->referrer ? bvw->referrer : "none");
2779 
2780   g_object_get (element, "extra-headers", &extra_headers, NULL);
2781   if (extra_headers == NULL) {
2782     extra_headers = gst_structure_new_empty ("extra-headers");
2783   }
2784   g_assert (GST_IS_STRUCTURE (extra_headers));
2785 
2786   if (bvw->referrer != NULL) {
2787     gst_structure_set (extra_headers,
2788                        "Referer" /* not a typo! */,
2789                        G_TYPE_STRING,
2790                        bvw->referrer,
2791                        NULL);
2792   } else {
2793     gst_structure_remove_field (extra_headers,
2794                                 "Referer" /* not a typo! */);
2795   }
2796 
2797   g_object_set (element, "extra-headers", extra_headers, NULL);
2798   gst_structure_free (extra_headers);
2799 }
2800 
2801 static void
playbin_source_setup_cb(GstElement * playbin,GstElement * source,BaconVideoWidget * bvw)2802 playbin_source_setup_cb (GstElement       *playbin,
2803 			 GstElement       *source,
2804 			 BaconVideoWidget *bvw)
2805 {
2806   GST_DEBUG ("Got source of type '%s'", G_OBJECT_TYPE_NAME (source));
2807   if (g_strcmp0 (G_OBJECT_TYPE_NAME (source), "GstCurlHttpSrc") == 0)
2808     g_warning ("Download buffering not supported with GstCurlHttpSrc, see https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/issues/551");
2809   bvw_set_user_agent_on_element (bvw, source);
2810   bvw_set_referrer_on_element (bvw, source);
2811   bvw_set_auth_on_element (bvw, source);
2812   bvw_set_proxy_on_element (bvw, source);
2813 }
2814 
2815 static void
playbin_element_setup_cb(GstElement * playbin,GstElement * element,BaconVideoWidget * bvw)2816 playbin_element_setup_cb (GstElement *playbin,
2817 			  GstElement *element,
2818 			  BaconVideoWidget *bvw)
2819 {
2820   char *template;
2821 
2822   if (g_strcmp0 (G_OBJECT_TYPE_NAME (element), "GstDownloadBuffer") != 0)
2823     return;
2824 
2825   /* See also bacon_video_widget_initable_init() */
2826   template = g_build_filename (g_get_user_cache_dir (), "totem", "stream-buffer", "XXXXXX", NULL);
2827   g_object_set (element, "temp-template", template, NULL);
2828   GST_DEBUG ("Reconfigured file download template to '%s'", template);
2829   g_free (template);
2830 }
2831 
2832 static void
playbin_deep_notify_cb(GstObject * gstobject,GstObject * prop_object,GParamSpec * prop,BaconVideoWidget * bvw)2833 playbin_deep_notify_cb (GstObject  *gstobject,
2834 			GstObject  *prop_object,
2835 			GParamSpec *prop,
2836 			BaconVideoWidget *bvw)
2837 {
2838   if (g_str_equal (prop->name, "temp-location") == FALSE)
2839     return;
2840 
2841   g_clear_pointer (&bvw->download_filename, g_free);
2842   g_object_get (G_OBJECT (prop_object),
2843 		"temp-location", &bvw->download_filename,
2844 		NULL);
2845 }
2846 
2847 static gboolean
bvw_query_timeout(BaconVideoWidget * bvw)2848 bvw_query_timeout (BaconVideoWidget *bvw)
2849 {
2850   gint64 pos = -1;
2851 
2852   /* check pos of stream */
2853   if (gst_element_query_position (bvw->play, GST_FORMAT_TIME, &pos)) {
2854     if (pos != -1) {
2855       got_time_tick (GST_ELEMENT (bvw->play), pos, bvw);
2856     }
2857   } else {
2858     GST_DEBUG ("could not get position");
2859   }
2860 
2861   return TRUE;
2862 }
2863 
2864 static gboolean
bvw_query_buffering_timeout(BaconVideoWidget * bvw)2865 bvw_query_buffering_timeout (BaconVideoWidget *bvw)
2866 {
2867   GstQuery *query;
2868   GstElement *element;
2869 
2870   element = bvw->play;
2871 
2872   query = gst_query_new_buffering (GST_FORMAT_PERCENT);
2873   if (gst_element_query (element, query)) {
2874     gint64 stop, estimated_total;
2875     gdouble fill;
2876     guint n_ranges, i, pos;
2877 
2878     gst_query_parse_buffering_range (query, NULL, NULL, &stop, &estimated_total);
2879 
2880     /* stop expresses the last bit of data that we have from the currently downloading
2881      * region and is a good value to use for the fill level if it is after our
2882      * current position. */
2883     pos = bvw->current_position * GST_FORMAT_PERCENT_MAX;
2884     if (stop < pos)
2885       stop = -1;
2886 
2887     n_ranges = gst_query_get_n_buffering_ranges (query);
2888 
2889     for (i = 0; i < n_ranges; i++) {
2890       gint64 n_start, n_stop;
2891       gst_query_parse_nth_buffering_range (query, i, &n_start, &n_stop);
2892 
2893       /* take first stop after current offset if not known */
2894       if (stop == -1 && n_stop > pos)
2895         stop = n_stop;
2896 
2897       GST_DEBUG ("%s range %d: start %" G_GINT64_FORMAT " stop %" G_GINT64_FORMAT,
2898 		 n_stop == stop ? "*" : " ",
2899 		 i, n_start, n_stop);
2900     }
2901     /* if no fill level, just take the current position */
2902     if (stop == -1)
2903       stop = pos;
2904 
2905     /* estimated_total is the amount of time it will take to download the
2906      * remaining part of the file, from the current position to the end. */
2907     bvw->buffering_left = estimated_total;
2908     GST_DEBUG ("stop %" G_GINT64_FORMAT ", buffering left %" G_GINT64_FORMAT,
2909                stop, bvw->buffering_left);
2910 
2911     fill = (gdouble) stop / GST_FORMAT_PERCENT_MAX;
2912     GST_DEBUG ("download buffer filled up to %f%% (element: %s)", fill * 100.0,
2913 	       G_OBJECT_TYPE_NAME (element));
2914 
2915     g_signal_emit (bvw, bvw_signals[SIGNAL_DOWNLOAD_BUFFERING], 0, fill);
2916 
2917     /* Start playing when we've downloaded enough */
2918     if (bvw_download_buffering_done (bvw) != FALSE &&
2919 	bvw->target_state == GST_STATE_PLAYING) {
2920       GST_DEBUG ("Starting playback because the download buffer is filled enough");
2921       bacon_video_widget_play (bvw, NULL);
2922     }
2923   } else {
2924     g_debug ("Failed to query the source element for buffering info in percent");
2925   }
2926   gst_query_unref (query);
2927 
2928   return TRUE;
2929 }
2930 
2931 static void
caps_set(GObject * obj,GParamSpec * pspec,BaconVideoWidget * bvw)2932 caps_set (GObject * obj,
2933     GParamSpec * pspec, BaconVideoWidget * bvw)
2934 {
2935   GstPad *pad = GST_PAD (obj);
2936   GstStructure *s;
2937   GstCaps *caps;
2938 
2939   if (!(caps = gst_pad_get_current_caps (pad)))
2940     return;
2941 
2942   /* Get video decoder caps */
2943   s = gst_caps_get_structure (caps, 0);
2944   if (s) {
2945     const GValue *movie_par;
2946 
2947     /* We need at least width/height and framerate */
2948     if (!(gst_structure_get_fraction (s, "framerate", &bvw->video_fps_n,
2949           &bvw->video_fps_d) &&
2950           gst_structure_get_int (s, "width", &bvw->video_width) &&
2951           gst_structure_get_int (s, "height", &bvw->video_height)))
2952       return;
2953 
2954     /* Get the movie PAR if available */
2955     movie_par = gst_structure_get_value (s, "pixel-aspect-ratio");
2956     if (movie_par) {
2957       bvw->movie_par_n = gst_value_get_fraction_numerator (movie_par);
2958       bvw->movie_par_d = gst_value_get_fraction_denominator (movie_par);
2959     }
2960     else {
2961       /* Square pixels */
2962       bvw->movie_par_n = 1;
2963       bvw->movie_par_d = 1;
2964     }
2965 
2966     /* Now set for real */
2967     bacon_video_widget_set_aspect_ratio (bvw, bvw->ratio_type);
2968   }
2969 
2970   gst_caps_unref (caps);
2971 }
2972 
2973 static void
parse_stream_info(BaconVideoWidget * bvw)2974 parse_stream_info (BaconVideoWidget *bvw)
2975 {
2976   GstPad *videopad = NULL;
2977   gint n_audio, n_video;
2978 
2979   g_object_get (G_OBJECT (bvw->play), "n-audio", &n_audio,
2980       "n-video", &n_video, NULL);
2981 
2982   bvw_check_for_cover_pixbuf (bvw);
2983 
2984   bvw->media_has_video = FALSE;
2985   if (n_video > 0) {
2986     gint i;
2987 
2988     bvw->media_has_video = TRUE;
2989 
2990     for (i = 0; i < n_video && videopad == NULL; i++)
2991       g_signal_emit_by_name (bvw->play, "get-video-pad", i, &videopad);
2992   }
2993 
2994   bvw->media_has_audio = (n_audio > 0);
2995 
2996   if (videopad) {
2997     GstCaps *caps;
2998 
2999     if ((caps = gst_pad_get_current_caps (videopad))) {
3000       caps_set (G_OBJECT (videopad), NULL, bvw);
3001       gst_caps_unref (caps);
3002     }
3003     g_signal_connect (videopad, "notify::caps",
3004         G_CALLBACK (caps_set), bvw);
3005     gst_object_unref (videopad);
3006   }
3007 
3008   set_current_actor (bvw);
3009 }
3010 
3011 static void
playbin_stream_changed_cb(GstElement * obj,gpointer data)3012 playbin_stream_changed_cb (GstElement * obj, gpointer data)
3013 {
3014   BaconVideoWidget *bvw = BACON_VIDEO_WIDGET (data);
3015   GstMessage *msg;
3016 
3017   /* we're being called from the streaming thread, so don't do anything here */
3018   GST_LOG ("streams have changed");
3019   msg = gst_message_new_application (GST_OBJECT (bvw->play),
3020 				     gst_structure_new_empty ("stream-changed"));
3021   gst_element_post_message (bvw->play, msg);
3022 }
3023 
3024 static void
bacon_video_widget_finalize(GObject * object)3025 bacon_video_widget_finalize (GObject * object)
3026 {
3027   BaconVideoWidget *bvw = (BaconVideoWidget *) object;
3028 
3029   GST_DEBUG ("finalizing");
3030 
3031   g_type_class_unref (g_type_class_peek (BVW_TYPE_METADATA_TYPE));
3032   g_type_class_unref (g_type_class_peek (BVW_TYPE_DVD_EVENT));
3033   g_type_class_unref (g_type_class_peek (BVW_TYPE_ROTATION));
3034 
3035   unschedule_hiding_popup (bvw);
3036 
3037   if (bvw->bus) {
3038     /* make bus drop all messages to make sure none of our callbacks is ever
3039      * called again (main loop might be run again to display error dialog) */
3040     gst_bus_set_flushing (bvw->bus, TRUE);
3041 
3042     if (bvw->sig_bus_async)
3043       g_signal_handler_disconnect (bvw->bus, bvw->sig_bus_async);
3044 
3045     g_clear_pointer (&bvw->bus, gst_object_unref);
3046   }
3047 
3048   g_clear_pointer (&bvw->user_agent, g_free);
3049   g_clear_pointer (&bvw->referrer, g_free);
3050   g_clear_pointer (&bvw->mrl, g_free);
3051   g_clear_pointer (&bvw->subtitle_uri, g_free);
3052   g_clear_pointer (&bvw->busy_popup_ht, g_hash_table_destroy);
3053 
3054   g_clear_object (&bvw->clock);
3055 
3056   if (bvw->play != NULL)
3057     gst_element_set_state (bvw->play, GST_STATE_NULL);
3058 
3059   g_clear_object (&bvw->play);
3060 
3061   if (bvw->update_id) {
3062     g_source_remove (bvw->update_id);
3063     bvw->update_id = 0;
3064   }
3065 
3066   if (bvw->chapters) {
3067     g_list_free_full (bvw->chapters, (GDestroyNotify) gst_mini_object_unref);
3068     bvw->chapters = NULL;
3069   }
3070 
3071   g_clear_pointer (&bvw->tagcache, gst_tag_list_unref);
3072   g_clear_pointer (&bvw->audiotags, gst_tag_list_unref);
3073   g_clear_pointer (&bvw->videotags, gst_tag_list_unref);
3074 
3075   if (bvw->tag_update_id != 0)
3076     g_source_remove (bvw->tag_update_id);
3077   g_async_queue_unref (bvw->tag_update_queue);
3078 
3079   if (bvw->eos_id != 0) {
3080     g_source_remove (bvw->eos_id);
3081     bvw->eos_id = 0;
3082   }
3083 
3084   g_clear_object (&bvw->cursor);
3085 
3086   if (bvw->mount_cancellable)
3087     g_cancellable_cancel (bvw->mount_cancellable);
3088   g_clear_object (&bvw->mount_cancellable);
3089 
3090   g_mutex_clear (&bvw->seek_mutex);
3091 
3092   G_OBJECT_CLASS (parent_class)->finalize (object);
3093 }
3094 
3095 static void
bacon_video_widget_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)3096 bacon_video_widget_set_property (GObject * object, guint property_id,
3097                                  const GValue * value, GParamSpec * pspec)
3098 {
3099   BaconVideoWidget *bvw;
3100 
3101   bvw = BACON_VIDEO_WIDGET (object);
3102 
3103   switch (property_id) {
3104     case PROP_LOGO_MODE:
3105       bacon_video_widget_set_logo_mode (bvw, g_value_get_boolean (value));
3106       break;
3107     case PROP_REFERRER:
3108       bacon_video_widget_set_referrer (bvw, g_value_get_string (value));
3109       break;
3110     case PROP_USER_AGENT:
3111       bacon_video_widget_set_user_agent (bvw, g_value_get_string (value));
3112       break;
3113     case PROP_VOLUME:
3114       bacon_video_widget_set_volume (bvw, g_value_get_double (value));
3115       break;
3116     case PROP_DEINTERLACING:
3117       bacon_video_widget_set_deinterlacing (bvw, g_value_get_boolean (value));
3118       break;
3119     case PROP_BRIGHTNESS:
3120       bacon_video_widget_set_video_property (bvw, BVW_VIDEO_BRIGHTNESS, g_value_get_int (value));
3121       break;
3122     case PROP_CONTRAST:
3123       bacon_video_widget_set_video_property (bvw, BVW_VIDEO_CONTRAST, g_value_get_int (value));
3124       break;
3125     case PROP_SATURATION:
3126       bacon_video_widget_set_video_property (bvw, BVW_VIDEO_SATURATION, g_value_get_int (value));
3127       break;
3128     case PROP_HUE:
3129       bacon_video_widget_set_video_property (bvw, BVW_VIDEO_HUE, g_value_get_int (value));
3130       break;
3131     case PROP_AUDIO_OUTPUT_TYPE:
3132       bacon_video_widget_set_audio_output_type (bvw, g_value_get_enum (value));
3133       break;
3134     case PROP_AV_OFFSET:
3135       g_object_set_property (G_OBJECT (bvw->play), "av-offset", value);
3136       break;
3137     default:
3138       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3139       break;
3140   }
3141 }
3142 
3143 static void
bacon_video_widget_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)3144 bacon_video_widget_get_property (GObject * object, guint property_id,
3145                                  GValue * value, GParamSpec * pspec)
3146 {
3147   BaconVideoWidget *bvw;
3148 
3149   bvw = BACON_VIDEO_WIDGET (object);
3150 
3151   switch (property_id) {
3152     case PROP_LOGO_MODE:
3153       g_value_set_boolean (value, bacon_video_widget_get_logo_mode (bvw));
3154       break;
3155     case PROP_POSITION:
3156       g_value_set_double (value, bacon_video_widget_get_position (bvw));
3157       break;
3158     case PROP_STREAM_LENGTH:
3159       g_value_set_int64 (value, bacon_video_widget_get_stream_length (bvw));
3160       break;
3161     case PROP_PLAYING:
3162       g_value_set_boolean (value, bacon_video_widget_is_playing (bvw));
3163       break;
3164     case PROP_REFERRER:
3165       g_value_set_string (value, bvw->referrer);
3166       break;
3167     case PROP_SEEKABLE:
3168       g_value_set_boolean (value, bacon_video_widget_is_seekable (bvw));
3169       break;
3170     case PROP_USER_AGENT:
3171       g_value_set_string (value, bvw->user_agent);
3172       break;
3173     case PROP_VOLUME:
3174       g_value_set_double (value, bvw->volume);
3175       break;
3176     case PROP_DOWNLOAD_FILENAME:
3177       g_value_set_string (value, bvw->download_filename);
3178       break;
3179     case PROP_DEINTERLACING:
3180       g_value_set_boolean (value, bacon_video_widget_get_deinterlacing (bvw));
3181       break;
3182     case PROP_BRIGHTNESS:
3183       g_value_set_int (value, bacon_video_widget_get_video_property (bvw, BVW_VIDEO_BRIGHTNESS));
3184       break;
3185     case PROP_CONTRAST:
3186       g_value_set_int (value, bacon_video_widget_get_video_property (bvw, BVW_VIDEO_CONTRAST));
3187       break;
3188     case PROP_SATURATION:
3189       g_value_set_int (value, bacon_video_widget_get_video_property (bvw, BVW_VIDEO_SATURATION));
3190       break;
3191     case PROP_HUE:
3192       g_value_set_int (value, bacon_video_widget_get_video_property (bvw, BVW_VIDEO_HUE));
3193       break;
3194     case PROP_AUDIO_OUTPUT_TYPE:
3195       g_value_set_enum (value, bacon_video_widget_get_audio_output_type (bvw));
3196       break;
3197     case PROP_AV_OFFSET:
3198       g_object_get_property (G_OBJECT (bvw->play), "av-offset", value);
3199       break;
3200     case PROP_REVEAL_CONTROLS:
3201       g_value_set_boolean (value, bvw->reveal_controls);
3202       break;
3203     default:
3204       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
3205       break;
3206   }
3207 }
3208 
3209 /* ============================================================= */
3210 /*                                                               */
3211 /*                       Public Methods                          */
3212 /*                                                               */
3213 /* ============================================================= */
3214 
3215 /**
3216  * bacon_video_widget_get_subtitle:
3217  * @bvw: a #BaconVideoWidget
3218  *
3219  * Returns the index of the current subtitles.
3220  *
3221  * If the widget is not playing, <code class="literal">-2</code> will be returned. If no subtitles are
3222  * being used, <code class="literal">-1</code> is returned.
3223  *
3224  * Return value: the subtitle index
3225  **/
3226 int
bacon_video_widget_get_subtitle(BaconVideoWidget * bvw)3227 bacon_video_widget_get_subtitle (BaconVideoWidget * bvw)
3228 {
3229   int subtitle = -1;
3230   gint flags;
3231 
3232   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -2);
3233   g_return_val_if_fail (bvw->play != NULL, -2);
3234 
3235   g_object_get (bvw->play, "flags", &flags, NULL);
3236 
3237   if ((flags & GST_PLAY_FLAG_TEXT) == 0)
3238     return -2;
3239 
3240   g_object_get (G_OBJECT (bvw->play), "current-text", &subtitle, NULL);
3241 
3242   return subtitle;
3243 }
3244 
3245 static gboolean
sublang_is_valid(int sublang,int n_sublang)3246 sublang_is_valid (int sublang,
3247 		  int n_sublang)
3248 {
3249   if (sublang == -1 ||
3250       sublang == -2)
3251     return TRUE;
3252   if (sublang < 0)
3253     return FALSE;
3254   if (sublang >= n_sublang)
3255     return FALSE;
3256   return TRUE;
3257 }
3258 
3259 /**
3260  * bacon_video_widget_set_subtitle:
3261  * @bvw: a #BaconVideoWidget
3262  * @subtitle: a subtitle index
3263  *
3264  * Sets the subtitle index for @bvw. If @subtitle is <code class="literal">-1</code>, no subtitles will
3265  * be used.
3266  **/
3267 void
bacon_video_widget_set_subtitle(BaconVideoWidget * bvw,int subtitle)3268 bacon_video_widget_set_subtitle (BaconVideoWidget * bvw, int subtitle)
3269 {
3270   GstTagList *tags;
3271   gint flags;
3272   gint n_text;
3273 
3274   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3275   g_return_if_fail (bvw->play != NULL);
3276 
3277   g_object_get (bvw->play, "flags", &flags, "n-text", &n_text, NULL);
3278 
3279   g_return_if_fail (sublang_is_valid (subtitle, n_text));
3280 
3281   if (subtitle == -2) {
3282     flags &= ~GST_PLAY_FLAG_TEXT;
3283     subtitle = -1;
3284   } else {
3285     flags |= GST_PLAY_FLAG_TEXT;
3286   }
3287 
3288   g_object_set (bvw->play, "flags", flags, "current-text", subtitle, NULL);
3289 
3290   if (flags & GST_PLAY_FLAG_TEXT) {
3291     g_object_get (bvw->play, "current-text", &subtitle, NULL);
3292 
3293     g_signal_emit_by_name (G_OBJECT (bvw->play), "get-text-tags", subtitle, &tags);
3294     bvw_update_tags (bvw, tags, "text");
3295   }
3296 }
3297 
3298 /**
3299  * bacon_video_widget_set_next_subtitle:
3300  * @bvw: a #BaconVideoWidget
3301  *
3302  * Switch to the next text subtitle index for the current video. See
3303  * bacon_video_widget_set_subtitle().
3304  *
3305  * Since: 3.12
3306  */
3307 void
bacon_video_widget_set_next_subtitle(BaconVideoWidget * bvw)3308 bacon_video_widget_set_next_subtitle (BaconVideoWidget *bvw)
3309 {
3310   int n_text;
3311   int current_text;
3312 
3313   g_object_get (bvw->play, "current-text", &current_text, "n-text", &n_text, NULL);
3314 
3315   current_text++;
3316   if (current_text >= n_text)
3317     current_text = -2;
3318 
3319   bacon_video_widget_set_subtitle (bvw, current_text);
3320 }
3321 
3322 static gboolean
bvw_chapter_compare_func(GstTocEntry * entry,BaconVideoWidget * bvw)3323 bvw_chapter_compare_func (GstTocEntry      *entry,
3324 			  BaconVideoWidget *bvw)
3325 {
3326   gint64 start, stop;
3327 
3328   if (!gst_toc_entry_get_start_stop_times (entry, &start, &stop))
3329     return -1;
3330 
3331   if (bvw->current_time >= start / GST_MSECOND &&
3332       bvw->current_time < stop / GST_MSECOND)
3333     return 0;
3334 
3335   return -1;
3336 }
3337 
3338 static GList *
bvw_get_current_chapter(BaconVideoWidget * bvw)3339 bvw_get_current_chapter (BaconVideoWidget *bvw)
3340 {
3341   return g_list_find_custom (bvw->chapters, bvw, (GCompareFunc) bvw_chapter_compare_func);
3342 }
3343 
3344 /**
3345  * bacon_video_widget_has_next_track:
3346  * @bvw: a #BaconVideoWidget
3347  *
3348  * Determines whether there is another track after the current one, typically
3349  * as a chapter on a DVD.
3350  *
3351  * Return value: %TRUE if there is another track, %FALSE otherwise
3352  **/
3353 gboolean
bacon_video_widget_has_next_track(BaconVideoWidget * bvw)3354 bacon_video_widget_has_next_track (BaconVideoWidget *bvw)
3355 {
3356   GList *l;
3357 
3358   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
3359 
3360   if (bvw->mrl == NULL)
3361     return FALSE;
3362 
3363   if (g_str_has_prefix (bvw->mrl, "dvd:/"))
3364     return TRUE;
3365 
3366   l = bvw_get_current_chapter (bvw);
3367   if (l != NULL && l->next != NULL)
3368     return TRUE;
3369 
3370   return FALSE;
3371 }
3372 
3373 /**
3374  * bacon_video_widget_has_previous_track:
3375  * @bvw: a #BaconVideoWidget
3376  *
3377  * Determines whether there is another track before the current one, typically
3378  * as a chapter on a DVD.
3379  *
3380  * Return value: %TRUE if there is another track, %FALSE otherwise
3381  **/
3382 gboolean
bacon_video_widget_has_previous_track(BaconVideoWidget * bvw)3383 bacon_video_widget_has_previous_track (BaconVideoWidget *bvw)
3384 {
3385   GstFormat fmt;
3386   gint64 val;
3387   GList *l;
3388 
3389   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
3390 
3391   if (bvw->mrl == NULL)
3392     return FALSE;
3393 
3394   if (g_str_has_prefix (bvw->mrl, "dvd:/"))
3395     return TRUE;
3396 
3397   /* Look in the chapters first */
3398   l = bvw_get_current_chapter (bvw);
3399   if (l != NULL && l->prev != NULL)
3400     return TRUE;
3401 
3402   fmt = gst_format_get_by_nick ("chapter");
3403   /* If chapter isn't registered, then there's no chapters support */
3404   if (fmt == GST_FORMAT_UNDEFINED)
3405     return FALSE;
3406 
3407   if (gst_element_query_position (bvw->play, fmt, &val))
3408     return (val > 0);
3409 
3410   return FALSE;
3411 }
3412 
3413 static GList *
get_lang_list_for_type(BaconVideoWidget * bvw,const gchar * type_name)3414 get_lang_list_for_type (BaconVideoWidget * bvw, const gchar * type_name)
3415 {
3416   GList *ret = NULL;
3417   gint i, n;
3418   const char *prop;
3419   const char *signal;
3420 
3421   if (g_str_equal (type_name, "AUDIO")) {
3422     prop = "n-audio";
3423     signal = "get-audio-tags";
3424   } else if (g_str_equal (type_name, "TEXT")) {
3425     prop = "n-text";
3426     signal = "get-text-tags";
3427   } else {
3428     g_critical ("Invalid stream type '%s'", type_name);
3429     return NULL;
3430   }
3431 
3432   n = 0;
3433   g_object_get (G_OBJECT (bvw->play), prop, &n, NULL);
3434   if (n == 0)
3435     return NULL;
3436 
3437   for (i = 0; i < n; i++) {
3438     GstTagList *tags = NULL;
3439     BvwLangInfo *info;
3440 
3441     g_signal_emit_by_name (G_OBJECT (bvw->play), signal, i, &tags);
3442 
3443     info = g_new0 (BvwLangInfo, 1);
3444 
3445     if (tags) {
3446       gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &info->language);
3447       if (g_str_equal (type_name, "AUDIO"))
3448 	gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &info->codec);
3449 
3450       gst_tag_list_unref (tags);
3451     }
3452 
3453     if (info->language == NULL)
3454       info->language = g_strdup ("und");
3455     ret = g_list_prepend (ret, info);
3456   }
3457 
3458   return g_list_reverse (ret);
3459 }
3460 
3461 void
bacon_video_widget_lang_info_free(BvwLangInfo * info)3462 bacon_video_widget_lang_info_free (BvwLangInfo *info)
3463 {
3464   if (info == NULL)
3465     return;
3466   g_free (info->language);
3467   g_free (info->codec);
3468   g_free (info);
3469 }
3470 
3471 /**
3472  * bacon_video_widget_get_subtitles:
3473  * @bvw: a #BaconVideoWidget
3474  *
3475  * Returns a list of #BvwLangInfo for each subtitle track.
3476  *
3477  * Return value: a #GList of #BvwLangInfo, or %NULL; free each element with bacon_video_widget_lang_info_free() and the list with g_list_free()
3478  **/
3479 GList *
bacon_video_widget_get_subtitles(BaconVideoWidget * bvw)3480 bacon_video_widget_get_subtitles (BaconVideoWidget * bvw)
3481 {
3482   GList *list;
3483 
3484   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
3485   g_return_val_if_fail (bvw->play != NULL, NULL);
3486 
3487   list = get_lang_list_for_type (bvw, "TEXT");
3488 
3489   return list;
3490 }
3491 
3492 /**
3493  * bacon_video_widget_get_languages:
3494  * @bvw: a #BaconVideoWidget
3495  *
3496  * Returns a list of #BvwLangInfo for each audio track.
3497  *
3498  * Return value: a #GList of #BvwLangInfo, or %NULL; free each element with bacon_video_widget_lang_info_free() and the list with g_list_free()
3499  **/
3500 GList *
bacon_video_widget_get_languages(BaconVideoWidget * bvw)3501 bacon_video_widget_get_languages (BaconVideoWidget * bvw)
3502 {
3503   GList *list;
3504 
3505   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
3506   g_return_val_if_fail (bvw->play != NULL, NULL);
3507 
3508   list = get_lang_list_for_type (bvw, "AUDIO");
3509 
3510   /* When we have only one language, we don't need to show
3511    * any languages, we default to the only track */
3512   if (g_list_length (list) == 1) {
3513     g_list_free_full (list, (GDestroyNotify) bacon_video_widget_lang_info_free);
3514     return NULL;
3515   }
3516 
3517   return list;
3518 }
3519 
3520 /**
3521  * bacon_video_widget_get_language:
3522  * @bvw: a #BaconVideoWidget
3523  *
3524  * Returns the index of the current audio language.
3525  *
3526  * If the widget is not playing, or the default language is in use, <code class="literal">-1</code> will be returned.
3527  *
3528  * Return value: the audio language index
3529  **/
3530 int
bacon_video_widget_get_language(BaconVideoWidget * bvw)3531 bacon_video_widget_get_language (BaconVideoWidget * bvw)
3532 {
3533   int language = -1;
3534 
3535   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
3536   g_return_val_if_fail (bvw->play != NULL, -1);
3537 
3538   g_object_get (G_OBJECT (bvw->play), "current-audio", &language, NULL);
3539 
3540   return language;
3541 }
3542 
3543 /**
3544  * bacon_video_widget_set_language:
3545  * @bvw: a #BaconVideoWidget
3546  * @language: an audio language index
3547  *
3548  * Sets the audio language index for @bvw. If @language is <code class="literal">-1</code>, the default language will
3549  * be used.
3550  **/
3551 void
bacon_video_widget_set_language(BaconVideoWidget * bvw,int language)3552 bacon_video_widget_set_language (BaconVideoWidget * bvw, int language)
3553 {
3554   GstTagList *tags;
3555   int n_lang;
3556 
3557   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3558   g_return_if_fail (bvw->play != NULL);
3559 
3560   g_object_get (bvw->play, "n-audio", &n_lang, NULL);
3561 
3562   g_return_if_fail (sublang_is_valid (language, n_lang));
3563 
3564   if (language == -1)
3565     language = 0;
3566   else if (language == -2)
3567     language = -1;
3568 
3569   GST_DEBUG ("setting language to %d", language);
3570 
3571   g_object_set (bvw->play, "current-audio", language, NULL);
3572 
3573   g_object_get (bvw->play, "current-audio", &language, NULL);
3574   GST_DEBUG ("current-audio now: %d", language);
3575 
3576   g_signal_emit_by_name (G_OBJECT (bvw->play), "get-audio-tags", language, &tags);
3577   bvw_update_tags (bvw, tags, "audio");
3578 
3579   /* so it updates its metadata for the newly-selected stream */
3580   g_signal_emit (bvw, bvw_signals[SIGNAL_GOT_METADATA], 0, NULL);
3581   g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
3582 }
3583 
3584 /**
3585  * bacon_video_widget_set_next_language:
3586  * @bvw: a #BaconVideoWidget
3587  *
3588  * Switch to the next audio language index for the current video. See
3589  * bacon_video_widget_set_language().
3590  *
3591  * Since: 3.12
3592  */
3593 void
bacon_video_widget_set_next_language(BaconVideoWidget * bvw)3594 bacon_video_widget_set_next_language (BaconVideoWidget *bvw)
3595 {
3596   int n_audio;
3597   int current_audio;
3598 
3599   g_object_get (bvw->play, "current-audio", &current_audio, "n-audio", &n_audio, NULL);
3600 
3601   current_audio++;
3602   if (current_audio >= n_audio)
3603     current_audio = -2;
3604 
3605   bacon_video_widget_set_language (bvw, current_audio);
3606 }
3607 
3608 /**
3609  * bacon_video_widget_set_deinterlacing:
3610  * @bvw: a #BaconVideoWidget
3611  * @deinterlace: %TRUE if videos should be automatically deinterlaced, %FALSE otherwise
3612  *
3613  * Sets whether the widget should deinterlace videos.
3614  **/
3615 void
bacon_video_widget_set_deinterlacing(BaconVideoWidget * bvw,gboolean deinterlace)3616 bacon_video_widget_set_deinterlacing (BaconVideoWidget * bvw,
3617                                       gboolean deinterlace)
3618 {
3619   gint flags;
3620 
3621   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3622   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
3623 
3624   g_object_get (bvw->play, "flags", &flags, NULL);
3625   if (deinterlace)
3626     flags |= GST_PLAY_FLAG_DEINTERLACE;
3627   else
3628     flags &= ~GST_PLAY_FLAG_DEINTERLACE;
3629   g_object_set (bvw->play, "flags", flags, NULL);
3630 
3631   g_object_notify (G_OBJECT (bvw), "deinterlacing");
3632 }
3633 
3634 /**
3635  * bacon_video_widget_get_deinterlacing:
3636  * @bvw: a #BaconVideoWidget
3637  *
3638  * Returns whether deinterlacing of videos is enabled for this widget.
3639  *
3640  * Return value: %TRUE if automatic deinterlacing is enabled, %FALSE otherwise
3641  **/
3642 gboolean
bacon_video_widget_get_deinterlacing(BaconVideoWidget * bvw)3643 bacon_video_widget_get_deinterlacing (BaconVideoWidget * bvw)
3644 {
3645   gint flags;
3646 
3647   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
3648   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
3649 
3650   g_object_get (bvw->play, "flags", &flags, NULL);
3651 
3652   return !!(flags & GST_PLAY_FLAG_DEINTERLACE);
3653 }
3654 
3655 static gint
get_num_audio_channels(BaconVideoWidget * bvw)3656 get_num_audio_channels (BaconVideoWidget * bvw)
3657 {
3658   gint channels;
3659 
3660   switch (bvw->speakersetup) {
3661     case BVW_AUDIO_SOUND_STEREO:
3662       channels = 2;
3663       break;
3664     case BVW_AUDIO_SOUND_4CHANNEL:
3665       channels = 4;
3666       break;
3667     case BVW_AUDIO_SOUND_5CHANNEL:
3668       channels = 5;
3669       break;
3670     case BVW_AUDIO_SOUND_41CHANNEL:
3671       /* so alsa has this as 5.1, but empty center speaker. We don't really
3672        * do that yet. ;-). So we'll take the placebo approach. */
3673     case BVW_AUDIO_SOUND_51CHANNEL:
3674       channels = 6;
3675       break;
3676     case BVW_AUDIO_SOUND_AC3PASSTHRU:
3677     default:
3678       g_return_val_if_reached (-1);
3679   }
3680 
3681   return channels;
3682 }
3683 
3684 static GstCaps *
fixate_to_num(const GstCaps * in_caps,gint channels)3685 fixate_to_num (const GstCaps * in_caps, gint channels)
3686 {
3687   gint n, count;
3688   GstStructure *s;
3689   const GValue *v;
3690   GstCaps *out_caps;
3691 
3692   out_caps = gst_caps_copy (in_caps);
3693 
3694   count = gst_caps_get_size (out_caps);
3695   for (n = 0; n < count; n++) {
3696     s = gst_caps_get_structure (out_caps, n);
3697     v = gst_structure_get_value (s, "channels");
3698     if (!v)
3699       continue;
3700 
3701     /* get channel count (or list of ~) */
3702     gst_structure_fixate_field_nearest_int (s, "channels", channels);
3703   }
3704 
3705   return out_caps;
3706 }
3707 
3708 static void
set_audio_filter(BaconVideoWidget * bvw)3709 set_audio_filter (BaconVideoWidget *bvw)
3710 {
3711   gint channels;
3712   GstCaps *caps, *res;
3713   GstPad *pad, *peer_pad;
3714 
3715   /* reset old */
3716   g_object_set (bvw->audio_capsfilter, "caps", NULL, NULL);
3717 
3718   /* construct possible caps to filter down to our chosen caps */
3719   /* Start with what the audio sink supports, but limit the allowed
3720    * channel count to our speaker output configuration */
3721   pad = gst_element_get_static_pad (bvw->audio_capsfilter, "src");
3722   peer_pad = gst_pad_get_peer (pad);
3723   gst_object_unref (pad);
3724 
3725   caps = gst_pad_get_current_caps (peer_pad);
3726   gst_object_unref (peer_pad);
3727 
3728   if ((channels = get_num_audio_channels (bvw)) == -1)
3729     return;
3730 
3731   res = fixate_to_num (caps, channels);
3732   gst_caps_unref (caps);
3733 
3734   /* set */
3735   if (res && gst_caps_is_empty (res)) {
3736     gst_caps_unref (res);
3737     res = NULL;
3738   }
3739   g_object_set (bvw->audio_capsfilter, "caps", res, NULL);
3740 
3741   if (res) {
3742     gst_caps_unref (res);
3743   }
3744 
3745   /* reset */
3746   pad = gst_element_get_static_pad (bvw->audio_capsfilter, "src");
3747   gst_pad_set_caps (pad, NULL);
3748   gst_object_unref (pad);
3749 }
3750 
3751 /**
3752  * bacon_video_widget_get_audio_output_type:
3753  * @bvw: a #BaconVideoWidget
3754  *
3755  * Returns the current audio output type (e.g. how many speaker channels)
3756  * from #BvwAudioOutputType.
3757  *
3758  * Return value: the audio output type, or <code class="literal">-1</code>
3759  **/
3760 BvwAudioOutputType
bacon_video_widget_get_audio_output_type(BaconVideoWidget * bvw)3761 bacon_video_widget_get_audio_output_type (BaconVideoWidget *bvw)
3762 {
3763   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
3764 
3765   return bvw->speakersetup;
3766 }
3767 
3768 /**
3769  * bacon_video_widget_set_audio_output_type:
3770  * @bvw: a #BaconVideoWidget
3771  * @type: the new audio output type
3772  *
3773  * Sets the audio output type (number of speaker channels) in the video widget.
3774  **/
3775 void
bacon_video_widget_set_audio_output_type(BaconVideoWidget * bvw,BvwAudioOutputType type)3776 bacon_video_widget_set_audio_output_type (BaconVideoWidget *bvw,
3777                                           BvwAudioOutputType type)
3778 {
3779   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3780 
3781   if (type == bvw->speakersetup)
3782     return;
3783   else if (type == BVW_AUDIO_SOUND_AC3PASSTHRU)
3784     return;
3785 
3786   bvw->speakersetup = type;
3787   g_object_notify (G_OBJECT (bvw), "audio-output-type");
3788 
3789   set_audio_filter (bvw);
3790 }
3791 
3792 /**
3793  * bacon_video_widget_show_popup:
3794  * @bvw: a #BaconVideoWidget
3795  *
3796  * Show the video controls popup, and schedule for it to be hidden again after
3797  * a timeout.
3798  *
3799  * Since: 3.12
3800  */
3801 void
bacon_video_widget_show_popup(BaconVideoWidget * bvw)3802 bacon_video_widget_show_popup (BaconVideoWidget *bvw)
3803 {
3804   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3805 
3806   set_controls_visibility (bvw, TRUE, FALSE);
3807   schedule_hiding_popup (bvw);
3808 }
3809 
3810 /**
3811  * bacon_video_widget_mark_popup_busy:
3812  * @bvw: a #BaconVideoWidget
3813  * @reason: human-readable reason for the controls popup being marked as busy
3814  *
3815  * Mark the video controls popup as busy, for the given @reason. Use
3816  * bacon_video_widget_unmark_popup_busy() to undo.
3817  *
3818  * Since: 3.12
3819  */
3820 void
bacon_video_widget_mark_popup_busy(BaconVideoWidget * bvw,const char * reason)3821 bacon_video_widget_mark_popup_busy (BaconVideoWidget *bvw,
3822 				    const char       *reason)
3823 {
3824   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3825 
3826   g_hash_table_insert (bvw->busy_popup_ht,
3827 		       g_strdup (reason),
3828 		       GINT_TO_POINTER (1));
3829 
3830   set_controls_visibility (bvw, TRUE, FALSE);
3831 
3832   GST_DEBUG ("Adding popup busy for reason %s", reason);
3833 
3834   unschedule_hiding_popup (bvw);
3835 }
3836 
3837 /**
3838  * bacon_video_widget_unmark_popup_busy:
3839  * @bvw: a #BaconVideoWidget
3840  * @reason: human-readable reason for the controls popup being unmarked as busy
3841  *
3842  * Unmark the video controls popup as busy, for the given @reason. The popup
3843  * must previously have been marked as busy using
3844  * bacon_video_widget_mark_popup_busy().
3845  *
3846  * Since: 3.12
3847  */
3848 void
bacon_video_widget_unmark_popup_busy(BaconVideoWidget * bvw,const char * reason)3849 bacon_video_widget_unmark_popup_busy (BaconVideoWidget *bvw,
3850 				      const char       *reason)
3851 {
3852   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
3853 
3854   g_hash_table_remove (bvw->busy_popup_ht, reason);
3855 
3856   GST_DEBUG ("Removing popup busy for reason %s", reason);
3857 
3858   if (g_hash_table_size (bvw->busy_popup_ht) == 0 &&
3859       clutter_actor_get_opacity (bvw->controls) != 0) {
3860     GST_DEBUG ("Will hide popup soon");
3861     schedule_hiding_popup (bvw);
3862   }
3863 }
3864 
3865 /**
3866  * bacon_video_widget_get_controls_object:
3867  * @bvw: a #BaconVideoWidget
3868  *
3869  * Get the widget which displays the video controls.
3870  *
3871  * Returns: (transfer none): controls widget
3872  * Since: 3.12
3873  */
3874 GObject *
bacon_video_widget_get_controls_object(BaconVideoWidget * bvw)3875 bacon_video_widget_get_controls_object (BaconVideoWidget *bvw)
3876 {
3877   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
3878 
3879   return G_OBJECT (bvw->controls);
3880 }
3881 
3882 /**
3883  * bacon_video_widget_get_header_controls_object:
3884  * @bvw: a #BaconVideoWidget
3885  *
3886  * Get the widget which displays the video header controls.
3887  *
3888  * Returns: (transfer none): header controls widget
3889  * Since: 3.20
3890  */
3891 GObject *
bacon_video_widget_get_header_controls_object(BaconVideoWidget * bvw)3892 bacon_video_widget_get_header_controls_object (BaconVideoWidget *bvw)
3893 {
3894   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
3895 
3896   return G_OBJECT (gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (bvw->header_controls)));
3897 }
3898 
3899 /* =========================================== */
3900 /*                                             */
3901 /*               Play/Pause, Stop              */
3902 /*                                             */
3903 /* =========================================== */
3904 
3905 static GError*
bvw_error_from_gst_error(BaconVideoWidget * bvw,GstMessage * err_msg)3906 bvw_error_from_gst_error (BaconVideoWidget *bvw, GstMessage * err_msg)
3907 {
3908   const gchar *src_typename;
3909   GError *ret = NULL;
3910   GError *e = NULL;
3911   char *dbg = NULL;
3912   int http_error_code;
3913 
3914   GST_LOG ("resolving %" GST_PTR_FORMAT, err_msg);
3915 
3916   src_typename = (err_msg->src) ? G_OBJECT_TYPE_NAME (err_msg->src) : NULL;
3917 
3918   gst_message_parse_error (err_msg, &e, &dbg);
3919 
3920   /* FIXME:
3921    * Unemitted errors:
3922    * BVW_ERROR_BROKEN_FILE
3923    */
3924 
3925   /* Can't open optical disc? */
3926   if (is_error (e, RESOURCE, NOT_FOUND) ||
3927       is_error (e, RESOURCE, OPEN_READ)) {
3928     if (g_str_has_prefix (bvw->mrl, "dvd:")) {
3929       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_INVALID_DEVICE,
3930 				 "The DVD device you specified seems to be invalid.");
3931       goto done;
3932     } else if (g_str_has_prefix (bvw->mrl, "vcd:")) {
3933       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_INVALID_DEVICE,
3934 				 "The VCD device you specified seems to be invalid.");
3935       goto done;
3936     }
3937   }
3938 
3939   /* Check for encrypted DVD */
3940   if (is_error (e, RESOURCE, READ) &&
3941       g_str_has_prefix (bvw->mrl, "dvd:")) {
3942     GModule *module;
3943     gpointer sym;
3944 
3945     module = g_module_open ("libdvdcss", 0);
3946     if (module == NULL ||
3947 	g_module_symbol (module, "dvdcss_open", &sym)) {
3948       g_clear_pointer (&module, g_module_close);
3949       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_DVD_ENCRYPTED,
3950 				 _("The source seems encrypted and can’t be read. Are you trying to play an encrypted DVD without libdvdcss?"));
3951       goto done;
3952     }
3953     g_clear_pointer (&module, g_module_close);
3954   }
3955 
3956   /* HTTP error codes */
3957   /* FIXME: bvw_get_http_error_code() calls gst_message_parse_error too */
3958   http_error_code = bvw_get_http_error_code (err_msg);
3959 
3960   if (is_error (e, RESOURCE, NOT_FOUND) ||
3961       http_error_code == 404) {
3962     if (strstr (e->message, "Cannot resolve hostname") != NULL) {
3963       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_UNKNOWN_HOST,
3964 				 _("The server you are trying to connect to is not known."));
3965     } else if (strstr (e->message, "Cannot connect to destination") != NULL) {
3966       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CONNECTION_REFUSED,
3967 				 _("The connection to this server was refused."));
3968     } else {
3969       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_NOT_FOUND,
3970 				 _("The specified movie could not be found."));
3971     }
3972     goto done;
3973   }
3974 
3975   if (http_error_code == 403) {
3976     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_PERMISSION,
3977 			       _("The server refused access to this file or stream."));
3978     goto done;
3979   }
3980 
3981   if (http_error_code == 401) {
3982     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_PERMISSION,
3983 			       _("Authentication is required to access this file or stream."));
3984     goto done;
3985   }
3986 
3987   if (http_error_code == 495) {
3988     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_READ_ERROR,
3989 			       _("SSL/TLS support is missing. Check your installation."));
3990     goto done;
3991   }
3992 
3993   if (is_error (e, RESOURCE, OPEN_READ)) {
3994     if (strstr (dbg, g_strerror (EACCES)) != NULL) {
3995       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_PERMISSION,
3996 				 _("You are not allowed to open this file."));
3997       goto done;
3998     }
3999     if (strstr (dbg, "Error parsing URL.") != NULL) {
4000       ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_INVALID_LOCATION,
4001 				 _("This location is not a valid one."));
4002       goto done;
4003     }
4004   }
4005 
4006   if (is_error (e, RESOURCE, OPEN_READ) ||
4007       is_error (e, RESOURCE, READ)) {
4008     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_READ_ERROR,
4009 			       _("The movie could not be read."));
4010     goto done;
4011   }
4012 
4013   if (is_error (e, STREAM, DECRYPT)) {
4014     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_ENCRYPTED,
4015 			       _("This file is encrypted and cannot be played back."));
4016     goto done;
4017   }
4018 
4019   if (is_error (e, STREAM, TYPE_NOT_FOUND)) {
4020     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_EMPTY_FILE,
4021 			       _("The file you tried to play is an empty file."));
4022     goto done;
4023   }
4024 
4025   if (e->domain == GST_RESOURCE_ERROR) {
4026     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_FILE_GENERIC,
4027                                e->message);
4028     goto done;
4029   }
4030 
4031   if (is_error (e, CORE, MISSING_PLUGIN) ||
4032       is_error (e, STREAM, CODEC_NOT_FOUND) ||
4033       is_error (e, STREAM, WRONG_TYPE) ||
4034       is_error (e, STREAM, NOT_IMPLEMENTED) ||
4035       (is_error (e, STREAM, FORMAT) && strstr (dbg, "no video pad or visualizations"))) {
4036     if (bvw->missing_plugins != NULL) {
4037       gchar **descs, *msg = NULL;
4038       guint num;
4039 
4040       descs = bvw_get_missing_plugins_descriptions (bvw->missing_plugins);
4041       num = g_list_length (bvw->missing_plugins);
4042 
4043       if (is_error (e, CORE, MISSING_PLUGIN)) {
4044         /* should be exactly one missing thing (source or converter) */
4045         msg = g_strdup_printf (_("The playback of this movie requires a %s "
4046 				 "plugin which is not installed."), descs[0]);
4047 	ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_NO_PLUGIN_FOR_FILE, msg);
4048 	g_free (msg);
4049       } else {
4050         gchar *desc_list;
4051 
4052         desc_list = g_strjoinv ("\n", descs);
4053         msg = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "The playback of this movie "
4054             "requires a %s plugin which is not installed.", "The playback "
4055             "of this movie requires the following plugins which are not "
4056             "installed:\n\n%s", num), (num == 1) ? descs[0] : desc_list);
4057         g_free (desc_list);
4058 	ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED, msg);
4059 	g_free (msg);
4060       }
4061       g_strfreev (descs);
4062     } else {
4063       if (g_str_has_prefix (bvw->mrl, "rtsp:")) {
4064 	ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_NETWORK_UNREACHABLE,
4065 				   _("This stream cannot be played. It’s possible that a firewall is blocking it."));
4066       } else {
4067 	ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_CODEC_NOT_HANDLED,
4068 				   _("An audio or video stream is not handled due to missing codecs. "
4069 				     "You might need to install additional plugins to be able to play some types of movies"));
4070       }
4071     }
4072     goto done;
4073   }
4074 
4075   if (is_error (e, STREAM, FAILED) &&
4076 	     src_typename &&
4077 	     strncmp (src_typename, "GstTypeFind", 11) == 0) {
4078     ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_READ_ERROR,
4079 			       _("This file cannot be played over the network. Try downloading it locally first."));
4080     goto done;
4081   }
4082 
4083   /* generic error, no code; take message */
4084   ret = g_error_new_literal (BVW_ERROR, BVW_ERROR_GENERIC,
4085 			     e->message);
4086 
4087 done:
4088   g_error_free (e);
4089   g_free (dbg);
4090   bvw_clear_missing_plugins_messages (bvw);
4091 
4092   return ret;
4093 }
4094 
4095 static char *
get_target_uri(GFile * file)4096 get_target_uri (GFile *file)
4097 {
4098   GFileInfo *info;
4099   char *target;
4100 
4101   info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
4102   if (info == NULL)
4103     return NULL;
4104   target = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI));
4105   g_object_unref (info);
4106 
4107   return target;
4108 }
4109 
4110 /**
4111  * bacon_video_widget_open:
4112  * @bvw: a #BaconVideoWidget
4113  * @mrl: an MRL
4114  *
4115  * Opens the given @mrl in @bvw for playing.
4116  *
4117  * The MRL is loaded and waiting to be played with bacon_video_widget_play().
4118  **/
4119 void
bacon_video_widget_open(BaconVideoWidget * bvw,const char * mrl)4120 bacon_video_widget_open (BaconVideoWidget *bvw,
4121                          const char       *mrl)
4122 {
4123   GFile *file;
4124 
4125   g_return_if_fail (mrl != NULL);
4126   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4127   g_return_if_fail (bvw->play != NULL);
4128 
4129   /* So we aren't closed yet... */
4130   if (bvw->mrl) {
4131     bacon_video_widget_close (bvw);
4132   }
4133 
4134   GST_DEBUG ("mrl = %s", GST_STR_NULL (mrl));
4135 
4136   /* this allows non-URI type of files in the thumbnailer and so on */
4137   file = g_file_new_for_commandline_arg (mrl);
4138 
4139   if (g_file_has_uri_scheme (file, "trash") != FALSE ||
4140       g_file_has_uri_scheme (file, "recent") != FALSE) {
4141     bvw->mrl = get_target_uri (file);
4142     GST_DEBUG ("Found target location '%s' for original MRL '%s'",
4143 	       GST_STR_NULL (bvw->mrl), mrl);
4144   } else if (g_file_has_uri_scheme (file, "cdda") != FALSE) {
4145     char *path;
4146     path = g_file_get_path (file);
4147     bvw->mrl = g_filename_to_uri (path, NULL, NULL);
4148     g_free (path);
4149   } else {
4150     bvw->mrl = g_strdup (mrl);
4151   }
4152 
4153   g_object_unref (file);
4154 
4155   bvw->got_redirect = FALSE;
4156   bvw->media_has_video = FALSE;
4157   bvw->media_has_audio = FALSE;
4158 
4159   /* Flush the bus to make sure we don't get any messages
4160    * from the previous URI, see bug #607224.
4161    */
4162   gst_bus_set_flushing (bvw->bus, TRUE);
4163   bvw->target_state = GST_STATE_READY;
4164   gst_element_set_state (bvw->play, GST_STATE_READY);
4165   gst_bus_set_flushing (bvw->bus, FALSE);
4166 
4167   g_object_set (bvw->play, "uri", bvw->mrl, NULL);
4168 
4169   bvw->seekable = -1;
4170   bvw->target_state = GST_STATE_PAUSED;
4171   bvw_clear_missing_plugins_messages (bvw);
4172 
4173   bacon_video_widget_mark_popup_busy (bvw, "opening file");
4174 
4175   gst_element_set_state (bvw->play, GST_STATE_PAUSED);
4176 
4177   g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
4178 }
4179 
4180 /**
4181  * bacon_video_widget_play:
4182  * @bvw: a #BaconVideoWidget
4183  * @error: a #GError, or %NULL
4184  *
4185  * Plays the currently-loaded video in @bvw.
4186  *
4187  * Errors from the GStreamer backend will be returned asynchronously via the
4188  * #BaconVideoWidget::error signal, even if this function returns %TRUE.
4189  *
4190  * Return value: %TRUE on success, %FALSE otherwise
4191  **/
4192 gboolean
bacon_video_widget_play(BaconVideoWidget * bvw,GError ** error)4193 bacon_video_widget_play (BaconVideoWidget * bvw, GError ** error)
4194 {
4195   GstState cur_state;
4196 
4197   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4198   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
4199   g_return_val_if_fail (bvw->mrl != NULL, FALSE);
4200 
4201   bvw->target_state = GST_STATE_PLAYING;
4202 
4203   /* Don't try to play if we're already doing that */
4204   gst_element_get_state (bvw->play, &cur_state, NULL, 0);
4205   if (cur_state == GST_STATE_PLAYING)
4206     return TRUE;
4207 
4208   /* Lie when trying to play a file whilst we're download buffering */
4209   if (bvw->download_buffering != FALSE &&
4210       bvw_download_buffering_done (bvw) == FALSE) {
4211     GST_DEBUG ("download buffering in progress, not playing");
4212     return TRUE;
4213   }
4214 
4215   /* Or when we're buffering */
4216   if (bvw->buffering != FALSE) {
4217     GST_DEBUG ("buffering in progress, not playing");
4218     return TRUE;
4219   }
4220 
4221   /* just lie and do nothing in this case */
4222   if (bvw->plugin_install_in_progress && cur_state != GST_STATE_PAUSED) {
4223     GST_DEBUG ("plugin install in progress and nothing to play, not playing");
4224     return TRUE;
4225   } else if (bvw->mount_in_progress) {
4226     GST_DEBUG ("Mounting in progress, not playing");
4227     return TRUE;
4228   } else if (bvw->auth_dialog != NULL) {
4229     GST_DEBUG ("Authentication in progress, not playing");
4230     return TRUE;
4231   }
4232 
4233   /* Set direction to forward */
4234   if (bvw_set_playback_direction (bvw, TRUE) == FALSE) {
4235     GST_DEBUG ("Failed to reset direction back to forward to play");
4236     return FALSE;
4237   }
4238 
4239   bacon_video_widget_unmark_popup_busy (bvw, "opening file");
4240 
4241   GST_DEBUG ("play");
4242   gst_element_set_state (bvw->play, GST_STATE_PLAYING);
4243 
4244   /* will handle all errors asynchroneously */
4245   return TRUE;
4246 }
4247 
4248 /**
4249  * bacon_video_widget_can_direct_seek:
4250  * @bvw: a #BaconVideoWidget
4251  *
4252  * Determines whether direct seeking is possible for the current stream.
4253  *
4254  * Return value: %TRUE if direct seeking is possible, %FALSE otherwise
4255  **/
4256 gboolean
bacon_video_widget_can_direct_seek(BaconVideoWidget * bvw)4257 bacon_video_widget_can_direct_seek (BaconVideoWidget *bvw)
4258 {
4259   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4260   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
4261 
4262   if (bvw->mrl == NULL)
4263     return FALSE;
4264 
4265   if (bvw->download_buffering != FALSE)
4266     return TRUE;
4267 
4268   /* (instant seeking only make sense with video,
4269    * hence no cdda:// here) */
4270   if (g_str_has_prefix (bvw->mrl, "file://") ||
4271       g_str_has_prefix (bvw->mrl, "dvd:/") ||
4272       g_str_has_prefix (bvw->mrl, "vcd:/") ||
4273       g_str_has_prefix (bvw->mrl, "trash:/"))
4274     return TRUE;
4275 
4276   return FALSE;
4277 }
4278 
4279 static gboolean
bacon_video_widget_seek_time_no_lock(BaconVideoWidget * bvw,gint64 _time,GstSeekFlags flag,GError ** error)4280 bacon_video_widget_seek_time_no_lock (BaconVideoWidget *bvw,
4281 				      gint64 _time,
4282 				      GstSeekFlags flag,
4283 				      GError **error)
4284 {
4285   if (bvw_set_playback_direction (bvw, TRUE) == FALSE)
4286     return FALSE;
4287 
4288   bvw->seek_time = -1;
4289 
4290   gst_element_set_state (bvw->play, GST_STATE_PAUSED);
4291 
4292   gst_element_seek (bvw->play, bvw->rate,
4293 		    GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | flag,
4294 		    GST_SEEK_TYPE_SET, _time * GST_MSECOND,
4295 		    GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
4296 
4297   return TRUE;
4298 }
4299 
4300 /**
4301  * bacon_video_widget_seek_time:
4302  * @bvw: a #BaconVideoWidget
4303  * @_time: the time to which to seek, in milliseconds
4304  * @accurate: whether to use accurate seek, an accurate seek might be slower for some formats (see GStreamer docs)
4305  * @error: a #GError, or %NULL
4306  *
4307  * Seeks the currently-playing stream to the absolute position @time, in milliseconds.
4308  *
4309  * Return value: %TRUE on success, %FALSE otherwise
4310  **/
4311 gboolean
bacon_video_widget_seek_time(BaconVideoWidget * bvw,gint64 _time,gboolean accurate,GError ** error)4312 bacon_video_widget_seek_time (BaconVideoWidget *bvw, gint64 _time, gboolean accurate, GError **error)
4313 {
4314   GstClockTime cur_time;
4315   GstSeekFlags  flag;
4316 
4317   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4318   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
4319 
4320   GST_LOG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (_time * GST_MSECOND));
4321 
4322   /* Don't say we'll seek past the end */
4323   _time = MIN (_time, bvw->stream_length);
4324 
4325   /* Emit a time tick of where we are going, we are paused */
4326   got_time_tick (bvw->play, _time * GST_MSECOND, bvw);
4327 
4328   /* Is there a pending seek? */
4329   g_mutex_lock (&bvw->seek_mutex);
4330 
4331   /* If there's no pending seek, or
4332    * it's been too long since the seek,
4333    * or we don't have an accurate seek requested */
4334   cur_time = gst_clock_get_internal_time (bvw->clock);
4335   if (bvw->seek_req_time == GST_CLOCK_TIME_NONE ||
4336       cur_time > bvw->seek_req_time + SEEK_TIMEOUT ||
4337       accurate) {
4338     bvw->seek_time = -1;
4339     bvw->seek_req_time = cur_time;
4340     g_mutex_unlock (&bvw->seek_mutex);
4341   } else {
4342     GST_LOG ("Not long enough since last seek, queuing it");
4343     bvw->seek_time = _time;
4344     g_mutex_unlock (&bvw->seek_mutex);
4345     return TRUE;
4346   }
4347 
4348   flag = (accurate ? GST_SEEK_FLAG_ACCURATE : GST_SEEK_FLAG_NONE);
4349   bacon_video_widget_seek_time_no_lock (bvw, _time, flag, error);
4350 
4351   return TRUE;
4352 }
4353 
4354 /**
4355  * bacon_video_widget_seek:
4356  * @bvw: a #BaconVideoWidget
4357  * @position: the percentage of the way through the stream to which to seek
4358  * @error: a #GError, or %NULL
4359  *
4360  * Seeks the currently-playing stream to @position as a percentage of the total
4361  * stream length.
4362  *
4363  * Return value: %TRUE on success, %FALSE otherwise
4364  **/
4365 gboolean
bacon_video_widget_seek(BaconVideoWidget * bvw,double position,GError ** error)4366 bacon_video_widget_seek (BaconVideoWidget *bvw, double position, GError **error)
4367 {
4368   gint64 seek_time, length_nanos;
4369 
4370   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4371   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
4372 
4373   length_nanos = (gint64) (bvw->stream_length * GST_MSECOND);
4374   seek_time = (gint64) (length_nanos * position);
4375 
4376   GST_LOG ("Seeking to %3.2f%% %" GST_TIME_FORMAT, position,
4377       GST_TIME_ARGS (seek_time));
4378 
4379   return bacon_video_widget_seek_time (bvw, seek_time / GST_MSECOND, FALSE, error);
4380 }
4381 
4382 /**
4383  * bacon_video_widget_step:
4384  * @bvw: a #BaconVideoWidget
4385  * @forward: the direction of the frame step
4386  * @error: a #GError, or %NULL
4387  *
4388  * Step one frame forward, if @forward is %TRUE, or backwards, if @forward is %FALSE
4389  *
4390  * Return value: %TRUE on success, %FALSE otherwise
4391  **/
4392 gboolean
bacon_video_widget_step(BaconVideoWidget * bvw,gboolean forward,GError ** error)4393 bacon_video_widget_step (BaconVideoWidget *bvw, gboolean forward, GError **error)
4394 {
4395   GstEvent *event;
4396   gboolean retval;
4397 
4398   if (bvw_set_playback_direction (bvw, forward) == FALSE)
4399     return FALSE;
4400 
4401   event = gst_event_new_step (GST_FORMAT_BUFFERS, 1, 1.0, TRUE, FALSE);
4402 
4403   retval = gst_element_send_event (bvw->play, event);
4404 
4405   if (retval != FALSE)
4406     bvw_query_timeout (bvw);
4407   else
4408     GST_WARNING ("Failed to step %s", DIRECTION_STR);
4409 
4410   return retval;
4411 }
4412 
4413 static void
bvw_stop_play_pipeline(BaconVideoWidget * bvw)4414 bvw_stop_play_pipeline (BaconVideoWidget * bvw)
4415 {
4416   GstState cur_state;
4417 
4418   gst_element_get_state (bvw->play, &cur_state, NULL, 0);
4419   if (cur_state > GST_STATE_READY) {
4420     GstMessage *msg;
4421 
4422     GST_DEBUG ("stopping");
4423     gst_element_set_state (bvw->play, GST_STATE_READY);
4424 
4425     /* process all remaining state-change messages so everything gets
4426      * cleaned up properly (before the state change to NULL flushes them) */
4427     GST_DEBUG ("processing pending state-change messages");
4428     while ((msg = gst_bus_pop_filtered (bvw->bus, GST_MESSAGE_STATE_CHANGED))) {
4429       gst_bus_async_signal_func (bvw->bus, msg, NULL);
4430       gst_message_unref (msg);
4431     }
4432   }
4433 
4434   /* and now drop all following messages until we start again. The
4435    * bus is set to flush=false again in bacon_video_widget_open()
4436    */
4437   gst_bus_set_flushing (bvw->bus, TRUE);
4438 
4439   /* Now in READY or lower */
4440   bvw->target_state = GST_STATE_READY;
4441 
4442   bvw->buffering = FALSE;
4443   bvw->plugin_install_in_progress = FALSE;
4444   bvw->download_buffering = FALSE;
4445   g_clear_pointer (&bvw->download_filename, g_free);
4446   bvw->buffering_left = -1;
4447   bvw_reconfigure_fill_timeout (bvw, 0);
4448   bvw->movie_par_n = bvw->movie_par_d = 1;
4449   g_clear_object (&bvw->cover_pixbuf);
4450   clutter_actor_hide (bvw->spinner);
4451   g_object_set (G_OBJECT (bvw->spinner), "percent", 0.0, NULL);
4452   totem_aspect_frame_set_internal_rotation (TOTEM_ASPECT_FRAME (bvw->frame), 0.0);
4453   GST_DEBUG ("stopped");
4454 }
4455 
4456 /**
4457  * bacon_video_widget_stop:
4458  * @bvw: a #BaconVideoWidget
4459  *
4460  * Stops playing the current stream and resets to the first position in the stream.
4461  **/
4462 void
bacon_video_widget_stop(BaconVideoWidget * bvw)4463 bacon_video_widget_stop (BaconVideoWidget * bvw)
4464 {
4465   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4466   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4467 
4468   GST_LOG ("Stopping");
4469   bvw_stop_play_pipeline (bvw);
4470 
4471   /* Reset position to 0 when stopping */
4472   got_time_tick (GST_ELEMENT (bvw->play), 0, bvw);
4473 }
4474 
4475 /**
4476  * bacon_video_widget_close:
4477  * @bvw: a #BaconVideoWidget
4478  *
4479  * Closes the current stream and frees the resources associated with it.
4480  **/
4481 void
bacon_video_widget_close(BaconVideoWidget * bvw)4482 bacon_video_widget_close (BaconVideoWidget * bvw)
4483 {
4484   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4485   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4486 
4487   GST_LOG ("Closing");
4488   bvw_stop_play_pipeline (bvw);
4489 
4490   g_clear_pointer (&bvw->mrl, g_free);
4491   g_clear_pointer (&bvw->subtitle_uri, g_free);
4492   g_object_set (G_OBJECT (bvw->play), "suburi", NULL, NULL);
4493   g_clear_pointer (&bvw->subtitle_uri, g_free);
4494   g_clear_pointer (&bvw->user_id, g_free);
4495   g_clear_pointer (&bvw->user_pw, g_free);
4496 
4497   bvw->is_live = FALSE;
4498   bvw->is_menu = FALSE;
4499   bvw->has_angles = FALSE;
4500   bvw->rate = FORWARD_RATE;
4501 
4502   bvw->current_time = 0;
4503   bvw->seek_req_time = GST_CLOCK_TIME_NONE;
4504   bvw->seek_time = -1;
4505   bvw->stream_length = 0;
4506 
4507   if (bvw->eos_id != 0)
4508     g_source_remove (bvw->eos_id);
4509 
4510   if (bvw->chapters) {
4511     g_list_free_full (bvw->chapters, (GDestroyNotify) gst_mini_object_unref);
4512     bvw->chapters = NULL;
4513   }
4514 
4515   g_clear_pointer (&bvw->tagcache, gst_tag_list_unref);
4516   g_clear_pointer (&bvw->audiotags, gst_tag_list_unref);
4517   g_clear_pointer (&bvw->videotags, gst_tag_list_unref);
4518 
4519   g_object_notify (G_OBJECT (bvw), "seekable");
4520   g_signal_emit (bvw, bvw_signals[SIGNAL_CHANNELS_CHANGE], 0);
4521   got_time_tick (GST_ELEMENT (bvw->play), 0, bvw);
4522 }
4523 
4524 static void
bvw_do_navigation_command(BaconVideoWidget * bvw,GstNavigationCommand command)4525 bvw_do_navigation_command (BaconVideoWidget * bvw, GstNavigationCommand command)
4526 {
4527   if (bvw->navigation)
4528     gst_navigation_send_command (bvw->navigation, command);
4529 }
4530 
4531 /**
4532  * bacon_video_widget_set_text_subtitle:
4533  * @bvw: a #BaconVideoWidget
4534  * @subtitle_uri: (allow-none): the URI of a subtitle file, or %NULL
4535  *
4536  * Sets the URI for the text subtitle file to be displayed alongside
4537  * the current video. Use %NULL if you want to unload the current text subtitle
4538  * file being used.
4539  */
4540 void
bacon_video_widget_set_text_subtitle(BaconVideoWidget * bvw,const gchar * subtitle_uri)4541 bacon_video_widget_set_text_subtitle (BaconVideoWidget * bvw,
4542 				      const gchar * subtitle_uri)
4543 {
4544   GstState cur_state;
4545 
4546   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4547   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4548   g_return_if_fail (bvw->mrl != NULL);
4549 
4550   GST_LOG ("Setting subtitle as %s", GST_STR_NULL (subtitle_uri));
4551 
4552   if (subtitle_uri == NULL &&
4553       bvw->subtitle_uri == NULL)
4554     return;
4555 
4556   /* Wait for the previous state change to finish */
4557   gst_element_get_state (bvw->play, NULL, NULL, GST_CLOCK_TIME_NONE);
4558 
4559   /* -> READY */
4560   gst_element_get_state (bvw->play, &cur_state, NULL, 0);
4561   if (cur_state > GST_STATE_READY) {
4562     gst_element_set_state (bvw->play, GST_STATE_READY);
4563     /* Block for new state */
4564     gst_element_get_state (bvw->play, NULL, NULL, GST_CLOCK_TIME_NONE);
4565   }
4566 
4567   g_free (bvw->subtitle_uri);
4568   bvw->subtitle_uri = g_strdup (subtitle_uri);
4569   g_object_set (G_OBJECT (bvw->play), "suburi", subtitle_uri, NULL);
4570 
4571   /* And back to the original state */
4572   if (cur_state > GST_STATE_READY) {
4573     gst_element_set_state (bvw->play, cur_state);
4574     /* Block for new state */
4575     gst_element_get_state (bvw->play, NULL, NULL, GST_CLOCK_TIME_NONE);
4576   }
4577 
4578   if (bvw->current_time > 0)
4579     bacon_video_widget_seek_time_no_lock (bvw, bvw->current_time,
4580 					  GST_SEEK_FLAG_ACCURATE, NULL);
4581 }
4582 
4583 static void
handle_dvd_seek(BaconVideoWidget * bvw,int offset,const char * fmt_name)4584 handle_dvd_seek (BaconVideoWidget *bvw,
4585 		 int               offset,
4586 		 const char       *fmt_name)
4587 {
4588   GstFormat fmt;
4589   gint64 val;
4590 
4591   fmt = gst_format_get_by_nick (fmt_name);
4592   if (!fmt)
4593     return;
4594 
4595   bvw_set_playback_direction (bvw, TRUE);
4596 
4597   if (gst_element_query_position (bvw->play, fmt, &val)) {
4598     GST_DEBUG ("current %s is: %" G_GINT64_FORMAT, fmt_name, val);
4599     val += offset;
4600     GST_DEBUG ("seeking to %s: %" G_GINT64_FORMAT, fmt_name, val);
4601     gst_element_seek (bvw->play, FORWARD_RATE, fmt, GST_SEEK_FLAG_FLUSH,
4602 		      GST_SEEK_TYPE_SET, val, GST_SEEK_TYPE_NONE, G_GINT64_CONSTANT (0));
4603     bvw->rate = FORWARD_RATE;
4604   } else {
4605     GST_DEBUG ("failed to query position (%s)", fmt_name);
4606   }
4607 }
4608 
4609 static gboolean
handle_chapters_seek(BaconVideoWidget * bvw,gboolean forward)4610 handle_chapters_seek (BaconVideoWidget *bvw,
4611 		      gboolean          forward)
4612 {
4613   GList *l;
4614   GstTocEntry *entry;
4615   gint64 start;
4616 
4617   l = bvw_get_current_chapter (bvw);
4618   if (!l)
4619     return FALSE;
4620 
4621   entry = NULL;
4622   if (forward && l->next)
4623     entry = l->next->data;
4624   else if (!forward) {
4625     gint64 current_start;
4626     if (gst_toc_entry_get_start_stop_times (l->data, &current_start, NULL)) {
4627       if (bvw->current_time - current_start / GST_MSECOND < REWIND_OR_PREVIOUS &&
4628 	  bvw->current_time - current_start / GST_MSECOND > 0 &&
4629 	  l->prev) {
4630 	entry = l->prev->data;
4631       } else {
4632 	entry = l->data;
4633       }
4634     }
4635   }
4636 
4637   if (!entry)
4638     return FALSE;
4639 
4640   if (!gst_toc_entry_get_start_stop_times (entry, &start, NULL))
4641     return FALSE;
4642 
4643   GST_DEBUG ("Found chapter and seeking to %" G_GINT64_FORMAT, start / GST_MSECOND);
4644 
4645   return bacon_video_widget_seek_time (bvw, start / GST_MSECOND, FALSE, NULL);
4646 }
4647 
4648 /**
4649  * bacon_video_widget_dvd_event:
4650  * @bvw: a #BaconVideoWidget
4651  * @type: the type of DVD event to issue
4652  *
4653  * Issues a DVD navigation event to the video widget, such as one to skip to the
4654  * next chapter, or navigate to the DVD title menu.
4655  *
4656  * This is a no-op if the current stream is not navigable.
4657  **/
4658 void
bacon_video_widget_dvd_event(BaconVideoWidget * bvw,BvwDVDEvent type)4659 bacon_video_widget_dvd_event (BaconVideoWidget * bvw,
4660                               BvwDVDEvent type)
4661 {
4662   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4663   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4664 
4665   GST_DEBUG ("Sending event '%s'", get_type_name (BVW_TYPE_DVD_EVENT, type));
4666 
4667   switch (type) {
4668     case BVW_DVD_ROOT_MENU:
4669       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_MENU);
4670       break;
4671     case BVW_DVD_TITLE_MENU:
4672       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_TITLE_MENU);
4673       break;
4674     case BVW_DVD_SUBPICTURE_MENU:
4675       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_SUBPICTURE_MENU);
4676       break;
4677     case BVW_DVD_AUDIO_MENU:
4678       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_AUDIO_MENU);
4679       break;
4680     case BVW_DVD_ANGLE_MENU:
4681       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_ANGLE_MENU);
4682       break;
4683     case BVW_DVD_CHAPTER_MENU:
4684       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DVD_CHAPTER_MENU);
4685       break;
4686     case BVW_DVD_ROOT_MENU_UP:
4687       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_UP);
4688       break;
4689     case BVW_DVD_ROOT_MENU_DOWN:
4690       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_DOWN);
4691       break;
4692     case BVW_DVD_ROOT_MENU_LEFT:
4693       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_LEFT);
4694       break;
4695     case BVW_DVD_ROOT_MENU_RIGHT:
4696       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_RIGHT);
4697       break;
4698     case BVW_DVD_ROOT_MENU_SELECT:
4699       bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_ACTIVATE);
4700       break;
4701     case BVW_DVD_NEXT_CHAPTER:
4702       if (!handle_chapters_seek (bvw, TRUE))
4703 	handle_dvd_seek (bvw, 1, "chapter");
4704       break;
4705     case BVW_DVD_PREV_CHAPTER:
4706       if (!handle_chapters_seek (bvw, FALSE))
4707 	handle_dvd_seek (bvw, -1, "chapter");
4708       break;
4709     case BVW_DVD_NEXT_TITLE:
4710       handle_dvd_seek (bvw, 1, "title");
4711       break;
4712     case BVW_DVD_PREV_TITLE:
4713       handle_dvd_seek (bvw, -1, "title");
4714       break;
4715     default:
4716       GST_WARNING ("unhandled type %d", type);
4717       break;
4718   }
4719 }
4720 
4721 /**
4722  * bacon_video_widget_set_logo:
4723  * @bvw: a #BaconVideoWidget
4724  * @name: the icon name of the logo
4725  *
4726  * Sets the logo displayed on the video widget when no stream is loaded.
4727  **/
4728 void
bacon_video_widget_set_logo(BaconVideoWidget * bvw,const gchar * name)4729 bacon_video_widget_set_logo (BaconVideoWidget *bvw, const gchar *name)
4730 {
4731   GtkIconTheme *theme;
4732   GError *error = NULL;
4733 
4734   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4735   g_return_if_fail (name != NULL);
4736 
4737   if (bvw->logo_pixbuf != NULL)
4738     g_object_unref (bvw->logo_pixbuf);
4739 
4740   theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (bvw)));
4741   bvw->logo_pixbuf = gtk_icon_theme_load_icon (theme, name, LOGO_SIZE, 0, &error);
4742 
4743   if (error) {
4744     g_warning ("An error occurred trying to open logo %s: %s", name, error->message);
4745     g_error_free (error);
4746     return;
4747   }
4748 
4749   set_current_actor (bvw);
4750 }
4751 
4752 /**
4753  * bacon_video_widget_set_logo_mode:
4754  * @bvw: a #BaconVideoWidget
4755  * @logo_mode: %TRUE to display the logo, %FALSE otherwise
4756  *
4757  * Sets whether to display a logo set with @bacon_video_widget_set_logo when
4758  * no stream is loaded. If @logo_mode is %FALSE, nothing will be displayed
4759  * and the video widget will take up no space. Otherwise, the logo will be
4760  * displayed and will requisition a corresponding amount of space.
4761  **/
4762 void
bacon_video_widget_set_logo_mode(BaconVideoWidget * bvw,gboolean logo_mode)4763 bacon_video_widget_set_logo_mode (BaconVideoWidget * bvw, gboolean logo_mode)
4764 {
4765   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4766 
4767   logo_mode = logo_mode != FALSE;
4768 
4769   if (bvw->logo_mode != logo_mode) {
4770     bvw->logo_mode = logo_mode;
4771 
4772     set_current_actor (bvw);
4773 
4774     g_object_notify (G_OBJECT (bvw), "logo-mode");
4775     g_object_notify (G_OBJECT (bvw), "seekable");
4776   }
4777 }
4778 
4779 /**
4780  * bacon_video_widget_get_logo_mode
4781  * @bvw: a #BaconVideoWidget
4782  *
4783  * Gets whether the logo is displayed when no stream is loaded.
4784  *
4785  * Return value: %TRUE if the logo is displayed, %FALSE otherwise
4786  **/
4787 gboolean
bacon_video_widget_get_logo_mode(BaconVideoWidget * bvw)4788 bacon_video_widget_get_logo_mode (BaconVideoWidget * bvw)
4789 {
4790   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4791 
4792   return bvw->logo_mode;
4793 }
4794 
4795 static gboolean
bvw_check_for_cover_pixbuf(BaconVideoWidget * bvw)4796 bvw_check_for_cover_pixbuf (BaconVideoWidget * bvw)
4797 {
4798   GValue value = { 0, };
4799 
4800   /* for efficiency reasons (decoding of encoded image into pixbuf) we assume
4801    * that all potential images come in the same taglist, so once we've
4802    * determined the best image/cover, we assume that's really the best one
4803    * for this stream, even if more tag messages come in later (this should
4804    * not be a problem in practice) */
4805   if (bvw->cover_pixbuf)
4806     return TRUE;
4807 
4808   bacon_video_widget_get_metadata (bvw, BVW_INFO_COVER, &value);
4809   if (G_VALUE_HOLDS_OBJECT (&value)) {
4810     bvw->cover_pixbuf = g_value_dup_object (&value);
4811     g_value_unset (&value);
4812   }
4813 
4814   return (bvw->cover_pixbuf != NULL);
4815 }
4816 
4817 static const GdkPixbuf *
bvw_get_logo_pixbuf(BaconVideoWidget * bvw)4818 bvw_get_logo_pixbuf (BaconVideoWidget * bvw)
4819 {
4820   if (bvw_check_for_cover_pixbuf (bvw))
4821     return bvw->cover_pixbuf;
4822   else
4823     return bvw->logo_pixbuf;
4824 }
4825 
4826 /**
4827  * bacon_video_widget_pause:
4828  * @bvw: a #BaconVideoWidget
4829  *
4830  * Pauses the current stream in the video widget.
4831  *
4832  * If a live stream is being played, playback is stopped entirely.
4833  **/
4834 void
bacon_video_widget_pause(BaconVideoWidget * bvw)4835 bacon_video_widget_pause (BaconVideoWidget * bvw)
4836 {
4837   GstStateChangeReturn ret;
4838   GstState state;
4839 
4840   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4841   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4842   g_return_if_fail (bvw->mrl != NULL);
4843 
4844   /* Get the current state */
4845   ret = gst_element_get_state (GST_ELEMENT (bvw->play), &state, NULL, 0);
4846 
4847   if (bvw->is_live != FALSE &&
4848       ret != GST_STATE_CHANGE_NO_PREROLL &&
4849       ret != GST_STATE_CHANGE_SUCCESS &&
4850       state > GST_STATE_READY) {
4851     GST_LOG ("Stopping because we have a live stream");
4852     bacon_video_widget_stop (bvw);
4853     return;
4854   }
4855 
4856   GST_LOG ("Pausing");
4857   bvw->target_state = GST_STATE_PAUSED;
4858   gst_element_set_state (GST_ELEMENT (bvw->play), GST_STATE_PAUSED);
4859 }
4860 
4861 /**
4862  * bacon_video_widget_set_subtitle_font:
4863  * @bvw: a #BaconVideoWidget
4864  * @font: a font description string
4865  *
4866  * Sets the font size and style in which to display subtitles.
4867  *
4868  * @font is a Pango font description string, as understood by
4869  * pango_font_description_from_string().
4870  **/
4871 void
bacon_video_widget_set_subtitle_font(BaconVideoWidget * bvw,const gchar * font)4872 bacon_video_widget_set_subtitle_font (BaconVideoWidget * bvw,
4873                                           const gchar * font)
4874 {
4875   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4876   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4877 
4878   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->play), "subtitle-font-desc"))
4879     return;
4880   g_object_set (bvw->play, "subtitle-font-desc", font, NULL);
4881 }
4882 
4883 /**
4884  * bacon_video_widget_set_subtitle_encoding:
4885  * @bvw: a #BaconVideoWidget
4886  * @encoding: an encoding system
4887  *
4888  * Sets the encoding system for the subtitles, so that they can be decoded
4889  * properly.
4890  **/
4891 void
bacon_video_widget_set_subtitle_encoding(BaconVideoWidget * bvw,const char * encoding)4892 bacon_video_widget_set_subtitle_encoding (BaconVideoWidget *bvw,
4893                                           const char *encoding)
4894 {
4895   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4896   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4897 
4898   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (bvw->play), "subtitle-encoding"))
4899     return;
4900   g_object_set (bvw->play, "subtitle-encoding", encoding, NULL);
4901 }
4902 
4903 /**
4904  * bacon_video_widget_set_user_agent:
4905  * @bvw: a #BaconVideoWidget
4906  * @user_agent: a HTTP user agent string, or %NULL to use the default
4907  *
4908  * Sets the HTTP user agent string to use when fetching HTTP ressources.
4909  **/
4910 void
bacon_video_widget_set_user_agent(BaconVideoWidget * bvw,const char * user_agent)4911 bacon_video_widget_set_user_agent (BaconVideoWidget *bvw,
4912                                    const char *user_agent)
4913 {
4914   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4915 
4916   if (g_strcmp0 (user_agent, bvw->user_agent) == 0)
4917     return;
4918 
4919   g_free (bvw->user_agent);
4920   bvw->user_agent = g_strdup (user_agent);
4921 
4922   g_object_notify (G_OBJECT (bvw), "user-agent");
4923 }
4924 
4925 /**
4926  * bacon_video_widget_set_referrer:
4927  * @bvw: a #BaconVideoWidget
4928  * @referrer: a HTTP referrer URI, or %NULL
4929  *
4930  * Sets the HTTP referrer URI to use when fetching HTTP ressources.
4931  **/
4932 void
bacon_video_widget_set_referrer(BaconVideoWidget * bvw,const char * referrer)4933 bacon_video_widget_set_referrer (BaconVideoWidget *bvw,
4934                                  const char *referrer)
4935 {
4936   char *frag;
4937 
4938   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4939 
4940   if (g_strcmp0 (referrer, bvw->referrer) == 0)
4941     return;
4942 
4943   g_free (bvw->referrer);
4944   bvw->referrer = g_strdup (referrer);
4945 
4946   /* Referrer URIs must not have a fragment */
4947   if ((frag = strchr (bvw->referrer, '#')) != NULL)
4948     *frag = '\0';
4949 
4950   g_object_notify (G_OBJECT (bvw), "referrer");
4951 }
4952 
4953 /**
4954  * bacon_video_widget_can_set_volume:
4955  * @bvw: a #BaconVideoWidget
4956  *
4957  * Returns whether the volume level can be set, given the current settings.
4958  *
4959  * The volume cannot be set if the audio output type is set to
4960  * %BVW_AUDIO_SOUND_AC3PASSTHRU.
4961  *
4962  * Return value: %TRUE if the volume can be set, %FALSE otherwise
4963  **/
4964 gboolean
bacon_video_widget_can_set_volume(BaconVideoWidget * bvw)4965 bacon_video_widget_can_set_volume (BaconVideoWidget * bvw)
4966 {
4967   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
4968   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
4969 
4970   if (bvw->speakersetup == BVW_AUDIO_SOUND_AC3PASSTHRU)
4971     return FALSE;
4972 
4973   return !bvw->uses_audio_fakesink;
4974 }
4975 
4976 /**
4977  * bacon_video_widget_set_volume:
4978  * @bvw: a #BaconVideoWidget
4979  * @volume: the new volume level, as a percentage between <code class="literal">0</code> and <code class="literal">1</code>
4980  *
4981  * Sets the volume level of the stream as a percentage between <code class="literal">0</code> and <code class="literal">1</code>.
4982  *
4983  * If bacon_video_widget_can_set_volume() returns %FALSE, this is a no-op.
4984  **/
4985 void
bacon_video_widget_set_volume(BaconVideoWidget * bvw,double volume)4986 bacon_video_widget_set_volume (BaconVideoWidget * bvw, double volume)
4987 {
4988   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
4989   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
4990 
4991   if (bacon_video_widget_can_set_volume (bvw) != FALSE) {
4992     volume = CLAMP (volume, 0.0, 1.0);
4993     gst_stream_volume_set_volume (GST_STREAM_VOLUME (bvw->play),
4994                                   GST_STREAM_VOLUME_FORMAT_CUBIC,
4995                                   volume);
4996 
4997     bvw->volume = volume;
4998     g_object_notify (G_OBJECT (bvw), "volume");
4999   }
5000 }
5001 
5002 /**
5003  * bacon_video_widget_get_volume:
5004  * @bvw: a #BaconVideoWidget
5005  *
5006  * Returns the current volume level, as a percentage between <code class="literal">0</code> and <code class="literal">1</code>.
5007  *
5008  * Return value: the volume as a percentage between <code class="literal">0</code> and <code class="literal">1</code>
5009  **/
5010 double
bacon_video_widget_get_volume(BaconVideoWidget * bvw)5011 bacon_video_widget_get_volume (BaconVideoWidget * bvw)
5012 {
5013   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0.0);
5014   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), 0.0);
5015 
5016   return bvw->volume;
5017 }
5018 
5019 /**
5020  * bacon_video_widget_set_aspect_ratio:
5021  * @bvw: a #BaconVideoWidget
5022  * @ratio: the new aspect ratio
5023  *
5024  * Sets the aspect ratio used by the widget, from #BvwAspectRatio.
5025  *
5026  * Changes to this take effect immediately.
5027  **/
5028 void
bacon_video_widget_set_aspect_ratio(BaconVideoWidget * bvw,BvwAspectRatio ratio)5029 bacon_video_widget_set_aspect_ratio (BaconVideoWidget *bvw,
5030                                 BvwAspectRatio ratio)
5031 {
5032   GstMessage *msg;
5033 
5034   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5035 
5036   bvw->ratio_type = ratio;
5037   msg = gst_message_new_application (GST_OBJECT (bvw->play),
5038       gst_structure_new ("video-size", "width", G_TYPE_INT,
5039           bvw->video_width, "height", G_TYPE_INT,
5040           bvw->video_height, NULL));
5041   gst_element_post_message (bvw->play, msg);
5042 }
5043 
5044 /**
5045  * bacon_video_widget_get_aspect_ratio:
5046  * @bvw: a #BaconVideoWidget
5047  *
5048  * Returns the current aspect ratio used by the widget, from
5049  * #BvwAspectRatio.
5050  *
5051  * Return value: the aspect ratio
5052  **/
5053 BvwAspectRatio
bacon_video_widget_get_aspect_ratio(BaconVideoWidget * bvw)5054 bacon_video_widget_get_aspect_ratio (BaconVideoWidget *bvw)
5055 {
5056   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 0);
5057 
5058   return bvw->ratio_type;
5059 }
5060 
5061 /**
5062  * bacon_video_widget_set_zoom:
5063  * @bvw: a #BaconVideoWidget
5064  * @mode: the #BvwZoomMode
5065  *
5066  * Sets the zoom type applied to the video when it is displayed.
5067  **/
5068 void
bacon_video_widget_set_zoom(BaconVideoWidget * bvw,BvwZoomMode mode)5069 bacon_video_widget_set_zoom (BaconVideoWidget *bvw,
5070                              BvwZoomMode       mode)
5071 {
5072   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5073 
5074   if (bvw->frame == NULL)
5075     return;
5076 
5077   totem_aspect_frame_set_expand (TOTEM_ASPECT_FRAME (bvw->frame),
5078 			      (mode == BVW_ZOOM_EXPAND));
5079 }
5080 
5081 /**
5082  * bacon_video_widget_get_zoom:
5083  * @bvw: a #BaconVideoWidget
5084  *
5085  * Returns the zoom mode applied to videos displayed by the widget.
5086  *
5087  * Return value: a #BvwZoomMode
5088  **/
5089 BvwZoomMode
bacon_video_widget_get_zoom(BaconVideoWidget * bvw)5090 bacon_video_widget_get_zoom (BaconVideoWidget *bvw)
5091 {
5092   gboolean expand;
5093 
5094   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), BVW_ZOOM_NONE);
5095 
5096   expand = totem_aspect_frame_get_expand (TOTEM_ASPECT_FRAME (bvw->frame));
5097   return expand ? BVW_ZOOM_EXPAND : BVW_ZOOM_NONE;
5098 }
5099 
5100 /**
5101  * bacon_video_widget_set_rotation:
5102  * @bvw: a #BaconVideoWidget
5103  * @rotation: the #BvwRotation of the video in degrees
5104  *
5105  * Sets the rotation to be applied to the video when it is displayed.
5106  **/
5107 void
bacon_video_widget_set_rotation(BaconVideoWidget * bvw,BvwRotation rotation)5108 bacon_video_widget_set_rotation (BaconVideoWidget *bvw,
5109 				 BvwRotation       rotation)
5110 {
5111   gfloat angle;
5112 
5113   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5114 
5115   if (bvw->frame == NULL)
5116     return;
5117 
5118   GST_DEBUG ("Rotating to %s (%f degrees) from %s",
5119 	     get_type_name (BVW_TYPE_ROTATION, rotation),
5120 	     rotation * 90.0,
5121 	     get_type_name (BVW_TYPE_ROTATION, bvw->rotation));
5122 
5123   bvw->rotation = rotation;
5124 
5125   angle = rotation * 90.0;
5126   totem_aspect_frame_set_rotation (TOTEM_ASPECT_FRAME (bvw->frame), angle);
5127 }
5128 
5129 /**
5130  * bacon_video_widget_get_rotation:
5131  * @bvw: a #BaconVideoWidget
5132  *
5133  * Returns the angle of rotation of the video, in degrees.
5134  *
5135  * Return value: a #BvwRotation.
5136  **/
5137 BvwRotation
bacon_video_widget_get_rotation(BaconVideoWidget * bvw)5138 bacon_video_widget_get_rotation (BaconVideoWidget *bvw)
5139 {
5140   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), BVW_ROTATION_R_ZERO);
5141 
5142   return bvw->rotation;
5143 }
5144 
5145 /* Search for the color balance channel corresponding to type and return it. */
5146 static GstColorBalanceChannel *
bvw_get_color_balance_channel(GstColorBalance * color_balance,BvwVideoProperty type)5147 bvw_get_color_balance_channel (GstColorBalance * color_balance,
5148     BvwVideoProperty type)
5149 {
5150   const GList *channels;
5151 
5152   channels = gst_color_balance_list_channels (color_balance);
5153 
5154   for (; channels != NULL; channels = channels->next) {
5155     GstColorBalanceChannel *c = channels->data;
5156 
5157     if (type == BVW_VIDEO_BRIGHTNESS && g_strrstr (c->label, "BRIGHTNESS"))
5158       return g_object_ref (c);
5159     else if (type == BVW_VIDEO_CONTRAST && g_strrstr (c->label, "CONTRAST"))
5160       return g_object_ref (c);
5161     else if (type == BVW_VIDEO_SATURATION && g_strrstr (c->label, "SATURATION"))
5162       return g_object_ref (c);
5163     else if (type == BVW_VIDEO_HUE && g_strrstr (c->label, "HUE"))
5164       return g_object_ref (c);
5165   }
5166 
5167   return NULL;
5168 }
5169 
5170 /**
5171  * bacon_video_widget_get_video_property:
5172  * @bvw: a #BaconVideoWidget
5173  * @type: the type of property
5174  *
5175  * Returns the given property of the video display, such as its brightness or saturation.
5176  *
5177  * It is returned as a percentage in the full range of integer values; from <code class="literal">0</code>
5178  * to <code class="literal">65535</code> (inclusive), where <code class="literal">32768</code> is the default.
5179  *
5180  * Return value: the property's value, in the range <code class="literal">0</code> to <code class="literal">65535</code>
5181  **/
5182 int
bacon_video_widget_get_video_property(BaconVideoWidget * bvw,BvwVideoProperty type)5183 bacon_video_widget_get_video_property (BaconVideoWidget *bvw,
5184                                        BvwVideoProperty type)
5185 {
5186   GstColorBalanceChannel *found_channel = NULL;
5187   int ret, cur;
5188 
5189   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), 65535/2);
5190   g_return_val_if_fail (bvw->play != NULL, 65535/2);
5191 
5192   ret = 0;
5193 
5194 
5195   found_channel = bvw_get_color_balance_channel (GST_COLOR_BALANCE (bvw->play), type);
5196   cur = gst_color_balance_get_value (GST_COLOR_BALANCE (bvw->play), found_channel);
5197 
5198   GST_DEBUG ("channel %s: cur=%d, min=%d, max=%d", found_channel->label,
5199 	     cur, found_channel->min_value, found_channel->max_value);
5200 
5201   ret = floor (0.5 +
5202 	       ((double) cur - found_channel->min_value) * 65535 /
5203 	       ((double) found_channel->max_value - found_channel->min_value));
5204 
5205   GST_DEBUG ("channel %s: returning value %d", found_channel->label, ret);
5206   g_object_unref (found_channel);
5207   return ret;
5208 }
5209 
5210 /**
5211  * bacon_video_widget_has_menus:
5212  * @bvw: a #BaconVideoWidget
5213  *
5214  * Returns whether the widget is currently displaying a menu,
5215  * such as a DVD menu.
5216  *
5217  * Return value: %TRUE if a menu is displayed, %FALSE otherwise
5218  **/
5219 gboolean
bacon_video_widget_has_menus(BaconVideoWidget * bvw)5220 bacon_video_widget_has_menus (BaconVideoWidget *bvw)
5221 {
5222     g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
5223 
5224     if (bacon_video_widget_is_playing (bvw) == FALSE)
5225         return FALSE;
5226 
5227     return bvw->is_menu;
5228 }
5229 
5230 /**
5231  * bacon_video_widget_has_angles:
5232  * @bvw: a #BaconVideoWidget
5233  *
5234  * Returns whether the widget is currently playing a stream with
5235  * multiple angles.
5236  *
5237  * Return value: %TRUE if the current video stream has multiple
5238  * angles, %FALSE otherwise
5239  **/
5240 gboolean
bacon_video_widget_has_angles(BaconVideoWidget * bvw)5241 bacon_video_widget_has_angles (BaconVideoWidget *bvw)
5242 {
5243     guint n_video;
5244 
5245     g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
5246 
5247     if (bacon_video_widget_is_playing (bvw) == FALSE)
5248         return FALSE;
5249 
5250     if (bvw->has_angles)
5251         return TRUE;
5252 
5253     g_object_get (G_OBJECT (bvw->play), "n-video", &n_video, NULL);
5254 
5255     return n_video > 1;
5256 }
5257 
5258 /**
5259  * bacon_video_widget_set_next_angle:
5260  * @bvw: a #BaconVideoWidget
5261  *
5262  * Select the next angle, or video track in the playing stream.
5263  **/
5264 void
bacon_video_widget_set_next_angle(BaconVideoWidget * bvw)5265 bacon_video_widget_set_next_angle (BaconVideoWidget *bvw)
5266 {
5267     guint n_video, current_video;
5268 
5269     g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5270 
5271     if (bacon_video_widget_is_playing (bvw) == FALSE)
5272         return;
5273 
5274     if (bvw->has_angles) {
5275         GST_DEBUG ("Sending event 'next-angle'");
5276         bvw_do_navigation_command (bvw, GST_NAVIGATION_COMMAND_NEXT_ANGLE);
5277         return;
5278     }
5279 
5280     g_object_get (G_OBJECT (bvw->play),
5281 		  "current-video", &current_video,
5282 		  "n-video", &n_video,
5283 		  NULL);
5284 
5285     if (n_video <= 1) {
5286         GST_DEBUG ("Not setting next video stream, we have %d video streams", n_video);
5287 	return;
5288     }
5289 
5290     current_video++;
5291     if (current_video == n_video)
5292       current_video = 0;
5293 
5294     GST_DEBUG ("Setting current-video to %d/%d", current_video, n_video);
5295     g_object_set (G_OBJECT (bvw->play), "current-video", current_video, NULL);
5296 }
5297 
5298 static gboolean
notify_volume_idle_cb(BaconVideoWidget * bvw)5299 notify_volume_idle_cb (BaconVideoWidget *bvw)
5300 {
5301   gdouble vol;
5302 
5303   vol = gst_stream_volume_get_volume (GST_STREAM_VOLUME (bvw->play),
5304                                       GST_STREAM_VOLUME_FORMAT_CUBIC);
5305 
5306   bvw->volume = vol;
5307 
5308   g_object_notify (G_OBJECT (bvw), "volume");
5309 
5310   return FALSE;
5311 }
5312 
5313 static void
notify_volume_cb(GObject * object,GParamSpec * pspec,BaconVideoWidget * bvw)5314 notify_volume_cb (GObject             *object,
5315 		  GParamSpec          *pspec,
5316 		  BaconVideoWidget    *bvw)
5317 {
5318   guint id;
5319 
5320   id = g_idle_add ((GSourceFunc) notify_volume_idle_cb, bvw);
5321   g_source_set_name_by_id (id, "[totem] notify_volume_idle_cb");
5322 }
5323 
5324 /**
5325  * bacon_video_widget_set_video_property:
5326  * @bvw: a #BaconVideoWidget
5327  * @type: the type of property
5328  * @value: the property's value, in the range <code class="literal">0</code> to <code class="literal">65535</code>
5329  *
5330  * Sets the given property of the video display, such as its brightness or saturation.
5331  *
5332  * It should be given as a percentage in the full range of integer values; from <code class="literal">0</code>
5333  * to <code class="literal">65535</code> (inclusive), where <code class="literal">32768</code> is the default.
5334  **/
5335 void
bacon_video_widget_set_video_property(BaconVideoWidget * bvw,BvwVideoProperty type,int value)5336 bacon_video_widget_set_video_property (BaconVideoWidget *bvw,
5337                                        BvwVideoProperty type,
5338                                        int value)
5339 {
5340   GstColorBalanceChannel *found_channel = NULL;
5341   int i_value;
5342 
5343   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5344   g_return_if_fail (bvw->play != NULL);
5345 
5346   GST_DEBUG ("set video property type %d to value %d", type, value);
5347 
5348   if ( !(value <= 65535 && value >= 0) )
5349     return;
5350 
5351   found_channel = bvw_get_color_balance_channel (GST_COLOR_BALANCE (bvw->play), type);
5352   i_value = floor (0.5 + value * ((double) found_channel->max_value -
5353 				  found_channel->min_value) / 65535 + found_channel->min_value);
5354 
5355   GST_DEBUG ("channel %s: set to %d/65535", found_channel->label, value);
5356 
5357   gst_color_balance_set_value (GST_COLOR_BALANCE (bvw->play), found_channel, i_value);
5358 
5359   GST_DEBUG ("channel %s: val=%d, min=%d, max=%d", found_channel->label,
5360 	     i_value, found_channel->min_value, found_channel->max_value);
5361 
5362   g_object_unref (found_channel);
5363 
5364   /* Notify of the property change */
5365   g_object_notify (G_OBJECT (bvw), video_props_str[type]);
5366 
5367   GST_DEBUG ("setting value %d", value);
5368 }
5369 
5370 /**
5371  * bacon_video_widget_get_position:
5372  * @bvw: a #BaconVideoWidget
5373  *
5374  * Returns the current position in the stream, as a value between
5375  * <code class="literal">0</code> and <code class="literal">1</code>.
5376  *
5377  * Return value: the current position, or <code class="literal">-1</code>
5378  **/
5379 double
bacon_video_widget_get_position(BaconVideoWidget * bvw)5380 bacon_video_widget_get_position (BaconVideoWidget * bvw)
5381 {
5382   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
5383   return bvw->current_position;
5384 }
5385 
5386 /**
5387  * bacon_video_widget_get_current_time:
5388  * @bvw: a #BaconVideoWidget
5389  *
5390  * Returns the current position in the stream, as the time (in milliseconds)
5391  * since the beginning of the stream.
5392  *
5393  * Return value: time since the beginning of the stream, in milliseconds, or <code class="literal">-1</code>
5394  **/
5395 gint64
bacon_video_widget_get_current_time(BaconVideoWidget * bvw)5396 bacon_video_widget_get_current_time (BaconVideoWidget * bvw)
5397 {
5398   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
5399   return bvw->current_time;
5400 }
5401 
5402 /**
5403  * bacon_video_widget_get_stream_length:
5404  * @bvw: a #BaconVideoWidget
5405  *
5406  * Returns the total length of the stream, in milliseconds.
5407  *
5408  * Return value: the stream length, in milliseconds, or <code class="literal">-1</code>
5409  **/
5410 gint64
bacon_video_widget_get_stream_length(BaconVideoWidget * bvw)5411 bacon_video_widget_get_stream_length (BaconVideoWidget * bvw)
5412 {
5413   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), -1);
5414 
5415   if (bvw->stream_length == 0 && bvw->play != NULL) {
5416     gint64 len = -1;
5417 
5418     if (gst_element_query_duration (bvw->play, GST_FORMAT_TIME, &len) && len != -1) {
5419       bvw->stream_length = len / GST_MSECOND;
5420     }
5421   }
5422 
5423   return bvw->stream_length;
5424 }
5425 
5426 /**
5427  * bacon_video_widget_is_playing:
5428  * @bvw: a #BaconVideoWidget
5429  *
5430  * Returns whether the widget is currently playing a stream.
5431  *
5432  * Return value: %TRUE if a stream is playing, %FALSE otherwise
5433  **/
5434 gboolean
bacon_video_widget_is_playing(BaconVideoWidget * bvw)5435 bacon_video_widget_is_playing (BaconVideoWidget * bvw)
5436 {
5437   gboolean ret;
5438 
5439   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
5440   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
5441 
5442   ret = (bvw->target_state == GST_STATE_PLAYING);
5443   GST_LOG ("%splaying", (ret) ? "" : "not ");
5444 
5445   return ret;
5446 }
5447 
5448 /**
5449  * bacon_video_widget_is_seekable:
5450  * @bvw: a #BaconVideoWidget
5451  *
5452  * Returns whether seeking is possible in the current stream.
5453  *
5454  * If no stream is loaded, %FALSE is returned.
5455  *
5456  * Return value: %TRUE if the stream is seekable, %FALSE otherwise
5457  **/
5458 gboolean
bacon_video_widget_is_seekable(BaconVideoWidget * bvw)5459 bacon_video_widget_is_seekable (BaconVideoWidget * bvw)
5460 {
5461   gboolean res;
5462   gint old_seekable;
5463 
5464   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
5465   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
5466 
5467   if (bvw->mrl == NULL)
5468     return FALSE;
5469 
5470   old_seekable = bvw->seekable;
5471 
5472   if (bvw->is_menu != FALSE)
5473     return FALSE;
5474 
5475   if (bvw->seekable == -1) {
5476     GstQuery *query;
5477 
5478     query = gst_query_new_seeking (GST_FORMAT_TIME);
5479     if (gst_element_query (bvw->play, query)) {
5480       gst_query_parse_seeking (query, NULL, &res, NULL, NULL);
5481       GST_DEBUG ("seeking query says the stream is%s seekable", (res) ? "" : " not");
5482       bvw->seekable = (res) ? 1 : 0;
5483     } else {
5484       GST_DEBUG ("seeking query failed");
5485     }
5486     gst_query_unref (query);
5487   }
5488 
5489   if (bvw->seekable != -1) {
5490     res = (bvw->seekable != 0);
5491     goto done;
5492   }
5493 
5494   /* Try to guess from duration. This is very unreliable
5495    * though so don't save it */
5496   if (bvw->stream_length == 0) {
5497     res = (bacon_video_widget_get_stream_length (bvw) > 0);
5498   } else {
5499     res = (bvw->stream_length > 0);
5500   }
5501 
5502 done:
5503 
5504   if (old_seekable != bvw->seekable)
5505     g_object_notify (G_OBJECT (bvw), "seekable");
5506 
5507   GST_DEBUG ("stream is%s seekable", (res) ? "" : " not");
5508   return res;
5509 }
5510 
5511 static gint
bvw_get_current_stream_num(BaconVideoWidget * bvw,const gchar * stream_type)5512 bvw_get_current_stream_num (BaconVideoWidget * bvw,
5513     const gchar *stream_type)
5514 {
5515   gchar *lower, *cur_prop_str;
5516   gint stream_num = -1;
5517 
5518   if (bvw->play == NULL)
5519     return stream_num;
5520 
5521   lower = g_ascii_strdown (stream_type, -1);
5522   cur_prop_str = g_strconcat ("current-", lower, NULL);
5523   g_object_get (bvw->play, cur_prop_str, &stream_num, NULL);
5524   g_free (cur_prop_str);
5525   g_free (lower);
5526 
5527   GST_LOG ("current %s stream: %d", stream_type, stream_num);
5528   return stream_num;
5529 }
5530 
5531 static GstTagList *
bvw_get_tags_of_current_stream(BaconVideoWidget * bvw,const gchar * stream_type)5532 bvw_get_tags_of_current_stream (BaconVideoWidget * bvw,
5533     const gchar *stream_type)
5534 {
5535   GstTagList *tags = NULL;
5536   gint stream_num = -1;
5537   gchar *lower, *cur_sig_str;
5538 
5539   stream_num = bvw_get_current_stream_num (bvw, stream_type);
5540   if (stream_num < 0)
5541     return NULL;
5542 
5543   lower = g_ascii_strdown (stream_type, -1);
5544   cur_sig_str = g_strconcat ("get-", lower, "-tags", NULL);
5545   g_signal_emit_by_name (bvw->play, cur_sig_str, stream_num, &tags);
5546   g_free (cur_sig_str);
5547   g_free (lower);
5548 
5549   GST_LOG ("current %s stream tags %" GST_PTR_FORMAT, stream_type, tags);
5550   return tags;
5551 }
5552 
5553 static GstCaps *
bvw_get_caps_of_current_stream(BaconVideoWidget * bvw,const gchar * stream_type)5554 bvw_get_caps_of_current_stream (BaconVideoWidget * bvw,
5555     const gchar *stream_type)
5556 {
5557   GstCaps *caps = NULL;
5558   gint stream_num = -1;
5559   GstPad *current;
5560   gchar *lower, *cur_sig_str;
5561 
5562   stream_num = bvw_get_current_stream_num (bvw, stream_type);
5563   if (stream_num < 0)
5564     return NULL;
5565 
5566   lower = g_ascii_strdown (stream_type, -1);
5567   cur_sig_str = g_strconcat ("get-", lower, "-pad", NULL);
5568   g_signal_emit_by_name (bvw->play, cur_sig_str, stream_num, &current);
5569   g_free (cur_sig_str);
5570   g_free (lower);
5571 
5572   if (current != NULL) {
5573     caps = gst_pad_get_current_caps (current);
5574     gst_object_unref (current);
5575   }
5576   GST_LOG ("current %s stream caps: %" GST_PTR_FORMAT, stream_type, caps);
5577   return caps;
5578 }
5579 
5580 static gboolean
audio_caps_have_LFE(GstStructure * s)5581 audio_caps_have_LFE (GstStructure * s)
5582 {
5583   guint64 mask;
5584   int channels;
5585 
5586   if (!gst_structure_get_int (s, "channels", &channels) ||
5587       channels == 0)
5588     return FALSE;
5589 
5590   if (!gst_structure_get (s, "channel-mask", GST_TYPE_BITMASK, &mask, NULL))
5591     return FALSE;
5592 
5593   if (mask & GST_AUDIO_CHANNEL_POSITION_LFE1 ||
5594       mask & GST_AUDIO_CHANNEL_POSITION_LFE2)
5595     return TRUE;
5596 
5597   return FALSE;
5598 }
5599 
5600 static void
bacon_video_widget_get_metadata_string(BaconVideoWidget * bvw,BvwMetadataType type,GValue * value)5601 bacon_video_widget_get_metadata_string (BaconVideoWidget * bvw,
5602                                         BvwMetadataType type,
5603                                         GValue * value)
5604 {
5605   char *string = NULL;
5606   gboolean res = FALSE;
5607 
5608   g_value_init (value, G_TYPE_STRING);
5609 
5610   if (bvw->play == NULL) {
5611     g_value_set_string (value, NULL);
5612     return;
5613   }
5614 
5615   switch (type) {
5616     case BVW_INFO_TITLE:
5617       if (bvw->tagcache != NULL) {
5618         res = gst_tag_list_get_string_index (bvw->tagcache,
5619                                              GST_TAG_TITLE, 0, &string);
5620       }
5621       break;
5622     case BVW_INFO_ARTIST:
5623       if (bvw->tagcache != NULL) {
5624         res = gst_tag_list_get_string_index (bvw->tagcache,
5625                                              GST_TAG_ARTIST, 0, &string);
5626       }
5627       break;
5628     case BVW_INFO_YEAR:
5629       if (bvw->tagcache != NULL) {
5630         GDate *date;
5631         GstDateTime *datetime;
5632 
5633         if ((res = gst_tag_list_get_date (bvw->tagcache,
5634                                           GST_TAG_DATE, &date))) {
5635           string = g_strdup_printf ("%d", g_date_get_year (date));
5636           g_date_free (date);
5637         } else if ((res = gst_tag_list_get_date_time (bvw->tagcache,
5638                                                       GST_TAG_DATE_TIME, &datetime))) {
5639           string = g_strdup_printf ("%d", gst_date_time_get_year (datetime));
5640           gst_date_time_unref (datetime);
5641         }
5642       }
5643       break;
5644     case BVW_INFO_COMMENT:
5645       if (bvw->tagcache != NULL) {
5646         res = gst_tag_list_get_string_index (bvw->tagcache,
5647                                              GST_TAG_COMMENT, 0, &string);
5648 
5649         /* Use the Comment; if that fails, use Description as specified by:
5650          * http://xiph.org/vorbis/doc/v-comment.html */
5651         if (!res) {
5652           res = gst_tag_list_get_string_index (bvw->tagcache,
5653                                                GST_TAG_DESCRIPTION, 0, &string);
5654         }
5655       }
5656       break;
5657     case BVW_INFO_ALBUM:
5658       if (bvw->tagcache != NULL) {
5659         res = gst_tag_list_get_string_index (bvw->tagcache,
5660                                              GST_TAG_ALBUM, 0, &string);
5661       }
5662       break;
5663     case BVW_INFO_CONTAINER:
5664       if (bvw->tagcache != NULL) {
5665         res = gst_tag_list_get_string_index (bvw->tagcache,
5666                                              GST_TAG_CONTAINER_FORMAT, 0, &string);
5667       }
5668       break;
5669     case BVW_INFO_VIDEO_CODEC: {
5670       GstTagList *tags;
5671 
5672       /* try to get this from the stream info first */
5673       if ((tags = bvw_get_tags_of_current_stream (bvw, "video"))) {
5674         res = gst_tag_list_get_string (tags, GST_TAG_CODEC, &string);
5675 	gst_tag_list_unref (tags);
5676       }
5677 
5678       /* if that didn't work, try the aggregated tags */
5679       if (!res && bvw->tagcache != NULL) {
5680         res = gst_tag_list_get_string (bvw->tagcache,
5681             GST_TAG_VIDEO_CODEC, &string);
5682       }
5683       break;
5684     }
5685     case BVW_INFO_AUDIO_CODEC: {
5686       GstTagList *tags;
5687 
5688       /* try to get this from the stream info first */
5689       if ((tags = bvw_get_tags_of_current_stream (bvw, "audio"))) {
5690         res = gst_tag_list_get_string (tags, GST_TAG_CODEC, &string);
5691 	gst_tag_list_unref (tags);
5692       }
5693 
5694       /* if that didn't work, try the aggregated tags */
5695       if (!res && bvw->tagcache != NULL) {
5696         res = gst_tag_list_get_string (bvw->tagcache,
5697             GST_TAG_AUDIO_CODEC, &string);
5698       }
5699       break;
5700     }
5701     case BVW_INFO_AUDIO_CHANNELS: {
5702       GstStructure *s;
5703       GstCaps *caps;
5704 
5705       caps = bvw_get_caps_of_current_stream (bvw, "audio");
5706       if (caps) {
5707         gint channels = 0;
5708 
5709         s = gst_caps_get_structure (caps, 0);
5710         if ((res = gst_structure_get_int (s, "channels", &channels))) {
5711           /* FIXME: do something more sophisticated - but what? */
5712           if (channels > 2 && audio_caps_have_LFE (s)) {
5713             string = g_strdup_printf ("%s %d.1", _("Surround"), channels - 1);
5714           } else if (channels == 1) {
5715             string = g_strdup (_("Mono"));
5716           } else if (channels == 2) {
5717             string = g_strdup (_("Stereo"));
5718           } else {
5719             string = g_strdup_printf ("%d", channels);
5720           }
5721         }
5722         gst_caps_unref (caps);
5723       }
5724       break;
5725     }
5726 
5727     case BVW_INFO_DURATION:
5728     case BVW_INFO_TRACK_NUMBER:
5729     case BVW_INFO_COVER:
5730     case BVW_INFO_HAS_VIDEO:
5731     case BVW_INFO_DIMENSION_X:
5732     case BVW_INFO_DIMENSION_Y:
5733     case BVW_INFO_VIDEO_BITRATE:
5734     case BVW_INFO_FPS:
5735     case BVW_INFO_HAS_AUDIO:
5736     case BVW_INFO_AUDIO_BITRATE:
5737     case BVW_INFO_AUDIO_SAMPLE_RATE:
5738       /* Not strings */
5739     default:
5740       g_assert_not_reached ();
5741     }
5742 
5743   /* Remove line feeds */
5744   if (string && strstr (string, "\n") != NULL)
5745     g_strdelimit (string, "\n", ' ');
5746   if (string != NULL)
5747     string = g_strstrip (string);
5748 
5749   if (res && string && *string != '\0' && g_utf8_validate (string, -1, NULL)) {
5750     g_value_take_string (value, string);
5751     GST_DEBUG ("%s = '%s'", get_type_name (BVW_TYPE_METADATA_TYPE, type), string);
5752   } else {
5753     g_value_set_string (value, NULL);
5754     g_free (string);
5755   }
5756 
5757   return;
5758 }
5759 
5760 static void
bacon_video_widget_get_metadata_int(BaconVideoWidget * bvw,BvwMetadataType type,GValue * value)5761 bacon_video_widget_get_metadata_int (BaconVideoWidget * bvw,
5762                                      BvwMetadataType type,
5763                                      GValue * value)
5764 {
5765   int integer = 0;
5766 
5767   g_value_init (value, G_TYPE_INT);
5768 
5769   if (bvw->play == NULL) {
5770     g_value_set_int (value, 0);
5771     return;
5772   }
5773 
5774   switch (type) {
5775     case BVW_INFO_DURATION:
5776       integer = bacon_video_widget_get_stream_length (bvw) / 1000;
5777       break;
5778     case BVW_INFO_TRACK_NUMBER:
5779       if (bvw->tagcache == NULL)
5780         break;
5781       if (!gst_tag_list_get_uint (bvw->tagcache,
5782                                   GST_TAG_TRACK_NUMBER, (guint *) &integer))
5783         integer = 0;
5784       break;
5785     case BVW_INFO_DIMENSION_X:
5786       integer = bvw->video_width;
5787       break;
5788     case BVW_INFO_DIMENSION_Y:
5789       integer = bvw->video_height;
5790       break;
5791     case BVW_INFO_AUDIO_BITRATE:
5792       if (bvw->audiotags == NULL)
5793         break;
5794       if (gst_tag_list_get_uint (bvw->audiotags, GST_TAG_BITRATE,
5795           (guint *)&integer) ||
5796           gst_tag_list_get_uint (bvw->audiotags, GST_TAG_NOMINAL_BITRATE,
5797           (guint *)&integer)) {
5798         integer /= 1000;
5799       }
5800       break;
5801     case BVW_INFO_VIDEO_BITRATE:
5802       if (bvw->videotags == NULL)
5803         break;
5804       if (gst_tag_list_get_uint (bvw->videotags, GST_TAG_BITRATE,
5805           (guint *)&integer) ||
5806           gst_tag_list_get_uint (bvw->videotags, GST_TAG_NOMINAL_BITRATE,
5807           (guint *)&integer)) {
5808         integer /= 1000;
5809       }
5810       break;
5811     case BVW_INFO_AUDIO_SAMPLE_RATE: {
5812       GstStructure *s;
5813       GstCaps *caps;
5814 
5815       caps = bvw_get_caps_of_current_stream (bvw, "audio");
5816       if (caps) {
5817         s = gst_caps_get_structure (caps, 0);
5818         gst_structure_get_int (s, "rate", &integer);
5819         gst_caps_unref (caps);
5820       }
5821       break;
5822     }
5823 
5824     case BVW_INFO_TITLE:
5825     case BVW_INFO_ARTIST:
5826     case BVW_INFO_YEAR:
5827     case BVW_INFO_COMMENT:
5828     case BVW_INFO_ALBUM:
5829     case BVW_INFO_COVER:
5830     case BVW_INFO_CONTAINER:
5831     case BVW_INFO_HAS_VIDEO:
5832     case BVW_INFO_VIDEO_CODEC:
5833     case BVW_INFO_HAS_AUDIO:
5834     case BVW_INFO_AUDIO_CODEC:
5835     case BVW_INFO_AUDIO_CHANNELS:
5836       /* Not ints */
5837     default:
5838       g_assert_not_reached ();
5839     }
5840 
5841   g_value_set_int (value, integer);
5842   GST_DEBUG ("%s = %d", get_type_name (BVW_TYPE_METADATA_TYPE, type), integer);
5843 
5844   return;
5845 }
5846 
5847 static void
bacon_video_widget_get_metadata_bool(BaconVideoWidget * bvw,BvwMetadataType type,GValue * value)5848 bacon_video_widget_get_metadata_bool (BaconVideoWidget * bvw,
5849                                       BvwMetadataType type,
5850                                       GValue * value)
5851 {
5852   gboolean boolean = FALSE;
5853 
5854   g_value_init (value, G_TYPE_BOOLEAN);
5855 
5856   if (bvw->play == NULL) {
5857     g_value_set_boolean (value, FALSE);
5858     return;
5859   }
5860 
5861   GST_DEBUG ("tagcache  = %" GST_PTR_FORMAT, bvw->tagcache);
5862   GST_DEBUG ("videotags = %" GST_PTR_FORMAT, bvw->videotags);
5863   GST_DEBUG ("audiotags = %" GST_PTR_FORMAT, bvw->audiotags);
5864 
5865   switch (type)
5866   {
5867     case BVW_INFO_HAS_VIDEO:
5868       boolean = bvw->media_has_video;
5869       break;
5870     case BVW_INFO_HAS_AUDIO:
5871       boolean = bvw->media_has_audio;
5872       break;
5873 
5874     case BVW_INFO_TITLE:
5875     case BVW_INFO_ARTIST:
5876     case BVW_INFO_YEAR:
5877     case BVW_INFO_COMMENT:
5878     case BVW_INFO_ALBUM:
5879     case BVW_INFO_DURATION:
5880     case BVW_INFO_TRACK_NUMBER:
5881     case BVW_INFO_COVER:
5882     case BVW_INFO_CONTAINER:
5883     case BVW_INFO_DIMENSION_X:
5884     case BVW_INFO_DIMENSION_Y:
5885     case BVW_INFO_VIDEO_BITRATE:
5886     case BVW_INFO_VIDEO_CODEC:
5887     case BVW_INFO_FPS:
5888     case BVW_INFO_AUDIO_BITRATE:
5889     case BVW_INFO_AUDIO_CODEC:
5890     case BVW_INFO_AUDIO_SAMPLE_RATE:
5891     case BVW_INFO_AUDIO_CHANNELS:
5892       /* Not bools */
5893     default:
5894       g_assert_not_reached ();
5895   }
5896 
5897   g_value_set_boolean (value, boolean);
5898   GST_DEBUG ("%s = %s", get_type_name (BVW_TYPE_METADATA_TYPE, type), (boolean) ? "yes" : "no");
5899 
5900   return;
5901 }
5902 
5903 /**
5904  * bacon_video_widget_get_metadata:
5905  * @bvw: a #BaconVideoWidget
5906  * @type: the type of metadata to return
5907  * @value: a #GValue
5908  *
5909  * Provides metadata of the given @type about the current stream in @value.
5910  *
5911  * Free the #GValue with g_value_unset().
5912  **/
5913 void
bacon_video_widget_get_metadata(BaconVideoWidget * bvw,BvwMetadataType type,GValue * value)5914 bacon_video_widget_get_metadata (BaconVideoWidget * bvw,
5915                                  BvwMetadataType type,
5916                                  GValue * value)
5917 {
5918   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
5919   g_return_if_fail (GST_IS_ELEMENT (bvw->play));
5920 
5921   switch (type)
5922     {
5923     case BVW_INFO_TITLE:
5924     case BVW_INFO_ARTIST:
5925     case BVW_INFO_YEAR:
5926     case BVW_INFO_COMMENT:
5927     case BVW_INFO_ALBUM:
5928     case BVW_INFO_CONTAINER:
5929     case BVW_INFO_VIDEO_CODEC:
5930     case BVW_INFO_AUDIO_CODEC:
5931     case BVW_INFO_AUDIO_CHANNELS:
5932       bacon_video_widget_get_metadata_string (bvw, type, value);
5933       break;
5934     case BVW_INFO_DURATION:
5935     case BVW_INFO_DIMENSION_X:
5936     case BVW_INFO_DIMENSION_Y:
5937     case BVW_INFO_AUDIO_BITRATE:
5938     case BVW_INFO_VIDEO_BITRATE:
5939     case BVW_INFO_TRACK_NUMBER:
5940     case BVW_INFO_AUDIO_SAMPLE_RATE:
5941       bacon_video_widget_get_metadata_int (bvw, type, value);
5942       break;
5943     case BVW_INFO_HAS_VIDEO:
5944     case BVW_INFO_HAS_AUDIO:
5945       bacon_video_widget_get_metadata_bool (bvw, type, value);
5946       break;
5947     case BVW_INFO_COVER:
5948       {
5949 	GdkPixbuf *pixbuf;
5950 
5951 	if (!bvw->tagcache)
5952 	  break;
5953 
5954 	pixbuf = totem_gst_tag_list_get_cover (bvw->tagcache);
5955 	if (pixbuf) {
5956 	  g_value_init (value, GDK_TYPE_PIXBUF);
5957 	  g_value_take_object (value, pixbuf);
5958         }
5959       }
5960       break;
5961     case BVW_INFO_FPS:
5962       {
5963 	float fps = 0.0;
5964 
5965 	if (bvw->video_fps_d > 0)
5966 	  fps = (float) bvw->video_fps_n / (float) bvw->video_fps_d;
5967 
5968 	g_value_init (value, G_TYPE_FLOAT);
5969 	g_value_set_float (value, fps);
5970       }
5971       break;
5972     default:
5973       g_return_if_reached ();
5974     }
5975 
5976   return;
5977 }
5978 
5979 /* Screenshot functions */
5980 
5981 /**
5982  * bacon_video_widget_can_get_frames:
5983  * @bvw: a #BaconVideoWidget
5984  * @error: a #GError, or %NULL
5985  *
5986  * Determines whether individual frames from the current stream can
5987  * be returned using bacon_video_widget_get_current_frame().
5988  *
5989  * Frames cannot be returned for audio-only streams, unless visualisations
5990  * are enabled.
5991  *
5992  * Return value: %TRUE if frames can be captured, %FALSE otherwise
5993  **/
5994 gboolean
bacon_video_widget_can_get_frames(BaconVideoWidget * bvw,GError ** error)5995 bacon_video_widget_can_get_frames (BaconVideoWidget * bvw, GError ** error)
5996 {
5997   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
5998   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
5999 
6000   /* check for video */
6001   if (!bvw->media_has_video) {
6002     g_set_error_literal (error, BVW_ERROR, BVW_ERROR_CANNOT_CAPTURE,
6003         _("Media contains no supported video streams."));
6004     return FALSE;
6005   }
6006 
6007   return TRUE;
6008 }
6009 
6010 /**
6011  * bacon_video_widget_get_current_frame:
6012  * @bvw: a #BaconVideoWidget
6013  *
6014  * Returns a #GdkPixbuf containing the current frame from the playing
6015  * stream. This will wait for any pending seeks to complete before
6016  * capturing the frame.
6017  *
6018  * Return value: the current frame, or %NULL; unref with g_object_unref()
6019  **/
6020 GdkPixbuf *
bacon_video_widget_get_current_frame(BaconVideoWidget * bvw)6021 bacon_video_widget_get_current_frame (BaconVideoWidget * bvw)
6022 {
6023   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), NULL);
6024   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), NULL);
6025 
6026   /* no video info */
6027   if (!bvw->video_width || !bvw->video_height) {
6028     GST_DEBUG ("Could not take screenshot: %s", "no video info");
6029     g_warning ("Could not take screenshot: %s", "no video info");
6030     return NULL;
6031   }
6032 
6033   return totem_gst_playbin_get_frame (bvw->play);
6034 }
6035 
6036 /* =========================================== */
6037 /*                                             */
6038 /*          Widget typing & Creation           */
6039 /*                                             */
6040 /* =========================================== */
6041 
6042 /**
6043  * bacon_video_widget_get_option_group:
6044  *
6045  * Returns the #GOptionGroup containing command-line options for
6046  * #BaconVideoWidget.
6047  *
6048  * Applications must call either this exactly once.
6049  *
6050  * Return value: a #GOptionGroup giving command-line options for #BaconVideoWidget
6051  **/
6052 GOptionGroup*
bacon_video_widget_get_option_group(void)6053 bacon_video_widget_get_option_group (void)
6054 {
6055   return gst_init_get_option_group ();
6056 }
6057 
6058 GQuark
bacon_video_widget_error_quark(void)6059 bacon_video_widget_error_quark (void)
6060 {
6061   static GQuark q; /* 0 */
6062 
6063   if (G_UNLIKELY (q == 0)) {
6064     q = g_quark_from_static_string ("bvw-error-quark");
6065   }
6066   return q;
6067 }
6068 
6069 static gboolean
bvw_set_playback_direction(BaconVideoWidget * bvw,gboolean forward)6070 bvw_set_playback_direction (BaconVideoWidget *bvw, gboolean forward)
6071 {
6072   gboolean is_forward;
6073   gboolean retval;
6074   float target_rate;
6075   GstEvent *event;
6076   gint64 cur = 0;
6077 
6078   is_forward = (bvw->rate > 0.0);
6079   if (forward == is_forward)
6080     return TRUE;
6081 
6082   retval = FALSE;
6083   target_rate = (forward ? FORWARD_RATE : REVERSE_RATE);
6084 
6085   if (gst_element_query_position (bvw->play, GST_FORMAT_TIME, &cur)) {
6086     GST_DEBUG ("Setting playback direction to %s at %"G_GINT64_FORMAT"", DIRECTION_STR, cur);
6087     event = gst_event_new_seek (target_rate,
6088 				GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
6089 				GST_SEEK_TYPE_SET, forward ? cur : G_GINT64_CONSTANT (0),
6090 				GST_SEEK_TYPE_SET, forward ? G_GINT64_CONSTANT (0) : cur);
6091     if (gst_element_send_event (bvw->play, event) == FALSE) {
6092       GST_WARNING ("Failed to set playback direction to %s", DIRECTION_STR);
6093     } else {
6094       gst_element_get_state (bvw->play, NULL, NULL, GST_CLOCK_TIME_NONE);
6095       bvw->rate = target_rate;
6096       retval = TRUE;
6097     }
6098   } else {
6099     GST_LOG ("Failed to query position to set playback to %s", DIRECTION_STR);
6100   }
6101 
6102   return retval;
6103 }
6104 
6105 static gboolean
navigation_event(ClutterActor * actor,ClutterEvent * event,BaconVideoWidget * bvw)6106 navigation_event (ClutterActor *actor,
6107                   ClutterEvent *event,
6108                   BaconVideoWidget *bvw)
6109 {
6110   ClutterGstFrame *frame =
6111     clutter_gst_video_sink_get_frame (CLUTTER_GST_VIDEO_SINK (bvw->video_sink));
6112   gfloat actor_width, actor_height;
6113   gfloat x, y;
6114 
6115   if (frame == NULL)
6116     return CLUTTER_EVENT_PROPAGATE;
6117 
6118   /* Get event coordinates into the actor's coordinates. */
6119   clutter_event_get_coords (event, &x, &y);
6120   clutter_actor_transform_stage_point (actor, x, y, &x, &y);
6121 
6122   clutter_actor_get_size (actor, &actor_width, &actor_height);
6123 
6124   /* Convert event's coordinates into the frame's coordinates. */
6125   x = x * frame->resolution.width / actor_width;
6126   y = y * frame->resolution.height / actor_height;
6127 
6128   if (event->type == CLUTTER_MOTION) {
6129     gst_navigation_send_mouse_event (GST_NAVIGATION (bvw->video_sink),
6130                                      "mouse-move", 0, x, y);
6131   } else if (event->type == CLUTTER_BUTTON_PRESS ||
6132              event->type == CLUTTER_BUTTON_RELEASE) {
6133     ClutterButtonEvent *bevent = (ClutterButtonEvent *) event;
6134     const char *type = (event->type == CLUTTER_BUTTON_PRESS) ?
6135       "mouse-button-press" : "mouse-button-release";
6136     gst_navigation_send_mouse_event (GST_NAVIGATION (bvw->video_sink), type,
6137                                      bevent->button, x, y);
6138   }
6139 
6140   return CLUTTER_EVENT_PROPAGATE;
6141 }
6142 
6143 static void
listen_navigation_events(ClutterActor * actor,BaconVideoWidget * bvw)6144 listen_navigation_events (ClutterActor *actor,
6145                           BaconVideoWidget *bvw)
6146 {
6147   const char * const events[] = {
6148     "button-press-event",
6149     "button-release-event",
6150     "motion-event"
6151   };
6152   guint i;
6153 
6154   for (i = 0; i < G_N_ELEMENTS (events); i++)
6155     g_signal_connect (actor, events[i], G_CALLBACK (navigation_event), bvw);
6156 }
6157 
6158 static GstElement *
element_make_or_warn(const char * plugin,const char * name)6159 element_make_or_warn (const char *plugin,
6160 		      const char *name)
6161 {
6162   GstElement *element;
6163   element = gst_element_factory_make (plugin, name);
6164   if (!element)
6165     g_warning ("Element '%s' is missing, verify your installation", plugin);
6166   return element;
6167 }
6168 
6169 static gboolean
bacon_video_widget_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)6170 bacon_video_widget_initable_init (GInitable     *initable,
6171 				  GCancellable  *cancellable,
6172 				  GError       **error)
6173 {
6174   BaconVideoWidget *bvw;
6175   GstElement *audio_sink = NULL;
6176   gchar *version_str;
6177   GstPlayFlags flags;
6178   ClutterActor *layout;
6179   GstElement *audio_bin;
6180   GstPad *audio_pad;
6181   ClutterAction *action;
6182   GObject *item;
6183   char *template;
6184 
6185   bvw = BACON_VIDEO_WIDGET (initable);
6186 
6187 #ifndef GST_DISABLE_GST_DEBUG
6188   if (_totem_gst_debug_cat == NULL) {
6189     GST_DEBUG_CATEGORY_INIT (_totem_gst_debug_cat, "totem", 0,
6190         "Totem GStreamer Backend");
6191   }
6192 #endif
6193 
6194   version_str = gst_version_string ();
6195   GST_DEBUG ("Initialised %s", version_str);
6196   g_free (version_str);
6197 
6198   gst_pb_utils_init ();
6199 
6200   /* Instantiate all the fallible plugins */
6201   bvw->play = element_make_or_warn ("playbin", "play");
6202   bvw->audio_pitchcontrol = element_make_or_warn ("scaletempo", "scaletempo");
6203   bvw->video_sink = GST_ELEMENT (clutter_gst_video_sink_new ());
6204   audio_sink = element_make_or_warn ("autoaudiosink", "audio-sink");
6205 
6206   if (!bvw->play ||
6207       !bvw->audio_pitchcontrol ||
6208       !bvw->video_sink ||
6209       !audio_sink) {
6210     if (bvw->video_sink)
6211       g_object_ref_sink (bvw->video_sink);
6212     if (audio_sink)
6213       g_object_ref_sink (audio_sink);
6214     g_set_error_literal (error, BVW_ERROR, BVW_ERROR_PLUGIN_LOAD,
6215 			 _("Some necessary plug-ins are missing. "
6216 			   "Make sure that the program is correctly installed."));
6217     return FALSE;
6218   }
6219 
6220   bvw->bus = gst_element_get_bus (bvw->play);
6221 
6222   /* Add the download flag, for streaming buffering,
6223    * and the deinterlace flag, for video only */
6224   g_object_get (bvw->play, "flags", &flags, NULL);
6225   flags |= GST_PLAY_FLAG_DOWNLOAD | GST_PLAY_FLAG_DEINTERLACE;
6226   g_object_set (bvw->play, "flags", flags, NULL);
6227 
6228   /* Keep in sync with playbin_element_setup_cb() */
6229   template = g_build_filename (g_get_user_cache_dir (), "totem", "stream-buffer", NULL);
6230   g_mkdir_with_parents (template, 0700);
6231   g_free (template);
6232 
6233   gst_bus_add_signal_watch (bvw->bus);
6234 
6235   bvw->sig_bus_async =
6236       g_signal_connect (bvw->bus, "message",
6237                         G_CALLBACK (bvw_bus_message_cb),
6238                         bvw);
6239 
6240   bvw->speakersetup = BVW_AUDIO_SOUND_STEREO;
6241   bvw->ratio_type = BVW_RATIO_AUTO;
6242 
6243   bvw->cursor_shown = TRUE;
6244   bvw->logo_mode = FALSE;
6245 
6246   bvw->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (bvw));
6247   clutter_actor_set_text_direction (bvw->stage,
6248 				    CLUTTER_TEXT_DIRECTION_LTR);
6249   clutter_actor_set_layout_manager (bvw->stage,
6250                                     clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
6251   clutter_actor_set_name (bvw->stage, "stage");
6252   clutter_actor_set_background_color (bvw->stage, CLUTTER_COLOR_Black);
6253 
6254   /* Video sink, with aspect frame */
6255   bvw->texture = g_object_new (CLUTTER_TYPE_ACTOR,
6256                                      "content", g_object_new (CLUTTER_GST_TYPE_CONTENT,
6257                                                               "sink", bvw->video_sink,
6258                                                               NULL),
6259 				     "name", "texture",
6260 				     "reactive", TRUE,
6261 				     NULL);
6262   listen_navigation_events (bvw->texture, bvw);
6263 
6264   /* The logo */
6265   bvw->logo_frame = clutter_actor_new ();
6266   clutter_actor_set_name (bvw->logo_frame, "logo-frame");
6267   bvw->logo = clutter_image_new ();
6268   clutter_actor_set_content (bvw->logo_frame, bvw->logo);
6269   clutter_actor_set_content_gravity (bvw->logo_frame, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
6270   clutter_actor_add_child (bvw->stage, bvw->logo_frame);
6271   clutter_actor_hide (CLUTTER_ACTOR (bvw->logo_frame));
6272 
6273   /* The video */
6274   bvw->frame = totem_aspect_frame_new ();
6275   clutter_actor_set_name (bvw->frame, "frame");
6276   totem_aspect_frame_set_child (TOTEM_ASPECT_FRAME (bvw->frame), bvw->texture);
6277 
6278   clutter_actor_add_child (bvw->stage, bvw->frame);
6279 
6280   clutter_actor_set_child_above_sibling (bvw->stage,
6281 					 bvw->logo_frame,
6282 					 bvw->frame);
6283 
6284   /* The video's actions */
6285   action = clutter_tap_action_new ();
6286   clutter_actor_add_action (bvw->texture, action);
6287   g_signal_connect (action, "tap",
6288 		    G_CALLBACK (bacon_video_widget_tap), bvw);
6289 
6290   action = clutter_swipe_action_new ();
6291   clutter_gesture_action_set_threshold_trigger_distance (CLUTTER_GESTURE_ACTION (action), 80.0, 80.0);
6292   clutter_actor_add_action (bvw->texture, action);
6293   g_signal_connect (action, "swipe",
6294 		    G_CALLBACK (bacon_video_widget_swipe), bvw);
6295 
6296   /* The spinner */
6297   bvw->spinner = bacon_video_spinner_actor_new ();
6298   clutter_actor_set_name (bvw->spinner, "spinner");
6299   clutter_actor_add_child (bvw->stage, bvw->spinner);
6300   clutter_actor_set_child_above_sibling (bvw->stage,
6301 					 bvw->spinner,
6302 					 bvw->frame);
6303   clutter_actor_hide (bvw->spinner);
6304 
6305   /* Fullscreen header controls */
6306   bvw->header_controls = gtk_clutter_actor_new ();
6307   clutter_actor_set_opacity (bvw->header_controls, OVERLAY_OPACITY);
6308   clutter_actor_set_name (bvw->header_controls, "header-controls");
6309   clutter_actor_add_constraint (bvw->header_controls,
6310                                 clutter_bind_constraint_new (bvw->stage,
6311                                                              CLUTTER_BIND_WIDTH,
6312                                                              0));
6313   layout = g_object_new (CLUTTER_TYPE_ACTOR,
6314 			 "layout-manager", clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_START),
6315 			 NULL);
6316   clutter_actor_set_name (layout, "layout");
6317   clutter_actor_add_child (layout, bvw->header_controls);
6318   clutter_actor_add_child (bvw->stage, layout);
6319 
6320   /* The controls */
6321   bvw->controls = bacon_video_controls_actor_new ();
6322   clutter_actor_set_name (bvw->controls, "controls");
6323   layout = g_object_new (CLUTTER_TYPE_ACTOR,
6324 			 "layout-manager", clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER, CLUTTER_BIN_ALIGNMENT_END),
6325 			 NULL);
6326   clutter_actor_set_name (layout, "layout");
6327   clutter_actor_add_child (layout, bvw->controls);
6328 
6329   clutter_actor_add_child (bvw->stage, layout);
6330   clutter_actor_set_child_above_sibling (bvw->stage,
6331 					 layout,
6332 					 bvw->logo_frame);
6333 
6334   clutter_actor_set_opacity (bvw->controls, 0);
6335 
6336   item = g_object_get_data (G_OBJECT (bvw->controls), "seek_scale");
6337   g_signal_connect (item, "scroll-event",
6338 		    G_CALLBACK (bacon_video_widget_handle_scroll), bvw);
6339   item = g_object_get_data (G_OBJECT (bvw->controls), "volume_button");
6340   g_signal_connect (item, "scroll-event",
6341 		    G_CALLBACK (bacon_video_widget_handle_scroll), bvw);
6342 
6343   /* And tell playbin */
6344   g_object_set (bvw->play, "video-sink", bvw->video_sink, NULL);
6345 
6346   /* Link the audiopitch element */
6347   bvw->audio_capsfilter =
6348     gst_element_factory_make ("capsfilter", "audiofilter");
6349   audio_bin = gst_bin_new ("audiosinkbin");
6350   gst_bin_add_many (GST_BIN (audio_bin),
6351                     bvw->audio_capsfilter,
6352 		    audio_sink, NULL);
6353   gst_element_link_many (bvw->audio_capsfilter,
6354 			 audio_sink,
6355 			 NULL);
6356 
6357   audio_pad = gst_element_get_static_pad (bvw->audio_capsfilter, "sink");
6358   gst_element_add_pad (audio_bin, gst_ghost_pad_new ("sink", audio_pad));
6359   gst_object_unref (audio_pad);
6360 
6361   /* And tell playbin */
6362   g_object_set (bvw->play, "audio-sink", audio_bin, NULL);
6363   g_object_set (bvw->play, "audio-filter", bvw->audio_pitchcontrol, NULL);
6364 
6365   /* Set default connection speed */
6366   /* Cast the value to guint64 to match the type of the 'connection-speed'
6367    * property to avoid problems reading variable arguments on 32-bit systems. */
6368   g_object_set (bvw->play, "connection-speed", (guint64) MAX_NETWORK_SPEED, NULL);
6369 
6370   g_signal_connect (G_OBJECT (bvw->play), "notify::volume",
6371       G_CALLBACK (notify_volume_cb), bvw);
6372   g_signal_connect (bvw->play, "source-setup",
6373       G_CALLBACK (playbin_source_setup_cb), bvw);
6374   g_signal_connect (bvw->play, "element-setup",
6375       G_CALLBACK (playbin_element_setup_cb), bvw);
6376   g_signal_connect (bvw->play, "video-changed",
6377       G_CALLBACK (playbin_stream_changed_cb), bvw);
6378   g_signal_connect (bvw->play, "audio-changed",
6379       G_CALLBACK (playbin_stream_changed_cb), bvw);
6380   g_signal_connect (bvw->play, "text-changed",
6381       G_CALLBACK (playbin_stream_changed_cb), bvw);
6382   g_signal_connect (bvw->play, "deep-notify::temp-location",
6383       G_CALLBACK (playbin_deep_notify_cb), bvw);
6384 
6385   g_signal_connect (bvw->play, "video-tags-changed",
6386       G_CALLBACK (video_tags_changed_cb), bvw);
6387   g_signal_connect (bvw->play, "audio-tags-changed",
6388       G_CALLBACK (audio_tags_changed_cb), bvw);
6389   g_signal_connect (bvw->play, "text-tags-changed",
6390       G_CALLBACK (text_tags_changed_cb), bvw);
6391 
6392   return TRUE;
6393 }
6394 
6395 static void
bacon_video_widget_initable_iface_init(GInitableIface * iface)6396 bacon_video_widget_initable_iface_init (GInitableIface *iface)
6397 {
6398   iface->init = bacon_video_widget_initable_init;
6399 }
6400 
6401 /**
6402  * bacon_video_widget_new:
6403  * @error: a #GError, or %NULL
6404  *
6405  * Creates a new #BaconVideoWidget.
6406  *
6407  * A #BvwError will be returned on error.
6408  *
6409  * Return value: a new #BaconVideoWidget, or %NULL; destroy with gtk_widget_destroy()
6410  **/
6411 GtkWidget *
bacon_video_widget_new(GError ** error)6412 bacon_video_widget_new (GError ** error)
6413 {
6414   return GTK_WIDGET (g_initable_new (BACON_TYPE_VIDEO_WIDGET, NULL, error, NULL));
6415 }
6416 
6417 /**
6418  * bacon_video_widget_get_rate:
6419  * @bvw: a #BaconVideoWidget
6420  *
6421  * Get the current playback rate, with 1.0 being normal rate.
6422  *
6423  * Returns: the current playback rate
6424  **/
6425 gfloat
bacon_video_widget_get_rate(BaconVideoWidget * bvw)6426 bacon_video_widget_get_rate (BaconVideoWidget *bvw)
6427 {
6428   return bvw->rate;
6429 }
6430 
6431 /**
6432  * bacon_video_widget_set_rate:
6433  * @bvw: a #BaconVideoWidget
6434  * @new_rate: the new playback rate
6435  *
6436  * Sets the current playback rate.
6437  *
6438  * Returns: %TRUE on success, %FALSE on failure.
6439  **/
6440 gboolean
bacon_video_widget_set_rate(BaconVideoWidget * bvw,gfloat new_rate)6441 bacon_video_widget_set_rate (BaconVideoWidget *bvw,
6442 			     gfloat            new_rate)
6443 {
6444   GstEvent *event;
6445   gboolean retval = FALSE;
6446   gint64 cur;
6447 
6448   g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
6449   g_return_val_if_fail (GST_IS_ELEMENT (bvw->play), FALSE);
6450 
6451   if (new_rate == bvw->rate)
6452     return TRUE;
6453 
6454   /* set upper and lower limit for rate */
6455   if (new_rate < 0.5)
6456     return retval;
6457   if (new_rate > 2.0)
6458     return retval;
6459 
6460   if (gst_element_query_position (bvw->play, GST_FORMAT_TIME, &cur)) {
6461     GST_DEBUG ("Setting new rate at %"G_GINT64_FORMAT"", cur);
6462     event = gst_event_new_seek (new_rate,
6463 				GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
6464 				GST_SEEK_TYPE_SET, cur,
6465 				GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
6466     if (gst_element_send_event (bvw->play, event) == FALSE) {
6467       GST_DEBUG ("Failed to change rate");
6468     } else {
6469       gst_element_get_state (bvw->play, NULL, NULL, GST_CLOCK_TIME_NONE);
6470       bvw->rate = new_rate;
6471       retval = TRUE;
6472     }
6473   } else {
6474     GST_DEBUG ("failed to query position");
6475   }
6476 
6477   return retval;
6478 }
6479 
6480 /**
6481  * bacon_video_widget_set_fullscreen:
6482  * @bvw: a #BaconVideoWidget
6483  * @fullscreen: the new fullscreen state
6484  *
6485  * Sets the fullscreen state, enabling a toplevel header bar sliding from
6486  * the top of the video widget.
6487  **/
6488 void
bacon_video_widget_set_fullscreen(BaconVideoWidget * bvw,gboolean fullscreen)6489 bacon_video_widget_set_fullscreen (BaconVideoWidget *bvw,
6490                                    gboolean          fullscreen)
6491 {
6492   g_return_if_fail (BACON_IS_VIDEO_WIDGET (bvw));
6493 
6494   g_object_set (bvw->header_controls, "visible", fullscreen, NULL);
6495 }
6496 
6497 /*
6498  * vim: sw=2 ts=8 cindent noai bs=2
6499  */
6500