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