/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2007-2012
 *			All rights reserved
 *
 *  This file is part of GPAC / Scene Graph 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.
 *
 */


#include <gpac/setup.h>

#ifdef GPAC_HAS_SPIDERMONKEY

/*base SVG type*/
#include <gpac/nodes_svg.h>
#include <gpac/nodes_mpeg4.h>
#include <gpac/nodes_x3d.h>
/*dom events*/
#include <gpac/events.h>

#include <gpac/download.h>
#include <gpac/network.h>
#include <gpac/options.h>
#include <gpac/xml.h>


#if defined(GPAC_ANDROID) && !defined(XP_UNIX)
#define XP_UNIX
#endif

#if !defined(__GNUC__)
# if defined(_WIN32_WCE)
#  pragma comment(lib, "js32")
# elif defined (_WIN64)
#  pragma comment(lib, "js")
# elif defined (WIN32)
#  pragma comment(lib, "js")
# endif
#endif

#include <gpac/internal/scenegraph_dev.h>
#include <gpac/internal/smjs_api.h>

#include <gpac/modules/js_usr.h>
#include <gpac/internal/terminal_dev.h>
#include <gpac/internal/compositor_dev.h>
#include <gpac/term_info.h>

#define GPAC_JS_RTI_REFRESH_RATE	200

typedef struct
{
	u32 nb_loaded;
	GF_Terminal *term;

	GF_JSClass gpacClass;
	GF_JSClass gpacEvtClass;
	GF_JSClass odmClass;
	GF_JSClass anyClass;

	GF_JSClass storageClass;

	jsval evt_fun;
	GF_TermEventFilter evt_filter;
	GF_Event *evt;

	JSContext *c;
	JSObject *evt_filter_obj, *evt_obj;
	JSObject *gpac_obj;

	u32 rti_refresh_rate;
	GF_SystemRTInfo rti;

	//list of config files for storage
	GF_List *storages;

} GF_GPACJSExt;


static GF_Terminal *gpac_get_term(JSContext *c, JSObject *obj)
{
	GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	return ext ? ext->term : NULL;
}

static SMJS_FUNC_PROP_GET( gpac_getProperty)

const char *res;
s32 prop_id;
GF_Terminal *term = gpac_get_term(c, obj);
if (!term) return JS_FALSE;

if (!SMJS_ID_IS_INT(id)) return JS_TRUE;
prop_id = SMJS_ID_TO_INT(id);

switch (prop_id) {
case -1: //"last_working_directory"
	res = gf_cfg_get_key(term->user->config, "General", "LastWorkingDir");
	if (!res) res = gf_cfg_get_key(term->user->config, "General", "ModulesDirectory");
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, res));
	break;

case -2: //"scale_x"))
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->scale_x)) );
	break;

case -3: //"scale_y"
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->scale_y)) );
	break;

case -4: //"translation_x"
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->trans_x)) );
	break;

case -5: //"translation_y"
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->trans_y)) );
	break;

case -6: //"rectangular_textures"
{
	Bool any_size = GF_FALSE;
#ifndef GPAC_DISABLE_3D
	if (term->compositor->gl_caps.npot_texture || term->compositor->gl_caps.rect_texture)
		any_size = GF_TRUE;
#endif
	*vp = BOOLEAN_TO_JSVAL( any_size ? JS_TRUE : JS_FALSE );
}
break;

case -7: //"batteryOn"
{
	Bool on_battery = GF_FALSE;
	gf_sys_get_battery_state(&on_battery, NULL, NULL, NULL, NULL);
	*vp = BOOLEAN_TO_JSVAL( on_battery ? JS_TRUE : JS_FALSE );
}
break;

case -8: //	"batteryCharging"
{
	u32 on_charge = 0;
	gf_sys_get_battery_state(NULL, &on_charge, NULL, NULL, NULL);
	*vp = BOOLEAN_TO_JSVAL( on_charge ? JS_TRUE : JS_FALSE );
}
break;

case -9: //	"batteryPercent"
{
	u32 level = 0;
	gf_sys_get_battery_state(NULL, NULL, &level, NULL, NULL);
	*vp = INT_TO_JSVAL( level );
}
break;

case -10: //"batteryLifeTime"
{
	u32 level = 0;
	gf_sys_get_battery_state(NULL, NULL, NULL, &level, NULL);
	*vp = INT_TO_JSVAL( level );
}
break;

case -11: //"batteryFullLifeTime"
{
	u32 level = 0;
	gf_sys_get_battery_state(NULL, NULL, NULL, NULL, &level);
	*vp = INT_TO_JSVAL( level );
}
break;

case -12: //"hostname"
{
	char hostname[100];
	gf_sk_get_host_name((char*)hostname);
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, hostname));
}
break;

case -13: //"fullscreen"
	*vp = BOOLEAN_TO_JSVAL( term->compositor->fullscreen ? JS_TRUE : JS_FALSE);
	break;

case -14: //"current_path"
{
	char *url = gf_url_concatenate(term->root_scene->root_od->net_service->url, "");
	if (!url) url = gf_strdup("");
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, url));
	gf_free(url);
}
break;

case -15: //"volume"
	*vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_AUDIO_VOLUME));
	break;

case -16: //"navigation"
	*vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_NAVIGATION));
	break;

case -17: //"navigation_type"
	*vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_NAVIGATION_TYPE) );
	break;

case -18: //"hardware_yuv"
	*vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_YUV) ? 1 : 0 );
	break;

case -19: //"hardware_rgb"
	*vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGB) ? 1 : 0 );
	break;

case -20: //"hardware_rgba"
{
	u32 has_rgba = (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGBA) ? 1 : 0;
#ifndef GPAC_DISABLE_3D
	if (term->compositor->hybrid_opengl || term->compositor->is_opengl) has_rgba = 1;
#endif
	*vp = INT_TO_JSVAL( has_rgba  );
}
break;

case -21: //"hardware_stretch"
	*vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_STRETCH) ? 1 : 0 );
	break;

case -22: //"screen_width"
	*vp = INT_TO_JSVAL( term->compositor->video_out->max_screen_width);
	break;

case -23: //"screen_height"
	*vp = INT_TO_JSVAL( term->compositor->video_out->max_screen_height);
	break;

case -24: //"http_max_bitrate"
	*vp = INT_TO_JSVAL( gf_dm_get_data_rate(term->downloader));
	break;

case -25: //"http_bitrate"
	*vp = INT_TO_JSVAL( gf_dm_get_global_rate(term->downloader) / 1000);
	break;

case -26: //"fps"
	*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, gf_term_get_framerate(term, 0) ) );
	break;

case -27: //"cpu_load" || "cpu"
{
	GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0);
	*vp = INT_TO_JSVAL(ext->rti.process_cpu_usage);
}
break;

case -28: //"nb_cores"
{
	GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0);
	*vp = INT_TO_JSVAL(ext->rti.nb_cores);
}
break;

case -29: //"system_memory"
{
	GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0);
	*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, (Double) ext->rti.physical_memory ) );
}
break;

case -30: //"memory"
{
	GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0);
	*vp = INT_TO_JSVAL(ext->rti.process_memory);
}
break;

case -31: //"argc"
	*vp = INT_TO_JSVAL(gf_sys_get_argc() );
	break;

case -36://"dpi_x"
	*vp = INT_TO_JSVAL(term->compositor->video_out->dpi_x);
	break;

case -37://"dpi_y"
	*vp = INT_TO_JSVAL(term->compositor->video_out->dpi_y);
	break;
}

return JS_TRUE;
}


static SMJS_FUNC_PROP_SET( gpac_setProperty)

s32 prop_id;
char *prop_val;
GF_Terminal *term = gpac_get_term(c, obj);
if (!term) return JS_FALSE;

if (!SMJS_ID_IS_INT(id)) return JS_TRUE;
prop_id = SMJS_ID_TO_INT(id);

switch (prop_id) {
case -1: //"last_working_directory"
	if (!JSVAL_IS_STRING(*vp)) {
		return JS_FALSE;
	}
	prop_val = SMJS_CHARS(c, *vp);
	gf_cfg_set_key(term->user->config, "General", "LastWorkingDir", prop_val);
	SMJS_FREE(c, prop_val);
	break;
case -32://"caption"
{
	GF_Event evt;
	char *caption;
	if (!JSVAL_IS_STRING(*vp)) {
		return JS_FALSE;
	}
	evt.type = GF_EVENT_SET_CAPTION;
	caption = SMJS_CHARS(c, *vp);
	if (!strnicmp(caption, "gpac://", 7)) {
		evt.caption.caption = caption + 7;
	} else {
		evt.caption.caption = caption;
	}

	gf_term_user_event(term, &evt);
	SMJS_FREE(c, (char*)caption);
}
break;
case -13://"fullscreen"
{
	/*no fullscreen for iOS (always on)*/
#ifndef GPAC_IPHONE
	Bool res = (JSVAL_TO_BOOLEAN(*vp)==JS_TRUE) ? 1 : 0;
	if (term->compositor->fullscreen != res) {
		gf_term_set_option(term, GF_OPT_FULLSCREEN, res);
	}
#endif
}
break;
case -15: //"volume"
	if (JSVAL_IS_NUMBER(*vp)) {
		jsdouble d;
		SMJS_GET_NUMBER(*vp, d);
		gf_term_set_option(term, GF_OPT_AUDIO_VOLUME, (u32) d);
	} else if (JSVAL_IS_INT(*vp)) {
		gf_term_set_option(term, GF_OPT_AUDIO_VOLUME, JSVAL_TO_INT(*vp));
	}
	break;

case -16: //"navigation"
	gf_term_set_option(term, GF_OPT_NAVIGATION, JSVAL_TO_INT(*vp) );
	break;
case -17: //"navigation_type"
	gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0);
	break;
