This source file includes following definitions.
- gf_inline_restart
- gf_inline_set_scene
- gf_inline_on_modified
- gf_inline_check_restart
- gf_scene_mpeg4_inline_check_restart
- gf_scene_mpeg4_inline_restart
- gf_inline_traverse
- gf_inline_is_hardcoded_proto
- gf_inline_get_proto_lib
- gf_inline_is_protolib_object
- gf_inline_is_default_viewpoint
- gf_init_inline
- storage_get_cfg
- storage_get_section
- storage_parse_sf
- gf_storage_load
- storage_serialize_sf
- gf_storage_save
- gf_storage_traverse
- on_force_restore
- on_force_save
- gf_scene_init_storage
- gf_scene_get_keynav
#include <gpac/constants.h>
#include <gpac/network.h>
#include <gpac/internal/terminal_dev.h>
#include "media_control.h"
#include <gpac/compositor.h>
#include <gpac/nodes_x3d.h>
#include <gpac/crypt.h>
#ifndef GPAC_DISABLE_SVG
#include <gpac/scenegraph_svg.h>
#endif
#ifndef GPAC_DISABLE_VRML
void gf_inline_restart(GF_Scene *scene)
{
scene->needs_restart = 1;
gf_term_invalidate_compositor(scene->root_od->term);
}
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);
if (!mo) return GF_FALSE;
gf_term_invalidate_compositor(parent->root_od->term);
if (!mo->odm) return GF_FALSE;
if (!mo->odm->subscene) {
gf_term_invalidate_compositor(parent->root_od->term);
return GF_FALSE;
}
gf_node_set_private((GF_Node *)root, mo->odm->subscene);
mo->odm->subscene->object_attached = GF_TRUE;
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;
if (mo) {
Bool changed = GF_TRUE;
if (ODID != GF_MEDIA_EXTERNAL_ID) {
if (ODID && (ODID==scene->root_od->OD->objectDescriptorID)) 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);
switch (gf_node_get_tag(node)) {
case TAG_MPEG4_Inline:
#ifndef GPAC_DISABLE_X3D
case TAG_X3D_Inline:
#endif
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;
gf_odm_disconnect(scene->root_od, GF_TRUE);
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 {
gf_term_lock_media_queue(scene->root_od->term, GF_TRUE);
if (!scene->root_od->addon && (scene->root_od->OD->objectDescriptorID==GF_MEDIA_EXTERNAL_ID)) {
scene->root_od->action_type = GF_ODM_ACTION_DELETE;
} else {
scene->root_od->action_type = GF_ODM_ACTION_STOP;
}
if (gf_list_find(scene->root_od->term->media_queue, scene->root_od)<0)
gf_list_add(scene->root_od->term->media_queue, scene->root_od);
gf_term_lock_media_queue(scene->root_od->term, GF_FALSE);
}
}
}
}
}
if (ODID) {
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)
{
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) {
u32 now = gf_clock_time(ck);
u64 dur = scene->duration;
if (scene->root_od->media_ctrl->current_seg) {
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;
s = 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 {
gf_term_invalidate_compositor(scene->root_od->term);
}
}
}
}
}
void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene)
{
gf_inline_check_restart(scene);
if (scene->needs_restart) {
gf_term_invalidate_compositor(scene->root_od->term);
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 {
gf_odm_stop(scene->root_od, GF_TRUE);
gf_scene_disconnect(scene, GF_FALSE);
if (scene->root_od->media_ctrl) scene->root_od->media_ctrl->current_seg = current_seg;
gf_odm_start(scene->root_od, 0);
}
}
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_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);
if (mo->num_open) {
mo->num_open --;
if (!mo->num_open) {
gf_term_lock_media_queue(scene->root_od->term, GF_TRUE);
if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID) {
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);
}
scene->root_od->action_type = GF_ODM_ACTION_DELETE;
gf_list_add(scene->root_od->term->media_queue, scene->root_od);
} else {
scene->root_od->action_type = GF_ODM_ACTION_SCENE_DISCONNECT;
gf_list_add(scene->root_od->term->media_queue, scene->root_od);
}
gf_term_lock_media_queue(scene->root_od->term, GF_FALSE);
}
}
return;
}
if (!scene) {
M_Inline *inl = (M_Inline *)n;
gf_inline_set_scene(inl);
scene = (GF_Scene *)gf_node_get_private(n);
if (!scene) {
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 (!scene->graph_attached) {
gf_node_dirty_set(n, 0, GF_TRUE);
if (scene->object_attached)
gf_term_invalidate_compositor(scene->root_od->term);
return;
}
gf_node_dirty_clear(n, 0);
current_url = scene->current_url;
scene->current_url = & ((M_Inline*)n)->url;
gf_sc_traverse_subscene(scene->root_od->term->compositor, n, scene->graph, rs);
scene->current_url = current_url;
gf_inline_check_restart(scene);
if (scene->needs_restart) {
if (scene->needs_restart==2) {
scene->needs_restart = 0;
gf_inline_on_modified(n);
return;
}
scene->needs_restart = 0;
gf_term_lock_media_queue(scene->root_od->term, GF_TRUE);
scene->root_od->action_type = GF_ODM_ACTION_SCENE_INLINE_RESTART;
gf_list_add(scene->root_od->term->media_queue, scene->root_od);
gf_term_lock_media_queue(scene->root_od->term, GF_FALSE);
gf_node_dirty_set(n, 0, GF_TRUE);
return;
}
}
static Bool gf_inline_is_hardcoded_proto(GF_Terminal *term, 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(term->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 || !lib_url->count) return NULL;
if (gf_inline_is_hardcoded_proto(scene->root_od->term, lib_url)) return (void *) GF_SG_INTERNAL_PROTO;
i=0;
while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i))) {
if (!pl->mo || !pl->mo->odm || ! pl->mo->odm->net_service) 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;
}
}
}
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 (! pl->mo->odm->net_service) continue;
if (gf_mo_get_od_id(pl->url) != GF_MEDIA_EXTERNAL_ID) continue;
if (!gf_mo_is_same_url(pl->mo, lib_url, NULL, 0)) continue;
url1 = gf_url_concatenate(pl->mo->odm->net_service->url, lib_url->vals[0].url);
url2 = gf_url_concatenate(scene->root_od->net_service->url, lib_url->vals[0].url);
ok = GF_FALSE;
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;
}
}
if (!lib_url || !lib_url->count) return NULL;
if (gf_inline_is_hardcoded_proto(scene->root_od->term, 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);
if (pl->mo) gf_mo_play(pl->mo, 0, -1, GF_FALSE);
return NULL;
}
Bool gf_inline_is_protolib_object(GF_Scene *scene, GF_ObjectManager *odm)
{
u32 i;
GF_ProtoLink *pl;
i=0;
while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i))) {
if (pl->mo->odm == odm) return GF_TRUE;
}
return GF_FALSE;
}
GF_EXPORT
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;
seg_name = strrchr(scene->root_od->net_service->url, '#');
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;
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 GF_Config *storage_get_cfg(M_Storage *storage)
{
GF_Scene *scene;
scene = (GF_Scene *)gf_node_get_private((GF_Node*)storage);
return scene->root_od->term->user->config;
}
static char *storage_get_section(M_Storage *storage)
{
GF_Scene *scene;
char *szPath;
u8 hash[20];
char name[50];
u32 i;
size_t len;
scene = (GF_Scene *)gf_node_get_private((GF_Node*)storage);
len = strlen(scene->root_od->net_service->url)+strlen(storage->name.buffer)+2;
szPath = (char *)gf_malloc(sizeof(char)* len);
strcpy(szPath, scene->root_od->net_service->url);
strcat(szPath, "@");
strcat(szPath, storage->name.buffer);
gf_sha1_csum((u8*)szPath, (u32) strlen(szPath), hash);
gf_free(szPath);
strcpy(name, "@cache=");
for (i=0; i<20; i++) {
char t[3];
t[2] = 0;
sprintf(t, "%02X", hash[i]);
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;
GF_Config *cfg = storage_get_cfg(storage);
char *section = storage_get_section(storage);
if (!cfg || !section) return;
if (!gf_cfg_get_key_count(cfg, section)) {
gf_free(section);
return;
}
opt = gf_cfg_get_key(cfg, section, "expireAfterNTP");
gf_net_get_ntp(&sec, &frac);
sscanf(opt, "%u", &exp);
if (exp && (exp<=sec)) {
gf_cfg_del_section(cfg, section);
gf_free(section);
return;
}
count = gf_cfg_get_key_count(cfg, section)-1;
if (!count || (count!=storage->storageList.count)) {
gf_cfg_del_section(cfg, section);
gf_free(section);
return;
}
for (i=0; i<count; i++) {
GF_FieldInfo info;
sprintf(szID, "%d", i);
opt = gf_cfg_get_key(cfg, 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;
GF_Config *cfg = storage_get_cfg(storage);
char *section = storage_get_section(storage);
if (!cfg || !section) return;
gf_cfg_del_section(cfg, section);
if (storage->expireAfter) {
u32 sec, frac;
char szNTP[100];
gf_net_get_ntp(&sec, &frac);
sec += storage->expireAfter;
sprintf(szNTP, "%u", sec);
gf_cfg_set_key(cfg, section, "expireAfterNTP", szNTP);
} else {
gf_cfg_set_key(cfg, 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 {
char *slotval;
void *slot;
val = NULL;
for (j=0; j<((GenMFField *)info.far_ptr)->count; j++) {
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;
if (val) {
val = (char *)gf_realloc(val, strlen(val) + 3 + strlen((const char *)slot));
} else {
val = (char *)gf_malloc(3 + strlen((const char *)slot));
val[0] = 0;
}
strcat(val, "'");
strcat(val, slotval);
strcat(val, "'");
gf_free(slot);
}
}
if (val) {
gf_cfg_set_key(cfg, 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_ClientService *net_service = scene->root_od->net_service;
while (scene->root_od->parentscene) {
if (scene->root_od->parentscene->root_od->net_service != net_service)
break;
scene = scene->root_od->parentscene;
}
gf_list_del_item(scene->storages, n);
}
}
static void on_force_restore(GF_Node *n, struct _route *_route)
{
gf_storage_load((M_Storage *)n);
}
static void on_force_save(GF_Node *n, struct _route *_route)
{
gf_storage_save((M_Storage *)n);
}
void gf_scene_init_storage(GF_Scene *scene, GF_Node *node)
{
GF_ClientService *net_service;
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);
net_service = scene->root_od->net_service;
while (scene->root_od->parentscene) {
if (scene->root_od->parentscene->root_od->net_service != net_service)
break;
scene = scene->root_od->parentscene;
}
gf_list_add(scene->storages, node);
if (storage->_auto) gf_storage_load(storage);
}
#endif
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;
}