Make sure to get the timing tree in sane state after an internal jump
[laugh.git] / src / laugh-media.c
blob7c92cb2bd555c1e25b2876605920d3035884842a
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>
46 #define LAUGH_MEDIA_GET_PRIVATE(obj) \
47 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_MEDIA, LaughMediaPrivate))
49 enum {
50 ACTIVATED,
52 LAST_SIGNAL
55 typedef enum {
56 LaughMediaTypeUnknown = 0,
57 LaughMediaTypeAudio,
58 LaughMediaTypeImage,
59 LaughMediaTypeText,
60 LaughMediaTypeVideo
61 } LaughMediaType;
63 struct _LaughMediaPrivate
65 LaughRoleDisplay *display_role;
66 LaughRoleTiming *timing_role;
67 LaughMediaType type;
68 LaughSizeSetting size_setting;
69 LaughNode *region;
70 LaughIO *io;
71 gpointer *data;
72 guint intrinsic_width;
73 guint intrinsic_height;
74 gchar *uri;
77 static gpointer laugh_media_parent_class = ((void *)0);
78 static guint laugh_media_signals[LAST_SIGNAL] = { 0, };
79 static LaughRole laugh_media_media_role;
81 static void laugh_media_finalize (GObject *object)
83 G_OBJECT_CLASS (laugh_media_parent_class)->finalize (object);
86 static void laugh_media_dispose (GObject *object)
88 LaughMedia *self = LAUGH_MEDIA(object);
89 LaughRoleTiming *role = self->priv->timing_role;
91 if (self->priv->display_role) {
92 clutter_actor_destroy (self->priv->display_role->actor);
93 g_free (self->priv->display_role);
95 if (self->priv->data)
96 switch (self->priv->type) {
97 case LaughMediaTypeText:
98 g_free (self->priv->data);
99 break;
100 case LaughMediaTypeImage:
101 case LaughMediaTypeAudio:
102 g_object_unref (self->priv->data);
103 break;
104 default:
105 break;
107 if (self->priv->uri)
108 g_free (self->priv->uri);
110 G_OBJECT_CLASS (laugh_media_parent_class)->dispose (object);
112 laugh_timing_role_delete (role);
115 static void
116 _laugh_media_init (LaughNode *node, LaughInitializer *initializer)
118 LaughMedia *self = (LaughMedia *) node;
119 LaughNode *child;
120 LaughRoleTiming *role = self->priv->timing_role;
121 LaughRoleTiming *parent_timing_role = initializer->parent_segment;
123 if (node->attributes)
124 g_hash_table_foreach (node->attributes, laugh_attributes_set, node);
126 if (self->priv->io) {
127 initializer->init_pending = g_slist_append (initializer->init_pending,
128 node);
129 g_signal_connect (G_OBJECT (node), "initialized",
130 (GCallback) initializer->initialized, initializer);
133 laugh_timing_role_child_add (initializer->parent_segment, role);
135 initializer->parent_segment = role;
137 for (child = node->first_child; child; child = child->next_sibling)
138 laugh_node_init (child, initializer);
140 initializer->parent_segment = parent_timing_role;
143 void _lauch_media_mime_type (LaughIO *io, const gchar *mime, gpointer d)
145 LaughMedia *media = LAUGH_MEDIA (d);
146 LaughNode *node = (LaughNode *) media;
148 g_printf ("lauch_media_mime_type: %s\n", mime);
149 if (!strncmp (mime, "text/", 5)) {
150 media->priv->type = LaughMediaTypeText;
151 } else if (!strncmp (mime, "image/", 6)) {
152 media->priv->type = LaughMediaTypeImage;
153 } else if (!strncmp (mime, "audio/", 6)) {
154 media->priv->type = LaughMediaTypeAudio;
155 } else if (!strncmp (mime, "video/", 6)) {
156 media->priv->type = LaughMediaTypeVideo;
157 } else if (!strcmp (mime, "application/octet-stream")) {
158 if (LaughTagIdAudio)
159 media->priv->type = LaughMediaTypeAudio;
160 else if (LaughTagIdVideo)
161 media->priv->type = LaughMediaTypeVideo;
163 if (LaughMediaTypeUnknown == media->priv->type)
164 g_printerr ("%s mimetype '%s' not (yet) supported\n",
165 laugh_node_get_attribute (node, LaughAttrIdSrc), mime);
166 if (LaughMediaTypeImage != media->priv->type &&
167 LaughMediaTypeText != media->priv->type) {
168 if (LaughMediaTypeUnknown != media->priv->type)
169 media->priv->uri = g_strdup (laugh_io_get_uri (io));
170 laugh_io_cancel (io);
171 media->priv->io = NULL;
173 g_object_unref (G_OBJECT (io));
175 laugh_node_base_emit_initialized (node);
179 static void
180 _lauch_media_completed (LaughIO *io, gsize sz, gpointer data, gpointer d)
182 LaughMedia *media = LAUGH_MEDIA (d);
183 LaughNode *node = (LaughNode *) media;
184 LaughMediaPrivate *priv = media->priv;
186 g_printf ("_lauch_media_completed %s: %u\n", laugh_io_get_uri (io), sz);
187 if (data) {
188 switch (priv->type) {
189 case LaughMediaTypeText:
190 priv->data = data;
191 g_printf ("text: %s\n", (const char *)data);
192 break;
193 case LaughMediaTypeImage: {
194 GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
195 if (gdk_pixbuf_loader_write (loader,
196 (const guchar *) data, sz, NULL) &&
197 gdk_pixbuf_loader_close (loader, NULL)) {
198 GdkPixbuf *pix = gdk_pixbuf_loader_get_pixbuf (loader);
199 g_printf ("image:\n");
200 g_object_ref (pix);
201 priv->data = pix;
202 priv->intrinsic_width = gdk_pixbuf_get_width (pix);
203 priv->intrinsic_height = gdk_pixbuf_get_height (pix);
205 g_object_unref (loader);
206 g_free (data);
207 break;
209 case LaughMediaTypeAudio:
210 break;
211 case LaughMediaTypeVideo:
212 /*TODO: determine sizes*/
213 break;
214 default:
215 g_free (data);
216 break;
219 g_object_unref (G_OBJECT (io));
220 priv->io = NULL;
222 laugh_node_base_emit_initialized (node);
225 static void _laugh_media_actor_position (LaughNode *node)
227 LaughMedia *self = (LaughMedia *)node;
228 LaughMediaPrivate *priv = self->priv;
229 LaughRoleDisplay *region_display;
230 guint pw, ph;
231 float sx, sy, sw, sh;
232 const gchar *fit;
234 region_display =
235 (LaughRoleDisplay *) laugh_node_role_get (priv->region, LaughRoleTypeDisplay);
236 clutter_actor_get_size (region_display->actor, &pw, &ph);
237 laugh_size_setting_get (&priv->size_setting, pw, ph, &sx, &sy, &sw, &sh);
238 fit = laugh_node_get_attribute(node, LaughAttrIdFit);
239 if (!fit)
240 fit = laugh_node_get_attribute (priv->region, LaughAttrIdFit);
241 if (priv->intrinsic_width > 0 && priv->intrinsic_height > 0) {
242 if (!fit || !strcmp (fit, "hidden") || !strcmp (fit, "scroll")) {
243 sw = priv->intrinsic_width;
244 sh = priv->intrinsic_height;
245 } else if (!strcmp (fit, "meet")) {
246 float iasp = 1.0 * priv->intrinsic_width / priv->intrinsic_height;
247 float rasp = 1.0 * sw / sh;
248 if (iasp > rasp)
249 sh = priv->intrinsic_height * sw / priv->intrinsic_width;
250 else
251 sw = priv->intrinsic_width * sh / priv->intrinsic_height;
252 } else if (!strcmp (fit, "slice")) {
253 float iasp = 1.0 * priv->intrinsic_width / priv->intrinsic_height;
254 float rasp = 1.0 * sw / sh;
255 if (iasp > rasp)
256 sw = priv->intrinsic_width * sh / priv->intrinsic_height;
257 else
258 sh = priv->intrinsic_height * sw / priv->intrinsic_width;
261 clutter_actor_set_size (priv->display_role->actor, sw, sh);
262 clutter_actor_set_position (priv->display_role->actor, sx, sy);
265 static void _laugh_media_set_attribute (LaughNode *node,
266 LaughNodeAttributeId att, const gchar *value, gpointer *undo)
268 const gchar *val = value;
269 LaughMedia *self = (LaughMedia *)node;
270 LaughMediaPrivate *priv = self->priv;
272 laugh_node_base_set_attribute (node, att, val, undo);
274 if (!value && undo)
275 val = *(const gchar **)undo;
276 g_printf ("_laugh_media_set_attribute %s=%s\n", laugh_attribute_from_id (att), val);
278 if (laugh_timing_setting_set_attribute (priv->timing_role, att, val)) {
279 } else if (laugh_size_setting_set_attribute (&self->priv->size_setting, att, value) ||
280 LaughAttrIdFit == att) {
281 if (priv->region && priv->display_role)
282 _laugh_media_actor_position (node);
283 } else {
284 switch (att) {
285 case LaughAttrIdSrc:
286 if (val) {
287 priv->io = laugh_io_new (val,
288 laugh_document_get_uri (node->document));
290 g_signal_connect (G_OBJECT (priv->io), "mime-type",
291 (GCallback) _lauch_media_mime_type, (gpointer) node);
292 g_signal_connect (G_OBJECT (priv->io), "completed",
293 (GCallback) _lauch_media_completed, (gpointer) node);
295 laugh_io_open (priv->io);
297 break;
298 default:
299 break;
304 static void _laugh_media_region_destroyed (gpointer data, GObject *obj)
306 LaughMediaPrivate *priv = (LaughMediaPrivate *) data;
307 (void) obj;
309 priv->region = NULL;
310 g_free (priv->display_role);
311 priv->display_role = NULL;
314 static void _lauch_media_actor_destroyed (ClutterActor *actor, gpointer data)
316 LaughMedia *self = LAUGH_MEDIA(data);
318 g_free (self->priv->display_role);
319 self->priv->display_role = NULL;
322 static void _laugh_media_eos (ClutterMedia *media, LaughNode *node)
324 LaughMedia *self = (LaughMedia *)node;
325 LaughRoleTiming *role = self->priv->timing_role;
326 g_printf ("playing stopped %d %d\n", node->state == LaughStateBegun, node->state);
328 if (LaughStateBegun == node->state &&
329 role->dur &&
330 LaughTimingMedia == role->dur->type) {
331 laugh_timing_notify (role, role->dur->handler_id);
335 static
336 void _laugh_media_size_change (ClutterTexture *tex, gint w, gint h, LaughMedia *self)
338 g_printf ("size_change %d %d\n", w, h);
341 static gboolean
342 _lauch_media_actor_event (ClutterActor *actor, ClutterEvent *event, LaughMedia *self)
344 ClutterUnit x, y;
345 clutter_actor_transform_stage_point (actor,
346 CLUTTER_UNITS_FROM_INT (event->button.x),
347 CLUTTER_UNITS_FROM_INT (event->button.y),
348 &x, &y);
349 g_signal_emit (self, laugh_media_signals[ACTIVATED],
350 0, CLUTTER_UNITS_TO_INT(x), CLUTTER_UNITS_TO_INT(y));
351 g_printf ("button_press %d %d\n", CLUTTER_UNITS_TO_INT(x), CLUTTER_UNITS_TO_INT(y));
353 return FALSE;
356 static void _laugh_media_display_role_new (LaughNode *node)
358 LaughMedia *self = (LaughMedia *)node;
359 LaughRoleDisplay *display_role;
361 display_role = g_new0 (LaughRoleDisplay, 1);
362 display_role->role.type = LaughRoleTypeDisplay;
364 self->priv->display_role = display_role;
367 static void _laugh_media_start (LaughNode *node)
369 LaughMedia *self = (LaughMedia *)node;
370 LaughMediaPrivate *priv = self->priv;
371 LaughNode *region;
372 ClutterMedia *media = NULL;
373 const gchar *region_id = laugh_node_get_attribute(node, LaughAttrIdRegion);
375 if (!region_id) {
376 g_printerr ("'%s' default region not implemented\n",
377 laugh_tag_from_id (node->id));
378 return;
380 region = laugh_document_get_element_by_id (node->document,
381 laugh_document_id_from_string (node->document, region_id));
382 if (region && LAUGH_IS_LAYOUT(region)) {
383 priv->region = region;
384 } else {
385 g_printerr ("start '%s' region \"%s\" not found\n",
386 laugh_tag_from_id (node->id), region_id);
387 return;
389 g_object_weak_ref ((GObject *) region, _laugh_media_region_destroyed, priv);
391 switch (priv->type) {
392 case LaughMediaTypeText:
393 if (priv->data) {
394 _laugh_media_display_role_new (node);
395 priv->display_role->actor = clutter_label_new_with_text (
396 "sans 10", (const char *)priv->data);
398 break;
399 case LaughMediaTypeImage:
400 if (priv->data) {
401 _laugh_media_display_role_new (node);
402 priv->display_role->actor = clutter_texture_new_from_pixbuf (
403 (GdkPixbuf *)priv->data);
405 break;
406 case LaughMediaTypeAudio:
407 media = (ClutterMedia *) clutter_gst_audio_new ();
408 priv->data = (gpointer) media;
409 break;
410 case LaughMediaTypeVideo:
411 _laugh_media_display_role_new (node);
412 priv->display_role->actor = clutter_gst_video_texture_new ();
413 media = (ClutterMedia *) priv->display_role->actor;
414 g_object_set (G_OBJECT(priv->display_role->actor), "sync-size", FALSE, NULL);
415 g_signal_connect (CLUTTER_TEXTURE(priv->display_role->actor),
416 "size-change",
417 G_CALLBACK (_laugh_media_size_change), self);
418 break;
419 default:
420 break;
422 if (LaughMediaTypeAudio == priv->type || LaughMediaTypeVideo == priv->type) {
423 LaughRoleTiming *role = self->priv->timing_role;
424 if (!role->dur) {
425 role->dur = laugh_timing_new ();
426 role->dur->type = LaughTimingMedia;
428 role->dur->handler_id = g_signal_connect (G_OBJECT (media),
429 "eos", (GCallback) _laugh_media_eos, self);
431 g_printf ("playing gst %s\n", priv->uri);
432 clutter_media_set_uri (media, priv->uri);
433 clutter_media_set_playing (media, TRUE);
435 if (priv->display_role) {
436 LaughRoleDisplayGroup *group = (LaughRoleDisplayGroup *)
437 laugh_node_role_get (priv->region, LaughRoleTypeDisplayGroup);
438 clutter_container_add_actor (CLUTTER_CONTAINER (group->actor), priv->display_role->actor);
439 _laugh_media_actor_position (node);
440 CLUTTER_ACTOR_SET_FLAGS (priv->display_role->actor, CLUTTER_ACTOR_REACTIVE);
441 clutter_actor_show (priv->display_role->actor);
442 g_signal_connect (G_OBJECT (priv->display_role->actor), "button-press-event",
443 (GCallback) _lauch_media_actor_event, self);
444 g_signal_connect (G_OBJECT (priv->display_role->actor), "destroy",
445 (GCallback) _lauch_media_actor_destroyed, self);
449 static void _laugh_media_stop (LaughNode *node)
451 LaughMedia *self = (LaughMedia *)node;
452 LaughMediaPrivate *priv = self->priv;
454 switch (node->id) {
455 case LaughTagIdAudio:
456 case LaughTagIdVideo:
457 if (priv->data) {
458 ClutterMedia *m = CLUTTER_MEDIA (priv->data);
459 clutter_media_set_playing (m, FALSE);
460 g_object_unref (priv->data);
461 priv->data = NULL;
463 break;
464 default:
465 break;
468 if (priv->region) {
469 g_object_weak_unref ((GObject *) priv->region,
470 _laugh_media_region_destroyed, priv);
471 priv->region = NULL;
474 if (priv->display_role) {
475 clutter_actor_destroy (priv->display_role->actor);
476 g_free (priv->display_role);
477 priv->display_role = NULL;
481 static LaughRole *_laugh_media_role (LaughNode *node, LaughRoleType type)
483 LaughMedia *self = (LaughMedia *) node;
485 switch (type) {
486 case LaughRoleTypeDisplay:
487 return (LaughRole *) self->priv->display_role;
488 case LaughRoleTypeMedia:
489 return &laugh_media_media_role;
490 case LaughRoleTypeTiming:
491 return (LaughRole *) self->priv->timing_role;
492 default:
493 return NULL;
497 static void laugh_media_class_init (LaughMediaClass *klass)
499 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
500 LaughNodeClass *node_class = (LaughNodeClass *) klass;
502 laugh_media_parent_class = g_type_class_peek_parent (klass);
504 gobject_class->finalize = laugh_media_finalize;
505 gobject_class->dispose = laugh_media_dispose;
506 node_class->init = _laugh_media_init;
507 node_class->set_attribute = _laugh_media_set_attribute;
508 node_class->start = _laugh_media_start;
509 node_class->stop = _laugh_media_stop;
510 node_class->role = _laugh_media_role;
512 laugh_media_signals[ACTIVATED] =
513 g_signal_new ("activated",
514 G_TYPE_FROM_CLASS (gobject_class),
515 G_SIGNAL_RUN_LAST,
516 G_STRUCT_OFFSET (LaughMediaClass, activated),
517 NULL, NULL,
518 laugh_marshal_VOID__INT_INT,
519 G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
521 g_type_class_add_private (gobject_class, sizeof (LaughMediaPrivate));
524 static
525 void laugh_media_instance_init (GTypeInstance *instance, gpointer g_class)
527 LaughMedia *self = (LaughMedia *)instance;
529 self->priv = LAUGH_MEDIA_GET_PRIVATE (self);
531 self->priv->timing_role = laugh_timing_role_new ((LaughNode *)self);
534 GType laugh_media_get_type (void)
536 static GType type = 0;
537 if (type == 0) {
538 static const GTypeInfo info = {
539 sizeof (LaughMediaClass),
540 NULL, /* base_init */
541 NULL, /* base_finalize */
542 (GClassInitFunc) laugh_media_class_init, /* class_init */
543 NULL, /* class_finalize */
544 NULL, /* class_data */
545 sizeof (LaughMedia),
546 0, /* n_preallocs */
547 laugh_media_instance_init /* instance_init */
549 type = g_type_register_static (LAUGH_TYPE_NODE,
550 "LaughMediaType",
551 &info, 0);
552 laugh_media_media_role.type = LaughRoleTypeMedia;
554 return type;
557 LaughNode *laugh_media_new (LaughDocument *doc, LaughNodeTagId id,
558 GHashTable *attributes)
560 LaughNode *n = (LaughNode *)g_object_new(LAUGH_TYPE_MEDIA, NULL);
562 laugh_node_base_construct (doc, n, id, attributes);
564 return n;