case -33: //"disable_hardware_blit"
	term->compositor->disable_hardware_blit = JSVAL_TO_INT(*vp) ? 1 : 0;
	gf_sc_set_option(term->compositor, GF_OPT_REFRESH, 0);
	break;
case -34: //"disable_composite_blit"
{
	Bool new_val = JSVAL_TO_INT(*vp) ? 1 : 0;
	if (new_val != term->compositor->disable_composite_blit) {
		term->compositor->disable_composite_blit = new_val;
		term->compositor->rebuild_offscreen_textures = 1;
		gf_sc_set_option(term->compositor, GF_OPT_REFRESH, 0);
	}
}
break;
case -24:  //"http_max_bitrate"
{
	u32 new_rate = JSVAL_TO_INT(*vp);
	gf_dm_set_data_rate(term->downloader, new_rate);
}
break;
case -35: //"focus_highlight"
	term->compositor->disable_focus_highlight = JSVAL_TO_BOOLEAN(*vp) ? 0 : 1;
	break;
}
return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_get_option)
{
	const char *opt = NULL;
	char *sec_name, *key_name;
	s32 idx = -1;
	JSString *s;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	if (argc < 2) return JS_FALSE;

	if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE;
	if (!JSVAL_IS_STRING(argv[1]) && !JSVAL_IS_INT(argv[1])) return JS_FALSE;

	sec_name = SMJS_CHARS(c, argv[0]);
	key_name = NULL;
	if (JSVAL_IS_INT(argv[1])) {
		idx = JSVAL_TO_INT(argv[1]);
	} else if (JSVAL_IS_STRING(argv[1]) ) {
		key_name = SMJS_CHARS(c, argv[1]);
	}

	if (!stricmp(sec_name, "audiofilters")) {
		if (!term->compositor->audio_renderer->filter_chain.enable_filters
		        || !term->compositor->audio_renderer->filter_chain.filters->filter->GetOption) {
			SMJS_FREE(c, key_name);
			SMJS_FREE(c, sec_name);
			return JS_TRUE;
		}
		opt = term->compositor->audio_renderer->filter_chain.filters->filter->GetOption(term->compositor->audio_renderer->filter_chain.filters->filter, key_name);
	} else if (key_name) {
		opt = gf_cfg_get_key(term->user->config, sec_name, key_name);
	} else if (idx>=0) {
		opt = gf_cfg_get_key_name(term->user->config, sec_name, idx);
	} else {
		opt = NULL;
	}
	if (key_name) {
		SMJS_FREE(c, key_name);
	}
	SMJS_FREE(c, sec_name);

	if (opt) {
		s = JS_NewStringCopyZ(c, opt);
		SMJS_SET_RVAL( STRING_TO_JSVAL(s) );
	} else {
		SMJS_SET_RVAL( JSVAL_NULL );
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_set_option)
{
	char *sec_name, *key_name, *key_val;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;
	if (argc < 3) return JS_FALSE;

	if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE;
	if (!JSVAL_IS_STRING(argv[1])) return JS_FALSE;

	sec_name = SMJS_CHARS(c, argv[0]);
	key_name = SMJS_CHARS(c, argv[1]);
	key_val = NULL;
	if (JSVAL_IS_STRING(argv[2]))
		key_val = SMJS_CHARS(c, argv[2]);

	if (!stricmp(sec_name, "audiofilters")) {
		if (term->compositor->audio_renderer->filter_chain.enable_filters
		        && term->compositor->audio_renderer->filter_chain.filters->filter->SetOption) {
			term->compositor->audio_renderer->filter_chain.filters->filter->SetOption(term->compositor->audio_renderer->filter_chain.filters->filter, key_name, key_val);
		}
	} else {
		gf_cfg_set_key(term->user->config, sec_name, key_name, key_val);
		if (!strcmp(sec_name, "Audio") && !strcmp(key_name, "Filter")) {
			gf_sc_reload_audio_filters(term->compositor);
		}
	}
	SMJS_FREE(c, sec_name);
	SMJS_FREE(c, key_name);
	if (key_val) {
		SMJS_FREE(c, key_val);
	}
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_get_arg)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	if (argc < 1) return JS_FALSE;

	if (JSVAL_IS_INT(argv[0])) {
		u32 idx = JSVAL_TO_INT(argv[0]);
		const char *arg = gf_sys_get_arg(idx);
		if (arg) {
			SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, arg)) );
		} else {
			SMJS_SET_RVAL( JSVAL_NULL );
		}
		return JS_TRUE;
	} else {
		return JS_FALSE;
	}
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_set_back_color)
{
	SMJS_OBJ
	SMJS_ARGS
	u32 r, g, b, a, i;
	jsdouble d;
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	if (argc < 3) return JS_FALSE;
	r = g = b = 0;
	a = 255;
	for (i=0; i<argc; i++) {
		u32 v=0;
		if (JSVAL_IS_DOUBLE(argv[i])) {
			SMJS_GET_NUMBER(argv[i], d);
			v = (u32) (255 * d);
		} else if (JSVAL_IS_INT(argv[i])) {
			v = 255 * JSVAL_TO_INT(argv[i]);
		}
		if (i==0) r = v;
		else if (i==1) g = v;
		else if (i==2) b = v;
		else if (i==3) a = v;
	}
	term->compositor->back_color = term->compositor->default_back_color = GF_COL_ARGB(a, r, g, b);
	gf_sc_invalidate(term->compositor, NULL);
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_switch_quality)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	if (argc < 1) return JS_FALSE;

	if (JSVAL_IS_BOOLEAN(argv[0])) {
		Bool up = (JSVAL_TO_BOOLEAN(argv[0])==JS_TRUE) ? GF_TRUE : GF_FALSE;
		gf_term_switch_quality(term, up);
		return JS_TRUE;
	} else {
		return JS_FALSE;
	}
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_reload)
{
	SMJS_OBJ
	GF_Event evt;
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	memset(&evt, 0, sizeof(GF_Event));
	evt.type = GF_EVENT_RELOAD;
	term->user->EventProc(term->user->opaque, &evt);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_navigation_supported)
{
	SMJS_OBJ
	SMJS_ARGS
	u32 type;
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	if (argc < 1) return JS_FALSE;

	if (! JSVAL_IS_INT(argv[0]) ) {
		SMJS_SET_RVAL( BOOLEAN_TO_JSVAL(JS_FALSE) );
		return JS_TRUE;
	}
	type = JSVAL_TO_INT(argv[0]);
	SMJS_SET_RVAL( BOOLEAN_TO_JSVAL( gf_sc_navigation_supported(term->compositor, type) ? JS_TRUE : JS_FALSE ) );
	return JS_TRUE;
}

typedef struct
{
	JSContext *c;
	JSObject *array;
	Bool is_dir;
} enum_dir_cbk;

