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.
26 * @short_description: text/img/brush/ref/audio/video media classes.
33 #include "laugh-marshal.h"
34 #include "laugh-media.h"
35 #include "laugh-layout.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>
49 #define LAUGH_MEDIA_GET_PRIVATE(obj) \
50 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_MEDIA, LaughMediaPrivate))
59 LaughMediaTypeUnknown
= 0,
63 #ifdef HAVE_WEBKIT_CLUTTER
64 LaughMediaTypeTextHTML
,
69 struct _LaughMediaPrivate
71 LaughRoleDisplay
*display_role
;
72 LaughRoleTiming
*timing_role
;
74 LaughSizeSetting size_setting
;
78 guint intrinsic_width
;
79 guint intrinsic_height
;
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
);
106 case LaughMediaTypeImage
:
107 case LaughMediaTypeAudio
:
108 g_object_unref (self
->priv
->data
);
114 g_free (self
->priv
->uri
);
116 G_OBJECT_CLASS (laugh_media_parent_class
)->dispose (object
);
118 laugh_timing_role_delete (role
);
122 _laugh_media_init (LaughNode
*node
, LaughInitializer
*initializer
)
124 LaughMedia
*self
= (LaughMedia
*) node
;
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
,
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
;
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")) {
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
);
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
);
199 switch (priv
->type
) {
200 case LaughMediaTypeText
:
202 g_printf ("text: %s\n", (const char *)data
);
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");
213 priv
->intrinsic_width
= gdk_pixbuf_get_width (pix
);
214 priv
->intrinsic_height
= gdk_pixbuf_get_height (pix
);
216 g_object_unref (loader
);
220 case LaughMediaTypeAudio
:
222 case LaughMediaTypeVideo
:
223 /*TODO: determine sizes*/
230 g_object_unref (G_OBJECT (io
));
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
;
242 float sx
, sy
, sw
, sh
;
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
);
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
;
260 sh
= priv
->intrinsic_height
* sw
/ priv
->intrinsic_width
;
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
;
267 sw
= priv
->intrinsic_width
* sh
/ priv
->intrinsic_height
;
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
);
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
);
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
);
316 static void _laugh_media_region_destroyed (gpointer data
, GObject
*obj
)
318 LaughMediaPrivate
*priv
= (LaughMediaPrivate
*) data
;
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
&&
341 LaughTimingMedia
== role
->dur
->type
) {
342 laugh_timing_notify (role
, role
->dur
->handler_id
);
347 void _laugh_media_size_change (ClutterTexture
*tex
, gint w
, gint h
, LaughMedia
*self
)
349 g_printf ("size_change %d %d\n", w
, h
);
353 _lauch_media_actor_event (ClutterActor
*actor
, ClutterEvent
*event
, LaughMedia
*self
)
356 clutter_actor_transform_stage_point (actor
,
357 CLUTTER_UNITS_FROM_INT (event
->button
.x
),
358 CLUTTER_UNITS_FROM_INT (event
->button
.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
));
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
;
383 ClutterMedia
*media
= NULL
;
384 const gchar
*region_id
= laugh_node_get_attribute(node
, LaughAttrIdRegion
);
387 g_printerr ("'%s' default region not implemented\n",
388 laugh_tag_from_id (node
->id
));
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
;
396 g_printerr ("start '%s' region \"%s\" not found\n",
397 laugh_tag_from_id (node
->id
), region_id
);
400 g_object_weak_ref ((GObject
*) region
, _laugh_media_region_destroyed
, priv
);
402 switch (priv
->type
) {
403 case LaughMediaTypeText
:
405 _laugh_media_display_role_new (node
);
406 priv
->display_role
->actor
= clutter_label_new_with_text (
407 "sans 10", (const char *)priv
->data
);
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
);
417 case LaughMediaTypeImage
:
419 _laugh_media_display_role_new (node
);
420 priv
->display_role
->actor
= clutter_texture_new_from_pixbuf (
421 (GdkPixbuf
*)priv
->data
);
424 case LaughMediaTypeAudio
:
425 media
= (ClutterMedia
*) clutter_gst_audio_new ();
426 priv
->data
= (gpointer
) media
;
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
),
435 G_CALLBACK (_laugh_media_size_change
), self
);
440 if (LaughMediaTypeAudio
== priv
->type
|| LaughMediaTypeVideo
== priv
->type
) {
441 LaughRoleTiming
*role
= self
->priv
->timing_role
;
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
;
473 case LaughTagIdAudio
:
474 case LaughTagIdVideo
:
476 ClutterMedia
*m
= CLUTTER_MEDIA (priv
->data
);
477 clutter_media_set_playing (m
, FALSE
);
478 g_object_unref (priv
->data
);
487 g_object_weak_unref ((GObject
*) priv
->region
,
488 _laugh_media_region_destroyed
, priv
);
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
;
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
;
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
),
534 G_STRUCT_OFFSET (LaughMediaClass
, activated
),
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
));
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;
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 */
565 laugh_media_instance_init
/* instance_init */
567 type
= g_type_register_static (LAUGH_TYPE_NODE
,
570 laugh_media_media_role
.type
= LaughRoleTypeMedia
;
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
);