Coverage Report

Created: 2025-12-05 07:06

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