static Bool enum_dir_fct(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info)
{
	u32 i, len;
	char *sep;
	JSString *s;
	jsuint idx;
	jsval v;
	JSObject *obj;
	enum_dir_cbk *cbk = (enum_dir_cbk*)cbck;

	if (file_name && (file_name[0]=='.')) return 0;

	obj = JS_NewObject(cbk->c, 0, 0, 0);
	s = JS_NewStringCopyZ(cbk->c, file_name);
	JS_DefineProperty(cbk->c, obj, "name", STRING_TO_JSVAL(s), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);

	sep=NULL;
	len = (u32) strlen(file_path);
	for (i=0; i<len; i++) {
		sep = strchr("/\\", file_path[len-i-1]);
		if (sep) {
			sep = file_path+len-i-1;
			break;
		}
	}
	if (sep) {
		sep[0] = '/';
		sep[1] = 0;
		s = JS_NewStringCopyZ(cbk->c, file_path);
	} else {
		s = JS_NewStringCopyZ(cbk->c, file_path);
	}
	JS_DefineProperty(cbk->c, obj, "path", STRING_TO_JSVAL(s), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "directory", BOOLEAN_TO_JSVAL(cbk->is_dir ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "drive", BOOLEAN_TO_JSVAL(file_info->drive ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "hidden", BOOLEAN_TO_JSVAL(file_info->hidden ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "system", BOOLEAN_TO_JSVAL(file_info->system ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "size", INT_TO_JSVAL(file_info->size), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(cbk->c, obj, "last_modified", INT_TO_JSVAL(file_info->last_modified), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);


	JS_GetArrayLength(cbk->c, cbk->array, &idx);
	v = OBJECT_TO_JSVAL(obj);
	JS_SetElement(cbk->c, cbk->array, idx, &v);
	return 0;
}

static JSBool SMJS_FUNCTION(gpac_enum_directory)
{
	GF_Err err;
	enum_dir_cbk cbk;
	char *url = NULL;
	char *dir = NULL;
	char *filter = NULL;
	char *an_url;
	Bool dir_only = 0;
	Bool browse_root = 0;
	GF_Terminal *term;
	SMJS_OBJ
	SMJS_ARGS

	if ((argc >= 1) && JSVAL_IS_STRING(argv[0])) {
		dir = SMJS_CHARS(c, argv[0]);
		if (!strcmp(dir, "/")) browse_root = 1;
	}
	if ((argc >= 2) && JSVAL_IS_STRING(argv[1])) {
		filter = SMJS_CHARS(c, argv[1]);
		if (!strcmp(filter, "dir")) {
			dir_only = 1;
			filter = NULL;
		} else if (!strlen(filter)) {
			SMJS_FREE(c, filter);
			filter=NULL;
		}
	}
	if ((argc >= 3) && JSVAL_IS_BOOLEAN(argv[2])) {
		if (JSVAL_TO_BOOLEAN(argv[2])==JS_TRUE) {
			url = gf_url_concatenate(dir, "..");
			if (!strcmp(url, "..") || (url[0]==0)) {
				if ((dir[1]==':') && ((dir[2]=='/') || (dir[2]=='\\')) ) browse_root = 1;
				else if (!strcmp(dir, "/")) browse_root = 1;
			}
			if (!strcmp(url, "/")) browse_root = 1;
		}
	}

	if ( (!dir || !strlen(dir) ) && (!url || !strlen(url))) browse_root = 1;

	if (browse_root) {
		cbk.c = c;
		cbk.array = JS_NewArrayObject(c, 0, 0);
		cbk.is_dir = 1;
		gf_enum_directory("/", 1, enum_dir_fct, &cbk, NULL);
		SMJS_SET_RVAL( OBJECT_TO_JSVAL(cbk.array) );
		if (url) gf_free(url);
		SMJS_FREE(c, dir);
		SMJS_FREE(c, filter);
		return JS_TRUE;
	}

	cbk.c = c;
	cbk.array = JS_NewArrayObject(c, 0, 0);

	cbk.is_dir = 1;

	term = gpac_get_term(c, obj);
	/*concatenate with service url*/
	an_url = gf_url_concatenate(term->root_scene->root_od->net_service->url, url ? url : dir);
	if (an_url) {
		gf_free(url);
		url = an_url;
	}
	err = gf_enum_directory(url ? url : dir, 1, enum_dir_fct, &cbk, NULL);

	if (!dir_only) {
		cbk.is_dir = 0;
		err = gf_enum_directory(url ? url : dir, 0, enum_dir_fct, &cbk, filter);
		if (dir_only && (err==GF_IO_ERR)) {
			GF_Terminal *term = gpac_get_term(c, obj);
			/*try to concatenate with service url*/
			char *an_url = gf_url_concatenate(term->root_scene->root_od->net_service->url, url ? url : dir);
			gf_free(url);
			url = an_url;
			gf_enum_directory(url ? url : dir, 0, enum_dir_fct, &cbk, filter);
		}
	}

	SMJS_SET_RVAL( OBJECT_TO_JSVAL(cbk.array) );
	if (url) gf_free(url);
	SMJS_FREE(c, dir);
	SMJS_FREE(c, filter);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_set_size)
{
	Bool override_size_info = 0;
	u32 w, h;
	jsdouble d;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!term) return JS_FALSE;

	w = h = 0;
	if ((argc >= 1) && JSVAL_IS_NUMBER(argv[0])) {
		SMJS_GET_NUMBER(argv[0], d);
		w = (u32) d;
	}
	if ((argc >= 2) && JSVAL_IS_NUMBER(argv[1])) {
		SMJS_GET_NUMBER(argv[1], d);
		h = (u32) d;
	}
	if ((argc >= 3) && JSVAL_IS_BOOLEAN(argv[2]) && (JSVAL_TO_BOOLEAN(argv[2])==JS_TRUE) )
		override_size_info = 1;

	if (w && h) {
		GF_Event evt;
		if (override_size_info) {
			term->compositor->scene_width = w;
			term->compositor->scene_height = h;
			term->compositor->has_size_info = 1;
			return JS_TRUE;
		}
		if (term->user->os_window_handler) {
			evt.type = GF_EVENT_SCENE_SIZE;
			evt.size.width = w;
			evt.size.height = h;
			gf_term_send_event(term, &evt);
		} else {
			gf_sc_set_size(term->compositor, w, h);
		}
	} else if (override_size_info) {
		term->compositor->has_size_info = 0;
		term->compositor->recompute_ar = 1;
	}

	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_exit)
{
	GF_Event evt;
	SMJS_OBJ
	GF_Terminal *term = gpac_get_term(c, obj);
	memset(&evt, 0, sizeof(GF_Event));
	evt.type = GF_EVENT_QUIT;
	gf_term_send_event(term, &evt);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_set_3d)
{
	u32 type_3d = 0;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (argc && JSVAL_IS_INT(argv[0])) type_3d = JSVAL_TO_INT(argv[0]);
	if (term->compositor->inherit_type_3d != type_3d) {
		term->compositor->inherit_type_3d = type_3d;
		term->compositor->root_visual_setup = 0;
		gf_sc_reset_graphics(term->compositor);
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_move_window)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_Event evt;
	GF_Terminal *term = gpac_get_term(c, obj);
	if (argc < 2) return JS_TRUE;
	if (!JSVAL_IS_INT(argv[0]) || !JSVAL_IS_INT(argv[1])) return JS_TRUE;

	evt.type = GF_EVENT_MOVE;
	evt.move.relative = 1;
	evt.move.x = JSVAL_TO_INT(argv[0]);
	evt.move.y = JSVAL_TO_INT(argv[1]);
	term->compositor->video_out->ProcessEvent(term->compositor->video_out, &evt);

	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_error_string)
{
	const char *str;
	SMJS_ARGS

	if (argc < 1) return JS_TRUE;
	if (!JSVAL_IS_INT(argv[0]) ) return JS_TRUE;
	str = gf_error_to_string(JSVAL_TO_INT(argv[0]));

	SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, str)) );
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_get_scene_time)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_SceneGraph *sg = NULL;
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!argc || !JSVAL_IS_OBJECT(argv[0])) {
		sg = term->root_scene->graph;
	} else {
		GF_Node *n = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0]));
		sg = n ? n->sgprivate->scenegraph : term->root_scene->graph;
	}
	SMJS_SET_RVAL( DOUBLE_TO_JSVAL( JS_NewDouble(c, sg->GetSceneTime(sg->userpriv) ) ) );

	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_trigger_gc)
{
	SMJS_OBJ
	GF_SceneGraph *sg = NULL;
	GF_Terminal *term = gpac_get_term(c, obj);

	sg = term->root_scene->graph;
	sg->trigger_gc = GF_TRUE;
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_migrate_url)
{
	char *url;
	u32 i, count;
	GF_NetworkCommand com;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!argc || !JSVAL_IS_STRING(argv[0])) return JS_FALSE;

	url = SMJS_CHARS(c, argv[0]);
	if (!url) return JS_FALSE;

	count = gf_list_count(term->root_scene->resources);
	for (i=0; i<count; i++) {
		GF_ObjectManager *odm = gf_list_get(term->root_scene->resources, i);
		if (!odm->net_service) continue;
		if (!strstr(url, odm->net_service->url)) continue;

		memset(&com, 0, sizeof(GF_NetworkCommand));
		com.base.command_type = GF_NET_SERVICE_MIGRATION_INFO;
		odm->net_service->ifce->ServiceCommand(odm->net_service->ifce, &com);
		if (com.migrate.data) {
			SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.migrate.data)) );
		} else {
			SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, odm->net_service->url)) );
		}
		break;
	}
	SMJS_FREE(c, url);
	return JS_TRUE;
}

static SMJS_FUNC_PROP_GET( odm_getProperty)

GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);
GF_MediaInfo odi;
s32 prop_id;
char *str;
if (!SMJS_ID_IS_INT(id)) return JS_TRUE;

gf_term_get_object_info(odm->term, odm, &odi);

prop_id = SMJS_ID_TO_INT(id);
switch (prop_id) {
case -1:
	*vp = INT_TO_JSVAL(odi.od->objectDescriptorID);
	break;
case -2:
	*vp = INT_TO_JSVAL(odm->subscene ? gf_list_count(odm->subscene->resources) : 0);
	break;
case -3:
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.service_url));
	break;
case -4:
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, odi.duration) );
	break;
case -5:
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, odi.current_time) );
	break;
case -6:
	*vp = INT_TO_JSVAL( odi.clock_drift);
	break;
case -7:
	if (odi.status==0) str = "Stopped";
	else if (odi.status==1) str = "Playing";
	else if (odi.status==2) str = "Paused";
	else if (odi.status==3) str = "Not Setup";
	else str = "Setup Failed";
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, str));
	break;
