| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Compositor sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| /*for OD service types*/ | |
| #include <gpac/constants.h> | |
| /*for URL concatenation*/ | |
| #include <gpac/network.h> | |
| #include <gpac/internal/compositor_dev.h> | |
| #include <gpac/compositor.h> | |
| #include <gpac/nodes_x3d.h> | |
| #include <gpac/crypt.h> | |
| /*SVG properties*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| #include <gpac/scenegraph_svg.h> | |
| #endif | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) | |
| void gf_inline_restart(GF_Scene *scene) | |
| { | |
| scene->needs_restart = 1; | |
| gf_sc_invalidate(scene->compositor, NULL); | |
| } | |
| static Bool gf_inline_set_scene(M_Inline *root) | |
| { | |
| GF_MediaObject *mo; | |
| GF_Scene *parent; | |
| GF_SceneGraph *graph = gf_node_get_graph((GF_Node *) root); | |
| parent = (GF_Scene *)gf_sg_get_private(graph); | |
| if (!parent) return GF_FALSE; | |
| mo = gf_scene_get_media_object_ex(parent, &root->url, GF_MEDIA_OBJECT_SCENE, GF_FALSE, NULL, GF_FALSE, (GF_Node*)root, NULL); | |
| if (!mo) return GF_FALSE; | |
| if (mo->connect_state > MO_CONNECT_BUFFERING) { | |
| gf_sg_vrml_mf_reset(&root->url, GF_SG_VRML_MFURL); | |
| return GF_FALSE; | |
| } | |
| //invalidate as soon as we have an mo (eg something is attached to the scene) | |
| gf_sc_invalidate(parent->compositor, NULL); | |
| if (!mo->odm) return GF_FALSE; | |
| if (!mo->odm->subscene) { | |
| gf_sc_invalidate(parent->compositor, NULL); | |
| return GF_FALSE; | |
| } | |
| /*assign inline scene as private stack of inline node, and remember inline node for event propagation*/ | |
| gf_node_set_private((GF_Node *)root, mo->odm->subscene); | |
| mo->odm->subscene->object_attached = GF_TRUE; | |
| if (gf_list_find(mo->odm->subscene->attached_inlines, root)<0) | |
| gf_list_add(mo->odm->subscene->attached_inlines, root); | |
| /*play*/ | |
| gf_mo_play(mo, 0, -1, GF_FALSE); | |
| return GF_TRUE; | |
| } | |
| void gf_inline_on_modified(GF_Node *node) | |
| { | |
| u32 ODID; | |
| GF_MediaObject *mo; | |
| M_Inline *pInline = (M_Inline *) node; | |
| GF_Scene *scene = (GF_Scene *)gf_node_get_private(node); | |
| ODID = gf_mo_get_od_id(&pInline->url); | |
| if (scene) { | |
| mo = (scene->root_od) ? scene->root_od->mo : NULL; | |
| /*disconnect current inline if we're the last one using it (same as regular OD session leave/join)*/ | |
| if (mo) { | |
| Bool changed = GF_TRUE; | |
| if (ODID != GF_MEDIA_EXTERNAL_ID) { | |
| if (ODID && (ODID==scene->root_od->ID)) changed = GF_FALSE; | |
| } else { | |
| if (gf_mo_is_same_url(mo, &pInline->url, NULL, 0) ) changed = GF_FALSE; | |
| } | |
| if (mo->num_open) { | |
| if (!changed) return; | |
| gf_scene_notify_event(scene, GF_EVENT_UNLOAD, node, NULL, GF_OK, GF_TRUE); | |
| gf_node_dirty_parents(node); | |
| gf_mo_event_target_remove_by_node(mo, node); | |
| /*reset the scene pointer as it may get destroyed - unregister from scene object if not done yet*/ | |
| switch (gf_node_get_tag(node)) { | |
| case TAG_MPEG4_Inline: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Inline: | |
| #endif | |
| { | |
| GF_Scene *subscene = (GF_Scene *)gf_node_get_private(node); | |
| if (subscene) | |
| gf_list_del_item(subscene->attached_inlines, node); | |
| gf_node_set_private(node, NULL); | |
| } | |
| break; | |
| } | |
| scene->object_attached = GF_FALSE; | |
| mo->num_open --; | |
| if (!mo->num_open) { | |
| if (ODID == GF_MEDIA_EXTERNAL_ID) { | |
| GF_Scene *parent = scene->root_od->parentscene; | |
| /*!!! THIS WILL DESTROY THE INLINE SCENE OBJECT !!!*/ | |
| gf_odm_disconnect(scene->root_od, GF_TRUE); | |
| /*and force removal of the media object*/ | |
| if (parent) { | |
| if (gf_list_del_item(parent->scene_objects, mo)>=0) { | |
| gf_sg_vrml_mf_reset(&mo->URLs, GF_SG_VRML_MFURL); | |
| gf_mo_del(mo); | |
| } | |
| } | |
| } else { | |
| /*external media are completely unloaded, except addons which are only declared once */ | |
| if (!scene->root_od->addon && (scene->root_od->ID==GF_MEDIA_EXTERNAL_ID)) { | |
| gf_odm_disconnect(scene->root_od, 2); | |
| } else { | |
| gf_odm_stop_or_destroy(scene->root_od); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /*force a redraw and load scene at next pass - we cannot load the scene now because | |
| - we can be in a JS call (eg JS mutex blocked) | |
| - locating scene objects matching the new url needs exclusive access to the MediaObject list, achieved with the term net mutex | |
| - another service may already be setting up objects (eg exclusive access to the net mutex grabbed), which can trigger event forwarding | |
| - some event forwarders may request JS context (eg access to JS mutex) | |
| In such a case we would end up in a deadlock - this needs urgent fixing ... | |
| */ | |
| if (ODID) { | |
| /*if no parent we must process the url change as we may not be traversed later on (not in the scene tree)*/ | |
| if (gf_node_get_parent(node, 0)==NULL) { | |
| gf_inline_set_scene(pInline); | |
| } else { | |
| gf_node_dirty_parents(node); | |
| } | |
| } | |
| } | |
| static void gf_inline_check_restart(GF_Scene *scene) | |
| { | |
| /*no ctrl if no duration*/ | |
| if (!scene->duration) return; | |
| if (!scene->needs_restart) gf_odm_check_segment_switch(scene->root_od); | |
| if (scene->needs_restart) return; | |
| if (scene->root_od->media_ctrl && scene->root_od->media_ctrl->control->loop) { | |
| GF_Clock *ck = gf_odm_get_media_clock(scene->root_od); | |
| if (ck->has_seen_eos && !ck->nb_paused) { | |
| u64 dur = scene->duration; | |
| if (scene->root_od->media_ctrl->current_seg) { | |
| /*only process when all segments are played*/ | |
| if (gf_list_count(scene->root_od->media_ctrl->seg) <= scene->root_od->media_ctrl->current_seg) { | |
| scene->needs_restart = 1; | |
| scene->root_od->media_ctrl->current_seg = 0; | |
| } | |
| } | |
| else { | |
| Double s, e; | |
| u64 now = gf_clock_time_absolute(ck); | |
| s = (Double) now; | |
| s /= 1000; | |
| e = -1; | |
| MC_GetRange(scene->root_od->media_ctrl, &s, &e); | |
| if ((e>=0) && (e<GF_MAX_FLOAT)) dur = (u32) (e*1000); | |
| if (dur<=now) { | |
| scene->needs_restart = 1; | |
| scene->root_od->media_ctrl->current_seg = 0; | |
| } else { | |
| /*trigger render until to watch for restart...*/ | |
| gf_sc_invalidate(scene->compositor, NULL); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene) | |
| { | |
| gf_inline_check_restart(scene); | |
| if (scene->needs_restart) { | |
| gf_sc_invalidate(scene->compositor, NULL); | |
| return; | |
| } | |
| } | |
| void gf_scene_mpeg4_inline_restart(GF_Scene *scene) | |
| { | |
| u32 current_seg = 0; | |
| if (scene->root_od->media_ctrl) current_seg = scene->root_od->media_ctrl->current_seg; | |
| if (scene->is_dynamic_scene) { | |
| s64 from = 0; | |
| if (scene->root_od->media_ctrl) { | |
| if (scene->root_od->media_ctrl->media_stop<=0) { | |
| from = (s64) (scene->root_od->media_ctrl->media_stop * 1000) - 1; | |
| } | |
| else if (scene->root_od->media_ctrl->media_start>=0) { | |
| scene->root_od->media_ctrl->current_seg = current_seg; | |
| from = (s64) (scene->root_od->media_ctrl->media_start * 1000); | |
| } | |
| } | |
| gf_scene_restart_dynamic(scene, from, 0, 0); | |
| } else { | |
| /*we cannot use gf_mo_restart since it only sets the needs_restart for inline scenes. | |
| The rational is that gf_mo_restart can be called from the parent scene (OK) or from the scene itself, in | |
| which case shutting down the graph would crash the compositor. We therefore need two render passes to | |
| safely restart an inline scene*/ | |
| /*1- stop main object from playing but don't disconnect channels*/ | |
| gf_odm_stop(scene->root_od, GF_TRUE); | |
| /*2- close all ODs inside the scene and reset the graph*/ | |
| gf_scene_disconnect(scene, GF_FALSE); | |
| if (scene->root_od->media_ctrl) scene->root_od->media_ctrl->current_seg = current_seg; | |
| /*3- restart the scene*/ | |
| gf_odm_start(scene->root_od); | |
| } | |
| } | |
| static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) | |
| { | |
| MFURL *current_url; | |
| GF_Scene *scene = (GF_Scene *)gf_node_get_private(n); | |
| if (is_destroy) { | |
| GF_MediaObject *mo; | |
| if (!scene) return; | |
| mo = scene->root_od ? scene->root_od->mo : NULL; | |
| gf_list_del_item(scene->attached_inlines, n); | |
| gf_scene_notify_event(scene, GF_EVENT_UNLOAD, n, NULL, GF_OK, GF_TRUE); | |
| if (!mo) return; | |
| gf_mo_event_target_remove_by_node(mo, n); | |
| /*disconnect current inline if we're the last one using it (same as regular OD session leave/join)*/ | |
| if (mo->num_open) { | |
| mo->num_open --; | |
| if (!mo->num_open) { | |
| /*this is unspecified in the spec: whenever an inline not using the | |
| OD framework is destroyed, destroy the associated resource*/ | |
| if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID) { | |
| /*get parent scene and remove MediaObject in case the resource | |
| gets re-requested later on*/ | |
| GF_Scene *parent_scene = (GF_Scene *)gf_sg_get_private(gf_node_get_graph((GF_Node *) n) ); | |
| if (gf_list_del_item(parent_scene->scene_objects, mo)>=0) { | |
| gf_sg_vrml_mf_reset(&mo->URLs, GF_SG_VRML_MFURL); | |
| if (mo->odm) { | |
| gf_odm_reset_media_control(mo->odm, 1); | |
| mo->odm->mo = NULL; | |
| } | |
| gf_mo_del(mo); | |
| } | |
| gf_odm_disconnect(scene->root_od, 2); | |
| } else { | |
| gf_odm_stop(scene->root_od, 1); | |
| gf_scene_disconnect(scene->root_od->subscene, GF_TRUE); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| //if no private scene is associated get the node parent graph, retrieve the IS and find the OD | |
| if (!scene) { | |
| M_Inline *inl = (M_Inline *)n; | |
| if (!inl->url.count) | |
| return; | |
| gf_inline_set_scene(inl); | |
| scene = (GF_Scene *)gf_node_get_private(n); | |
| if (!scene) { | |
| /*just like protos, we must invalidate parent graph until attached*/ | |
| if (inl->url.count) { | |
| if (!inl->url.vals[0].OD_ID && (!inl->url.vals[0].url || !strlen(inl->url.vals[0].url) ) ) { | |
| gf_sg_vrml_mf_reset(&inl->url, GF_SG_VRML_MFURL); | |
| } else { | |
| gf_node_dirty_set(n, 0, GF_TRUE); | |
| } | |
| } | |
| return; | |
| } | |
| } | |
| /*if not attached return (attaching the graph cannot be done in render since render is not called while unattached :) */ | |
| if (!scene->graph_attached) { | |
| M_Inline *inl = (M_Inline *)n; | |
| if (inl->url.count) { | |
| /*just like protos, we must invalidate parent graph until attached*/ | |
| gf_node_dirty_set(n, 0, GF_TRUE); | |
| //and request bew anim frame until attached | |
| if (scene->object_attached) | |
| gf_sc_invalidate(scene->compositor, NULL); | |
| } | |
| return; | |
| } | |
| /*clear dirty flags for any sub-inlines, bitmaps or protos*/ | |
| gf_node_dirty_clear(n, 0); | |
| current_url = scene->current_url; | |
| scene->current_url = & ((M_Inline*)n)->url; | |
| gf_sc_traverse_subscene(scene->compositor, n, scene->graph, rs); | |
| scene->current_url = current_url; | |
| //do we have to restart for next frame ? If so let's do it | |
| gf_inline_check_restart(scene); | |
| /*if we need to restart, shutdown graph and do it*/ | |
| if (scene->needs_restart) { | |
| /*special case: scene change*/ | |
| if (scene->needs_restart==2) { | |
| scene->needs_restart = 0; | |
| gf_inline_on_modified(n); | |
| return; | |
| } | |
| scene->needs_restart = 0; | |
| gf_scene_mpeg4_inline_restart(scene); | |
| gf_node_dirty_set(n, 0, GF_TRUE); | |
| return; | |
| } | |
| } | |
| static Bool gf_inline_is_hardcoded_proto(GF_Compositor *compositor, MFURL *url) | |
| { | |
| u32 i; | |
| for (i=0; i<url->count; i++) { | |
| if (!url->vals[i].url) continue; | |
| if (strstr(url->vals[i].url, "urn:inet:gpac:builtin")) return GF_TRUE; | |
| if (gf_sc_uri_is_hardcoded_proto(compositor, url->vals[i].url)) | |
| return GF_TRUE; | |
| } | |
| return GF_FALSE; | |
| } | |
| GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url) | |
| { | |
| GF_ProtoLink *pl; | |
| u32 i; | |
| GF_Scene *scene = (GF_Scene *) _is; | |
| if (!scene) return NULL; | |
| //this is a scene reset, destroy all proto links | |
| if (!lib_url) { | |
| while (gf_list_count(scene->extern_protos)) { | |
| pl = gf_list_pop_back(scene->extern_protos); | |
| if (pl->mo) { | |
| //proto link was not attached, manual discard | |
| if (!pl->mo->odm) { | |
| gf_sg_vrml_mf_reset(&pl->mo->URLs, GF_SG_VRML_MFURL); | |
| gf_list_del_item(scene->scene_objects, pl->mo); | |
| gf_mo_del(pl->mo); | |
| } | |
| } | |
| gf_free(pl); | |
| } | |
| return NULL; | |
| } | |
| if (!lib_url->count) | |
| return NULL; | |
| if (gf_inline_is_hardcoded_proto(scene->compositor, lib_url)) return (void *) GF_SG_INTERNAL_PROTO; | |
| i=0; | |
| while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i))) { | |
| /*not ready yet*/ | |
| if (!pl->mo || !pl->mo->odm) continue; | |
| if (gf_mo_get_od_id(pl->url) != GF_MEDIA_EXTERNAL_ID) { | |
| if (gf_mo_get_od_id(pl->url) == gf_mo_get_od_id(lib_url)) { | |
| if (!pl->mo->odm || !pl->mo->odm->subscene) return NULL; | |
| return pl->mo->odm->subscene->graph; | |
| } | |
| } | |
| } | |
| /*for string URL based protos, recursively check until top if the proto lib is not already present*/ | |
| if (lib_url->vals[0].url) { | |
| GF_Scene *check_scene = scene; | |
| while (check_scene) { | |
| i=0; | |
| while ((pl = (GF_ProtoLink*)gf_list_enum(check_scene->extern_protos, &i))) { | |
| char *url1, *url2; | |
| Bool ok; | |
| if (!pl->mo || !pl->mo->odm) continue; | |
| if (gf_mo_get_od_id(pl->url) != GF_MEDIA_EXTERNAL_ID) continue; | |
| /*not the same url*/ | |
| if (!gf_mo_is_same_url(pl->mo, lib_url, NULL, 0)) continue; | |
| ok = GF_FALSE; | |
| /*check the url path is the same*/ | |
| url1 = gf_url_concatenate(pl->mo->odm->scene_ns->url, lib_url->vals[0].url); | |
| url2 = gf_url_concatenate(scene->root_od->scene_ns->url, lib_url->vals[0].url); | |
| if (url1 && url2 && !strcmp(url1, url2)) ok=GF_TRUE; | |
| if (url1) gf_free(url1); | |
| if (url2) gf_free(url2); | |
| if (!ok) continue; | |
| if (!pl->mo->odm || !pl->mo->odm->subscene) return NULL; | |
| return pl->mo->odm->subscene->graph; | |
| } | |
| check_scene = check_scene->root_od->parentscene; | |
| } | |
| } | |
| /*not found, let's try to load it*/ | |
| if (!lib_url || !lib_url->count) return NULL; | |
| /*internal, don't waste resources*/ | |
| if (gf_inline_is_hardcoded_proto(scene->compositor, lib_url)) return NULL; | |
| i=0; | |
| while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i)) ) { | |
| if (pl->url == lib_url) return NULL; | |
| if (pl->url->vals[0].OD_ID && (pl->url->vals[0].OD_ID == lib_url->vals[0].OD_ID)) return NULL; | |
| if (pl->url->vals[0].url && lib_url->vals[0].url && !stricmp(pl->url->vals[0].url, lib_url->vals[0].url) ) return NULL; | |
| } | |
| pl = (GF_ProtoLink*)gf_malloc(sizeof(GF_ProtoLink)); | |
| pl->url = lib_url; | |
| gf_list_add(scene->extern_protos, pl); | |
| pl->mo = gf_scene_get_media_object(scene, lib_url, GF_MEDIA_OBJECT_SCENE, GF_FALSE); | |
| /*this may already be destroyed*/ | |
| if (pl->mo) gf_mo_play(pl->mo, 0, -1, GF_FALSE); | |
| /*and return NULL*/ | |
| return NULL; | |
| } | |
| Bool gf_inline_is_default_viewpoint(GF_Node *node) | |
| { | |
| const char *nname, *seg_name; | |
| GF_SceneGraph *sg = gf_node_get_graph(node); | |
| GF_Scene *scene = sg ? (GF_Scene *) gf_sg_get_private(sg) : NULL; | |
| if (!scene) return GF_FALSE; | |
| nname = gf_node_get_name(node); | |
| if (!nname) return GF_FALSE; | |
| /*check any viewpoint*/ | |
| seg_name = strrchr(scene->root_od->scene_ns->url, '#'); | |
| /*check the URL of the parent*/ | |
| if (!seg_name && scene->current_url) { | |
| if (scene->current_url->count && scene->current_url->vals[0].url) | |
| seg_name = strrchr(scene->current_url->vals[0].url, '#'); | |
| } else if (!seg_name && scene->root_od->mo && scene->root_od->mo->URLs.count && scene->root_od->mo->URLs.vals[0].url) { | |
| seg_name = strrchr(scene->root_od->mo->URLs.vals[0].url, '#'); | |
| } | |
| if (!seg_name) return GF_FALSE; | |
| seg_name += 1; | |
| /*look for a media segment with this name - if none found, this is a viewpoint name*/ | |
| if (gf_odm_find_segment(scene->root_od, (char *) seg_name) != NULL) return GF_FALSE; | |
| return (!strcmp(nname, seg_name) ? GF_TRUE : GF_FALSE); | |
| } | |
| void gf_init_inline(GF_Scene *scene, GF_Node *node) | |
| { | |
| gf_node_set_callback_function(node, gf_inline_traverse); | |
| } | |
| static char *storage_get_section(M_Storage *storage) | |
| { | |
| GF_Scene *scene; | |
| char *szPath = NULL; | |
| u8 hash[20]; | |
| char name[50]; | |
| u32 i; | |
| scene = (GF_Scene *)gf_node_get_private((GF_Node*)storage); | |
| gf_dynstrcat(&szPath, scene->root_od->scene_ns->url, NULL); | |
| gf_dynstrcat(&szPath, storage->name.buffer, "@"); | |
| if (!szPath) return NULL; | |
| gf_sha1_csum((u8*)szPath, (u32) strlen(szPath), hash); | |
| gf_free(szPath); | |
| gf_strcpy(name, "@cache="); | |
| for (i=0; i<20; i++) { | |
| char t[3]; | |
| t[2] = 0; | |
| sprintf(t, "%02X", hash[i]); | |
| gf_strcat(name, t); | |
| } | |
| return gf_strdup(name); | |
| } | |
| static void storage_parse_sf(void *ptr, u32 fieldType, char *opt) | |
| { | |
| Float v1, v2, v3; | |
| switch (fieldType) { | |
| case GF_SG_VRML_SFBOOL: | |
| sscanf(opt, "%d", ((SFBool *)ptr)); | |
| break; | |
| case GF_SG_VRML_SFINT32: | |
| sscanf(opt, "%d", ((SFInt32 *)ptr) ); | |
| break; | |
| case GF_SG_VRML_SFTIME: | |
| sscanf(opt, "%lf", ((SFTime *)ptr) ); | |
| break; | |
| case GF_SG_VRML_SFFLOAT: | |
| sscanf(opt, "%g", &v1); | |
| * (SFFloat *)ptr = FLT2FIX(v1); | |
| break; | |
| case GF_SG_VRML_SFVEC2F: | |
| sscanf(opt, "%g %g", &v1, &v2); | |
| ((SFVec2f*)ptr)->x = FLT2FIX(v1); | |
| ((SFVec2f*)ptr)->y = FLT2FIX(v2); | |
| break; | |
| case GF_SG_VRML_SFVEC3F: | |
| sscanf(opt, "%g %g %g", &v1, &v2, &v3); | |
| ((SFVec3f*)ptr)->x = FLT2FIX(v1); | |
| ((SFVec3f*)ptr)->y = FLT2FIX(v2); | |
| ((SFVec3f*)ptr)->z = FLT2FIX(v3); | |
| break; | |
| case GF_SG_VRML_SFSTRING: | |
| if ( ((SFString *)ptr)->buffer) gf_free(((SFString *)ptr)->buffer); | |
| ((SFString *)ptr)->buffer = gf_strdup(opt); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static void gf_storage_load(M_Storage *storage) | |
| { | |
| const char *opt; | |
| char szID[20]; | |
| u32 i, count; | |
| u32 sec, exp, frac; | |
| char *section = storage_get_section(storage); | |
| if (!section) return; | |
| if (!gf_opts_get_key_count(section)) { | |
| gf_free(section); | |
| return; | |
| } | |
| opt = gf_opts_get_key(section, "expireAfterNTP"); | |
| gf_net_get_ntp(&sec, &frac); | |
| sscanf(opt, "%u", &exp); | |
| if (exp && (exp<=sec)) { | |
| gf_opts_del_section(section); | |
| gf_free(section); | |
| return; | |
| } | |
| count = gf_opts_get_key_count(section)-1; | |
| if (!count || (count!=storage->storageList.count)) { | |
| gf_opts_del_section(section); | |
| gf_free(section); | |
| return; | |
| } | |
| for (i=0; i<count; i++) { | |
| GF_FieldInfo info; | |
| sprintf(szID, "%d", i); | |
| opt = gf_opts_get_key(section, szID); | |
| if (!opt) break; | |
| if (!storage->storageList.vals[i].node) break; | |
| if (gf_node_get_field(storage->storageList.vals[i].node, storage->storageList.vals[i].fieldIndex, &info) != GF_OK) break; | |
| if (gf_sg_vrml_is_sf_field(info.fieldType)) { | |
| storage_parse_sf(info.far_ptr, info.fieldType, (char *) opt); | |
| } else { | |
| u32 sftype = gf_sg_vrml_get_sf_type(info.fieldType); | |
| char *sep, *val; | |
| void *slot; | |
| gf_sg_vrml_mf_reset(info.far_ptr, info.fieldType); | |
| while (1) { | |
| val = (char *)strchr(opt, '\''); | |
| sep = val ? strchr(val+1, '\'') : NULL; | |
| if (!val || !sep) break; | |
| sep[0] = 0; | |
| gf_sg_vrml_mf_append(info.far_ptr, info.fieldType, &slot); | |
| storage_parse_sf(slot, sftype, val+1); | |
| sep[0] = '\''; | |
| opt = sep+1; | |
| } | |
| } | |
| gf_node_changed(storage->storageList.vals[i].node, &info); | |
| } | |
| gf_free(section); | |
| } | |
| char *storage_serialize_sf(void *ptr, u32 fieldType) | |
| { | |
| char szVal[50]; | |
| switch (fieldType) { | |
| case GF_SG_VRML_SFBOOL: | |
| sprintf(szVal, "%d", *((SFBool *)ptr) ? 1 : 0); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFINT32: | |
| sprintf(szVal, "%d", *((SFInt32 *)ptr) ); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFTIME: | |
| sprintf(szVal, "%g", *((SFTime *)ptr) ); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFFLOAT: | |
| sprintf(szVal, "%g", FIX2FLT( *((SFFloat *)ptr) ) ); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFVEC2F: | |
| sprintf(szVal, "%g %g", FIX2FLT( ((SFVec2f *)ptr)->x), FIX2FLT( ((SFVec2f *)ptr)->y) ); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFVEC3F: | |
| sprintf(szVal, "%g %g %g", FIX2FLT( ((SFVec3f *)ptr)->x), FIX2FLT( ((SFVec3f *)ptr)->y) , FIX2FLT( ((SFVec3f *)ptr)->z) ); | |
| return gf_strdup(szVal); | |
| case GF_SG_VRML_SFSTRING: | |
| return gf_strdup( ((SFString *)ptr)->buffer ? ((SFString *)ptr)->buffer : ""); | |
| default: | |
| break; | |
| } | |
| return NULL; | |
| } | |
| void gf_storage_save(M_Storage *storage) | |
| { | |
| char szID[20]; | |
| u32 i, j; | |
| char *section = storage_get_section(storage); | |
| if (!section) return; | |
| gf_opts_del_section(section); | |
| if (storage->expireAfter) { | |
| u32 sec, frac; | |
| char szNTP[100]; | |
| gf_net_get_ntp(&sec, &frac); | |
| sec += storage->expireAfter; | |
| sprintf(szNTP, "%u", sec); | |
| gf_opts_set_key(section, "expireAfterNTP", szNTP); | |
| } else { | |
| gf_opts_set_key(section, "expireAfterNTP", "0"); | |
| } | |
| for (i=0; i<storage->storageList.count; i++) { | |
| char *val; | |
| GF_FieldInfo info; | |
| sprintf(szID, "%d", i); | |
| if (!storage->storageList.vals[i].node) break; | |
| if (gf_node_get_field(storage->storageList.vals[i].node, storage->storageList.vals[i].fieldIndex, &info) != GF_OK) break; | |
| if (gf_sg_vrml_is_sf_field(info.fieldType)) { | |
| val = storage_serialize_sf(info.far_ptr, info.fieldType); | |
| } else { | |
| //u32 sftype = gf_sg_vrml_get_sf_type(info.fieldType); | |
| val = NULL; | |
| for (j=0; j<((GenMFField *)info.far_ptr)->count; j++) { | |
| char *slotval; | |
| void *slot; | |
| if (gf_sg_vrml_mf_get_item(info.far_ptr, info.fieldType, &slot, j) != GF_OK) break; | |
| slotval = storage_serialize_sf(info.far_ptr, info.fieldType); | |
| if (!slotval) break; | |
| gf_dynstrcat(&val, "'", NULL); | |
| gf_dynstrcat(&val, slotval, NULL); | |
| gf_dynstrcat(&val, "'", NULL); | |
| gf_free(slot); | |
| } | |
| } | |
| if (val) { | |
| gf_opts_set_key(section, szID, val); | |
| gf_free(val); | |
| } | |
| } | |
| gf_free(section); | |
| } | |
| static void gf_storage_traverse(GF_Node *n, void *rs, Bool is_destroy) | |
| { | |
| if (is_destroy) { | |
| GF_Scene *scene = (GF_Scene *)gf_node_get_private(n); | |
| GF_SceneNamespace *scene_ns = scene->root_od->scene_ns; | |
| while (scene->root_od->parentscene) { | |
| if (scene->root_od->parentscene->root_od->scene_ns != scene_ns) | |
| break; | |
| scene = scene->root_od->parentscene; | |
| } | |
| gf_list_del_item(scene->storages, n); | |
| } | |
| } | |
| static void on_force_restore(GF_Node *n, struct _route *_route) | |
| { | |
| if (n) gf_storage_load((M_Storage *)n); | |
| } | |
| static void on_force_save(GF_Node *n, struct _route *_route) | |
| { | |
| if (n) gf_storage_save((M_Storage *)n); | |
| } | |
| void gf_scene_init_storage(GF_Scene *scene, GF_Node *node) | |
| { | |
| GF_SceneNamespace *scene_ns; | |
| M_Storage *storage = (M_Storage *) node; | |
| if (!storage->name.buffer || !strlen(storage->name.buffer) ) return; | |
| if (!storage->storageList.count) return; | |
| storage->on_forceSave = on_force_save; | |
| storage->on_forceRestore = on_force_restore; | |
| gf_node_set_callback_function(node, gf_storage_traverse); | |
| gf_node_set_private(node, scene); | |
| scene_ns = scene->root_od->scene_ns; | |
| while (scene->root_od->parentscene) { | |
| if (scene->root_od->parentscene->root_od->scene_ns != scene_ns) | |
| break; | |
| scene = scene->root_od->parentscene; | |
| } | |
| gf_list_add(scene->storages, node); | |
| if (storage->_auto) gf_storage_load(storage); | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| Bool aptr; | |
| on_force_save(NULL, NULL); | |
| on_force_restore(NULL, NULL); | |
| storage_parse_sf(&aptr, GF_SG_VRML_SFBOOL, "true"); | |
| } | |
| #endif | |
| } | |
| #endif //!defined(GPAC_DISABLE_VRML)d && !defined(GPAC_DISABLE_COMPOSITOR) | |
| #if !defined(GPAC_DISABLE_COMPOSITOR) | |
| GF_Node *gf_scene_get_keynav(GF_SceneGraph *sg, GF_Node *sensor) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 i, count; | |
| GF_Scene *scene = (GF_Scene *)gf_sg_get_private(sg); | |
| if (!scene) return NULL; | |
| if (!sensor) return (GF_Node *)gf_list_get(scene->keynavigators, 0); | |
| count = gf_list_count(scene->keynavigators); | |
| for (i=0; i<count; i++) { | |
| M_KeyNavigator *kn = (M_KeyNavigator *)gf_list_get(scene->keynavigators, i); | |
| if (kn->sensor==sensor) return (GF_Node *) kn; | |
| } | |
| #endif | |
| return NULL; | |
| } | |
| #endif //!defined(GPAC_DISABLE_COMPOSITOR) |