6 #include "yh-youtube-browser.h"
7 #include "yh-youtube.h"
10 #include <clutter/clutter.h>
11 #include <clutter-gst/clutter-gst.h>
22 struct _YHYoutubeBrowserPrivate {
25 ClutterModelIter *iter;
30 ClutterActor *related;
31 ClutterActor *related_label;
38 ClutterActor *description;
43 ClutterTimeline *timeline_in;
44 ClutterTimeline *timeline_out;
46 ClutterEffectTemplate *template;
50 G_DEFINE_TYPE (YHYoutubeBrowser, yh_youtube_browser, CLUTTER_TYPE_ACTOR)
52 #define YOUTUBE_BROWSER_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
54 YH_TYPE_YOUTUBE_BROWSER, \
55 YHYoutubeBrowserPrivate))
64 static guint signals[LAST_SIGNAL] = { 0, };
67 video_out_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
69 clutter_actor_destroy (actor);
79 video_bg_button_press_cb (ClutterActor *actor,
80 ClutterButtonEvent *event,
81 YHYoutubeBrowser *browser)
83 YHYoutubeBrowserPrivate *priv = browser->priv;
85 clutter_effect_fade (priv->template,
88 (ClutterEffectCompleteFunc)video_out_complete_cb,
91 /* Disconnect handler and just block events until we're faded out */
92 g_signal_handlers_disconnect_by_func (actor,
93 video_bg_button_press_cb,
95 g_signal_connect (actor, "button-press-event",
96 G_CALLBACK (true_function), NULL);
102 video_in_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
104 g_signal_handlers_disconnect_by_func (actor,
107 g_signal_connect (actor, "button-press-event",
108 G_CALLBACK (video_bg_button_press_cb), browser);
112 video_error_cb (ClutterMedia *media, GError *error)
114 g_warning ("Error from video: %s", error->message);
118 video_buffer_notify_cb (ClutterMedia *media,
120 YHYoutubeBrowser *browser)
122 if ((clutter_media_get_buffer_percent (media) == 100) &&
123 (!clutter_media_get_playing (media)))
124 clutter_media_set_playing (media, TRUE);
128 link_cb (YHYoutube *youtube, const gchar *url, YHYoutubeBrowser *browser)
130 ClutterUnit width, height;
131 ClutterActor *group, *rect, *player, *player_bg;
133 YHYoutubeBrowserPrivate *priv = browser->priv;
135 priv->loading = FALSE;
137 clutter_actor_get_sizeu (priv->frame, &width, &height);
139 group = clutter_group_new ();
141 rect = clutter_rectangle_new_with_color (&stage_color);
142 clutter_actor_set_opacity (rect, 128);
143 clutter_actor_set_size (rect, CLUTTER_STAGE_WIDTH(), CLUTTER_STAGE_HEIGHT());
145 player_bg = clutter_rectangle_new_with_color (&frame_color);
146 clutter_actor_set_sizeu (player_bg,
147 (width*4)/5 + UFRAME,
148 (height*4)/5 + UFRAME);
150 player = clutter_gst_video_texture_new ();
151 g_signal_connect (player, "error", G_CALLBACK (video_error_cb), browser);
152 g_signal_connect (player, "notify::buffer-percent",
153 G_CALLBACK (video_buffer_notify_cb), browser);
155 g_object_set (G_OBJECT (player),
161 clutter_actor_set_sizeu (player,
165 clutter_container_add (CLUTTER_CONTAINER (group),
166 rect, player_bg, player, NULL);
167 clutter_actor_show_all (group);
169 clutter_container_add_actor (CLUTTER_CONTAINER (priv->group), group);
170 clutter_actor_set_position (group, 0, 0);
172 clutter_actor_set_anchor_point_from_gravity (player,
173 CLUTTER_GRAVITY_CENTER);
174 clutter_actor_set_anchor_point_from_gravity (player_bg,
175 CLUTTER_GRAVITY_CENTER);
176 clutter_actor_set_positionu (player, width/2, height/2);
177 clutter_actor_set_positionu (player_bg, width/2, height/2);
179 clutter_actor_set_opacity (group, 0x00);
180 clutter_effect_fade (priv->template,
183 (ClutterEffectCompleteFunc)video_in_complete_cb,
185 clutter_actor_set_reactive (group, TRUE);
186 g_signal_connect (group, "button-press-event",
187 G_CALLBACK (true_function), browser);
191 thumb_button_press_cb (ClutterActor *actor,
192 ClutterButtonEvent *event,
193 YHYoutubeBrowser *browser)
197 YHYoutubeBrowserPrivate *priv = browser->priv;
202 clutter_model_iter_get (priv->iter, YH_YOUTUBE_COL_URIS, &uris, -1);
213 /* We assume the http link comes first
214 * (which it does, but should probably check)
216 yh_youtube_get_http_link (priv->youtube, uris[0]);
219 priv->loading = TRUE;
225 in_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
227 YHYoutubeBrowserPrivate *priv = browser->priv;
229 priv->timeline_in = NULL;
233 out_complete_cb (ClutterActor *actor, YHYoutubeBrowser *browser)
235 YHYoutubeBrowserPrivate *priv = browser->priv;
237 priv->timeline_out = NULL;
241 crossfade_timeout (YHYoutubeBrowser *browser)
243 YHYoutubeBrowserPrivate *priv = browser->priv;
245 if (!priv->current_thumb)
248 if (priv->current_thumb->next ||
249 (priv->current_thumb != priv->thumbs))
251 ClutterActor *in, *out;
253 out = CLUTTER_ACTOR (priv->current_thumb->data);
255 priv->current_thumb = priv->current_thumb->next ?
256 priv->current_thumb->next : priv->thumbs;
258 in = CLUTTER_ACTOR (priv->current_thumb->data);
260 /* Cross-fade effect */
261 priv->timeline_out = clutter_effect_fade (priv->template,
264 (ClutterEffectCompleteFunc)
267 priv->timeline_in = clutter_effect_fade (priv->template,
270 (ClutterEffectCompleteFunc)
279 complete_cb (YHYoutube *youtube, void *handle, YHYoutubeBrowser *browser)
282 YHYoutubeBrowserPrivate *priv = browser->priv;
284 if ((l = g_list_find (priv->thumb_handles, handle)))
285 priv->thumb_handles = g_list_delete_link (priv->thumb_handles, l);
289 thumbnail_cb (YHYoutube *youtube, GdkPixbuf *pixbuf, YHYoutubeBrowser *browser)
292 YHYoutubeBrowserPrivate *priv = browser->priv;
297 thumb = clutter_texture_new_from_pixbuf (pixbuf);
301 priv->thumbs = g_list_append (priv->thumbs, thumb);
303 clutter_container_add_actor (CLUTTER_CONTAINER (priv->group), thumb);
304 clutter_actor_set_positionu (thumb,
305 clutter_actor_get_xu (priv->prev),
307 clutter_actor_set_sizeu (thumb,
308 clutter_actor_get_widthu (priv->frame)/2 -
310 (clutter_actor_get_heightu (priv->frame)*3)/4 -
312 clutter_actor_show (thumb);
313 clutter_actor_set_opacity (thumb, 0x00);
315 clutter_actor_set_reactive (thumb, TRUE);
316 g_signal_connect (thumb, "button-press-event",
317 G_CALLBACK (thumb_button_press_cb), browser);
319 if (!priv->current_thumb)
322 priv->current_thumb = priv->thumbs;
323 priv->timeline_in = clutter_effect_fade (priv->template,
326 (ClutterEffectCompleteFunc)
329 priv->fade_timeout = g_timeout_add_seconds (5,
330 (GSourceFunc)crossfade_timeout,
336 free_thumbs (YHYoutubeBrowser *self)
338 YHYoutubeBrowserPrivate *priv = self->priv;
340 if (priv->fade_timeout)
342 g_source_remove (priv->fade_timeout);
343 priv->fade_timeout = 0;
346 if (priv->timeline_in)
348 clutter_timeline_pause (priv->timeline_in);
349 g_object_unref (priv->timeline_in);
350 priv->timeline_in = NULL;
353 if (priv->timeline_out)
355 clutter_timeline_pause (priv->timeline_out);
356 g_object_unref (priv->timeline_out);
357 priv->timeline_out = NULL;
360 while (priv->thumb_handles)
362 yh_youtube_cancel (priv->youtube, priv->thumb_handles->data);
363 priv->thumb_handles = g_list_delete_link (priv->thumb_handles,
364 priv->thumb_handles);
369 clutter_actor_destroy (CLUTTER_ACTOR (priv->thumbs->data));
370 priv->thumbs = g_list_delete_link (priv->thumbs, priv->thumbs);
373 priv->current_thumb = NULL;
377 fill_details (YHYoutubeBrowser *self)
379 gchar *title, *author, *description, **thumbs;
380 ClutterModelIter *next_iter;
384 YHYoutubeBrowserPrivate *priv = self->priv;
390 clutter_model_iter_get (priv->iter,
391 YH_YOUTUBE_COL_TITLE, &title,
392 YH_YOUTUBE_COL_AUTHOR, &author,
393 YH_YOUTUBE_COL_DESCRIPTION, &description,
394 YH_YOUTUBE_COL_RATING, &rating,
395 YH_YOUTUBE_COL_THUMBS, &thumbs,
398 clutter_label_set_text (CLUTTER_LABEL (priv->title), title);
399 clutter_label_set_text (CLUTTER_LABEL (priv->author), author);
400 clutter_label_set_text (CLUTTER_LABEL (priv->description), description);
402 switch ((gint)(rating + 0.5))
405 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★••••");
408 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★•••");
411 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★••");
414 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★★•");
417 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "★★★★★");
420 clutter_label_set_text (CLUTTER_LABEL (priv->rating), "No rating");
423 if (clutter_model_iter_is_first (priv->iter))
425 clutter_actor_set_opacity (priv->prev, 128);
426 clutter_actor_set_reactive (priv->prev, FALSE);
430 clutter_actor_set_opacity (priv->prev, 255);
431 clutter_actor_set_reactive (priv->prev, TRUE);
434 row = clutter_model_iter_get_row (priv->iter);
435 next_iter = clutter_model_get_iter_at_row (priv->model, row + 1);
438 clutter_actor_set_opacity (priv->next, 128);
439 clutter_actor_set_reactive (priv->next, FALSE);
443 clutter_actor_set_opacity (priv->next, 255);
444 clutter_actor_set_reactive (priv->next, TRUE);
445 g_object_unref (next_iter);
451 for (i = 0; thumbs[i]; i++)
453 priv->thumb_handles = g_list_append (priv->thumb_handles,
454 yh_youtube_get_thumb (priv->youtube,
462 yh_youtube_browser_get_property (GObject *object, guint property_id,
463 GValue *value, GParamSpec *pspec)
465 YHYoutubeBrowser *self = YH_YOUTUBE_BROWSER (object);
470 g_value_set_object (value, self->priv->model);
474 g_value_set_object (value, self->priv->youtube);
478 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
483 yh_youtube_browser_set_property (GObject *object, guint property_id,
484 const GValue *value, GParamSpec *pspec)
486 YHYoutubeBrowser *self = YH_YOUTUBE_BROWSER (object);
491 self->priv->model = CLUTTER_MODEL (g_value_dup_object (value));
492 self->priv->iter = clutter_model_get_first_iter (self->priv->model);
493 if (self->priv->youtube)
498 self->priv->youtube = YH_YOUTUBE (g_value_dup_object (value));
499 g_signal_connect (self->priv->youtube, "complete",
500 G_CALLBACK (complete_cb), self);
501 g_signal_connect (self->priv->youtube, "thumbnail",
502 G_CALLBACK (thumbnail_cb), self);
503 g_signal_connect (self->priv->youtube, "link",
504 G_CALLBACK (link_cb), self);
505 if (self->priv->model)
510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
515 yh_youtube_browser_dispose (GObject *object)
517 YHYoutubeBrowser *browser = YH_YOUTUBE_BROWSER (object);
518 YHYoutubeBrowserPrivate *priv = browser->priv;
520 free_thumbs (browser);
524 g_object_unref (priv->iter);
530 g_object_unref (priv->model);
536 g_object_unref (priv->template);
537 priv->template = NULL;
542 clutter_actor_unparent (priv->group);
548 g_signal_handlers_disconnect_by_func (priv->youtube,
551 g_signal_handlers_disconnect_by_func (priv->youtube,
554 g_signal_handlers_disconnect_by_func (priv->youtube,
557 g_object_unref (priv->youtube);
558 priv->youtube = NULL;
561 if (G_OBJECT_CLASS (yh_youtube_browser_parent_class)->dispose)
562 G_OBJECT_CLASS (yh_youtube_browser_parent_class)->dispose (object);
566 yh_youtube_browser_paint (ClutterActor *actor)
568 YHYoutubeBrowserPrivate *priv = YH_YOUTUBE_BROWSER (actor)->priv;
570 clutter_actor_paint (priv->group);
574 yh_youtube_browser_pick (ClutterActor *actor, const ClutterColor *color)
576 yh_youtube_browser_paint (actor);
580 yh_youtube_browser_request_coords (ClutterActor *actor, ClutterActorBox *box)
582 ClutterUnit width, height;
584 YHYoutubeBrowserPrivate *priv = YH_YOUTUBE_BROWSER (actor)->priv;
586 width = box->x2 - box->x1;
587 height = box->y2 - box->y1;
589 clutter_actor_set_sizeu (priv->frame, width, height);
591 clutter_actor_set_widthu (priv->title, width/2 - (UBORDER*3)/2);
592 clutter_actor_set_widthu (priv->author, width/2 - (UBORDER*3)/2);
593 clutter_actor_set_widthu (priv->rating, width/2 - (UBORDER*3)/2);
594 clutter_actor_set_widthu (priv->description,
595 width/2 - (UBORDER*3)/2);
596 clutter_actor_set_clipu (priv->description, 0, 0,
597 clutter_actor_get_widthu (priv->description),
598 height - clutter_actor_get_yu (priv->description) -
601 clutter_actor_set_positionu (priv->related,
603 (height*3)/4 - UBORDER/2);
604 clutter_actor_set_sizeu (priv->related, width/2 - (UBORDER*3)/2,
606 clutter_actor_set_anchor_point_from_gravity (priv->related_label,
607 CLUTTER_GRAVITY_CENTER);
608 clutter_actor_set_positionu (priv->related_label,
609 clutter_actor_get_xu (priv->related) +
610 clutter_actor_get_widthu (priv->related)/2,
611 clutter_actor_get_yu (priv->related) +
612 clutter_actor_get_heightu (priv->related)/2);
614 clutter_actor_set_positionu (priv->prev,
617 clutter_actor_get_heightu (priv->related));
618 clutter_actor_set_sizeu (priv->prev, width/4 - UBORDER, height/4 - UBORDER/2 -
619 clutter_actor_get_heightu (priv->related));
621 clutter_actor_set_positionu (priv->next,
622 clutter_actor_get_xu (priv->prev) +
623 clutter_actor_get_widthu (priv->prev) + UBORDER/2,
625 clutter_actor_get_heightu (priv->related));
626 clutter_actor_set_sizeu (priv->next, width/4 - UBORDER, height/4 - UBORDER/2 -
627 clutter_actor_get_heightu (priv->related));
629 CLUTTER_ACTOR_CLASS (yh_youtube_browser_parent_class)->
630 request_coords (actor, box);
634 yh_youtube_browser_class_init (YHYoutubeBrowserClass *klass)
636 GObjectClass *object_class = G_OBJECT_CLASS (klass);
637 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
639 g_type_class_add_private (klass, sizeof (YHYoutubeBrowserPrivate));
641 object_class->get_property = yh_youtube_browser_get_property;
642 object_class->set_property = yh_youtube_browser_set_property;
643 object_class->dispose = yh_youtube_browser_dispose;
645 actor_class->paint = yh_youtube_browser_paint;
646 actor_class->pick = yh_youtube_browser_pick;
647 actor_class->request_coords = yh_youtube_browser_request_coords;
649 g_object_class_install_property (object_class,
651 g_param_spec_object ("model",
656 G_PARAM_CONSTRUCT_ONLY));
658 g_object_class_install_property (object_class,
660 g_param_spec_object ("youtube",
662 "Youtube data provider",
665 G_PARAM_CONSTRUCT_ONLY));
668 g_signal_new ("related",
669 G_TYPE_FROM_CLASS (klass),
671 G_STRUCT_OFFSET (YHYoutubeBrowserClass, related),
673 g_cclosure_marshal_VOID__OBJECT,
674 G_TYPE_NONE, 1, CLUTTER_TYPE_MODEL_ITER);
678 prev_pressed_cb (ClutterActor *actor,
680 YHYoutubeBrowser *self)
682 self->priv->iter = clutter_model_iter_prev (self->priv->iter);
688 next_pressed_cb (ClutterActor *actor,
690 YHYoutubeBrowser *self)
692 self->priv->iter = clutter_model_iter_next (self->priv->iter);
698 related_pressed_cb (ClutterActor *actor,
700 YHYoutubeBrowser *self)
702 g_signal_emit (self, signals[RELATED], 0, self->priv->iter);
707 yh_youtube_browser_init (YHYoutubeBrowser *self)
711 GError *error = NULL;
712 YHYoutubeBrowserPrivate *priv = self->priv = YOUTUBE_BROWSER_PRIVATE (self);
714 priv->group = clutter_group_new ();
715 clutter_actor_set_parent (priv->group, CLUTTER_ACTOR (self));
718 priv->frame = clutter_rectangle_new_with_color (&entry_color);
719 clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (priv->frame), 6);
720 clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (priv->frame),
723 /* Related videos button */
724 priv->related = clutter_rectangle_new_with_color (&bg_color);
725 clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (priv->related), FRAME);
726 clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (priv->related),
728 priv->related_label = clutter_label_new_full (font, "Related videos",
730 clutter_actor_set_reactive (CLUTTER_ACTOR (priv->related), TRUE);
731 g_signal_connect (priv->related, "button-press-event",
732 G_CALLBACK (related_pressed_cb), self);
735 pixbuf = gdk_pixbuf_new_from_file_at_size (PKGDATADIR "/go-previous.svg",
736 CLUTTER_STAGE_WIDTH () / 5,
737 CLUTTER_STAGE_HEIGHT () / 4,
741 g_warning ("Error loading pixbuf: %s", error->message);
742 g_error_free (error);
746 priv->prev = clutter_texture_new_from_pixbuf (pixbuf);
747 g_signal_connect (priv->prev, "button-press-event",
748 G_CALLBACK (prev_pressed_cb), self);
751 pixbuf = gdk_pixbuf_new_from_file_at_size (PKGDATADIR "/go-next.svg",
752 CLUTTER_STAGE_WIDTH () / 5,
753 CLUTTER_STAGE_HEIGHT () / 4,
757 g_warning ("Error loading pixbuf: %s", error->message);
758 g_error_free (error);
761 priv->next = clutter_texture_new_from_pixbuf (pixbuf);
762 g_signal_connect (priv->next, "button-press-event",
763 G_CALLBACK (next_pressed_cb), self);
766 priv->title = clutter_label_new_full (font, "", &text_color);
767 clutter_label_set_ellipsize (CLUTTER_LABEL (priv->title),
768 PANGO_ELLIPSIZE_END);
771 priv->author = clutter_label_new_full (small_font, "", &text_color);
772 clutter_label_set_ellipsize (CLUTTER_LABEL (priv->author),
773 PANGO_ELLIPSIZE_END);
776 priv->rating = clutter_label_new_full (small_font, "", &text_color);
777 clutter_label_set_ellipsize (CLUTTER_LABEL (priv->rating),
778 PANGO_ELLIPSIZE_END);
781 priv->description = clutter_label_new_full (small_font, "", &text_color);
782 clutter_label_set_line_wrap (CLUTTER_LABEL (priv->description), TRUE);
784 /* Add widgets to group, they'll be sized (mostly) by request-coords */
785 clutter_container_add (CLUTTER_CONTAINER (priv->group),
796 clutter_actor_set_position (priv->title, BORDER, BORDER/2);
797 clutter_actor_set_position (priv->author,
799 clutter_actor_get_y (priv->title) +
800 clutter_actor_get_height (priv->title) +
802 clutter_actor_set_position (priv->rating,
804 clutter_actor_get_y (priv->author) +
805 clutter_actor_get_height (priv->author) +
807 clutter_actor_set_position (priv->description,
809 clutter_actor_get_y (priv->rating) +
810 clutter_actor_get_height (priv->rating) +
813 clutter_actor_show_all (priv->group);
815 /* Create template for cycling preview image cross-fades */
816 priv->template = clutter_effect_template_new (
817 clutter_timeline_new_for_duration (750),
818 CLUTTER_ALPHA_RAMP_INC);
822 yh_youtube_browser_new (ClutterModel *model, YHYoutube *youtube)
824 return CLUTTER_ACTOR (g_object_new (YH_TYPE_YOUTUBE_BROWSER,
826 "youtube", youtube, NULL));