case -8:
	*vp = INT_TO_JSVAL( odi.buffer);
	break;
case -9:
	*vp = INT_TO_JSVAL( odi.db_unit_count);
	break;
case -10:
	*vp = INT_TO_JSVAL( odi.cb_unit_count);
	break;
case -11:
	*vp = INT_TO_JSVAL( odi.cb_max_count);
	break;
case -12:
	if (odi.od_type==GF_STREAM_SCENE) str = "Scene";
	else if (odi.od_type==GF_STREAM_OD) str = "Object Descriptor";
	else if (odi.od_type==GF_STREAM_VISUAL) str = "Video";
	else if (odi.od_type==GF_STREAM_AUDIO) str = "Audio";
	else if (odi.od_type==GF_STREAM_TEXT) str = "Text";
	else if (odm->subscene) str = "Subscene";
	else str = "Unknow";
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, str));
	break;
case -13:
	*vp = INT_TO_JSVAL( odi.sample_rate);
	break;
case -14:
	*vp = INT_TO_JSVAL( odi.num_channels);
	break;
case -15:
	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, odi.lang_code ? odi.lang_code : gf_4cc_to_str(odi.lang) ) );
	break;
case -16:
	*vp = INT_TO_JSVAL( odi.width);
	break;
case -17:
	*vp = INT_TO_JSVAL( odi.height);
	break;
case -18:
	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, gf_4cc_to_str(odi.pixelFormat) ) );
	break;
case -19:
	if (odi.par) {
		char szPar[50];
		sprintf(szPar, "%d:%d", (odi.par>>16)&0xFF, (odi.par)&0xFF );
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szPar ) );
	} else {
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "1:1" ) );
	}
	break;
case -20:
	*vp = INT_TO_JSVAL(odi.nb_dec_frames);
	break;
case -21:
	*vp = INT_TO_JSVAL(odi.nb_dropped);
	break;
case -22:
	*vp = INT_TO_JSVAL(odi.max_dec_time);
	break;
case -23:
	*vp = INT_TO_JSVAL(odi.total_dec_time);
	break;
case -24:
	*vp = INT_TO_JSVAL(odi.avg_bitrate);
	break;
case -25:
	*vp = INT_TO_JSVAL(odi.max_bitrate);
	break;
case -26:
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.service_handler));
	break;
case -27:
	*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.codec_name));
	break;
case -28:
{
	//first check network
	GF_NetworkCommand com;
	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_SERVICE_QUALITY_QUERY;
	com.base.on_channel = gf_list_get(odm->channels, 0);

	gf_term_service_command(odm->net_service, &com);
	if (com.quality_query.index) {
		*vp = INT_TO_JSVAL(com.quality_query.index);
		break;
	}
#if 0
	//use input channels
	if (odm->codec->type==GF_STREAM_VISUAL) {
		u32 count = gf_list_count(odm->codec->inChannels);
		if (count>1) {
			*vp = INT_TO_JSVAL(count);
			break;
		}
	}
	//use number of scalable addons
	if (odm->upper_layer_odm) {
		u32 count = 0;
		while (odm) {
			odm = odm->upper_layer_odm;
			count++;
		}
		*vp = INT_TO_JSVAL(count);
		break;
	}
#endif
	*vp = INT_TO_JSVAL(1);
	break;
}
case -29:
	*vp = INT_TO_JSVAL(odi.max_buffer);
	break;
case -30:
	*vp = INT_TO_JSVAL(odi.min_buffer);
	break;
case -31:
	*vp = INT_TO_JSVAL(odi.au_duration);
	break;
case -32:
	*vp = INT_TO_JSVAL(odi.nb_iraps);
	break;
case -33:
	*vp = INT_TO_JSVAL(odi.irap_total_dec_time);
	break;
case -34:
	*vp = INT_TO_JSVAL(odi.irap_max_dec_time);
	break;
case -35:
	*vp = INT_TO_JSVAL(odi.od ? odi.od->ServiceID : 0);
	break;
case -36:
	*vp = INT_TO_JSVAL( (!odm->addon && odm->subscene) ? odm->subscene->selected_service_id : odm->parentscene->selected_service_id);
	break;
case -37:
{
	GF_NetworkCommand com;
	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_GET_STATS;
	com.base.on_channel = gf_list_get(odm->channels, 0);
	gf_term_service_command(odm->net_service, &com);
	*vp = INT_TO_JSVAL(com.net_stats.bw_down/1000);
}
break;
case -38:
	*vp = INT_TO_JSVAL(gf_list_count(odm->net_service->dnloads) );
	break;
case -39:
	if ((s32) odm->timeshift_depth > 0) {
		*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, ((Double) odm->timeshift_depth) / 1000.0 ) );
	} else {
		*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, 0.0) );
	}
	break;
case -40:
{
	GF_NetworkCommand com;
	GF_Scene *scene;
	Double res = 0.0;

	if (!odm->timeshift_depth) {
		*vp = INT_TO_JSVAL(0);
		break;
	}

	scene = odm->subscene ? odm->subscene : odm->parentscene;

	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_GET_TIMESHIFT;

	//we may need to check the main addon for timeshifting
	if (scene->main_addon_selected) {
		u32 i, count = gf_list_count(scene->resources);
		for (i=0; i < count; i++) {
			GF_ObjectManager *an_odm = gf_list_get(scene->resources, i);
			if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) {
				odm = an_odm;
			}
		}
	}

	if (odm->timeshift_depth) {
		//can be NULL
		com.base.on_channel = gf_list_get(odm->channels, 0);
		gf_term_service_command(odm->net_service, &com);
		res = com.timeshift.time;
	} else if (scene->main_addon_selected) {
		GF_Clock *ck;
		ck = scene->dyn_ck;
		if (scene->scene_codec) ck = scene->scene_codec->ck;
		if (ck) {
			u32 now = gf_clock_time(scene->dyn_ck) ;
			u32 live = scene->obj_clock_at_main_activation + gf_sys_clock() - scene->sys_clock_at_main_activation;
			res = ((Double) live) / 1000.0;
			res -= ((Double) now) / 1000.0;

			if (res<0) {
				GF_Event evt;
				memset(&evt, 0, sizeof(evt));
				evt.type = GF_EVENT_TIMESHIFT_UNDERRUN;
				gf_term_send_event(odm->term, &evt);
				res=0;
			} else if (res && res*1000 > scene->timeshift_depth) {
				GF_Event evt;
				memset(&evt, 0, sizeof(evt));
				evt.type = GF_EVENT_TIMESHIFT_OVERFLOW;
				gf_term_send_event(odm->term, &evt);
				res=scene->timeshift_depth;
				res/=1000;
			}
		}
	}
	*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, res) );
}
break;
case -41:
	*vp = BOOLEAN_TO_JSVAL( (odm->addon || (!odm->subscene && odm->parentscene->root_od->addon)) ? JS_TRUE : JS_FALSE);
	break;
case -42:
{
	GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene;
	*vp = BOOLEAN_TO_JSVAL( scene->main_addon_selected ? JS_TRUE : JS_FALSE);
}
break;
case -43:
{
	GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene;
	*vp = BOOLEAN_TO_JSVAL( gf_sc_is_over(odm->term->compositor, scene->graph) ? JS_TRUE : JS_FALSE);
}
break;
case -44:
{
	GF_Channel *ch = gf_list_get(odm->channels, 0);
	*vp = BOOLEAN_TO_JSVAL((ch && ch->is_pulling) ? JS_TRUE : JS_FALSE);
}
break;
case -45:
	*vp = BOOLEAN_TO_JSVAL(odm->subscene && odm->subscene->is_dynamic_scene ? JS_TRUE : JS_FALSE);
	break;
case -46:
{
	GF_NetworkCommand com;
	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_SERVICE_INFO;
	com.info.service_id = odi.od->ServiceID;
	gf_term_service_command(odm->net_service, &com);
	if (com.info.name) {
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, com.info.name ) );
	} else {
		*vp = JSVAL_NULL;
	}
}
break;
case -47:
	*vp = INT_TO_JSVAL( odi.ntp_diff);
	break;

case -48:
{
	GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene;
	u32 i, count = gf_list_count(scene->resources);
	*vp = JSVAL_NULL;
	for (i=0; i < count; i++) {
		GF_ObjectManager *an_odm = gf_list_get(scene->resources, i);
		if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) {
			if (!strstr(an_odm->addon->url, "://")) {
				char szURL[GF_MAX_PATH];
				sprintf(szURL, "gpac://%s", an_odm->addon->url);
				*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szURL) );
			} else {
				*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, an_odm->addon->url) );
			}
		}
	}
}
break;
case -49:
{
	u32 i, count;
	GF_Err e;
	GF_NetworkCommand com;
	GF_Scene *scene;

	scene = odm->subscene ? odm->subscene : odm->parentscene;

	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_SERVICE_CAN_REVERSE_PLAYBACK;

	//we may need to check the main addon for timeshifting
	count = gf_list_count(scene->resources);
	for (i=0; i < count; i++) {
		GF_ObjectManager *an_odm = gf_list_get(scene->resources, i);
		if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) {
			odm = an_odm;
		}
	}

	//can be NULL
	com.base.on_channel = gf_list_get(odm->channels, 0);
	e = gf_term_service_command(odm->net_service, &com);
	*vp = BOOLEAN_TO_JSVAL((e==GF_OK) ? GF_TRUE : GF_FALSE );
}
break;
case -50:
	*vp = BOOLEAN_TO_JSVAL(odm && (odm->lower_layer_odm || odm->scalable_addon) ? JS_TRUE : JS_FALSE);
	break;
