Optionally enable clutter-webkit
[laugh.git] / src / laugh-media.c
blob0ae1ba2115e55909db24b177ad439e64c06b5c58
1 /*
2 * Laugh.
4 * An glib SMIL library.
6 * Authored By Koos Vriezen <koos.vriezen@gmail.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
24 /**
25 * SECTION:laugh-media
26 * @short_description: text/img/brush/ref/audio/video media classes.
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
33 #include "laugh-marshal.h"
34 #include "laugh-media.h"
35 #include "laugh-layout.h"
36 #include "laugh-io.h"
38 #include <string.h>
39 #include <glib/gprintf.h>
40 #include <clutter/clutter-label.h>
41 #include <clutter/clutter-group.h>
42 #include <clutter/clutter-rectangle.h>
43 #include <clutter/clutter-texture.h>
44 #include <clutter-gst/clutter-gst.h>
45 #ifdef HAVE_WEBKIT_CLUTTER
46 #include <webkit/webkit.h>
47 #endif
49 #define LAUGH_MEDIA_GET_PRIVATE(obj) \
50 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_MEDIA, LaughMediaPrivate))
52 enum {
53 ACTIVATED,
55 LAST_SIGNAL
58 typedef enum {
59 LaughMediaTypeUnknown = 0,
60 LaughMediaTypeAudio,
61 LaughMediaTypeImage,
62 LaughMediaTypeText,
63 #ifdef HAVE_WEBKIT_CLUTTER
64 LaughMediaTypeTextHTML,
65 #endif
66 LaughMediaTypeVideo
67 } LaughMediaType;
69 struct _LaughMediaPrivate
71 LaughRoleDisplay *display_role;
72 LaughRoleTiming *timing_role;
73 LaughMediaType type;
74 LaughSizeSetting size_setting;
75 LaughNode *region;
76 LaughIO *io;
77 gpointer *data;
78 guint intrinsic_width;
79 guint intrinsic_height;
80 gchar *uri;
83 static gpointer laugh_media_parent_class = ((void *)0);
84 static guint laugh_media_signals[LAST_SIGNAL] = { 0, };
85 static LaughRole laugh_media_media_role;
87 static void laugh_media_finalize (GObject *object)
89 G_OBJECT_CLASS (laugh_media_parent_class)->finalize (object);
92 static void laugh_media_dispose (GObject *object)
94 LaughMedia *self = LAUGH_MEDIA(object);
95 LaughRoleTiming *role = self->priv->timing_role;
97 if (self->priv->display_role) {
98 clutter_actor_destroy (self->priv->display_role->actor);
99 g_free (self->priv->display_role);
101 if (self->priv->data)
102 switch (self->priv->type) {
103 case LaughMediaTypeText:
104 g_free (self->priv->data);
105 break;
106 case LaughMediaTypeImage:
107 case LaughMediaTypeAudio:
108 g_object_unref (self->priv->data);
109 break;
110 default:
111 break;
113 if (self->priv->uri)
114 g_free (self->priv->uri);
116 G_OBJECT_CLASS (laugh_media_parent_class)->dispose (object);
118 laugh_timing_role_delete (role);
121 static void
122 _laugh_media_init (LaughNode *node, LaughInitializer *initializer)
124 LaughMedia *self = (LaughMedia *) node;
125 LaughNode *child;
126 LaughRoleTiming *role = self->priv->timing_role;
127 LaughRoleTiming *parent_timing_role = initializer->parent_segment;
129 if (node->attributes)
130 g_hash_table_foreach (node->attributes, laugh_attributes_set, node);
132 if (self->priv->io) {
133 initializer->init_pending = g_slist_append (initializer->init_pending,
134 node);
135 g_signal_connect (G_OBJECT (node), "initialized",
136 (GCallback) initializer->initialized, initializer);
139 laugh_timing_role_child_add (initializer->parent_segment, role);
141 initializer->parent_segment = role;
143 for (child = node->first_child; child; child = child->next_sibling)
144 laugh_node_init (child, initializer);
146 initializer->parent_segment = parent_timing_role;
149 void _lauch_media_mime_type (LaughIO *io, const gchar *mime, gpointer d)
151 LaughMedia *media = LAUGH_MEDIA (d);
152 LaughNode *node = (LaughNode *) media;
154 g_printf ("lauch_media_mime_type: %s\n", mime);
155 #ifdef HAVE_WEBKIT_CLUTTER
156 if (!strncmp (mime, "text/html", 9)) {
157 media->priv->type = LaughMediaTypeTextHTML;
158 } else
159 #endif
160 if (!strncmp (mime, "text/", 5)) {
161 media->priv->type = LaughMediaTypeText;
162 } else if (!strncmp (mime, "image/", 6)) {
163 media->priv->type = LaughMediaTypeImage;
164 } else if (!strncmp (mime, "audio/", 6)) {
165 media->priv->type = LaughMediaTypeAudio;
166 } else if (!strncmp (mime, "video/", 6)) {
167 media->priv->type = LaughMediaTypeVideo;
168 } else if (!strcmp (mime, "application/octet-stream")) {
169 if (LaughTagIdAudio)
170 media->priv->type = LaughMediaTypeAudio;
171 else if (LaughTagIdVideo)
172 media->priv->type = LaughMediaTypeVideo;
174 if (LaughMediaTypeUnknown == media->priv->type)
175 g_printerr ("%s mimetype '%s' not (yet) supported\n",
176 laugh_node_get_attribute (node, LaughAttrIdSrc), mime);
177 if (LaughMediaTypeImage != media->priv->type &&
178 LaughMediaTypeText != media->priv->type) {
179 if (LaughMediaTypeUnknown != media->priv->type)
180 media->priv->uri = g_strdup (laugh_io_get_uri (io));
181 laugh_io_cancel (io);
182 media->priv->io = NULL;
184 g_object_unref (G_OBJECT (io));
186 laugh_node_base_emit_initialized (node);
190 static void
191 _lauch_media_completed (LaughIO *io, gsize sz, gpointer data, gpointer d)
193 LaughMedia *media = LAUGH_MEDIA (d);
194 LaughNode *node = (LaughNode *) media;
195 LaughMediaPrivate *priv = media->priv;
197 g_printf ("_lauch_media_completed %s: %u\n", laugh_io_get_uri (io), sz);
198 if (data) {
199 switch (priv->type) {
200 case LaughMediaTypeText:
201 priv->data = data;
202 g_printf ("text: %s\n", (const char *)data);
203 break;
204 case LaughMediaTypeImage: {
205 GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
206 if (gdk_pixbuf_loader_write (loader,
207 (const guchar *) data, sz, NULL) &&
208 gdk_pixbuf_loader_close (loader, NULL)) {
209 GdkPixbuf *pix = gdk_pixbuf_loader_get_pixbuf (loader);
210 g_printf ("image:\n");
211 g_object_ref (pix);
212 priv->data = pix;
213 priv->intrinsic_width = gdk_pixbuf_get_width (pix);
214 priv->intrinsic_height = gdk_pixbuf_get_height (pix);
216 g_object_unref (loader);
217 g_free (data);
218 break;
220 case LaughMediaTypeAudio:
221 break;
222 case LaughMediaTypeVideo:
223 /*TODO: determine sizes*/
224 break;
225 default:
226 g_free (data);
227 break;
230 g_object_unref (G_OBJECT (io));
231 priv->io = NULL;
233 laugh_node_base_emit_initialized (node);
236 static void _laugh_media_actor_position (LaughNode *node)
238 LaughMedia *self = (LaughMedia *)node;
239 LaughMediaPrivate *priv = self->priv;
240 LaughRoleDisplay *region_display;
241 guint pw, ph;
242 float sx, sy, sw, sh;
243 const gchar *fit;
245 region_display =
246 (LaughRoleDisplay *) laugh_node_role_get (priv->region, LaughRoleTypeDisplay);
247 clutter_actor_get_size (region_display->actor, &pw, &ph);
248 laugh_size_setting_get (&priv->size_setting, pw, ph, &sx, &sy, &sw, &sh);
249 fit = laugh_node_get_attribute(node, LaughAttrIdFit);
250 if (!fit)
251 fit = laugh_node_get_attribute (priv->region, LaughAttrIdFit);
252 if (priv->intrinsic_width > 0 && priv->intrinsic_height > 0) {
253 if (!fit || !strcmp (fit, "hidden") || !strcmp (fit, "scroll")) {
254 sw = priv->intrinsic_width;
255 sh = priv->intrinsic_height;
256 } else if (!strcmp (fit, "meet")) {
257 float iasp = 1.0 * priv->intrinsic_width / priv->intrinsic_height;
258 float rasp = 1.0 * sw / sh;
259 if (iasp > rasp)
260 sh = priv->intrinsic_height * sw / priv->intrinsic_width;
261 else
262 sw = priv->intrinsic_width * sh / priv->intrinsic_height;
263 } else if (!strcmp (fit, "slice")) {
264 float iasp = 1.0 * priv->intrinsic_width / priv->intrinsic_height;
265 float rasp = 1.0 * sw / sh;
266 if (iasp > rasp)
267 sw = priv->intrinsic_width * sh / priv->intrinsic_height;
268 else
269 sh = priv->intrinsic_height * sw / priv->intrinsic_width;
272 clutter_actor_set_size (priv->display_role->actor, sw, sh);
273 clutter_actor_set_position (priv->display_role->actor, sx, sy);
276 static void _laugh_media_set_attribute (LaughNode *node,
277 LaughNodeAttributeId att, const gchar *value, gpointer *undo)
279 const gchar *val = value;
280 LaughMedia *self = (LaughMedia *)node;
281 LaughMediaPrivate *priv = self->priv;
283 LAUGH_TYPE_NODE_CLASS(laugh_media_parent_class)->
284 set_attribute(node, att, val, undo);
286 if (!value && undo)
287 val = *(const gchar **)undo;
288 g_printf ("_laugh_media_set_attribute %s=%s\n", laugh_attribute_from_id (att), val);
290 if (laugh_timing_setting_set_attribute (priv->timing_role, att, val)) {
291 } else if (laugh_size_setting_set_attribute (&self->priv->size_setting, att, value) ||
292 LaughAttrIdFit == att) {
293 if (priv->region && priv->display_role)
294 _laugh_media_actor_position (node);
295 } else {
296 switch (att) {
297 case LaughAttrIdSrc:
298 if (val) {
299 priv->io = laugh_io_new (val,
300 laugh_document_get_uri (node->document));
302 g_signal_connect (G_OBJECT (priv->io), "mime-type",
303 (GCallback) _lauch_media_mime_type, (gpointer) node);
304 g_signal_connect (G_OBJECT (priv->io), "completed",
305 (GCallback) _lauch_media_completed, (gpointer) node);
307 laugh_io_open (priv->io);
309 break;
310 default:
311 break;
316 static void _laugh_media_region_destroyed (gpointer data, GObject *obj)
318 LaughMediaPrivate *priv = (LaughMediaPrivate *) data;
319 (void) obj;
321 priv->region = NULL;
322 g_free (priv->display_role);
323 priv->display_role = NULL;
326 static void _lauch_media_actor_destroyed (ClutterActor *actor, gpointer data)
328 LaughMedia *self = LAUGH_MEDIA(data);
330 g_free (self->priv->display_role);
331 self->priv->display_role = NULL;
334 static void _laugh_media_eos (ClutterMedia *media, LaughNode *node)
336 LaughMedia *self = (LaughMedia *)node;
337 LaughRoleTiming *role = self->priv->timing_role;
339 if (LaughStateBegun == node->state &&
340 role->dur &&
341 LaughTimingMedia == role->dur->type) {
342 laugh_timing_notify (role, role->dur->handler_id);
346 static
347 void _laugh_media_size_change (ClutterTexture *tex, gint w, gint h, LaughMedia *self)
349 g_printf ("size_change %d %d\n", w, h);
352 static gboolean
353 _lauch_media_actor_event (ClutterActor *actor, ClutterEvent *event, LaughMedia *self)
355 ClutterUnit x, y;
356 clutter_actor_transform_stage_point (actor,
357 CLUTTER_UNITS_FROM_INT (event->button.x),
358 CLUTTER_UNITS_FROM_INT (event->button.y),
359 &x, &y);
360 g_signal_emit (self, laugh_media_signals[ACTIVATED],
361 0, CLUTTER_UNITS_TO_INT(x), CLUTTER_UNITS_TO_INT(y));
362 g_printf ("button_press %d %d\n", CLUTTER_UNITS_TO_INT(x), CLUTTER_UNITS_TO_INT(y));
364 return FALSE;
367 static void _laugh_media_display_role_new (LaughNode *node)
369 LaughMedia *self = (LaughMedia *)node;
370 LaughRoleDisplay *display_role;
372 display_role = g_new0 (LaughRoleDisplay, 1);
373 display_role->role.type = LaughRoleTypeDisplay;
375 self->priv->display_role = display_role;
378 static void _laugh_media_start (LaughNode *node)
380 LaughMedia *self = (LaughMedia *)node;
381 LaughMediaPrivate *priv = self->priv;
382 LaughNode *region;
383 ClutterMedia *media = NULL;
384 const gchar *region_id = laugh_node_get_attribute(node, LaughAttrIdRegion);
386 if (!region_id) {
387 g_printerr ("'%s' default region not implemented\n",
388 laugh_tag_from_id (node->id));
389 return;
391 region = laugh_document_get_element_by_id (node->document,
392 laugh_document_id_from_string (node->document, region_id));
393 if (region && LAUGH_IS_LAYOUT(region)) {
394 priv->region = region;
395 } else {
396 g_printerr ("start '%s' region \"%s\" not found\n",
397 laugh_tag_from_id (node->id), region_id);
398 return;
400 g_object_weak_ref ((GObject *) region, _laugh_media_region_destroyed, priv);
402 switch (priv->type) {
403 case LaughMediaTypeText:
404 if (priv->data) {
405 _laugh_media_display_role_new (node);
406 priv->display_role->actor = clutter_label_new_with_text (
407 "sans 10", (const char *)priv->data);
409 break;
410 #ifdef HAVE_WEBKIT_CLUTTER
411 case LaughMediaTypeTextHTML:
412 _laugh_media_display_role_new (node);
413 priv->display_role->actor = webkit_web_view_new (50, 50);
414 webkit_web_view_open ((WebKitWebView *)priv->display_role->actor, priv->uri);
415 break;
416 #endif
417 case LaughMediaTypeImage:
418 if (priv->data) {
419 _laugh_media_display_role_new (node);
420 priv->display_role->actor = clutter_texture_new_from_pixbuf (
421 (GdkPixbuf *)priv->data);
423 break;
424 case LaughMediaTypeAudio:
425 media = (ClutterMedia *) clutter_gst_audio_new ();
426 priv->data = (gpointer) media;
427 break;
428 case LaughMediaTypeVideo:
429 _laugh_media_display_role_new (node);
430 priv->display_role->actor = clutter_gst_video_texture_new ();
431 media = (ClutterMedia *) priv->display_role->actor;
432 g_object_set (G_OBJECT(priv->display_role->actor), "sync-size", FALSE, NULL);
433 g_signal_connect (CLUTTER_TEXTURE(priv->display_role->actor),
434 "size-change",
435 G_CALLBACK (_laugh_media_size_change), self);
436 break;
437 default:
438 break;
440 if (LaughMediaTypeAudio == priv->type || LaughMediaTypeVideo == priv->type) {
441 LaughRoleTiming *role = self->priv->timing_role;
442 if (!role->dur) {
443 role->dur = laugh_timing_new ();
444 role->dur->type = LaughTimingMedia;
446 role->dur->handler_id = g_signal_connect (G_OBJECT (media),
447 "eos", (GCallback) _laugh_media_eos, self);
449 g_printf ("playing gst %s\n", priv->uri);
450 clutter_media_set_uri (media, priv->uri);
451 clutter_media_set_playing (media, TRUE);
453 if (priv->display_role) {
454 LaughRoleDisplayGroup *group = (LaughRoleDisplayGroup *)
455 laugh_node_role_get (priv->region, LaughRoleTypeDisplayGroup);
456 clutter_container_add_actor (CLUTTER_CONTAINER (group->actor), priv->display_role->actor);
457 _laugh_media_actor_position (node);
458 CLUTTER_ACTOR_SET_FLAGS (priv->display_role->actor, CLUTTER_ACTOR_REACTIVE);
459 clutter_actor_show (priv->display_role->actor);
460 g_signal_connect (G_OBJECT (priv->display_role->actor), "button-press-event",
461 (GCallback) _lauch_media_actor_event, self);
462 g_signal_connect (G_OBJECT (priv->display_role->actor), "destroy",
463 (GCallback) _lauch_media_actor_destroyed, self);
467 static void _laugh_media_stop (LaughNode *node)
469 LaughMedia *self = (LaughMedia *)node;
470 LaughMediaPrivate *priv = self->priv;
472 switch (node->id) {
473 case LaughTagIdAudio:
474 case LaughTagIdVideo:
475 if (priv->data) {
476 ClutterMedia *m = CLUTTER_MEDIA (priv->data);
477 clutter_media_set_playing (m, FALSE);
478 g_object_unref (priv->data);
479 priv->data = NULL;
481 break;
482 default:
483 break;
486 if (priv->region) {
487 g_object_weak_unref ((GObject *) priv->region,
488 _laugh_media_region_destroyed, priv);
489 priv->region = NULL;
492 if (priv->display_role) {
493 clutter_actor_destroy (priv->display_role->actor);
494 g_free (priv->display_role);
495 priv->display_role = NULL;
499 static LaughRole *_laugh_media_role (LaughNode *node, LaughRoleType type)
501 LaughMedia *self = (LaughMedia *) node;
503 switch (type) {
504 case LaughRoleTypeDisplay:
505 return (LaughRole *) self->priv->display_role;
506 case LaughRoleTypeMedia:
507 return &laugh_media_media_role;
508 case LaughRoleTypeTiming:
509 return (LaughRole *) self->priv->timing_role;
510 default:
511 return NULL;
515 static void laugh_media_class_init (LaughMediaClass *klass)
517 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
518 LaughNodeClass *node_class = (LaughNodeClass *) klass;
520 laugh_media_parent_class = g_type_class_peek_parent (klass);
522 gobject_class->finalize = laugh_media_finalize;
523 gobject_class->dispose = laugh_media_dispose;
524 node_class->init = _laugh_media_init;
525 node_class->set_attribute = _laugh_media_set_attribute;
526 node_class->start = _laugh_media_start;
527 node_class->stop = _laugh_media_stop;
528 node_class->role = _laugh_media_role;
530 laugh_media_signals[ACTIVATED] =
531 g_signal_new ("activated",
532 G_TYPE_FROM_CLASS (gobject_class),
533 G_SIGNAL_RUN_LAST,
534 G_STRUCT_OFFSET (LaughMediaClass, activated),
535 NULL, NULL,
536 laugh_marshal_VOID__INT_INT,
537 G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
539 g_type_class_add_private (gobject_class, sizeof (LaughMediaPrivate));
542 static
543 void laugh_media_instance_init (GTypeInstance *instance, gpointer g_class)
545 LaughMedia *self = (LaughMedia *)instance;
547 self->priv = LAUGH_MEDIA_GET_PRIVATE (self);
549 self->priv->timing_role = laugh_timing_role_new ((LaughNode *)self);
552 GType laugh_media_get_type (void)
554 static GType type = 0;
555 if (type == 0) {
556 static const GTypeInfo info = {
557 sizeof (LaughMediaClass),
558 NULL, /* base_init */
559 NULL, /* base_finalize */
560 (GClassInitFunc) laugh_media_class_init, /* class_init */
561 NULL, /* class_finalize */
562 NULL, /* class_data */
563 sizeof (LaughMedia),
564 0, /* n_preallocs */
565 laugh_media_instance_init /* instance_init */
567 type = g_type_register_static (LAUGH_TYPE_NODE,
568 "LaughMediaType",
569 &info, 0);
570 laugh_media_media_role.type = LaughRoleTypeMedia;
572 return type;
575 LaughNode *laugh_media_new (LaughDocument *doc, LaughNodeTagId id,
576 GHashTable *attributes)
578 LaughNode *n = (LaughNode *)g_object_new(LAUGH_TYPE_MEDIA, NULL);
580 laugh_node_base_construct (doc, n, id, attributes);
582 return n;