1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Cyril Concolato - Jean le Feuvre
5 * Copyright (c) Telecom ParisTech 2005-2012
6 * All rights reserved
7 *
8 * This file is part of GPAC / Scene Compositor sub-project
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC 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 Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 #include "visual_manager.h"
27
28 #ifndef GPAC_DISABLE_SVG
29 #include "nodes_stacks.h"
30
31 static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status, GF_Node *audio, GF_Node *video);
32 static void svg_traverse_audio_ex(GF_Node *node, void *rs, Bool is_destroy, SVGPropertiesPointers *props);
33
34
35
svg_video_get_transform_behavior(GF_TraverseState * tr_state,SVGAllAttributes * atts,Fixed * cx,Fixed * cy,Fixed * angle)36 static Bool svg_video_get_transform_behavior(GF_TraverseState *tr_state, SVGAllAttributes *atts, Fixed *cx, Fixed *cy, Fixed *angle)
37 {
38 SFVec2f pt;
39 if (!atts->transformBehavior) return GF_FALSE;
40 if (*atts->transformBehavior == SVG_TRANSFORMBEHAVIOR_GEOMETRIC)
41 return GF_FALSE;
42
43 pt.x = atts->x ? atts->x->value : 0;
44 pt.y = atts->y ? atts->y->value : 0;
45 gf_mx2d_apply_point(&tr_state->transform, &pt);
46 *cx = pt.x;
47 *cy = pt.y;
48
49 *angle = 0;
50 switch (*atts->transformBehavior) {
51 case SVG_TRANSFORMBEHAVIOR_PINNED:
52 break;
53 case SVG_TRANSFORMBEHAVIOR_PINNED180:
54 *angle = GF_PI;
55 break;
56 case SVG_TRANSFORMBEHAVIOR_PINNED270:
57 *angle = -GF_PI/2;
58 break;
59 case SVG_TRANSFORMBEHAVIOR_PINNED90:
60 *angle = GF_PI/2;
61 break;
62 }
63 return GF_TRUE;
64 }
65
66
SVG_Draw_bitmap(GF_TraverseState * tr_state)67 static void SVG_Draw_bitmap(GF_TraverseState *tr_state)
68 {
69 DrawableContext *ctx = tr_state->ctx;
70 if (!tr_state->visual->DrawBitmap(tr_state->visual, tr_state, ctx)) {
71 visual_2d_texture_path(tr_state->visual, ctx->drawable->path, ctx, tr_state);
72 }
73 }
74
SVG_Build_Bitmap_Graph(SVG_video_stack * stack,GF_TraverseState * tr_state)75 static void SVG_Build_Bitmap_Graph(SVG_video_stack *stack, GF_TraverseState *tr_state)
76 {
77 u32 tag;
78 GF_Rect rc, new_rc;
79 Fixed x, y, width, height, txwidth, txheight;
80 Fixed rectx, recty, rectwidth, rectheight;
81 SVGAllAttributes atts;
82 SVG_PreserveAspectRatio pAR;
83 SVG_Element *e = (SVG_Element *)stack->drawable->node;
84
85 gf_svg_flatten_attributes(e, &atts);
86
87 tag = gf_node_get_tag(stack->drawable->node);
88 switch (tag) {
89 case TAG_SVG_image:
90 case TAG_SVG_video:
91 x = (atts.x ? atts.x->value : 0);
92 y = (atts.y ? atts.y->value : 0);
93 width = (atts.width ? atts.width->value : 0);
94 height = (atts.height ? atts.height->value : 0);
95 break;
96 default:
97 return;
98 }
99
100 if (!width || !height) return;
101
102 txheight = INT2FIX(stack->txh.height);
103 txwidth = INT2FIX(stack->txh.width);
104
105 if (!txwidth || !txheight) return;
106
107 if (!atts.preserveAspectRatio) {
108 pAR.defer = GF_FALSE;
109 pAR.meetOrSlice = SVG_MEETORSLICE_MEET;
110 pAR.align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
111 } else {
112 pAR = *atts.preserveAspectRatio;
113 }
114 if (pAR.defer) {
115 /* TODO */
116 rectwidth = width;
117 rectheight = height;
118 rectx = x+rectwidth/2;
119 recty = y+rectheight/2;
120 } else {
121
122 if (pAR.align==SVG_PRESERVEASPECTRATIO_NONE) {
123 rectwidth = width;
124 rectheight = height;
125 rectx = x+rectwidth/2;
126 recty = y+rectheight/2;
127 } else {
128 Fixed scale, scale_w, scale_h;
129 scale_w = gf_divfix(width, txwidth);
130 scale_h = gf_divfix(height, txheight);
131 if (pAR.meetOrSlice==SVG_MEETORSLICE_MEET) {
132 if (scale_w > scale_h) {
133 scale = scale_h;
134 rectwidth = gf_mulfix(txwidth, scale);
135 rectheight = height;
136 } else {
137 scale = scale_w;
138 rectwidth = width;
139 rectheight = gf_mulfix(txheight, scale);
140 }
141 } else {
142 if (scale_w < scale_h) {
143 scale = scale_h;
144 rectwidth = gf_mulfix(txwidth, scale);
145 rectheight = height;
146 } else {
147 scale = scale_w;
148 rectwidth = width;
149 rectheight = gf_mulfix(txheight, scale);
150 }
151 }
152
153 rectx = x + rectwidth/2;
154 recty = y + rectheight/2;
155 switch (pAR.align) {
156 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
157 break;
158 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
159 rectx += (width - rectwidth)/ 2;
160 break;
161 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
162 rectx += width - rectwidth;
163 break;
164 case SVG_PRESERVEASPECTRATIO_XMINYMID:
165 recty += (height - rectheight)/ 2;
166 break;
167 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
168 rectx += (width - rectwidth)/ 2;
169 recty += (height - rectheight) / 2;
170 break;
171 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
172 rectx += width - rectwidth;
173 recty += ( txheight - rectheight) / 2;
174 break;
175 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
176 recty += height - rectheight;
177 break;
178 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
179 rectx += (width - rectwidth)/ 2;
180 recty += height - rectheight;
181 break;
182 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
183 rectx += width - rectwidth;
184 recty += height - rectheight;
185 break;
186 }
187 }
188 }
189
190
191 gf_path_get_bounds(stack->drawable->path, &rc);
192 drawable_reset_path(stack->drawable);
193 gf_path_add_rect_center(stack->drawable->path, rectx, recty, rectwidth, rectheight);
194 gf_path_get_bounds(stack->drawable->path, &new_rc);
195 if (!gf_rect_equal(&rc, &new_rc))
196 drawable_mark_modified(stack->drawable, tr_state);
197 else if (stack->txh.flags & GF_SR_TEXTURE_PRIVATE_MEDIA)
198 drawable_mark_modified(stack->drawable, tr_state);
199
200 gf_node_dirty_clear(stack->drawable->node, GF_SG_SVG_GEOMETRY_DIRTY);
201 }
202
svg_open_texture(SVG_video_stack * stack)203 static void svg_open_texture(SVG_video_stack *stack)
204 {
205 gf_sc_texture_open(&stack->txh, &stack->txurl, GF_FALSE);
206 }
207
svg_play_texture(SVG_video_stack * stack,SVGAllAttributes * atts)208 static void svg_play_texture(SVG_video_stack *stack, SVGAllAttributes *atts)
209 {
210 SVGAllAttributes all_atts;
211 Bool lock_scene = GF_FALSE;
212 if (stack->txh.is_open) gf_sc_texture_stop_no_unregister(&stack->txh);
213
214 if (!atts) {
215 gf_svg_flatten_attributes((SVG_Element*)stack->txh.owner, &all_atts);
216 atts = &all_atts;
217 }
218 if (atts->syncBehavior) lock_scene = (*atts->syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE;
219
220 gf_sc_texture_play_from_to(&stack->txh, &stack->txurl,
221 atts->clipBegin ? (*atts->clipBegin) : 0.0,
222 atts->clipEnd ? (*atts->clipEnd) : -1.0,
223 GF_FALSE,
224 lock_scene);
225 }
226
svg_traverse_bitmap(GF_Node * node,void * rs,Bool is_destroy)227 static void svg_traverse_bitmap(GF_Node *node, void *rs, Bool is_destroy)
228 {
229 Fixed cx, cy, angle;
230 /*video stack is just an extension of image stack, type-casting is OK*/
231 SVG_video_stack *stack = (SVG_video_stack*)gf_node_get_private(node);
232 GF_TraverseState *tr_state = (GF_TraverseState *)rs;
233 SVGPropertiesPointers backup_props;
234 u32 backup_flags;
235 GF_Matrix2D backup_matrix;
236 GF_Matrix mx_3d;
237 DrawableContext *ctx;
238 SVGAllAttributes all_atts;
239
240 if (is_destroy) {
241 gf_sc_texture_destroy(&stack->txh);
242 gf_sg_mfurl_del(stack->txurl);
243
244 drawable_del(stack->drawable);
245 if (stack->audio) {
246 gf_node_unregister(stack->audio, NULL);
247 }
248 gf_free(stack);
249 return;
250 }
251
252 if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
253 SVG_Draw_bitmap(tr_state);
254 return;
255 }
256 else if (tr_state->traversing_mode==TRAVERSE_PICK) {
257 svg_drawable_pick(node, stack->drawable, tr_state);
258 return;
259 }
260 #ifndef GPAC_DISABLE_3D
261 else if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) {
262 if (!stack->drawable->mesh) {
263 stack->drawable->mesh = new_mesh();
264 mesh_from_path(stack->drawable->mesh, stack->drawable->path);
265 }
266 compositor_3d_draw_bitmap(stack->drawable, &tr_state->ctx->aspect, tr_state, 0, 0, FIX_ONE, FIX_ONE);
267 return;
268 }
269 #endif
270
271 /*flatten attributes and apply animations + inheritance*/
272 gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
273 if (!compositor_svg_traverse_base(node, &all_atts, (GF_TraverseState *)rs, &backup_props, &backup_flags))
274 return;
275
276 if (gf_node_dirty_get(node) & GF_SG_SVG_XLINK_HREF_DIRTY) {
277 if (!stack->txh.stream || gf_mo_url_changed(stack->txh.stream, &stack->txurl)) {
278
279 gf_term_get_mfurl_from_xlink(node, &stack->txurl);
280 stack->txh.width = stack->txh.height = 0;
281
282 /*remove associated audio if any*/
283 if (stack->audio) {
284 svg_audio_smil_evaluate_ex(NULL, 0, SMIL_TIMING_EVAL_REMOVE, stack->audio, stack->txh.owner);
285 gf_node_unregister(stack->audio, NULL);
286 stack->audio = NULL;
287 }
288 stack->audio_dirty = GF_TRUE;
289
290 if (stack->txurl.count) svg_play_texture(stack, &all_atts);
291 }
292 gf_node_dirty_clear(node, GF_SG_SVG_XLINK_HREF_DIRTY);
293 }
294
295 if (gf_node_dirty_get(node)) {
296 /*do not clear dirty state until the image is loaded*/
297 if (stack->txh.width) {
298 gf_node_dirty_clear(node, 0);
299 SVG_Build_Bitmap_Graph((SVG_video_stack*)gf_node_get_private(node), tr_state);
300 }
301 }
302
303 if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
304 if (!compositor_svg_is_display_off(tr_state->svg_props)) {
305 gf_path_get_bounds(stack->drawable->path, &tr_state->bounds);
306 compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
307
308 if (svg_video_get_transform_behavior(tr_state, &all_atts, &cx, &cy, &angle)) {
309 GF_Matrix2D mx;
310 tr_state->bounds.width = INT2FIX(stack->txh.width);
311 tr_state->bounds.height = INT2FIX(stack->txh.height);
312 tr_state->bounds.x = cx - tr_state->bounds.width/2;
313 tr_state->bounds.y = cy + tr_state->bounds.height/2;
314 gf_mx2d_init(mx);
315 gf_mx2d_add_rotation(&mx, 0, 0, angle);
316 gf_mx2d_apply_rect(&mx, &tr_state->bounds);
317 } else {
318 gf_mx2d_apply_rect(&tr_state->transform, &tr_state->bounds);
319 }
320
321 compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
322 }
323 } else if (tr_state->traversing_mode == TRAVERSE_SORT) {
324 if (!compositor_svg_is_display_off(tr_state->svg_props) && ( *(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN) ) {
325 GF_Matrix mx_bck;
326 Bool restore_mx = GF_FALSE;
327
328 compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
329
330 ctx = drawable_init_context_svg(stack->drawable, tr_state);
331 if (!ctx || !ctx->aspect.fill_texture ) return;
332
333 if (svg_video_get_transform_behavior(tr_state, &all_atts, &cx, &cy, &angle)) {
334 drawable_reset_path(stack->drawable);
335 gf_path_add_rect_center(stack->drawable->path, cx, cy, INT2FIX(stack->txh.width), INT2FIX(stack->txh.height));
336
337 gf_mx2d_copy(mx_bck, tr_state->transform);
338 restore_mx = GF_TRUE;
339
340 gf_mx2d_init(tr_state->transform);
341 gf_mx2d_add_rotation(&tr_state->transform, cx, cy, angle);
342 }
343
344 /*even if set this is not true*/
345 ctx->aspect.pen_props.width = 0;
346 ctx->flags |= CTX_NO_ANTIALIAS;
347
348 /*if rotation, transparent*/
349 ctx->flags &= ~CTX_IS_TRANSPARENT;
350 if (ctx->transform.m[1] || ctx->transform.m[3]) {
351 ctx->flags |= CTX_IS_TRANSPARENT;
352 ctx->flags &= ~CTX_NO_ANTIALIAS;
353 }
354 else if (ctx->aspect.fill_texture->transparent)
355 ctx->flags |= CTX_IS_TRANSPARENT;
356 else if (tr_state->svg_props->opacity && (tr_state->svg_props->opacity->type==SVG_NUMBER_VALUE) && (tr_state->svg_props->opacity->value!=FIX_ONE)) {
357 ctx->flags = CTX_IS_TRANSPARENT;
358 ctx->aspect.fill_color = GF_COL_ARGB(FIX2INT(0xFF * tr_state->svg_props->opacity->value), 0, 0, 0);
359 }
360
361 #ifndef GPAC_DISABLE_3D
362 if (tr_state->visual->type_3d) {
363 if (!stack->drawable->mesh) {
364 stack->drawable->mesh = new_mesh();
365 mesh_from_path(stack->drawable->mesh, stack->drawable->path);
366 }
367 compositor_3d_draw_bitmap(stack->drawable, &ctx->aspect, tr_state, 0, 0, FIX_ONE, FIX_ONE);
368 ctx->drawable = NULL;
369 } else
370 #endif
371 {
372 drawable_finalize_sort(ctx, tr_state, NULL);
373 }
374
375 if (restore_mx) gf_mx2d_copy(tr_state->transform, mx_bck);
376 compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
377 }
378 }
379 if (stack->audio) svg_traverse_audio_ex(stack->audio, rs, GF_FALSE, tr_state->svg_props);
380
381 memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
382 tr_state->svg_flags = backup_flags;
383 }
384
385 /*********************/
386 /* SVG image element */
387 /*********************/
388
SVG_Update_image(GF_TextureHandler * txh)389 static void SVG_Update_image(GF_TextureHandler *txh)
390 {
391 MFURL *txurl = &(((SVG_video_stack *)gf_node_get_private(txh->owner))->txurl);
392
393 /*setup texture if needed*/
394 if (!txh->is_open && txurl->count) {
395 gf_sc_texture_play_from_to(txh, txurl, 0, -1, GF_FALSE, GF_FALSE);
396 }
397
398 gf_sc_texture_update_frame(txh, GF_FALSE);
399 /*URL is present but not opened - redraw till fetch*/
400 if (txh->stream && !txh->stream_finished && (!txh->tx_io || txh->needs_refresh) ) {
401 /*mark all subtrees using this image as dirty*/
402 gf_node_dirty_parents(txh->owner);
403 gf_sc_invalidate(txh->compositor, NULL);
404 }
405 }
406
svg_traverse_image(GF_Node * node,void * rs,Bool is_destroy)407 static void svg_traverse_image(GF_Node *node, void *rs, Bool is_destroy)
408 {
409 svg_traverse_bitmap(node, rs, is_destroy);
410 }
411
compositor_init_svg_image(GF_Compositor * compositor,GF_Node * node)412 void compositor_init_svg_image(GF_Compositor *compositor, GF_Node *node)
413 {
414 SVG_video_stack *stack;
415 GF_SAFEALLOC(stack, SVG_video_stack)
416 if (!stack) {
417 GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg image stack\n"));
418 return;
419 }
420 stack->drawable = drawable_new();
421 stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
422 stack->drawable->node = node;
423
424 gf_sc_texture_setup(&stack->txh, compositor, node);
425 stack->txh.update_texture_fcnt = SVG_Update_image;
426 stack->txh.flags = GF_SR_TEXTURE_SVG;
427
428 /*force first processing of xlink-href*/
429 gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, GF_FALSE);
430
431 gf_node_set_private(node, stack);
432 gf_node_set_callback_function(node, svg_traverse_image);
433 }
434
435 /*********************/
436 /* SVG video element */
437 /*********************/
SVG_Update_video(GF_TextureHandler * txh)438 static void SVG_Update_video(GF_TextureHandler *txh)
439 {
440 GF_FieldInfo init_vis_info;
441 SVG_video_stack *stack = (SVG_video_stack *) gf_node_get_private(txh->owner);
442
443 if (!txh->stream) {
444 svg_open_texture(stack);
445
446 if (!txh->is_open) {
447 SVG_InitialVisibility init_vis;
448 if (stack->first_frame_fetched) return;
449
450 init_vis = SVG_INITIALVISIBILTY_WHENSTARTED;
451
452 if (gf_node_get_attribute_by_tag(txh->owner, TAG_SVG_ATT_initialVisibility, GF_FALSE, GF_FALSE, &init_vis_info) == GF_OK) {
453 init_vis = *(SVG_InitialVisibility *)init_vis_info.far_ptr;
454 }
455
456 /*opens stream only at first access to fetch first frame if needed*/
457 if (init_vis == SVG_INITIALVISIBILTY_ALWAYS) {
458 svg_play_texture((SVG_video_stack*)stack, NULL);
459 gf_sc_invalidate(txh->compositor, NULL);
460 }
461 }
462 return;
463 }
464
465 /*when fetching the first frame disable resync*/
466 gf_sc_texture_update_frame(txh, GF_FALSE);
467
468 /* only when needs_refresh = 1, first frame is fetched */
469 if (!stack->first_frame_fetched) {
470 if (txh->needs_refresh) {
471 stack->first_frame_fetched = GF_TRUE;
472 /*stop stream if needed*/
473 if (!gf_smil_timing_is_active(txh->owner)) {
474 gf_sc_texture_stop_no_unregister(txh);
475 //make sure the refresh flag is not cleared
476 txh->needs_refresh = GF_TRUE;
477 }
478 }
479 }
480
481 if (!stack->audio && stack->audio_dirty) {
482 u32 res = gf_mo_has_audio(stack->txh.stream);
483 if (res != 2) {
484 stack->audio_dirty = GF_FALSE;
485 if (res) {
486 GF_FieldInfo att_vid, att_aud;
487 stack->audio = gf_node_new(gf_node_get_graph(stack->txh.owner), TAG_SVG_audio);
488 gf_node_register(stack->audio, NULL);
489 if (gf_node_get_attribute_by_tag(stack->txh.owner, TAG_XLINK_ATT_href, GF_FALSE, GF_FALSE, &att_vid)==GF_OK) {
490 gf_node_get_attribute_by_tag(stack->audio, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &att_aud);
491 gf_svg_attributes_copy(&att_aud, &att_vid, GF_FALSE);
492 }
493 /*BYPASS SMIL TIMING MODULE!!*/
494 compositor_init_svg_audio(stack->txh.compositor, stack->audio, GF_TRUE);
495 }
496 }
497 }
498
499 /*we have no choice but retraversing the drawable until we're inactive since the movie framerate and
500 the compositor framerate are likely to be different */
501 if (!txh->stream_finished)
502 if (txh->needs_refresh)
503 gf_sc_invalidate(txh->compositor, NULL);
504
505 if (stack->stop_requested) {
506 stack->stop_requested = GF_FALSE;
507 gf_sc_texture_stop_no_unregister(&stack->txh);
508 }
509 }
510
svg_video_smil_evaluate(SMIL_Timing_RTI * rti,Fixed normalized_scene_time,GF_SGSMILTimingEvalState status)511 static void svg_video_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status)
512 {
513 SVG_video_stack *stack = (SVG_video_stack *)gf_node_get_private(gf_smil_get_element(rti));
514
515 switch (status) {
516 case SMIL_TIMING_EVAL_UPDATE:
517 if (!stack->txh.is_open) {
518 if (stack->txurl.count) {
519 svg_play_texture((SVG_video_stack*)stack, NULL);
520 }
521 }
522 else if (stack->txh.stream_finished && (gf_smil_get_media_duration(rti)<0) ) {
523 Double dur = gf_mo_get_duration(stack->txh.stream);
524 if (dur <= 0) {
525 dur = stack->txh.last_frame_time;
526 dur /= 1000;
527 }
528 gf_smil_set_media_duration(rti, dur);
529 }
530 break;
531 case SMIL_TIMING_EVAL_FREEZE:
532 case SMIL_TIMING_EVAL_REMOVE:
533 stack->stop_requested = GF_TRUE;
534 break;
535 case SMIL_TIMING_EVAL_REPEAT:
536 gf_sc_texture_restart(&stack->txh);
537 break;
538 default:
539 break;
540 }
541 if (stack->audio) svg_audio_smil_evaluate_ex(rti, normalized_scene_time, status, stack->audio, stack->txh.owner);
542 }
543
svg_traverse_video(GF_Node * node,void * rs,Bool is_destroy)544 static void svg_traverse_video(GF_Node *node, void *rs, Bool is_destroy)
545 {
546 svg_traverse_bitmap(node, rs, is_destroy);
547 }
548
compositor_init_svg_video(GF_Compositor * compositor,GF_Node * node)549 void compositor_init_svg_video(GF_Compositor *compositor, GF_Node *node)
550 {
551 SVG_video_stack *stack;
552 GF_SAFEALLOC(stack, SVG_video_stack)
553 if (!stack) {
554 GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg video stack\n"));
555 return;
556 }
557 stack->drawable = drawable_new();
558 stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
559 stack->drawable->node = node;
560
561 gf_sc_texture_setup(&stack->txh, compositor, node);
562 stack->txh.update_texture_fcnt = SVG_Update_video;
563 stack->txh.flags = GF_SR_TEXTURE_SVG;
564
565 /*force first processing of xlink-href*/
566 gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, GF_FALSE);
567
568 gf_smil_set_evaluation_callback(node, svg_video_smil_evaluate);
569
570 gf_node_set_private(node, stack);
571 gf_node_set_callback_function(node, svg_traverse_video);
572 }
573
svg_pause_video(GF_Node * n,Bool pause)574 void svg_pause_video(GF_Node *n, Bool pause)
575 {
576 SVG_video_stack *st = (SVG_video_stack *)gf_node_get_private(n);
577 if (!st) return;
578 if (pause) gf_mo_pause(st->txh.stream);
579 else gf_mo_resume(st->txh.stream);
580 }
581
compositor_svg_video_modified(GF_Compositor * compositor,GF_Node * node)582 void compositor_svg_video_modified(GF_Compositor *compositor, GF_Node *node)
583 {
584 /*if href has been modified, stop the video (and associated audio if any) right away - we cannot wait for next traversal to
585 process this as the video could be in a hidden subtree not traversed*/
586 if (gf_node_dirty_get(node) & GF_SG_SVG_XLINK_HREF_DIRTY) {
587 SVG_video_stack *st = (SVG_video_stack *)gf_node_get_private(node);
588 /*WARNING - stack may be NULL at this point when inserting the video from script*/
589 if (st && st->txh.is_open) {
590 if (st->audio) {
591 svg_audio_smil_evaluate_ex(NULL, 0, SMIL_TIMING_EVAL_REMOVE, st->audio, st->txh.owner);
592 gf_node_unregister(st->audio, NULL);
593 st->audio = NULL;
594 }
595 /*reset cached URL to avoid reopening the resource in the smil timing callback*/
596 gf_sg_vrml_mf_reset(&st->txurl, GF_SG_VRML_MFURL);
597 gf_sc_texture_stop(&st->txh);
598 }
599 }
600 gf_node_dirty_set(node, 0, GF_FALSE);
601 /*and force a redraw of next frame*/
602 gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME);
603 }
604
605
606 /*********************/
607 /* SVG audio element */
608 /*********************/
609
svg_audio_smil_evaluate_ex(SMIL_Timing_RTI * rti,Fixed normalized_scene_time,u32 status,GF_Node * slave_audio,GF_Node * video)610 static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status, GF_Node *slave_audio, GF_Node *video)
611 {
612 GF_Node *audio;
613 SVG_audio_stack *stack;
614
615 audio = slave_audio;
616 if (!audio) audio = gf_smil_get_element(rti);
617
618 stack = (SVG_audio_stack *)gf_node_get_private(audio);
619
620 switch (status) {
621 case SMIL_TIMING_EVAL_UPDATE:
622 if (!stack->is_active && !stack->is_error) {
623 if (stack->aurl.count) {
624 SVGAllAttributes atts;
625 Bool lock_timeline = GF_FALSE;
626 gf_svg_flatten_attributes((SVG_Element*) (video ? video : audio), &atts);
627
628 if (atts.syncBehavior) lock_timeline = (*atts.syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE;
629
630 if (gf_sc_audio_open(&stack->input, &stack->aurl,
631 atts.clipBegin ? (*atts.clipBegin) : 0.0,
632 atts.clipEnd ? (*atts.clipEnd) : -1.0,
633 lock_timeline) == GF_OK)
634 {
635 gf_mo_set_speed(stack->input.stream, FIX_ONE);
636 stack->is_active = GF_TRUE;
637 } else {
638 stack->is_error = GF_TRUE;
639 }
640 }
641 }
642 else if (!slave_audio && stack->input.stream_finished && (gf_smil_get_media_duration(rti) < 0) ) {
643 Double dur = gf_mo_get_duration(stack->input.stream);
644 if (dur <= 0) {
645 dur = stack->input.stream ? stack->input.stream->timestamp : 0;
646 dur /= 1000;
647 }
648 gf_smil_set_media_duration(rti, dur);
649 }
650 break;
651 case SMIL_TIMING_EVAL_REPEAT:
652 if (stack->is_active)
653 gf_sc_audio_restart(&stack->input);
654 break;
655 case SMIL_TIMING_EVAL_FREEZE:
656 gf_sc_audio_stop(&stack->input);
657 stack->is_active = GF_FALSE;
658 break;
659 case SMIL_TIMING_EVAL_REMOVE:
660 gf_sc_audio_stop(&stack->input);
661 stack->is_active = GF_FALSE;
662 break;
663 case SMIL_TIMING_EVAL_DEACTIVATE:
664 if (stack->is_active) {
665 gf_sc_audio_stop(&stack->input);
666 gf_sc_audio_unregister(&stack->input);
667 stack->is_active = GF_FALSE;
668 }
669 break;
670 }
671 }
672
svg_audio_smil_evaluate(SMIL_Timing_RTI * rti,Fixed normalized_scene_time,GF_SGSMILTimingEvalState status)673 static void svg_audio_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status)
674 {
675 svg_audio_smil_evaluate_ex(rti, normalized_scene_time, status, NULL, NULL);
676 }
677
678
svg_traverse_audio_ex(GF_Node * node,void * rs,Bool is_destroy,SVGPropertiesPointers * props)679 static void svg_traverse_audio_ex(GF_Node *node, void *rs, Bool is_destroy, SVGPropertiesPointers *props)
680 {
681 SVGAllAttributes all_atts;
682 SVGPropertiesPointers backup_props;
683 u32 backup_flags, restore;
684 GF_TraverseState *tr_state = (GF_TraverseState*)rs;
685 SVG_audio_stack *stack = (SVG_audio_stack *)gf_node_get_private(node);
686
687 if (is_destroy) {
688 gf_sc_audio_predestroy(&stack->input);
689 gf_sg_mfurl_del(stack->aurl);
690 gf_free(stack);
691 return;
692 }
693 if (stack->is_active) {
694 gf_sc_audio_register(&stack->input, (GF_TraverseState*)rs);
695 }
696
697 restore = 0;
698 if (!props) {
699 restore = 1;
700 gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
701 if (!compositor_svg_traverse_base(node, &all_atts, (GF_TraverseState *)rs, &backup_props, &backup_flags))
702 return;
703 props = tr_state->svg_props;
704 }
705
706 if (gf_node_dirty_get(node) & GF_SG_SVG_XLINK_HREF_DIRTY) {
707 SVGAllAttributes atts;
708 Bool lock_timeline = GF_FALSE;
709 if (stack->is_active)
710 gf_sc_audio_stop(&stack->input);
711
712 stack->is_error = GF_FALSE;
713
714 gf_node_dirty_clear(node, GF_SG_SVG_XLINK_HREF_DIRTY);
715 gf_term_get_mfurl_from_xlink(node, &(stack->aurl));
716
717 gf_svg_flatten_attributes((SVG_Element*) node, &atts);
718 if (atts.syncBehavior) lock_timeline = (*atts.syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE;
719
720 if (stack->aurl.count && (gf_sc_audio_open(&stack->input, &stack->aurl,
721 atts.clipBegin ? (*atts.clipBegin) : 0.0,
722 atts.clipEnd ? (*atts.clipEnd) : -1.0,
723 lock_timeline) == GF_OK)
724
725 ) {
726 gf_mo_set_speed(stack->input.stream, FIX_ONE);
727 stack->is_active = GF_TRUE;
728 } else if (stack->is_active) {
729 gf_sc_audio_unregister(&stack->input);
730 stack->is_active = GF_FALSE;
731 }
732 }
733
734 /*store mute flag*/
735 stack->input.is_muted = GF_FALSE;
736 if (tr_state->switched_off
737 || compositor_svg_is_display_off(props)
738 || (*(props->visibility) == SVG_VISIBILITY_HIDDEN) ) {
739
740 stack->input.is_muted = GF_TRUE;
741 }
742
743 stack->input.intensity = tr_state->svg_props->computed_audio_level;
744
745 if (restore) {
746 memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
747 tr_state->svg_flags = backup_flags;
748 }
749 }
svg_traverse_audio(GF_Node * node,void * rs,Bool is_destroy)750 static void svg_traverse_audio(GF_Node *node, void *rs, Bool is_destroy)
751 {
752 svg_traverse_audio_ex(node, rs, is_destroy, NULL);
753 }
754
compositor_init_svg_audio(GF_Compositor * compositor,GF_Node * node,Bool slaved_timing)755 void compositor_init_svg_audio(GF_Compositor *compositor, GF_Node *node, Bool slaved_timing)
756 {
757 SVG_audio_stack *stack;
758 GF_SAFEALLOC(stack, SVG_audio_stack)
759 if (!stack) return;
760 gf_sc_audio_setup(&stack->input, compositor, node);
761
762 /*force first processing of xlink-href*/
763 gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, GF_FALSE);
764
765 if (!slaved_timing)
766 gf_smil_set_evaluation_callback(node, svg_audio_smil_evaluate);
767
768 gf_node_set_private(node, stack);
769 gf_node_set_callback_function(node, svg_traverse_audio);
770 }
771
svg_pause_audio(GF_Node * n,Bool pause)772 void svg_pause_audio(GF_Node *n, Bool pause)
773 {
774 SVG_audio_stack *st = (SVG_audio_stack *)gf_node_get_private(n);
775 if (!st) return;
776 if (pause) gf_mo_pause(st->input.stream);
777 else gf_mo_resume(st->input.stream);
778 }
779
compositor_svg_get_image_texture(GF_Node * node)780 GF_TextureHandler *compositor_svg_get_image_texture(GF_Node *node)
781 {
782 SVG_video_stack *st = (SVG_video_stack *) gf_node_get_private(node);
783 return &(st->txh);
784 }
785
786
787
788
789 typedef struct
790 {
791 /*media stream*/
792 GF_MediaObject *resource;
793 Bool stop_requested, is_open;
794 Double clipBegin, clipEnd;
795 } SVG_updates_stack;
796
svg_updates_smil_evaluate(SMIL_Timing_RTI * rti,Fixed normalized_scene_time,GF_SGSMILTimingEvalState status)797 static void svg_updates_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status)
798 {
799 SVG_updates_stack *stack = (SVG_updates_stack *)gf_node_get_private(gf_smil_get_element(rti));
800
801 switch (status) {
802 case SMIL_TIMING_EVAL_UPDATE:
803 if (!stack->is_open) {
804 if (stack->resource ) gf_mo_play(stack->resource, stack->clipBegin, stack->clipEnd, GF_FALSE);
805 stack->is_open = GF_TRUE;
806 }
807 else if (gf_mo_is_done(stack->resource) && (gf_smil_get_media_duration(rti)<0) ) {
808 Double dur = gf_mo_get_duration(stack->resource);
809 gf_smil_set_media_duration(rti, dur);
810 }
811 break;
812 case SMIL_TIMING_EVAL_FREEZE:
813 case SMIL_TIMING_EVAL_REMOVE:
814 stack->is_open = GF_FALSE;
815 gf_mo_set_flag(stack->resource, GF_MO_DISPLAY_REMOVE, GF_TRUE);
816 gf_mo_stop(&stack->resource);
817 break;
818 case SMIL_TIMING_EVAL_REPEAT:
819 gf_mo_restart(stack->resource);
820 break;
821 default:
822 break;
823 }
824 }
825
svg_traverse_updates(GF_Node * node,void * rs,Bool is_destroy)826 static void svg_traverse_updates(GF_Node *node, void *rs, Bool is_destroy)
827 {
828 /*video stack is just an extension of image stack, type-casting is OK*/
829 SVG_updates_stack *stack = (SVG_updates_stack*)gf_node_get_private(node);
830 GF_TraverseState *tr_state = (GF_TraverseState *)rs;
831 SVGAllAttributes all_atts;
832 SVGPropertiesPointers backup_props;
833 u32 backup_flags, dirty_flags;
834
835 if (is_destroy) {
836 if (stack->resource) {
837 if (stack->is_open) {
838 gf_mo_set_flag(stack->resource, GF_MO_DISPLAY_REMOVE, GF_TRUE);
839 gf_mo_stop(&stack->resource);
840 }
841 gf_mo_unregister(node, stack->resource);
842 }
843 gf_free(stack);
844 return;
845 }
846
847 if (tr_state->traversing_mode!=TRAVERSE_SORT) return;
848
849 /*flatten attributes and apply animations + inheritance*/
850 gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
851 if (!compositor_svg_traverse_base(node, &all_atts, (GF_TraverseState *)rs, &backup_props, &backup_flags))
852 return;
853
854 dirty_flags = gf_node_dirty_get(node);
855 if (dirty_flags) {
856 stack->clipBegin = all_atts.clipBegin ? *all_atts.clipBegin : 0;
857 stack->clipEnd = all_atts.clipEnd ? *all_atts.clipEnd : -1;
858 if (dirty_flags & GF_SG_SVG_XLINK_HREF_DIRTY) {
859 GF_MediaObject *new_res;
860 MFURL url;
861 Bool lock_timeline=GF_FALSE;
862 url.vals = NULL;
863 url.count = 0;
864
865 if (all_atts.syncBehavior) lock_timeline = (*all_atts.syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE;
866
867 gf_term_get_mfurl_from_xlink(node, &url);
868
869 new_res = gf_mo_register(node, &url, lock_timeline, GF_FALSE);
870 gf_sg_mfurl_del(url);
871
872 if (stack->resource!=new_res) {
873 if (stack->resource) {
874 gf_mo_stop(&stack->resource);
875 gf_mo_unregister(node, stack->resource);
876 }
877 stack->resource = new_res;
878 if (stack->resource && stack->is_open) gf_mo_play(stack->resource, stack->clipBegin, stack->clipEnd, GF_FALSE);
879 }
880 }
881 gf_node_dirty_clear(node, 0);
882 }
883 memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
884 tr_state->svg_flags = backup_flags;
885 }
886
compositor_init_svg_updates(GF_Compositor * compositor,GF_Node * node)887 void compositor_init_svg_updates(GF_Compositor *compositor, GF_Node *node)
888 {
889 SVG_updates_stack *stack;
890 GF_SAFEALLOC(stack, SVG_updates_stack)
891 if (!stack) {
892 GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate laser updates stack\n"));
893 return;
894 }
895
896 /*force first processing of xlink-href*/
897 gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, GF_FALSE);
898
899 gf_smil_set_evaluation_callback(node, svg_updates_smil_evaluate);
900
901 gf_node_set_private(node, stack);
902 gf_node_set_callback_function(node, svg_traverse_updates);
903 stack->clipEnd = -1;
904 }
905
906 #endif //GPAC_DISABLE_SVG
907
908