case -51:
{
	GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene;
	u32 i, count = gf_list_count(scene->resources);

	*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, -1) );
	if (! scene->main_addon_selected) break;

	for (i=0; i < count; i++) {
		GF_ObjectManager *an_odm = gf_list_get(scene->resources, i);
		if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) {
			if (an_odm->duration) {
				Double now = gf_clock_time(scene->dyn_ck) / 1000.0;
				now -= ((Double) an_odm->addon->media_pts) / 90000.0;
				now += ((Double) an_odm->addon->media_timestamp) / an_odm->addon->media_timescale;
				*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, now) );
			}
		}
	}
}
break;
}

return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_get_quality)
{
	GF_NetworkCommand com;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm) return JS_TRUE;
	if (argc<1) return JS_TRUE;
	if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE;

	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_SERVICE_QUALITY_QUERY;
	com.quality_query.index = 1 + JSVAL_TO_INT(argv[0]);
	com.base.on_channel = gf_list_get(odm->channels, 0);

	if (gf_term_service_command(odm->net_service, &com) == GF_OK) {
		JSObject *a = JS_NewObject(c, NULL, NULL, NULL);

		JS_DefineProperty(c, a, "ID", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.ID ? com.quality_query.ID : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "mime", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.mime ? com.quality_query.mime : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "codec", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.codec ? com.quality_query.codec : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "width", INT_TO_JSVAL(com.quality_query.width), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "height", INT_TO_JSVAL(com.quality_query.height), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "bandwidth", INT_TO_JSVAL(com.quality_query.bandwidth), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "interlaced", BOOLEAN_TO_JSVAL(com.quality_query.interlaced), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "fps", DOUBLE_TO_JSVAL( JS_NewDouble(c, com.quality_query.fps) ), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "samplerate", INT_TO_JSVAL(com.quality_query.sample_rate), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "channels", INT_TO_JSVAL(com.quality_query.nb_channels), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "par_num", INT_TO_JSVAL(com.quality_query.par_num), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "par_den", INT_TO_JSVAL(com.quality_query.par_den), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "disabled", BOOLEAN_TO_JSVAL(com.quality_query.disabled), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "is_selected", BOOLEAN_TO_JSVAL(com.quality_query.is_selected), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
		JS_DefineProperty(c, a, "automatic", BOOLEAN_TO_JSVAL(com.quality_query.automatic), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);

		SMJS_SET_RVAL( OBJECT_TO_JSVAL(a) );
	} else {
		SMJS_SET_RVAL(JSVAL_NULL);
	}

	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_select_quality)
{
	GF_NetworkCommand com;
	char *ID = NULL;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm) return JS_TRUE;

	if ((argc==1) && JSVAL_IS_STRING(argv[0])) {
		ID = SMJS_CHARS(c, argv[0]);
	}
	if (!ID) {
		return JS_TRUE;
	}

	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.command_type = GF_NET_SERVICE_QUALITY_SWITCH;
	com.base.on_channel = gf_list_get(odm->channels, 0);
	if (!strcmp(ID, "auto")) {
		com.switch_quality.set_auto = 1;
	} else {
		com.switch_quality.ID = ID;
	}

	gf_term_service_command(odm->net_service, &com);

	SMJS_FREE(c, ID);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_disable_main_addon)
{
	SMJS_OBJ
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm || !odm->subscene || !odm->subscene->main_addon_selected) return JS_TRUE;

	gf_scene_resume_live(odm->subscene);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_select_service)
{
	u32 sid;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm) return JS_TRUE;
	if (argc<1) return JS_TRUE;
	if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE;
	sid = JSVAL_TO_INT(argv[0]);

	gf_term_select_service(odm->term, odm->subscene ? odm : odm->parentscene->root_od, sid);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_select)
{
	SMJS_OBJ
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm) return JS_TRUE;

#ifndef GPAC_DISABLE_PLAYER
	gf_scene_select_object(odm->parentscene, odm);
#endif
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gjs_odm_get_resource)
{
	GF_ObjectManager *an_odm = NULL;
	u32 idx;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (!odm) return JS_TRUE;
	if (argc<1) return JS_TRUE;
	if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE;
	idx = JSVAL_TO_INT(argv[0]);

	if (odm->subscene) {
		an_odm = gf_list_get(odm->subscene->resources, idx);
	}
	if (an_odm && an_odm->net_service) {
		JSObject *anobj;
		JSClass *_class = JS_GET_CLASS(c, obj);
		anobj = JS_NewObject(c, _class, 0, 0);
		SMJS_SET_PRIVATE(c, anobj, an_odm);
		SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) );
	} else {
		SMJS_SET_RVAL(JSVAL_NULL);
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_addon_layout)
{
	u32 pos, size;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (argc<2) return JS_TRUE;
	if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE;
	if (! JSVAL_IS_INT(argv[1]) ) return JS_TRUE;
	pos = JSVAL_TO_INT(argv[0]);
	size = JSVAL_TO_INT(argv[1]);

	gf_scene_set_addon_layout_info(odm->subscene, pos, size);
	return JS_TRUE;
}

static void do_enable_addon(GF_ObjectManager *odm, char *addon_url, Bool enable_if_defined, Bool disable_if_defined )
{
	GF_AssociatedContentLocation addon_info;
	memset(&addon_info, 0, sizeof(GF_AssociatedContentLocation));
	addon_info.external_URL = addon_url;
	addon_info.timeline_id = -100;
	addon_info.enable_if_defined = enable_if_defined;
	addon_info.disable_if_defined = disable_if_defined;
	gf_scene_register_associated_media(odm->subscene ? odm->subscene : odm->parentscene, &addon_info);
}

static JSBool SMJS_FUNCTION(gjs_odm_enable_addon)
{
	Bool do_disable = GF_FALSE;
	char *addon_url = NULL;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (! JSVAL_IS_STRING(argv[0]) ) return JS_TRUE;
	if ((argc==2) && JSVAL_IS_BOOLEAN(argv[1])) {
		do_disable = (Bool) JSVAL_TO_BOOLEAN(argv[1]);
	}


	addon_url = SMJS_CHARS(c, argv[0]);
	if (addon_url) {
		do_enable_addon(odm, addon_url, GF_TRUE, do_disable);
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_odm_declare_addon)
{
	char *addon_url = NULL;
	SMJS_OBJ
	SMJS_ARGS
	GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj);

	if (! JSVAL_IS_STRING(argv[0]) ) return JS_TRUE;

	addon_url = SMJS_CHARS(c, argv[0]);
	if (addon_url) {
		do_enable_addon(odm, addon_url, GF_FALSE, GF_FALSE);
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_get_object_manager)
{
	JSObject *anobj;
	u32 i, count;
	GF_ObjectManager *odm = NULL;
	char *service_url = NULL;
	SMJS_OBJ
	SMJS_ARGS
	GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	GF_Terminal *term = gpac_get_term(c, obj);
	GF_Scene *scene = term->root_scene;

	if (JSVAL_IS_STRING(argv[0]) ) {
		char *url;
		url = service_url = SMJS_CHARS(c, argv[0]);
		if (!service_url) {
			SMJS_SET_RVAL(JSVAL_NULL);
			return JS_TRUE;
		}
		if (!strncmp(service_url, "gpac://", 7)) url = service_url + 7;
		if (!strncmp(service_url, "file://", 7)) url = service_url + 7;
		count = gf_list_count(scene->resources);
		for (i=0; i<count; i++) {
			odm = gf_list_get(scene->resources, i);
			if (odm->net_service && !strcmp(odm->net_service->url, url)) break;
			odm = NULL;
		}
	}

	SMJS_FREE(c, service_url);

	if (!odm) {
		SMJS_SET_RVAL(JSVAL_NULL);
		return JS_TRUE;
	}

	anobj = JS_NewObject(c, &gjs->odmClass._class, 0, 0);
	SMJS_SET_PRIVATE(c, anobj, odm);

	SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) );
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gpac_new_storage)
{
	char szFile[GF_MAX_PATH];
	JSObject *anobj;
	GF_Config *storage = NULL;
	char *storage_url = NULL;
	u8 hash[20];
	char temp[3];
	SMJS_OBJ
	SMJS_ARGS
	GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);

	if (JSVAL_IS_STRING(argv[0]) ) {
		u32 i, count;
		storage_url = SMJS_CHARS(c, argv[0]);
		if (!storage_url) {
			SMJS_SET_RVAL(JSVAL_NULL);
			return JS_TRUE;
		}

		szFile[0]=0;
		gf_sha1_csum((u8 *)storage_url, (u32) strlen(storage_url), hash);
		for (i=0; i<20; i++) {
			sprintf(temp, "%02X", hash[i]);
			strcat(szFile, temp);
		}
		strcat(szFile, ".cfg");

		count = gf_list_count(gjs->storages);
		for (i=0; i<count; i++) {
			GF_Config *a_cfg = gf_list_get(gjs->storages, i);
			char *cfg_name = gf_cfg_get_filename(a_cfg);

			if (strstr(cfg_name, szFile)) {
				storage = a_cfg;
				gf_free(cfg_name);
				break;
			}
			gf_free(cfg_name);
		}

		if (!storage) {
			const char *storage_dir = gf_cfg_get_key(gjs->term->user->config, "General", "StorageDirectory");

			storage = gf_cfg_force_new(storage_dir, szFile);
			if (storage) {
				gf_cfg_set_key(storage, "GPAC", "StorageURL", storage_url);
				gf_list_add(gjs->storages, storage);
			}
		}

		SMJS_FREE(c, storage_url);
	}

	if (!storage) {
		SMJS_SET_RVAL(JSVAL_NULL);
		return JS_TRUE;
	}
	anobj = JS_NewObject(c, &gjs->storageClass._class, 0, 0);
	SMJS_SET_PRIVATE(c, anobj, storage);


	SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) );
	return JS_TRUE;
}

static SMJS_FUNC_PROP_GET( gpacevt_getProperty)

GF_GPACJSExt *gjs = SMJS_GET_PRIVATE(c, obj);
GF_Event *evt = gjs->evt;
if (!evt) return 0;

if (SMJS_ID_IS_INT(id)) {
	switch (SMJS_ID_TO_INT(id)) {
	case -1:
#ifndef GPAC_DISABLE_SVG
		*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, gf_dom_get_key_name(evt->key.key_code) ));
#endif
		break;
	case -2:
		*vp = INT_TO_JSVAL(evt->mouse.x);
		break;
	case -3:
		*vp = INT_TO_JSVAL(evt->mouse.y);
		break;
	case -4:
		if (gjs->term->compositor->hit_appear) *vp = BOOLEAN_TO_JSVAL(JS_TRUE);
		else if (gf_list_count(gjs->term->compositor->previous_sensors) ) *vp = BOOLEAN_TO_JSVAL(JS_TRUE);
		else if (gjs->term->compositor->text_selection) *vp = BOOLEAN_TO_JSVAL(JS_TRUE);
		else *vp = BOOLEAN_TO_JSVAL(JS_FALSE);
		break;
	case -5:
		*vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(evt->mouse.wheel_pos)) );
		break;
	case -6:
		*vp = INT_TO_JSVAL( evt->mouse.button);
		break;
	case -7:
		*vp = INT_TO_JSVAL(evt->type);
		break;
	case -8:
#ifndef GPAC_DISABLE_SVG
		*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, gf_dom_event_get_name(evt->type) ));
#endif
		break;
	case -9:
		*vp = INT_TO_JSVAL(evt->key.hw_code);
		break;
	}
} else if (SMJS_ID_IS_STRING(id)) {
	char *name = SMJS_CHARS_FROM_STRING(c, SMJS_ID_TO_STRING(id));
	if (!strcmp(name, "target_url")) {
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, evt->navigate.to_url) );
	}
	else if (!strcmp(name, "files")) {
		u32 i, idx;
		jsval v;
		JSObject *files_array = JS_NewArrayObject(c, 0, NULL);
		for (i=0; i<evt->open_file.nb_files; i++) {
			if (evt->open_file.files[i]) {
				JS_GetArrayLength(c, files_array, &idx);
				v = STRING_TO_JSVAL( JS_NewStringCopyZ(c, evt->open_file.files[i]) );
				JS_SetElement(c, files_array, idx, &v);
			}
		}
		*vp = OBJECT_TO_JSVAL(files_array);
	}
	SMJS_FREE(c, name);
}

return JS_TRUE;
}

static Bool gjs_event_filter(void *udta, GF_Event *evt, Bool consumed_by_compositor)
{
	u32 retry;
	Bool res;
	jsval argv[1], rval;
	GF_GPACJSExt *gjs = (GF_GPACJSExt *)udta;
	if (consumed_by_compositor) return 0;

	if (gjs->evt != NULL) return 0;

	/*fixme - events should all be handled by the compositor */
	res = 0;
	retry=100;
	while (retry && gjs->nb_loaded) {
		res = gf_mx_try_lock(gjs->term->compositor->mx);
		if (res) break;
		retry --;
		gf_sleep(0);
	}
	if (!res) return 0;

	res = 0;
	while (gjs->nb_loaded) {
		res = gf_sg_try_lock_javascript(gjs->c);
		if (res) break;
	}
	if (!res) return 0;

	rval = JSVAL_VOID;
	gjs->evt = evt;
	SMJS_SET_PRIVATE(gjs->c, gjs->evt_obj, gjs);
	argv[0] = OBJECT_TO_JSVAL(gjs->evt_obj);
	rval = BOOLEAN_TO_JSVAL(JS_TRUE);
	JS_CallFunctionValue(gjs->c, gjs->evt_filter_obj, gjs->evt_fun, 1, argv, &rval);
	SMJS_SET_PRIVATE(gjs->c, gjs->evt_obj, NULL);
	gjs->evt = NULL;
	gf_mx_v(gjs->term->compositor->mx);

	res = 0;
	if (JSVAL_IS_BOOLEAN(rval) ) {
		res = (JSVAL_TO_BOOLEAN(rval)==JS_TRUE) ? GF_TRUE : GF_FALSE;
	} else if (JSVAL_IS_INT(rval) ) {
		res = (JSVAL_TO_INT(rval)) ? 1 : 0;
	}

	gf_sg_lock_javascript(gjs->c, 0);
	return res;
}

static JSBool SMJS_FUNCTION(gpac_set_event_filter)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	if (!argc || !JSVAL_IS_OBJECT(argv[0])) return JS_FALSE;

	if (! JSVAL_IS_NULL(gjs->evt_fun) ) return JS_TRUE;

	gjs->evt_fun = argv[0];
	gjs->evt_filter_obj = obj;
	gjs->c = c;
	gjs->evt_filter.udta = gjs;
	gjs->evt_filter.on_event = gjs_event_filter;
	gf_term_add_event_filter(gjs->term, &gjs->evt_filter);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_set_focus)
{
	GF_Node *elt;
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!argc) return JS_FALSE;

	if (JSVAL_IS_NULL(argv[0])) {
		gf_sc_focus_switch_ring(term->compositor, 0, NULL, 0);
		return JS_TRUE;
	}

	if (JSVAL_IS_STRING(argv[0])) {
		char *focus_type = SMJS_CHARS(c, argv[0]);
		if (!stricmp(focus_type, "previous")) {
			gf_sc_focus_switch_ring(term->compositor, 1, NULL, 0);
		}
		else if (!stricmp(focus_type, "next")) {
			gf_sc_focus_switch_ring(term->compositor, 0, NULL, 0);
		}
		SMJS_FREE(c, focus_type);
	} else if (JSVAL_IS_OBJECT(argv[0])) {
		elt = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0]));
		if (!elt) return JS_TRUE;

		gf_sc_focus_switch_ring(term->compositor, 0, elt, 2);
	}

	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_get_scene)
{
	GF_Node *elt;
	u32 w, h;
	JSObject *scene_obj;
#ifndef GPAC_DISABLE_SCENEGRAPH
	GF_SceneGraph *sg;
#endif
	GF_Scene *scene=NULL;

	SMJS_OBJ
	SMJS_ARGS
	GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj);
	if (!gjs || !argc || !JSVAL_IS_OBJECT(argv[0])) return JS_FALSE;

	elt = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0]));
	if (!elt) return JS_TRUE;
	switch (elt->sgprivate->tag) {
#ifndef GPAC_DISABLE_VRML
	case TAG_MPEG4_Inline:
		scene = (GF_Scene *)gf_node_get_private(elt);
		break;
#endif

#ifndef GPAC_DISABLE_X3D
	case TAG_X3D_Inline:
		scene = (GF_Scene *)gf_node_get_private(elt);
		break;
#endif

#ifndef GPAC_DISABLE_SVG
	case TAG_SVG_animation:
		sg = gf_sc_animation_get_scenegraph(elt);
		scene = (GF_Scene *)gf_sg_get_private(sg);
		break;
#endif
	default:
		return JS_TRUE;
	}
	if (!scene) return JS_TRUE;


	scene_obj = JS_NewObject(c, &gjs->anyClass._class, 0, 0);
	SMJS_SET_PRIVATE(c, scene_obj, scene);
	gf_sg_get_scene_size_info(scene->graph, &w, &h);
	JS_DefineProperty(c, scene_obj, "width", INT_TO_JSVAL(w), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(c, scene_obj, "height", INT_TO_JSVAL(h), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	JS_DefineProperty(c, scene_obj, "connected", BOOLEAN_TO_JSVAL(scene->graph ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	SMJS_SET_RVAL( OBJECT_TO_JSVAL(scene_obj) );
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gpac_show_keyboard)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_Terminal *term = gpac_get_term(c, obj);
	if (!argc) return JS_FALSE;

	if (JSVAL_IS_BOOLEAN(argv[0])) {
		Bool show = JSVAL_TO_BOOLEAN(argv[0])==JS_TRUE;
		GF_Event evt;
		memset(&evt, 0, sizeof(GF_Event));
		evt.type = show ? GF_EVENT_TEXT_EDITING_START : GF_EVENT_TEXT_EDITING_END;
		gf_term_user_event(term, &evt);
	}
	return JS_TRUE;
}


static JSBool SMJS_FUNCTION(gjs_storage_get_option)
{
	const char *opt = NULL;
	char *sec_name, *key_name;
	s32 idx = -1;
	JSString *s;
	SMJS_OBJ
	SMJS_ARGS
	GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj);
	if (!config) return JS_FALSE;

	if (argc < 2) return JS_FALSE;

	if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE;
	if (!JSVAL_IS_STRING(argv[1]) && !JSVAL_IS_INT(argv[1])) return JS_FALSE;

	sec_name = SMJS_CHARS(c, argv[0]);
	key_name = NULL;
	if (JSVAL_IS_INT(argv[1])) {
		idx = JSVAL_TO_INT(argv[1]);
	} else if (JSVAL_IS_STRING(argv[1]) ) {
		key_name = SMJS_CHARS(c, argv[1]);
	}

	if (key_name) {
		opt = gf_cfg_get_key(config, sec_name, key_name);
	} else if (idx>=0) {
		opt = gf_cfg_get_key_name(config, sec_name, idx);
	} else {
		opt = NULL;
	}
	if (key_name) {
		SMJS_FREE(c, key_name);
	}
	SMJS_FREE(c, sec_name);

	if (opt) {
		s = JS_NewStringCopyZ(c, opt);
		SMJS_SET_RVAL( STRING_TO_JSVAL(s) );
	} else {
		SMJS_SET_RVAL( JSVAL_NULL );
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_storage_set_option)
{
	char *sec_name, *key_name, *key_val;
	SMJS_OBJ
	SMJS_ARGS
	GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj);
	if (!config) return JS_FALSE;

	if (argc < 3) return JS_FALSE;

	if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE;
	if (!JSVAL_IS_STRING(argv[1])) return JS_FALSE;

	sec_name = SMJS_CHARS(c, argv[0]);
	key_name = SMJS_CHARS(c, argv[1]);
	key_val = NULL;
	if (JSVAL_IS_STRING(argv[2]))
		key_val = SMJS_CHARS(c, argv[2]);

	gf_cfg_set_key(config, sec_name, key_name, key_val);

	SMJS_FREE(c, sec_name);
	SMJS_FREE(c, key_name);
	if (key_val) {
		SMJS_FREE(c, key_val);
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(gjs_storage_save)
{
	SMJS_OBJ
	GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj);
	if (!config) return JS_FALSE;

	gf_cfg_save(config);
	return JS_TRUE;
}

static void gjs_load(GF_JSUserExtension *jsext, GF_SceneGraph *scene, JSContext *c, JSObject *global, Bool unload)
{
	GF_GPACJSExt *gjs;
	GF_JSAPIParam par;

	JSPropertySpec gpacEvtClassProps[] = {
		SMJS_PROPERTY_SPEC("keycode",			-1,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("mouse_x",			-2,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("mouse_y",			-3,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("picked",			-4,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("wheel",				-5,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("button",			-6,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("type",				-7,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("name",				-8,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("hwkey",				-9,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0)
	};
	JSFunctionSpec gpacEvtClassFuncs[] = {
		SMJS_FUNCTION_SPEC(0, 0, 0)
	};

	JSPropertySpec gpacClassProps[] = {

		SMJS_PROPERTY_SPEC("last_working_directory",	-1,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0),
		SMJS_PROPERTY_SPEC("scale_x",					-2,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("scale_y",					-3,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("translation_x",				-4,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("translation_y",				-5,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("rectangular_textures",		-6,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("batteryOn",					-7,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("batteryCharging",			-8,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("batteryPercent",			-9,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("batteryLifeTime",			-10,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("batteryFullLifeTime",		-11,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("hostname",					-12,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("fullscreen",				-13,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0),
		SMJS_PROPERTY_SPEC("current_path",				-14,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("volume",					-15,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0),
		SMJS_PROPERTY_SPEC("navigation",				-16,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0),
		SMJS_PROPERTY_SPEC("navigation_type",			-17,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0),
		SMJS_PROPERTY_SPEC("hardware_yuv",				-18,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("hardware_rgb",				-19,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("hardware_rgba",				-20,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("hardware_stretch",			-21,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("screen_width",				-22,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("screen_height",				-23,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("http_max_bitrate",			-24,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0),
		SMJS_PROPERTY_SPEC("http_bitrate",				-25,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("fps",						-26,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("cpu_load",					-27,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("cpu",						-27,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("nb_cores",					-28,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("system_memory",				-29,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("memory",					-30,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("argc",						-31,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("caption",					-32,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0),
		SMJS_PROPERTY_SPEC("disable_hardware_blit",		-33,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0),
		SMJS_PROPERTY_SPEC("disable_composite_blit",	-34,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0),
		SMJS_PROPERTY_SPEC("focus_highlight",			-35,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0),
		SMJS_PROPERTY_SPEC("dpi_x",						-36,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("dpi_y",						-37,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),

		SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0)
	};
	JSFunctionSpec gpacClassFuncs[] = {
		SMJS_FUNCTION_SPEC("get_option",		gpac_get_option, 3),
		SMJS_FUNCTION_SPEC("set_option",		gpac_set_option, 4),
		SMJS_FUNCTION_SPEC("get_arg",		gpac_get_arg, 1),
		SMJS_FUNCTION_SPEC("enum_directory",	gpac_enum_directory, 1),
		SMJS_FUNCTION_SPEC("set_size",		gpac_set_size, 1),
		SMJS_FUNCTION_SPEC("exit",				gpac_exit, 0),
		SMJS_FUNCTION_SPEC("set_3d",				gpac_set_3d, 1),
		SMJS_FUNCTION_SPEC("move_window",			gpac_move_window, 2),
		SMJS_FUNCTION_SPEC("get_scene_time",		gpac_get_scene_time, 1),
		SMJS_FUNCTION_SPEC("migrate_url",			gpac_migrate_url, 1),
		SMJS_FUNCTION_SPEC("set_event_filter",	gpac_set_event_filter, 1),
		SMJS_FUNCTION_SPEC("set_focus",			gpac_set_focus, 1),
		SMJS_FUNCTION_SPEC("get_scene",			gpac_get_scene, 1),
		SMJS_FUNCTION_SPEC("error_string",		gpac_error_string, 1),
		SMJS_FUNCTION_SPEC("show_keyboard",		gpac_show_keyboard, 1),
		SMJS_FUNCTION_SPEC("trigger_gc",		gpac_trigger_gc, 1),
		SMJS_FUNCTION_SPEC("get_object_manager",		gpac_get_object_manager, 1),
		SMJS_FUNCTION_SPEC("new_storage",		gpac_new_storage, 1),
		SMJS_FUNCTION_SPEC("switch_quality",		gpac_switch_quality, 1),
		SMJS_FUNCTION_SPEC("reload",		gpac_reload, 1),
		SMJS_FUNCTION_SPEC("navigation_supported",		gpac_navigation_supported, 1),
		SMJS_FUNCTION_SPEC("set_back_color",		gpac_set_back_color, 3),


		SMJS_FUNCTION_SPEC(0, 0, 0)
	};

	JSPropertySpec odmClassProps[] = {
		SMJS_PROPERTY_SPEC("ID",				-1,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("nb_resources",			-2,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("service_url",		-3,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("duration",			-4,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("clock_time",		-5,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("clock_drift",		-6,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("status",			-7,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("buffer",			-8,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("db_unit_count",		-9,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("cb_unit_count",		-10,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("cb_capacity",		-11,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("type",				-12,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("samplerate",		-13,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("channels",			-14,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("lang",				-15,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("width",				-16,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("height",			-17,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("pixelformt",		-18,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("par",				-19,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("dec_frames",		-20,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("drop_frames",		-21,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("max_dec_time",		-22,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("total_dec_time",	-23,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("avg_bitrate",		-24,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("max_bitrate",		-25,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("service_handler",	-26,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("codec",				-27,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("nb_qualities",		-28,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("max_buffer",		-29,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("min_buffer",		-30,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("frame_duration",	-31,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("irap_frames",		-32,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("irap_dec_time",		-33,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("irap_max_time",		-34,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("service_id",		-35,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("selected_service",	-36,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("bandwidth_down",	-37,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("nb_http",			-38,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("timeshift_depth",	-39,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("timeshift_time",	-40,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("is_addon",			-41,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("main_addon_on",		-42,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("is_over",			-43,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("is_pulling",		-44,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("dynamic_scene",		-45,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("service_name",		-46,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("ntp_diff",			-47,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("main_addon_url",	-48,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("reverse_playback_supported",		-49,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("scalable_enhancement",		-50,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),
		SMJS_PROPERTY_SPEC("main_addon_media_time",		-51,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0),



		SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0)
	};

	JSFunctionSpec odmClassFuncs[] = {
		SMJS_FUNCTION_SPEC("declare_addon", gjs_odm_declare_addon, 1),
		SMJS_FUNCTION_SPEC("enable_addon", gjs_odm_enable_addon, 1),
		SMJS_FUNCTION_SPEC("addon_layout", gjs_odm_addon_layout, 1),
		SMJS_FUNCTION_SPEC("get_resource", gjs_odm_get_resource, 1),
		SMJS_FUNCTION_SPEC("get_quality", gjs_odm_get_quality, 1),
		SMJS_FUNCTION_SPEC("select_service", gjs_odm_select_service, 1),
		SMJS_FUNCTION_SPEC("select_quality", gjs_odm_select_quality, 1),
		SMJS_FUNCTION_SPEC("disable_main_addon", gjs_odm_disable_main_addon, 1),
		SMJS_FUNCTION_SPEC("select", gjs_odm_select, 1),

		SMJS_FUNCTION_SPEC(0, 0, 0)
	};


	JSPropertySpec storageClassProps[] = {
		SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0)
	};
	JSFunctionSpec storageClassFuncs[] = {
		SMJS_FUNCTION_SPEC("get_option",		gjs_storage_get_option, 2),
		SMJS_FUNCTION_SPEC("set_option",		gjs_storage_set_option, 3),
		SMJS_FUNCTION_SPEC("save",				gjs_storage_save, 0),
		SMJS_FUNCTION_SPEC(0, 0, 0)
	};


	gjs = jsext->udta;
	/*nothing to do on unload*/
	if (unload) {
		gjs->nb_loaded--;
		/*if we destroy the script context holding the gpac event filter (only one for the time being), remove the filter*/
		if ((gjs->c==c) && gjs->evt_filter.udta) {
			gf_term_remove_event_filter(gjs->term, &gjs->evt_filter);
			gjs->evt_filter.udta = NULL;
		}
		return;
	}

	gjs->nb_loaded++;

	if (!scene) return;

	JS_SETUP_CLASS(gjs->gpacClass, "GPAC", JSCLASS_HAS_PRIVATE, gpac_getProperty, gpac_setProperty, JS_FinalizeStub);

	if (!gjs->gpac_obj) {
		GF_JS_InitClass(c, global, 0, &gjs->gpacClass, 0, 0, gpacClassProps, gpacClassFuncs, 0, 0);
		gjs->gpac_obj = JS_DefineObject(c, global, "gpac", &gjs->gpacClass._class, 0, 0);

		if (scene->script_action) {
			if (scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_GET_TERM, scene->RootNode, &par)) {
				gjs->term = par.term;
			}
		}
		SMJS_SET_PRIVATE(c, gjs->gpac_obj, gjs);

		JS_SETUP_CLASS(gjs->odmClass, "ODM", JSCLASS_HAS_PRIVATE, odm_getProperty, JS_PropertyStub_forSetter, JS_FinalizeStub);
		GF_JS_InitClass(c, global, 0, &gjs->odmClass, 0, 0, odmClassProps, odmClassFuncs, 0, 0);


		JS_SETUP_CLASS(gjs->storageClass, "Storage", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
		GF_JS_InitClass(c, global, 0, &gjs->storageClass, 0, 0, storageClassProps, storageClassFuncs, 0, 0);
	} else {
		JS_DefineProperty(c, global, "gpac", OBJECT_TO_JSVAL(gjs->gpac_obj), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
	}

	if (!gjs->evt_obj) {
		JS_SETUP_CLASS(gjs->gpacEvtClass, "GPACEVT", JSCLASS_HAS_PRIVATE, gpacevt_getProperty, JS_PropertyStub_forSetter, JS_FinalizeStub);
		GF_JS_InitClass(c, global, 0, &gjs->gpacEvtClass, 0, 0, gpacEvtClassProps, gpacEvtClassFuncs, 0, 0);
		gjs->evt_obj = JS_DefineObject(c, global, "gpacevt", &gjs->gpacEvtClass._class, 0, 0);

#define DECLARE_GPAC_CONST(name) \
		JS_DefineProperty(c, global, #name, INT_TO_JSVAL(name), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);

		DECLARE_GPAC_CONST(GF_EVENT_CLICK);
		DECLARE_GPAC_CONST(GF_EVENT_MOUSEUP);
		DECLARE_GPAC_CONST(GF_EVENT_MOUSEDOWN);
		DECLARE_GPAC_CONST(GF_EVENT_MOUSEMOVE);
		DECLARE_GPAC_CONST(GF_EVENT_MOUSEWHEEL);
		DECLARE_GPAC_CONST(GF_EVENT_DBLCLICK);
		DECLARE_GPAC_CONST(GF_EVENT_KEYUP);
		DECLARE_GPAC_CONST(GF_EVENT_KEYDOWN);
		DECLARE_GPAC_CONST(GF_EVENT_TEXTINPUT);
		DECLARE_GPAC_CONST(GF_EVENT_CONNECT);
		DECLARE_GPAC_CONST(GF_EVENT_NAVIGATE_INFO);
		DECLARE_GPAC_CONST(GF_EVENT_NAVIGATE);
		DECLARE_GPAC_CONST(GF_EVENT_DROPFILE);
		DECLARE_GPAC_CONST(GF_EVENT_ADDON_DETECTED);
		DECLARE_GPAC_CONST(GF_EVENT_QUALITY_SWITCHED);
		DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_DEPTH);
		DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_UPDATE);
		DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_OVERFLOW);
		DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_UNDERRUN);
		DECLARE_GPAC_CONST(GF_EVENT_QUIT);
		DECLARE_GPAC_CONST(GF_EVENT_MAIN_ADDON_STATE);
		DECLARE_GPAC_CONST(GF_EVENT_SCENE_SIZE);

		DECLARE_GPAC_CONST(GF_NAVIGATE_NONE);
		DECLARE_GPAC_CONST(GF_NAVIGATE_WALK);
		DECLARE_GPAC_CONST(GF_NAVIGATE_FLY);
		DECLARE_GPAC_CONST(GF_NAVIGATE_PAN);
		DECLARE_GPAC_CONST(GF_NAVIGATE_GAME);
		DECLARE_GPAC_CONST(GF_NAVIGATE_SLIDE);
		DECLARE_GPAC_CONST(GF_NAVIGATE_EXAMINE);
		DECLARE_GPAC_CONST(GF_NAVIGATE_ORBIT);
		DECLARE_GPAC_CONST(GF_NAVIGATE_VR);

		DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_NONE);
		DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_2D);
		DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_3D);

		JS_SETUP_CLASS(gjs->anyClass, "GPACOBJECT", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
		GF_JS_InitClass(c, global, 0, &gjs->anyClass, 0, 0, 0, 0, 0, 0);

		gjs->evt_fun = JSVAL_NULL;
	}
}





GF_JSUserExtension *gjs_new()
{
	GF_JSUserExtension *dr;
	GF_GPACJSExt *gjs;
	dr = gf_malloc(sizeof(GF_JSUserExtension));
	memset(dr, 0, sizeof(GF_JSUserExtension));
	GF_REGISTER_MODULE_INTERFACE(dr, GF_JS_USER_EXT_INTERFACE, "GPAC JavaScript Bindings", "gpac distribution");

	GF_SAFEALLOC(gjs, GF_GPACJSExt);
	gjs->rti_refresh_rate = GPAC_JS_RTI_REFRESH_RATE;
	gjs->evt_fun = JSVAL_NULL;
	gjs->storages = gf_list_new();
	dr->load = gjs_load;
	dr->udta = gjs;
	return dr;
}


void gjs_delete(GF_BaseInterface *ifce)
{
	GF_JSUserExtension *dr = (GF_JSUserExtension *) ifce;
	GF_GPACJSExt *gjs = dr->udta;

	while (gf_list_count(gjs->storages)) {
		GF_Config *cfg = (GF_Config *) gf_list_pop_back(gjs->storages);
		gf_cfg_discard_changes(cfg);
		gf_cfg_del(cfg);
	}
	gf_list_del(gjs->storages);

	gf_free(gjs);
	gf_free(dr);
}

#endif


GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
	static u32 si [] = {
#ifdef GPAC_HAS_SPIDERMONKEY
		GF_JS_USER_EXT_INTERFACE,
#endif
		0
	};
	return si;
}

GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface(u32 InterfaceType)
{
#ifdef GPAC_HAS_SPIDERMONKEY
	if (InterfaceType == GF_JS_USER_EXT_INTERFACE) return (GF_BaseInterface *)gjs_new();
#endif
	return NULL;
}

GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
	switch (ifce->InterfaceType) {
#ifdef GPAC_HAS_SPIDERMONKEY
	case GF_JS_USER_EXT_INTERFACE:
		gjs_delete(ifce);
		break;
#endif
	}
}

GPAC_MODULE_STATIC_DECLARATION( gpac_js )
