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

/*
	ANY CHANGE TO THE API MUST BE REFLECTED IN THE DOCUMENTATION IN gpac/share/doc/idl/storage.js
	(no way to define inline JS doc with doxygen)
*/

#include <gpac/setup.h>

#ifdef GPAC_HAS_QJS
#include <gpac/main.h>
#include <gpac/thread.h>
#include <gpac/bitstream.h>
#include <gpac/network.h>
#include <gpac/base_coding.h>
#include <gpac/filters.h>

#include "../scenegraph/qjs_common.h"


static void qjs_init_runtime_libc(JSRuntime *rt);
static void qjs_uninit_runtime_libc(JSRuntime *rt);

extern void *user_log_cbk;

typedef struct
{
	JSRuntime *js_runtime;
	u32 nb_inst;

	GF_Mutex *mx;
	GF_List *allocated_contexts;

	//for logging
	void *prev_user_log_cbk;
	gf_log_cbk prev_log_cbk;
	JSValue log_fun;
	JSValue log_obj;
	u32 js_log_buf_size;
	//WARNING - libc mem, not gf_alloc
	char *js_log_buf;
	JSContext *log_ctx;
	char *js_orig_logs;

} GF_JSRuntime;

static GF_JSRuntime *js_rt = NULL;

typedef struct __js_sys_task {
	JSValue fun;
	JSValue _obj;

	u32 type;
	JSContext *ctx;
} JS_Sys_Task;

static void js_reset_logs(JSContext *ctx);

JSContext *gf_js_create_context()
{
	JSContext *ctx;
	if (!js_rt) {
		JSRuntime *js_runtime = JS_NewRuntime();
		if (!js_runtime) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[ECMAScript] Cannot allocate ECMAScript runtime\n"));
			return NULL;
		}
		GF_SAFEALLOC(js_rt, GF_JSRuntime);
		if (!js_rt) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[JS] Failed to create script runtime\n"));
			return NULL;
		}
		js_rt->js_runtime = js_runtime;
		js_rt->allocated_contexts = gf_list_new();
		js_rt->mx = gf_mx_new("JavaScript");
		GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[ECMAScript] ECMAScript runtime allocated %p\n", js_runtime));

		qjs_init_runtime_libc(js_rt->js_runtime);
	}
	js_rt->nb_inst++;

	gf_mx_p(js_rt->mx);

	ctx = JS_NewContext(js_rt->js_runtime);

	gf_list_add(js_rt->allocated_contexts, ctx);
	gf_mx_v(js_rt->mx);

	return ctx;
}

void gf_js_delete_context(JSContext *ctx)
{
	if (!js_rt) return;

	js_reset_logs(ctx);
	gf_js_call_gc(ctx);


	JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task();
	if (task && task->type == RMT_CALLBACK_JS) {
		gf_rmt_set_on_new_client_cbk(NULL, NULL);
		JS_FreeValue(ctx, task->fun);
		JS_FreeValue(ctx, task->_obj);
		gf_free(task);
	}

	gf_mx_p(js_rt->mx);
	gf_list_del_item(js_rt->allocated_contexts, ctx);
	JS_FreeContext(ctx);
	gf_mx_v(js_rt->mx);
	gf_js_call_gc(NULL);

	js_rt->nb_inst --;
	if (js_rt->nb_inst == 0) {
		//we delete the runtime in gf_sys_close (we use static vars for class IDs)
		//but perform GC
		JS_RunGC(js_rt->js_runtime);
	}
}

void gf_js_delete_runtime()
{
	//prevent destruction of the runtime if asked (used for android)
	if (gf_opts_get_bool("temp", "static-jsrt"))
		return;

	if (js_rt) {
		qjs_uninit_runtime_libc(js_rt->js_runtime);
		JS_FreeRuntime(js_rt->js_runtime);
		gf_list_del(js_rt->allocated_contexts);
		gf_mx_del(js_rt->mx);
		gf_free(js_rt);
		js_rt = NULL;
	}
}

void gf_js_call_gc(JSContext *c)
{
	gf_js_lock(c, 1);
	JS_RunGC(js_rt->js_runtime);
	gf_js_lock(c, 0);
}

Bool gf_js_context_is_valid(JSContext *ctx)
{
	if (gf_list_find(js_rt->allocated_contexts, ctx) < 0)
		return GF_FALSE;
	return GF_TRUE;
}

/*
 * locking/try-locking the JS context
 *
 * */
GF_EXPORT
void gf_js_lock(struct JSContext *cx, Bool LockIt)
{
	if (!js_rt) return;

	if (LockIt) {
		gf_mx_p(js_rt->mx);
	} else {
		gf_mx_v(js_rt->mx);
	}
}

GF_EXPORT
Bool gf_js_try_lock(struct JSContext *cx)
{
	gf_assert(cx);
	if (gf_mx_try_lock(js_rt->mx)) {
		return 1;
	}
	return 0;
}

JSRuntime *gf_js_get_rt()
{
	if (!js_rt) return NULL;
	return js_rt->js_runtime;
}


Bool is_js_log = GF_FALSE;
static JSValue js_print_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 ltool, u32 error_type)
{
	int i=0;
	Bool first=GF_TRUE;
	s32 logl = GF_LOG_INFO;
	Bool no_new_line = GF_FALSE;
	JSValue v, g;
	const char *c_logname=NULL;
	const char *log_name = "JS";

	if ((argc>1) && JS_IsNumber(argv[0])) {
		JS_ToInt32(ctx, &logl, argv[0]);
		i=1;
	}

	if (error_type)
		logl = GF_LOG_ERROR;
	g = JS_GetGlobalObject(ctx);
	v = JS_GetPropertyStr(ctx, g, "_gpac_log_name");
	if (!JS_IsUndefined(v) && !JS_IsNull(v)) {
		c_logname = JS_ToCString(ctx, v);
		JS_FreeValue(ctx, v);
		if (c_logname) {
			log_name = c_logname;
			if (!strlen(log_name))
				log_name = NULL;
		}
	}
	JS_FreeValue(ctx, g);

	if (log_name) {
#ifndef GPAC_DISABLE_LOG
		if (!is_js_log) {
			GF_LOG(logl, ltool, ("[%s] ", log_name));
		} else
#endif
			fprintf(stderr, "[%s] ", log_name);
	}
	if (error_type==2) {
#ifndef GPAC_DISABLE_LOG
		if (!is_js_log) {
			GF_LOG(logl, ltool, ("Throw "));
		} else
#endif
			fprintf(stderr, "Throw ");
	}

	for (; i < argc; i++) {
		const char *str = JS_ToCString(ctx, argv[i]);
		if (!str) return GF_JS_EXCEPTION(ctx);

		if (logl==-1) {
			gf_sys_format_help(stderr, GF_PRINTARG_HIGHLIGHT_FIRST, "%s\n", str);
		} else if (logl==-2) {
			gf_sys_format_help(stderr, 0, "%s\n", str);
		} else if (logl<0) {
			fprintf(stderr, "%s%s", (first) ? "" : " ", str);
		} else {
#ifndef GPAC_DISABLE_LOG
			if (!is_js_log) {
				GF_LOG(logl, ltool, ("%s%s", (first) ? "" : " ", str));
			} else
#endif
				fprintf(stderr, "%s%s", (first) ? "" : " ", str);

			if (JS_IsException(argv[i])) {
				js_dump_error_exc(ctx, argv[i]);
			}
		}
		if (i+1==argc) {
			u32 len = (u32) strlen(str);
			if (len && (str[len-1]=='\r')) no_new_line = GF_TRUE;
		}
		JS_FreeCString(ctx, str);
		first=GF_FALSE;
	}
	if (!no_new_line) {
#ifndef GPAC_DISABLE_LOG
		if (!is_js_log) {
			GF_LOG(logl, ltool, ("\n"));
		} else
#endif
			fprintf(stderr, "\n");
	}
	if (c_logname) JS_FreeCString(ctx, c_logname);
	return JS_UNDEFINED;
}
JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_print_ex(ctx, this_val, argc, argv, GF_LOG_CONSOLE, GF_FALSE);
}

void js_dump_error_exc(JSContext *ctx, const JSValue exception_val)
{
	Bool is_error;
	u32 err_type = 1;
	is_error = JS_IsError(ctx, exception_val);
	if (!is_error) err_type = 2;

	js_print_ex(ctx, JS_NULL, 1, (JSValueConst *)&exception_val, GF_LOG_SCRIPT, err_type);

	if (is_error) {
		JSValue val = JS_GetPropertyStr(ctx, exception_val, "stack");
		if (!JS_IsUndefined(val)) {
			const char *stack = JS_ToCString(ctx, val);
#ifndef GPAC_DISABLE_LOG
			GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s\n", stack) );
#else
			fprintf(stderr, "%s\n", stack);
#endif
			JS_FreeCString(ctx, stack);
		}
		JS_FreeValue(ctx, val);
	}
}

void js_dump_error(JSContext *ctx)
{
	JSValue exception_val = JS_GetException(ctx);
	js_dump_error_exc(ctx, exception_val);
	JS_FreeValue(ctx, exception_val);
}

#ifdef GPAC_DISABLE_QJS_LIBC
void js_std_loop(JSContext *ctx)
{
	while (1) {
		JSContext *ctx1;
		int err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
		if (err <= 0) {
			if (err < 0) {
				js_dump_error(ctx1);
			}
			break;
		}
	}
}
#endif




#define JS_CGETSET_MAGIC_DEF_ENUM(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE|JS_PROP_ENUMERABLE, JS_DEF_CGETSET_MAGIC, magic, .u = { .getset = { .get = { .getter_magic = fgetter }, .set = { .setter_magic = fsetter } } } }


static JSClassID bitstream_class_id = 0;
static JSClassID sha1_class_id = 0;
static JSClassID file_class_id = 0;
static JSClassID fileio_class_id = 0;
static JSClassID amix_class_id = 0;
static JSClassID core_class_id = 0;

typedef struct
{
	GF_BitStream *bs;
	JSValue buf_ref;
} JSBitstream;

#define GET_JSBS \
	JSBitstream *jbs = JS_GetOpaque(this_val, bitstream_class_id); \
	GF_BitStream *bs = jbs ? jbs->bs : NULL;


static void js_gpac_free(JSRuntime *rt, void *opaque, void *ptr)
{
	gf_free(ptr);
}

static void js_bs_finalize(JSRuntime *rt, JSValue this_val)
{
	GET_JSBS
	if (!bs) return;
	gf_bs_del(bs);
	JS_FreeValueRT(rt, jbs->buf_ref);
	gf_free(jbs);
}
static void js_bs_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
	GET_JSBS
	if (!bs) return;
	JS_MarkValue(rt, jbs->buf_ref, mark_func);
}

JSClassDef bitstreamClass = {
	"Bitstream",
	.finalizer = js_bs_finalize,
	.gc_mark = js_bs_gc_mark
};


static JSValue js_bs_get_u8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u8(bs) );
}
static JSValue js_bs_get_s8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s8 v;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	v = (s8) gf_bs_read_u8(bs);
	return JS_NewInt32(ctx, v );
}
static JSValue js_bs_get_u16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u16(bs) );
}
static JSValue js_bs_get_u16_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u16_le(bs) );
}
static JSValue js_bs_get_s16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s16 v;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	v = (s16) gf_bs_read_u16(bs);
	return JS_NewInt32(ctx, v );
}
static JSValue js_bs_get_u24(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u24(bs) );
}
static JSValue js_bs_get_s32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s32 v;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	v = (s32) gf_bs_read_u32(bs);
	return JS_NewInt32(ctx, v);
}
static JSValue js_bs_get_u32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u32(bs) );
}
static JSValue js_bs_get_u32_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_bs_read_u32_le(bs) );
}
static JSValue js_bs_get_u64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt64(ctx, gf_bs_read_u64(bs) );
}
static JSValue js_bs_get_u64_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt64(ctx, gf_bs_read_u64_le(bs) );
}
static JSValue js_bs_get_s64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 v;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	v = (s64) gf_bs_read_u64(bs);
	return JS_NewInt64(ctx, v );
}
static JSValue js_bs_get_float(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewFloat64(ctx, gf_bs_read_float(bs) );
}
static JSValue js_bs_get_double(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	return JS_NewFloat64(ctx, gf_bs_read_double(bs) );
}
static JSValue js_bs_get_bits(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s32 nb_bits;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &nb_bits, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (nb_bits<=32)
		return JS_NewInt32(ctx, gf_bs_read_int(bs, nb_bits) );
	return JS_NewInt64(ctx, gf_bs_read_long_int(bs, nb_bits) );
}
static JSValue js_bs_get_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char *res;
	JSValue ret;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	res = gf_bs_read_utf8(bs);
	if (!res) return JS_NULL;
	ret = JS_NewString(ctx, res);
	gf_free(res);
	return ret;
}

static JSValue js_bs_data_io(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 mode)
{
	s32 nb_bytes=0;
	s64 offset=0;
	size_t data_size;
	u8 *data=NULL;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	if (argc<1) return GF_JS_EXCEPTION(ctx);

	if (JS_IsObject(argv[0])) {
		data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
		if (argc>1) {
			if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
			if ((mode==2) && (argc>2)) {
				if (JS_ToInt64(ctx, &offset, argv[2])) return GF_JS_EXCEPTION(ctx);

			}
		}
	}
	if (!data) return GF_JS_EXCEPTION(ctx);

	if (nb_bytes> (s32) data_size) nb_bytes = (s32) data_size;
	else if (!nb_bytes) nb_bytes = (s32) data_size;

	if (mode==1) {
		data_size = gf_bs_write_data(bs, data, nb_bytes);
	} else if (mode==2) {
		GF_Err e = gf_bs_insert_data(bs, data, nb_bytes, offset);
		if (e) js_throw_err(ctx, e);
		return JS_UNDEFINED;
	} else {
		data_size = gf_bs_read_data(bs, data, nb_bytes);
	}
	return JS_NewInt32(ctx, (s32) data_size);
}

static JSValue js_bs_get_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_data_io(ctx, this_val, argc, argv, 0);
}

static JSValue js_bs_get_4cc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	u32 fcc = gf_bs_read_u32(bs);
	return JS_NewString(ctx, gf_4cc_to_str(fcc) );
}

static JSValue js_bs_skip_bytes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s32 nb_bytes;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &nb_bytes, argv[0])) return GF_JS_EXCEPTION(ctx);
	gf_bs_skip_bytes(bs, nb_bytes);
	return JS_UNDEFINED;
}
static JSValue js_bs_is_align(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	if (gf_bs_is_align(bs)) return JS_TRUE;
	return JS_FALSE;
}
static JSValue js_bs_align(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	gf_bs_align(bs);
	return JS_UNDEFINED;
}
static JSValue js_bs_truncate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	gf_bs_truncate(bs);
	return JS_UNDEFINED;
}
static JSValue js_bs_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	gf_bs_flush(bs);
	return JS_UNDEFINED;
}
static JSValue js_bs_epb_mode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	gf_bs_enable_emulation_byte_removal(bs, (JS_ToBool(ctx, argv[0])) ? GF_TRUE : GF_FALSE);
	return JS_UNDEFINED;
}

static JSValue js_bs_peek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u64 byte_offset=0;
	s32 nb_bits;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &nb_bits, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (argc>1) {
		if (JS_ToInt64(ctx, &byte_offset, argv[1])) return GF_JS_EXCEPTION(ctx);
	}
	return JS_NewInt32(ctx, gf_bs_peek_bits(bs, nb_bits, byte_offset));
}

static JSValue js_bs_put_val(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 mode)
{
	s64 val;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	switch (mode) {
	case 0: gf_bs_write_u8(bs, (u32) val); break;
	case 1: gf_bs_write_u8(bs, (u32) val); break;
	case 2: gf_bs_write_u16(bs, (u32) val); break;
	case 3: gf_bs_write_u16_le(bs, (u32) val); break;
	case 4: gf_bs_write_u16(bs, (u32) val); break;
	case 5: gf_bs_write_u24(bs, (u32) val); break;
	case 6: gf_bs_write_u32(bs, (u32) val); break;
	case 7: gf_bs_write_u32_le(bs, (u32) val); break;
	case 8: gf_bs_write_u32(bs, (u32) val); break;
	case 9: gf_bs_write_u64(bs, (u64) val); break;
	case 10: gf_bs_write_u64_le(bs, (u64) val); break;
	case 11: gf_bs_write_u64(bs, (u64) val); break;
	}
	return JS_UNDEFINED;
}
static JSValue js_bs_put_u8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 0);
}
static JSValue js_bs_put_s8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 1);
}
static JSValue js_bs_put_u16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 2);
}
static JSValue js_bs_put_u16_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 3);
}
static JSValue js_bs_put_s16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 4);
}
static JSValue js_bs_put_u24(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 5);
}
static JSValue js_bs_put_u32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 6);
}
static JSValue js_bs_put_u32_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 7);
}
static JSValue js_bs_put_s32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 8);
}
static JSValue js_bs_put_u64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 9);
}
static JSValue js_bs_put_u64_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 10);
}
static JSValue js_bs_put_s64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_put_val(ctx, this_val, argc, argv, 11);
}
static JSValue js_bs_put_bits(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 val;
	s32 nb_bits;
	GET_JSBS
	if (!bs || (argc!=2)) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &nb_bits, argv[1])) return GF_JS_EXCEPTION(ctx);
	if (nb_bits<=32)
		gf_bs_write_int(bs, (u32) val, nb_bits);
	else
		gf_bs_write_long_int(bs, val, nb_bits);
	return JS_UNDEFINED;
}
static JSValue js_bs_put_float(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	Double val;
	GET_JSBS
	if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
	if (JS_ToFloat64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	gf_bs_write_float(bs, (Float) val);
	return JS_UNDEFINED;
}
static JSValue js_bs_put_double(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	Double val;
	GET_JSBS
	if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
	if (JS_ToFloat64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	gf_bs_write_double(bs, val);
	return JS_UNDEFINED;
}
static JSValue js_bs_put_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_data_io(ctx, this_val, argc, argv, 1);
}
static JSValue js_bs_insert_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_bs_data_io(ctx, this_val, argc, argv, 2);
}

static JSValue js_bs_put_4cc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 val;
	GET_JSBS
	if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
	if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	const char *str = JS_ToCString(ctx, argv[0]);
	if (!str || (strlen(str) != 4)) {
		if (str) JS_FreeCString(ctx, str);
		return GF_JS_EXCEPTION(ctx);
	}
	val = GF_4CC(str[0], str[1], str[2], str[3]);
	JS_FreeCString(ctx, str);
	gf_bs_write_u32(bs, val);
	return JS_UNDEFINED;
}

static JSValue js_bs_put_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GET_JSBS
	if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
	if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	const char *str = JS_ToCString(ctx, argv[0]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	gf_bs_write_utf8(bs, str);
	JS_FreeCString(ctx, str);
	return JS_UNDEFINED;
}

static JSValue js_bs_get_content(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u8 *data;
	u32 size;
	JSValue res;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	gf_bs_get_content(bs, &data, &size);

	if (data) {
		res = JS_NewArrayBuffer(ctx, data, size, js_gpac_free, NULL, 0);
	} else {
		res = JS_NULL;
	}
	return res;
}

static JSValue js_bs_transfer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	Bool keep_src=GF_FALSE;
	GF_Err e;
	JSBitstream *jssrcbs;
	GET_JSBS
	if (!bs || !argc) return GF_JS_EXCEPTION(ctx);

	jssrcbs = JS_GetOpaque(argv[0], bitstream_class_id);
	if (!jssrcbs || !jssrcbs->bs) return GF_JS_EXCEPTION(ctx);
	if (argc>1) {
		keep_src = JS_ToBool(ctx, argv[1]);
	}
	e = gf_bs_transfer(bs, jssrcbs->bs, keep_src);
	if (e) return js_throw_err(ctx, e);
	return JS_UNDEFINED;
}

enum
{
	JS_BS_POS=0,
	JS_BS_SIZE,
	JS_BS_BIT_OFFSET,
	JS_BS_BIT_POS,
	JS_BS_AVAILABLE,
	JS_BS_BITS_AVAILABLE,
	JS_BS_OVERFLOW,
	JS_BS_REFRESH_SIZE,
};

static JSValue js_bs_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);
	switch (magic) {
	case JS_BS_POS:
		return JS_NewInt64(ctx, gf_bs_get_position(bs));
	case JS_BS_SIZE:
		return JS_NewInt64(ctx, gf_bs_get_size(bs));
	case JS_BS_REFRESH_SIZE:
		return JS_NewInt64(ctx, gf_bs_get_refreshed_size(bs));
	case JS_BS_BIT_OFFSET:
		return JS_NewInt64(ctx, gf_bs_get_bit_offset(bs));
	case JS_BS_BIT_POS:
		return JS_NewInt64(ctx, gf_bs_get_bit_position(bs));
	case JS_BS_AVAILABLE:
		return JS_NewInt64(ctx, gf_bs_available(bs));
	case JS_BS_BITS_AVAILABLE:
		return JS_NewInt32(ctx, gf_bs_bits_available(bs));
	case JS_BS_OVERFLOW:
		return JS_NewBool(ctx, gf_bs_is_overflow(bs) );
	}
	return JS_UNDEFINED;

}
static JSValue js_bs_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
	s64 ival;
	GET_JSBS
	if (!bs) return GF_JS_EXCEPTION(ctx);

	switch (magic) {
	case JS_BS_POS:
		if (JS_ToInt64(ctx, &ival, value)) return GF_JS_EXCEPTION(ctx);
		gf_bs_seek(bs, ival);
		break;
	}
	return JS_UNDEFINED;
}

static const JSCFunctionListEntry bitstream_funcs[] = {
	JS_CGETSET_MAGIC_DEF_ENUM("pos", js_bs_prop_get, js_bs_prop_set, JS_BS_POS),
	JS_CGETSET_MAGIC_DEF_ENUM("size", js_bs_prop_get, NULL, JS_BS_SIZE),
	JS_CGETSET_MAGIC_DEF_ENUM("bit_offset", js_bs_prop_get, NULL, JS_BS_BIT_OFFSET),
	JS_CGETSET_MAGIC_DEF_ENUM("bit_pos", js_bs_prop_get, NULL, JS_BS_BIT_POS),
	JS_CGETSET_MAGIC_DEF_ENUM("available", js_bs_prop_get, NULL, JS_BS_AVAILABLE),
	JS_CGETSET_MAGIC_DEF_ENUM("bits_available", js_bs_prop_get, NULL, JS_BS_BITS_AVAILABLE),
	JS_CGETSET_MAGIC_DEF_ENUM("refreshed_size", js_bs_prop_get, NULL, JS_BS_REFRESH_SIZE),
	JS_CGETSET_MAGIC_DEF_ENUM("overflow", js_bs_prop_get, NULL, JS_BS_OVERFLOW),

	JS_CFUNC_DEF("skip", 0, js_bs_skip_bytes),
	JS_CFUNC_DEF("is_align", 0, js_bs_is_align),
	JS_CFUNC_DEF("align", 0, js_bs_align),
	JS_CFUNC_DEF("truncate", 0, js_bs_truncate),
	JS_CFUNC_DEF("peek", 0, js_bs_peek),
	JS_CFUNC_DEF("flush", 0, js_bs_flush),
	JS_CFUNC_DEF("epb_mode", 0, js_bs_epb_mode),

	JS_CFUNC_DEF("get_u8", 0, js_bs_get_u8),
	JS_CFUNC_DEF("get_s8", 0, js_bs_get_s8),
	JS_CFUNC_DEF("get_u16", 0, js_bs_get_u16),
	JS_CFUNC_DEF("get_u16_le", 0, js_bs_get_u16_le),
	JS_CFUNC_DEF("get_s16", 0, js_bs_get_s16),
	JS_CFUNC_DEF("get_u24", 0, js_bs_get_u24),
	JS_CFUNC_DEF("get_u32", 0, js_bs_get_u32),
	JS_CFUNC_DEF("get_u32_le", 0, js_bs_get_u32_le),
	JS_CFUNC_DEF("get_s32", 0, js_bs_get_s32),
	JS_CFUNC_DEF("get_u64", 0, js_bs_get_u64),
	JS_CFUNC_DEF("get_u64_le", 0, js_bs_get_u64_le),
	JS_CFUNC_DEF("get_s64", 0, js_bs_get_s64),
	JS_CFUNC_DEF("get_bits", 0, js_bs_get_bits),
	JS_CFUNC_DEF("get_string", 0, js_bs_get_string),
	JS_CFUNC_DEF("get_float", 0, js_bs_get_float),
	JS_CFUNC_DEF("get_double", 0, js_bs_get_double),
	JS_CFUNC_DEF("get_data", 0, js_bs_get_data),
	JS_CFUNC_DEF("get_4cc", 0, js_bs_get_4cc),
	JS_CFUNC_DEF("put_u8", 0, js_bs_put_u8),
	JS_CFUNC_DEF("put_s8", 0, js_bs_put_s8),
	JS_CFUNC_DEF("put_u16", 0, js_bs_put_u16),
	JS_CFUNC_DEF("put_u16_le", 0, js_bs_put_u16_le),
	JS_CFUNC_DEF("put_s16", 0, js_bs_put_s16),
	JS_CFUNC_DEF("put_u24", 0, js_bs_put_u24),
	JS_CFUNC_DEF("put_u32", 0, js_bs_put_u32),
	JS_CFUNC_DEF("put_u32_le", 0, js_bs_put_u32_le),
	JS_CFUNC_DEF("put_s32", 0, js_bs_put_s32),
	JS_CFUNC_DEF("put_u64", 0, js_bs_put_u64),
	JS_CFUNC_DEF("put_u64_le", 0, js_bs_put_u64_le),
	JS_CFUNC_DEF("put_s64", 0, js_bs_put_s64),
	JS_CFUNC_DEF("put_bits", 0, js_bs_put_bits),
	JS_CFUNC_DEF("put_float", 0, js_bs_put_float),
	JS_CFUNC_DEF("put_double", 0, js_bs_put_double),
	JS_CFUNC_DEF("put_data", 0, js_bs_put_data),
	JS_CFUNC_DEF("put_4cc", 0, js_bs_put_4cc),
	JS_CFUNC_DEF("put_string", 0, js_bs_put_string),
	JS_CFUNC_DEF("insert_data", 0, js_bs_insert_data),
	JS_CFUNC_DEF("get_content", 0, js_bs_get_content),
	JS_CFUNC_DEF("transfer", 0, js_bs_transfer),
};

static JSValue bitstream_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
	JSValue anobj;
	JSBitstream *jbs;
	GF_BitStream *bs = NULL;

	if (!argc) {
		bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
	} else if (JS_IsObject(argv[0])){
		FILE *f = JS_GetOpaque(argv[0], file_class_id);
		if (f) {
			if ((argc>1) && JS_ToBool(ctx, argv[1])) {
				bs = gf_bs_from_file(f, GF_BITSTREAM_WRITE);
			} else {
				bs = gf_bs_from_file(f, GF_BITSTREAM_READ);
			}
		} else {
			u8 *data;
			size_t data_size;

			data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
			if (!data) return GF_JS_EXCEPTION(ctx);
			if ((argc>1) && JS_ToBool(ctx, argv[1])) {
				bs = gf_bs_new(data, data_size, GF_BITSTREAM_WRITE);
			} else {
				bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ);
			}
		}
	} else {
		return GF_JS_EXCEPTION(ctx);
	}
	GF_SAFEALLOC(jbs, JSBitstream);
	if (!jbs) {
		gf_bs_del(bs);
		return js_throw_err(ctx, GF_OUT_OF_MEM);
	}
	anobj = JS_NewObjectClass(ctx, bitstream_class_id);
	if (JS_IsException(anobj)) {
		gf_bs_del(bs);
		gf_free(jbs);
		return anobj;
	}
	jbs->bs = bs;
	jbs->buf_ref = JS_UNDEFINED;
	if (argc)
		jbs->buf_ref = JS_DupValue(ctx, argv[0]);

	JS_SetOpaque(anobj, jbs);
	return anobj;
}

//// RMTClient js class ////

static JSClassID js_sys_rmt_client_class_id;


static JSValue js_sys_enable_rmtws(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	Bool enable = GF_TRUE;
	if (argc > 0 && JS_IsBool(argv[0])) enable = JS_ToBool(ctx, argv[0]);
	gf_sys_enable_rmtws(enable);
	return JS_UNDEFINED;
}

static void js_sys_rmt_client_finalizer(JSRuntime *rt, JSValue val) {

	RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id);
	if (!client) return;

	JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client);
	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_client_finalizer client %p task %p\n", __FILE__, __LINE__, client, task));
	gf_rmt_client_set_on_data_cbk(client, NULL, NULL);

	JS_Sys_Task* deltask = gf_rmt_client_get_on_del_task(client);
	gf_rmt_client_set_on_del_cbk(client, NULL, NULL);

	JS_SetOpaque(val, NULL);

	if (task && task->type == RMT_CALLBACK_JS) {
		JS_FreeValue(task->ctx, task->fun);
		JS_FreeValue(task->ctx, task->_obj);
		gf_free(task);
	}

	if (deltask && deltask->type == RMT_CALLBACK_JS) {
		JS_FreeValue(deltask->ctx, deltask->fun);
		JS_FreeValue(deltask->ctx, deltask->_obj);
		gf_free(deltask);
	}

}

static void js_sys_rmt_client_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {

	RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id);
	if (!client) return;

	JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client);
	if (task && task->type == RMT_CALLBACK_JS) {
		JS_MarkValue(rt, task->fun, mark_func);
		JS_MarkValue(rt, task->_obj, mark_func);
	}

	task = gf_rmt_client_get_on_del_task(client);
	if (task && task->type == RMT_CALLBACK_JS) {
		JS_MarkValue(rt, task->fun, mark_func);
		JS_MarkValue(rt, task->_obj, mark_func);
	}

}

static JSClassDef js_sys_rmt_client_class = {
	"RMTClient",
	.finalizer = js_sys_rmt_client_finalizer,
	.gc_mark = js_sys_rmt_client_gc_mark
};

enum {
	JS_SYS_RMT_CLIENT_ON_DATA,
	JS_SYS_RMT_CLIENT_ON_CLOSE,
	JS_SYS_RMT_CLIENT_PEER_ADDRESS
};



static void js_sys_rmt_run_task(JS_Sys_Task* task, JSValue arg) {

	if (!task || task->type != RMT_CALLBACK_JS )
		return;

	int argc = JS_IsUndefined(arg) ? 0 : 1;

	gf_js_lock(task->ctx, GF_TRUE);

	JSValue ret = JS_Call(task->ctx, task->fun, task->_obj, argc, &arg);

	if (JS_IsException(ret)) {
		js_dump_error(task->ctx);
	}
	JS_FreeValue(task->ctx, ret);
	JS_FreeValue(task->ctx, arg);
	js_std_loop(task->ctx);
	gf_js_lock(task->ctx, GF_FALSE);

}


static void js_sys_rmt_on_del_client(void *udta) {
	if (udta) {
		JS_Sys_Task *task = udta;
		if (task->type == RMT_CALLBACK_JS && task->ctx && !JS_IsUndefined(task->_obj)) {

			GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d deleting opaque obj from task %p\n", __FILE__, __LINE__,task));

			if (JS_IsFunction(task->ctx, task->fun))
				js_sys_rmt_run_task(task, JS_UNDEFINED);

			js_sys_rmt_client_finalizer(NULL, task->_obj);

		}
	}
}


static void js_sys_rmt_client_on_data(void *udta, const u8* payload, u64 size, Bool is_binary) {
	JS_Sys_Task *task = udta;
	if (!task || task->type != RMT_CALLBACK_JS) return;

	JSValue arg;
	if (is_binary) {
		arg = JS_NewArrayBufferCopy(task->ctx, payload, size);
	} else {
		arg = JS_NewStringLen(task->ctx, payload, size);
	}

	js_sys_rmt_run_task(task, arg);

}

static JSValue js_sys_rmt_client_prop_get(JSContext *ctx, JSValueConst this_val, int magic) {

	RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
	if (!client)
		return JS_UNDEFINED;

	switch (magic) {
		case JS_SYS_RMT_CLIENT_PEER_ADDRESS:;
			const char* peer_address = gf_rmt_get_peer_address(client);
			if (peer_address) {
				return JS_NewString(ctx, peer_address);
			}
			break;
	}
	return JS_UNDEFINED;
}

static JSValue js_sys_rmt_client_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) {

	RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
	if (!client)
		return GF_JS_EXCEPTION(ctx);

	switch (magic) {
		case JS_SYS_RMT_CLIENT_ON_DATA:;

			JS_Sys_Task* oldtask = (JS_Sys_Task*) gf_rmt_client_get_on_data_task(client);
			if (oldtask && oldtask->type == RMT_CALLBACK_JS) {
				gf_rmt_client_set_on_data_cbk(client, NULL, NULL);
				JS_FreeValue(ctx, oldtask->fun);
				JS_FreeValue(ctx, oldtask->_obj);
				gf_free(oldtask);
			}

			if (JS_IsFunction(ctx, value)) {

				JS_Sys_Task *task;
				GF_SAFEALLOC(task, JS_Sys_Task);
				if (!task) return GF_JS_EXCEPTION(ctx);

				task->type = RMT_CALLBACK_JS;
				task->ctx = ctx;
				task->fun = JS_DupValue(ctx, value);
				task->_obj = JS_DupValue(ctx, this_val);

				gf_rmt_client_set_on_data_cbk(client, task, js_sys_rmt_client_on_data);
			}



			break;

		case JS_SYS_RMT_CLIENT_ON_CLOSE:

			if (JS_IsUndefined(value) || JS_IsNull(value)) {

				JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client);
				if (task && task->type == RMT_CALLBACK_JS) {
					// reset the js function but keep the ref to the client for on_delete
					JS_FreeValue(ctx, task->fun);
					task->fun = JS_UNDEFINED;
				}

			}

			if (JS_IsFunction(ctx, value)) {

				JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client);
				if (task && task->type == RMT_CALLBACK_JS) {
					JS_FreeValue(ctx, task->fun);
					JS_FreeValue(ctx, task->_obj);
				}
				else
					GF_SAFEALLOC(task, JS_Sys_Task);

				if (!task) return GF_JS_EXCEPTION(ctx);

				task->type = RMT_CALLBACK_JS;
				task->ctx = ctx;
				task->fun = JS_DupValue(ctx, value);
				task->_obj = JS_DupValue(ctx, this_val);

				gf_rmt_client_set_on_del_cbk(client, task, js_sys_rmt_on_del_client);
			}



			break;
	}
	return JS_UNDEFINED;
}

static JSValue js_sys_rmt_client_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {

	RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
	if (!client)
		return GF_JS_EXCEPTION(ctx);

	if (!argc)
		return JS_UNDEFINED;

	if (JS_IsString(argv[0])) {
		const char *msg = JS_ToCString(ctx, argv[0]);
		GF_Err e = gf_rmt_client_send_to_ws(client, msg, strlen(msg), GF_FALSE);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d sent msg <%s> to client <%p> returned <%d>\n", __FILE__, __LINE__, msg, client, e));
		JS_FreeCString(ctx, msg);
	}
	else if (JS_IsArrayBuffer(ctx, argv[0])) {
		u32 bufsize=0;
		u8* buf = JS_GetArrayBuffer(ctx, (size_t*)&bufsize, argv[0]);
		if (buf && bufsize) {
			GF_Err e = gf_rmt_client_send_to_ws(client, buf, bufsize, GF_TRUE);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d sent binary msg <%.*s> to client <%p> returned <%d>\n", __FILE__, __LINE__, bufsize, buf, client, e));
		}

	}

	return JS_UNDEFINED;
}

static const JSCFunctionListEntry js_sys_rmt_client_funcs[] = {
	JS_CGETSET_MAGIC_DEF("on_data", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_DATA),
	JS_CGETSET_MAGIC_DEF("on_close", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_CLOSE),

	JS_CGETSET_MAGIC_DEF("peer_address", js_sys_rmt_client_prop_get, NULL, JS_SYS_RMT_CLIENT_PEER_ADDRESS),

	JS_CFUNC_DEF("send", 1, js_sys_rmt_client_send)
};

static void js_sys_rmt_on_new_client(void *udta, void* new_client) {
	JS_Sys_Task *task = udta;
	if (!task) return;

	JSValue obj = JS_NewObjectClass(task->ctx, js_sys_rmt_client_class_id);
	if (JS_IsException(obj)) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("%s:%d obj JS_IsException\n", __FILE__, __LINE__));
	}
	JS_SetOpaque(obj, new_client);

	JS_Sys_Task *deltask;
	GF_SAFEALLOC(deltask, JS_Sys_Task);
	deltask->type = RMT_CALLBACK_JS;
	deltask->ctx = task->ctx;
	deltask->_obj = JS_DupValue(task->ctx, obj);

	gf_rmt_client_set_on_del_cbk((RMT_ClientCtx*)new_client, (void*)deltask, js_sys_rmt_on_del_client);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_on_new_client calling task %p with opaque %p\n", __FILE__, __LINE__, task, new_client));

	js_sys_rmt_run_task(task, obj);
}

enum
{
	JS_SYS_NB_CORES = 1,
	JS_SYS_SAMPLE_DUR,
	JS_SYS_TOTAL_CPU,
	JS_SYS_PROCESS_CPU,
	JS_SYS_TOTAL_CPU_DIFF,
	JS_SYS_PROCESS_CPU_DIFF,
	JS_SYS_CPU_IDLE,
	JS_SYS_TOTAL_CPU_USAGE,
	JS_SYS_PROCESS_CPU_USAGE,
	JS_SYS_PID,
	JS_SYS_THREADS,
	JS_SYS_PROCESS_MEM,
	JS_SYS_TOTAL_MEM,
	JS_SYS_TOTAL_MEM_AVAIL,
	JS_SYS_GPAC_MEM,

	JS_SYS_BATTERY_ON,
	JS_SYS_BATTERY_CHARGE,
	JS_SYS_BATTERY_PERCENT,
	JS_SYS_BATTERY_LIFETIME,
	JS_SYS_BATTERY_LIFETIME_FULL,
	JS_SYS_HOSTNAME,
	JS_SYS_LAST_WORK_DIR,
	JS_SYS_TEST_MODE,
	JS_SYS_OLD_ARCH,
	JS_SYS_LOG_COLOR,
	JS_SYS_QUIET,
	JS_SYS_USERNAME,
	JS_SYS_TIMEZONE,
	JS_SYS_FILES_OPEN,
	JS_SYS_CACHE_DIR,
	JS_SYS_SHARED_DIR,
	JS_SYS_VERSION,
	JS_SYS_VERSION_FULL,
	JS_SYS_COPYRIGHT,
	JS_SYS_V_MAJOR,
	JS_SYS_V_MINOR,
	JS_SYS_V_MICRO,
	JS_SYS_RMT_ON_NEW_CLIENT,
	JS_SYS_ON_LOG,
};



#define RTI_REFRESH_MS	200
static JSValue js_sys_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
	u32 uval;
	Bool bval;
	const char *res;
	GF_SystemRTInfo rti;

	if (magic<=JS_SYS_GPAC_MEM) {
		gf_sys_get_rti(RTI_REFRESH_MS, &rti, 0);
		switch (magic) {
		case JS_SYS_NB_CORES:
			return JS_NewInt32(ctx, rti.nb_cores);
		case JS_SYS_SAMPLE_DUR:
			return JS_NewInt32(ctx, rti.sampling_period_duration);
		case JS_SYS_TOTAL_CPU:
			return JS_NewInt32(ctx, rti.total_cpu_time);
		case JS_SYS_PROCESS_CPU:
			return JS_NewInt32(ctx, rti.process_cpu_time);
		case JS_SYS_TOTAL_CPU_DIFF:
			return JS_NewInt32(ctx, rti.total_cpu_time_diff);
		case JS_SYS_PROCESS_CPU_DIFF:
			return JS_NewInt32(ctx, rti.process_cpu_time_diff);
		case JS_SYS_CPU_IDLE:
			return JS_NewInt32(ctx, rti.cpu_idle_time);
		case JS_SYS_TOTAL_CPU_USAGE:
			return JS_NewInt32(ctx, rti.total_cpu_time);
		case JS_SYS_PROCESS_CPU_USAGE:
			return JS_NewInt32(ctx, rti.process_cpu_usage);
		case JS_SYS_PID:
			return JS_NewInt32(ctx, rti.pid);
		case JS_SYS_THREADS:
			return JS_NewInt32(ctx, rti.thread_count);
		case JS_SYS_PROCESS_MEM:
			return JS_NewInt64(ctx, rti.process_memory);
		case JS_SYS_TOTAL_MEM:
			return JS_NewInt64(ctx, rti.physical_memory);
		case JS_SYS_TOTAL_MEM_AVAIL:
			return JS_NewInt64(ctx, rti.physical_memory_avail);
		case JS_SYS_GPAC_MEM:
			return JS_NewInt64(ctx, rti.gpac_memory);
		}
		return JS_UNDEFINED;
	}

	switch (magic) {
	case JS_SYS_BATTERY_ON:
		bval = GF_FALSE;
		gf_sys_get_battery_state(&bval, NULL, NULL, NULL, NULL);
		return JS_NewBool(ctx, bval);

	case JS_SYS_BATTERY_CHARGE:
		uval = 0;
		gf_sys_get_battery_state(NULL, &uval, NULL, NULL, NULL);
		return JS_NewBool(ctx, uval ? 1 : 0);

	case JS_SYS_BATTERY_PERCENT:
		uval=0;
		gf_sys_get_battery_state(NULL, NULL, &uval, NULL, NULL);
		return JS_NewInt32(ctx, uval);

	case JS_SYS_BATTERY_LIFETIME:
		uval=0;
		gf_sys_get_battery_state(NULL, NULL, NULL, &uval, NULL);
		return JS_NewInt32(ctx, uval);

	case JS_SYS_BATTERY_LIFETIME_FULL:
		uval=0;
		gf_sys_get_battery_state(NULL, NULL, NULL, NULL, &uval);
		return JS_NewInt32(ctx, uval);

	case JS_SYS_HOSTNAME:
#ifdef GPAC_DISABLE_NETWORK
		return JS_NewString(ctx, "localhost");
#else
		{
			char hostname[100];
			gf_sk_get_host_name((char*)hostname);
			return JS_NewString(ctx, hostname);
		}
#endif
		break;
	case JS_SYS_LAST_WORK_DIR:
		res = gf_opts_get_key("core", "last-dir");
#ifdef WIN32
		if (!res) res = getenv("HOMEPATH");
		if (!res) res = "C:\\";
#elif defined(GPAC_CONFIG_DARWIN)
		if (!res) res = getenv("HOME");
		if (!res) res = "/Users";
#elif defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS)
		if (!res) res = (char *) gf_opts_get_key("core", "docs-dir");
#else
		if (!res) res = getenv("HOME");
		if (!res) res = "/home/";
#endif
		return JS_NewString(ctx, res);

	case JS_SYS_TEST_MODE:
		return JS_NewBool(ctx, gf_sys_is_test_mode() );
	case JS_SYS_OLD_ARCH:
		return JS_NewBool(ctx, gf_sys_old_arch_compat() );
	case JS_SYS_LOG_COLOR:
#ifdef GPAC_DISABLE_LOG
		return JS_NewBool(ctx, GF_FALSE );
#else
		return JS_NewBool(ctx, gf_log_use_color() );
#endif
	case JS_SYS_QUIET:
		return JS_NewBool(ctx, gf_sys_is_quiet() );
	case JS_SYS_USERNAME:
		{
			char username[1024];
			gf_get_user_name((char*)username);
			return JS_NewString(ctx, username);
		}
	case JS_SYS_TIMEZONE:
		return JS_NewInt32(ctx, gf_net_get_timezone());

	case JS_SYS_FILES_OPEN:
		return JS_NewInt32(ctx, gf_file_handles_count());

	case JS_SYS_CACHE_DIR:
		res = gf_get_default_cache_directory();
		if (res) return JS_NewString(ctx, res);
		return JS_NULL;

	case JS_SYS_SHARED_DIR:
	{
		char szDir[GF_MAX_PATH];
		if (! gf_opts_default_shared_directory(szDir)) return JS_NULL;
		return JS_NewString(ctx, szDir);
	}

	case JS_SYS_VERSION:
		return JS_NewString(ctx, GPAC_VERSION );
	case JS_SYS_VERSION_FULL:
		return JS_NewString(ctx, gf_gpac_version() );
	case JS_SYS_COPYRIGHT:
		return JS_NewString(ctx, gf_gpac_copyright() );

	case JS_SYS_V_MAJOR:
		return JS_NewInt32(ctx, GPAC_VERSION_MAJOR );
	case JS_SYS_V_MINOR:
		return JS_NewInt32(ctx, GPAC_VERSION_MINOR );
	case JS_SYS_V_MICRO:
		return JS_NewInt32(ctx, GPAC_VERSION_MICRO );
	}

	return JS_UNDEFINED;
}

static void js_reset_logs(JSContext *ctx)
{
	if (ctx != js_rt->log_ctx) return;

	gf_js_lock(ctx, GF_TRUE);
	js_rt->js_log_buf_size = 0;
	//WARNING - libc mem, not gf_alloc
	if (js_rt->js_log_buf) free(js_rt->js_log_buf);
	js_rt->js_log_buf = NULL;

	if (js_rt->js_orig_logs) {
		gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
		gf_free(js_rt->js_orig_logs);
		js_rt->js_orig_logs = NULL;
	}

	if (!js_rt->log_ctx) {
		gf_js_lock(ctx, GF_FALSE);
		return;
	}
	JS_FreeValue(js_rt->log_ctx, js_rt->log_fun);
	js_rt->log_fun = JS_UNDEFINED;
	JS_FreeValue(js_rt->log_ctx, js_rt->log_obj);
	js_rt->log_obj = JS_UNDEFINED;
	gf_log_set_callback(NULL, js_rt->prev_log_cbk);
	js_rt->log_ctx = NULL;
	gf_js_lock(ctx, GF_FALSE);
}

extern GF_Mutex *logs_mx;
static void js_log_cbk(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist)
{
	JSContext *ctx = js_rt->log_ctx;
	JSValue args[3];
	//in case mutex@debug is set, unlock logs mutex before locking JS runtime
	//not doing so could create a deadlock while unlocking logs_mx or the JS runtime
	gf_mx_v(logs_mx);
	gf_js_lock(ctx, GF_TRUE);

	args[0] = JS_NewString(ctx, gf_log_tool_name(log_tool) );
	args[1] = JS_NewInt32(ctx, log_level);

	va_list vlist_tmp;
	va_copy(vlist_tmp, vlist);
	u32 len = vsnprintf(NULL, 0, fmt, vlist_tmp);
	va_end(vlist_tmp);
	if (js_rt->js_log_buf_size < len+2) {
		js_rt->js_log_buf_size = len+2;
		//WARNING - libc mem, not gf_alloc
		js_rt->js_log_buf = realloc(js_rt->js_log_buf, js_rt->js_log_buf_size);
	}
	vsprintf(js_rt->js_log_buf, fmt, vlist);
	args[2] = JS_NewString(ctx, js_rt->js_log_buf);

	JSValue res = JS_Call(ctx, js_rt->log_fun, js_rt->log_obj, 3, args);
	JS_FreeValue(ctx, args[0]);
	JS_FreeValue(ctx, args[1]);
	JS_FreeValue(ctx, args[2]);
	JS_FreeValue(ctx, res);

	gf_js_lock(ctx, GF_FALSE);
	//and relock
	gf_mx_p(logs_mx);
}

static JSValue js_sys_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
	const char *prop_val;
	switch (magic) {
	case JS_SYS_LAST_WORK_DIR:
		if (!JS_IsString(value)) return GF_JS_EXCEPTION(ctx);
		prop_val = JS_ToCString(ctx, value);
		gf_opts_set_key("core", "last-dir", prop_val);
		JS_FreeCString(ctx, prop_val);
		break;
	case JS_SYS_RMT_ON_NEW_CLIENT:

		if (JS_IsUndefined(value) || JS_IsNull(value)) {

			JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task();
			if (task && task->type == RMT_CALLBACK_JS) {
				gf_rmt_set_on_new_client_cbk(NULL, NULL);
				JS_FreeValue(ctx, task->fun);
				JS_FreeValue(ctx, task->_obj);
				gf_free(task);
			}

		}

		if (JS_IsFunction(ctx, value)) {

			JS_Sys_Task *task;
			GF_SAFEALLOC(task, JS_Sys_Task);
			if (!task) return GF_JS_EXCEPTION(ctx);

			task->type = RMT_CALLBACK_JS;
			task->ctx = ctx;
			task->fun = JS_DupValue(ctx, value);
			task->_obj = JS_DupValue(ctx, this_val);

			gf_rmt_set_on_new_client_cbk(task, js_sys_rmt_on_new_client);
		}
		break;

	case JS_SYS_ON_LOG:
		JS_FreeValue(ctx, js_rt->log_fun);
		js_rt->log_fun = JS_UNDEFINED;
		JS_FreeValue(ctx, js_rt->log_obj);
		js_rt->log_obj = JS_UNDEFINED;
		js_rt->log_ctx = NULL;
		is_js_log = GF_FALSE;

		if (JS_IsUndefined(value) || JS_IsNull(value)) {
			if (js_rt->js_orig_logs) {
				gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
				gf_free(js_rt->js_orig_logs);
				js_rt->js_orig_logs = NULL;
			}
			gf_log_set_callback(NULL, js_rt->prev_log_cbk);
		} else if (JS_IsFunction(ctx, value)) {
			js_rt->log_fun = JS_DupValue(ctx, value);
			js_rt->log_obj = JS_DupValue(ctx, this_val);
			js_rt->prev_user_log_cbk = user_log_cbk;
			js_rt->prev_log_cbk = gf_log_set_callback(ctx, js_log_cbk);
			js_rt->log_ctx = ctx;
			is_js_log = GF_TRUE;
		}
		break;
	}
	return JS_UNDEFINED;
}

static JSValue js_sys_set_arg_used(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 idx;
	Bool used;
	if (argc < 2) return GF_JS_EXCEPTION(ctx);

	if (JS_ToInt32(ctx, &idx, argv[0]))
		return GF_JS_EXCEPTION(ctx);
	used = JS_ToBool(ctx, argv[1]);

	gf_sys_mark_arg_used(idx, used);
	return JS_UNDEFINED;
}


typedef struct
{
	JSContext *c;
	JSValue array;
	Bool is_dir;
	u32 idx;
} enum_dir_cbk;

static Bool js_enum_dir_fct(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info)
{
	u32 i, len;
	char *sep;
	JSValue s;
	JSValue obj;
	enum_dir_cbk *cbk = (enum_dir_cbk*)cbck;

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

	obj = JS_NewObject(cbk->c);
	JS_SetPropertyStr(cbk->c, obj, "name", JS_NewString(cbk->c, file_name) );

	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_NewString(cbk->c, file_path);
	} else {
		s = JS_NewString(cbk->c, file_path);
	}
	JS_SetPropertyStr(cbk->c, obj, "path", s);

	JS_SetPropertyStr(cbk->c, obj, "directory", JS_NewBool(cbk->c, cbk->is_dir) );
	JS_SetPropertyStr(cbk->c, obj, "drive", JS_NewBool(cbk->c, file_info->drive) );
	JS_SetPropertyStr(cbk->c, obj, "hidden", JS_NewBool(cbk->c, file_info->hidden) );
	JS_SetPropertyStr(cbk->c, obj, "system", JS_NewBool(cbk->c, file_info->system) );
	JS_SetPropertyStr(cbk->c, obj, "size", JS_NewInt64(cbk->c, file_info->size) );
	JS_SetPropertyStr(cbk->c, obj, "last_modified", JS_NewInt64(cbk->c, file_info->last_modified) );

	JS_SetPropertyUint32(cbk->c, cbk->array, cbk->idx, obj);
	cbk->idx++;
	return 0;
}

static JSValue js_sys_enum_directory(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_Err err;
	enum_dir_cbk cbk;
	char *url = NULL;
	const char *dir = NULL;
	const char *filter = NULL;
	Bool dir_only = 0;
	Bool browse_root = 0;

	if ((argc >= 1) && JS_IsString(argv[0])) {
		dir = JS_ToCString(ctx, argv[0]);
		if (!strcmp(dir, "/")) browse_root = 1;
	}
	if ((argc >= 2) && JS_IsString(argv[1])) {
		filter = JS_ToCString(ctx, argv[1]);
		if (!strcmp(filter, "dir")) {
			dir_only = 1;
			filter = NULL;
		} else if (!strlen(filter)) {
			JS_FreeCString(ctx, filter);
			filter=NULL;
		}
	}
	if ((argc >= 3) && JS_IsBool(argv[2])) {
		if (JS_ToBool(ctx, argv[2])) {
			url = gf_url_concatenate(dir, "..");
			if (dir && ( !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 = ctx;
		cbk.idx = 0;
		cbk.array = JS_NewArray(ctx);
		if (JS_IsException(cbk.array)) return cbk.array;
		cbk.is_dir = 1;
		gf_enum_directory("/", 1, js_enum_dir_fct, &cbk, NULL);
		if (url) gf_free(url);
		JS_FreeCString(ctx, dir);
		JS_FreeCString(ctx, filter);
		return cbk.array;
	}

	cbk.c = ctx;
	cbk.array = JS_NewArray(ctx);
	if (JS_IsException(cbk.array)) return cbk.array;
	cbk.is_dir = 1;
	cbk.idx = 0;

#if 0
	compositor = scenejs_get_compositor(ctx, this_val);

	/*concatenate with service url*/
	char *an_url = gf_url_concatenate(compositor->root_scene->root_od->scene_ns->url, url ? url : dir);
	if (an_url) {
		gf_free(url);
		url = an_url;
	}
#endif

	err = gf_enum_directory(url ? url : dir, 1, js_enum_dir_fct, &cbk, filter);
	if (err) return GF_JS_EXCEPTION(ctx);

	if (!dir_only) {
		cbk.is_dir = 0;
		err = gf_enum_directory(url ? url : dir, 0, js_enum_dir_fct, &cbk, filter);
		if (err) return GF_JS_EXCEPTION(ctx);
	}

	if (url) gf_free(url);
	JS_FreeCString(ctx, dir);
	JS_FreeCString(ctx, filter);
	return cbk.array;
}


static JSValue js_sys_error_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_Err e;
	if (argc < 1) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, (int32_t *) &e, argv[0]))
		return GF_JS_EXCEPTION(ctx);
	return JS_NewString(ctx, gf_error_to_string(e) );
}

static JSValue js_sys_prompt_input(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char in_char[2];

	if (!gf_prompt_has_input())
		return JS_NULL;
	in_char[0] = gf_prompt_get_char();
	in_char[1] = 0;
	return JS_NewString(ctx, in_char);
}

static JSValue js_sys_prompt_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char input[4096], *read;
	u32 len;
//#ifdef GPAC_ENABLE_COVERAGE
	if (argc) {
		return JS_NewString(ctx, "Coverage OK");
	}
//#endif // GPAC_ENABLE_COVERAGE

	read = fgets(input, 4095, stdin);
	if (!read) return JS_NULL;
	input[4095]=0;
	len = (u32) strlen(input);
	if (len && (input[len-1] == '\n')) {
		input[len-1] = 0;
		len--;
	}
	if (!len) return JS_NULL;
	return JS_NewString(ctx, input);
}

static JSValue js_sys_prompt_echo_off(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	Bool echo_off;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	echo_off = JS_ToBool(ctx, argv[0]);
	if (argc<2)
		gf_prompt_set_echo_off(echo_off);
	return JS_UNDEFINED;
}
static JSValue js_sys_prompt_code(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s32 code;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &code, argv[0])) return GF_JS_EXCEPTION(ctx);

	gf_sys_set_console_code(stderr, code);
	return JS_UNDEFINED;
}
static JSValue js_sys_prompt_size(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 w, h;
	JSValue res;
	GF_Err e = gf_prompt_get_size(&w, &h);
	if (e) return JS_NULL;
	res = JS_NewObject(ctx);
	JS_SetPropertyStr(ctx, res, "w", JS_NewInt32(ctx, w));
	JS_SetPropertyStr(ctx, res, "h", JS_NewInt32(ctx, h));
	return res;
}

const char *gf_dom_get_friendly_name(u32 key_identifier);

static JSValue js_sys_keyname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifndef GPAC_DISABLE_SVG
	u32 code;
	if (JS_ToInt32(ctx, &code, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewString(ctx, gf_dom_get_friendly_name(code));
#else
	return js_throw_err(ctx, GF_NOT_SUPPORTED);
#endif
}

GF_EventType gf_dom_event_type_by_name(const char *name);

static JSValue js_sys_evt_by_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifndef GPAC_DISABLE_SVG
	if (!argc) return GF_JS_EXCEPTION(ctx);
	const char *name = JS_ToCString(ctx, argv[0]);
	if (!name) return GF_JS_EXCEPTION(ctx);
	JSValue res = JS_NewInt32(ctx, gf_dom_event_type_by_name(name));
	JS_FreeCString(ctx, name);
	return res;
#else
	return js_throw_err(ctx, GF_NOT_SUPPORTED);
#endif

}


static JSValue js_sys_gc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	JS_RunGC(JS_GetRuntime(ctx));
	return JS_UNDEFINED;
}

static JSValue js_sys_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return JS_NewInt32(ctx, gf_sys_clock() );
}
static JSValue js_sys_clock_high_res(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return JS_NewInt64(ctx, gf_sys_clock_high_res() );
}
static JSValue js_sys_4cc_to_str(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u64 val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewString(ctx, gf_4cc_to_str((u32) val) );
}

static JSValue js_sys_rand_init(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	Bool reset=GF_FALSE;
	if (argc) reset = JS_ToBool(ctx, argv[0]);
	gf_rand_init(reset);
	return JS_UNDEFINED;
}
static JSValue js_sys_rand(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return JS_NewInt32(ctx, gf_rand());
}
static JSValue js_sys_rand64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u64 lrand = (u64) gf_rand();
	lrand<<=32;
	lrand |= gf_rand();
	return JS_NewInt64(ctx, lrand);
}

static JSValue js_sys_getenv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *str, *val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	str = JS_ToCString(ctx, argv[0]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	val = getenv(str);
	JS_FreeCString(ctx, str);
	return val ? JS_NewString(ctx, val) : JS_NULL;
}

static JSValue js_sys_get_utc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 y, mo, d, h, m, s;
	if (!argc) return JS_NewInt64(ctx, gf_net_get_utc() );
	if (argc==1) {
		u64 time;
		const char *date = JS_ToCString(ctx, argv[0]);
		if (!date) return GF_JS_EXCEPTION(ctx);
		time = gf_net_parse_date(date);
		JS_FreeCString(ctx, date);
		return JS_NewInt64(ctx, time);
	}

	if (argc != 6) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &y, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &mo, argv[1])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &d, argv[2])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &h, argv[3])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &m, argv[4])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &s, argv[5])) return GF_JS_EXCEPTION(ctx);

	return JS_NewInt64(ctx, gf_net_get_utc_ts(y, mo, d, h, m, s) );
}

static JSValue js_sys_get_ntp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 sec, frac;
	gf_net_get_ntp(&sec, &frac);
	JSValue ret = JS_NewObject(ctx);
	if (JS_IsException(ret)) return ret;
	JS_SetPropertyStr(ctx, ret, "n", JS_NewInt64(ctx, sec));
	JS_SetPropertyStr(ctx, ret, "d", JS_NewInt64(ctx, frac));
	return ret;
}

static JSValue js_sys_ntp_shift(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	JSValue v;
	u64 sec, frac, shift;
	s64 frac_usec;
	if ((argc<2) || !JS_IsObject(argv[0]))
		return GF_JS_EXCEPTION(ctx);

	v = JS_GetPropertyStr(ctx, argv[0], "n");
	if (JS_IsNull(v)) return GF_JS_EXCEPTION(ctx);
	JS_ToInt64(ctx, &sec, v);
	JS_FreeValue(ctx, v);

	v = JS_GetPropertyStr(ctx, argv[0], "d");
	if (JS_IsNull(v)) return GF_JS_EXCEPTION(ctx);
	JS_ToInt64(ctx, &frac, v);
	JS_FreeValue(ctx, v);

	JS_ToInt64(ctx, &shift, argv[1]);
	frac_usec = (s64) (frac * 1000000) / 0xFFFFFFFFULL;
	frac_usec += shift;
	while (frac_usec<0) {
		frac_usec += 1000000;
		sec -= 1;
	}
	while (frac_usec>1000000) {
		frac_usec -= 1000000;
		sec += 1;
	}
	frac = (frac_usec * 0xFFFFFFFFULL) / 1000000;
	v = JS_NewObject(ctx);
	if (JS_IsException(v)) return v;
	JS_SetPropertyStr(ctx, v, "n", JS_NewInt64(ctx, sec));
	JS_SetPropertyStr(ctx, v, "d", JS_NewInt64(ctx, frac));
	return v;
}


static JSValue js_sys_sleep(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 sleep_for=0;
	if (argc==1) {
		if (JS_ToInt32(ctx, &sleep_for, argv[0]))
			return GF_JS_EXCEPTION(ctx);
	}
	gf_sleep(sleep_for);
	return JS_UNDEFINED;
}


static JSValue js_sys_exit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 rval=0;
	if (argc==2) {
		return JS_UNDEFINED;
	}
	if (argc==1) {
		if (JS_ToInt32(ctx, &rval, argv[0]))
			return GF_JS_EXCEPTION(ctx);
	}
	exit(rval);
	return JS_UNDEFINED;
}
static JSValue js_sys_crc32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const u8 *data;
	size_t data_size;

	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_IsString(argv[0])) {
		u32 crc=0;
		const char *str = JS_ToCString(ctx, argv[0]);
		if (str) {
			crc = gf_crc_32(str, (u32) strlen(str) );
			JS_FreeCString(ctx, str);
		}
		return JS_NewInt32(ctx, crc );
	} else {
		data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
		if (!data) return GF_JS_EXCEPTION(ctx);
		return JS_NewInt32(ctx, gf_crc_32(data, (u32) data_size) );
	}
	return GF_JS_EXCEPTION(ctx);
}

static JSValue js_sys_sha1(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u8 csum[GF_SHA1_DIGEST_SIZE];
	const u8 *data;
	size_t data_size;

	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_IsString(argv[0])) {
		const char *filename = JS_ToCString(ctx, argv[0]);
		if (!filename) return GF_JS_EXCEPTION(ctx);
		gf_sha1_file(filename, csum);
		JS_FreeCString(ctx, filename);
		return JS_NewArrayBufferCopy(ctx, csum, GF_SHA1_DIGEST_SIZE);
	} else if (JS_IsObject(argv[0])) {
		data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
		if (!data) return GF_JS_EXCEPTION(ctx);

		gf_sha1_csum((u8 *) data, (u32) data_size, csum);
		return JS_NewArrayBufferCopy(ctx, csum, GF_SHA1_DIGEST_SIZE);
	}
	return GF_JS_EXCEPTION(ctx);
}


static JSValue js_sys_file_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *filename;
	u8 *data;
	u32 data_size;
	Bool as_utf8 = GF_FALSE;
	GF_Err e;
	JSValue res;
	if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	filename = JS_ToCString(ctx, argv[0]);
	if (!filename) return GF_JS_EXCEPTION(ctx);
	if ((argc>1) && JS_ToBool(ctx, argv[1]))
		as_utf8 = GF_TRUE;

	e = gf_file_load_data(filename, &data, &data_size);
	if (e) {
		res = js_throw_err_msg(ctx, e, "Failed to load file %s", filename);
	} else if (as_utf8) {
		if (!data || gf_utf8_is_legal(data, data_size)) {
			res = JS_NewString(ctx, data ? (const char *) data : "");
		} else {
			res = js_throw_err_msg(ctx, GF_NON_COMPLIANT_BITSTREAM, "Invalid UTF8 data in file %s", filename);
		}
		if (data)
			gf_free(data);
	} else {
		res = JS_NewArrayBuffer(ctx, data, data_size, js_gpac_free, NULL, 0);
	}
	JS_FreeCString(ctx, filename);
	return res;
}

/* load and evaluate a file */
static JSValue js_sys_load_script(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *filename;
	u8 *data=NULL;
	u32 data_size;
	JSValue res;
	GF_Err e;
	char *full_url = NULL;

	if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	filename = JS_ToCString(ctx, argv[0]);
	if (!filename) return GF_JS_EXCEPTION(ctx);

	if ((argc>1) && JS_ToBool(ctx, argv[1]) ) {
		const char *par_url = jsf_get_script_filename(ctx);
		full_url = gf_url_concatenate(par_url, filename);
		JS_FreeCString(ctx, filename);
		filename = full_url;
	}

	e = gf_file_load_data(filename, &data, &data_size);
	if (e) {
		res = js_throw_err_msg(ctx, e, "Failed to load file %s", filename);
	} else if (data) {
		if (!gf_utf8_is_legal(data, data_size)) {
			res = js_throw_err_msg(ctx, e, "Script file %s is not UTF-8", filename);
		} else {
			res = JS_Eval(ctx, (char *)data, data_size, filename, JS_EVAL_TYPE_GLOBAL);
		}
	} else {
		res = JS_UNDEFINED;
	}
	if (data) gf_free(data);

	if (full_url)
		gf_free(full_url);
	else
		JS_FreeCString(ctx, filename);
	return res;
}


static JSValue js_sys_compress_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_decomp)
{
	const u8 *data;
	size_t data_size;
	Bool use_gz=GF_FALSE;
	u32 out_size=0;
	u8 *out_ptr = NULL;
	JSValue res;
	GF_Err e;
	if (!argc || !JS_IsObject(argv[0])) return GF_JS_EXCEPTION(ctx);
	data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
	if (!data) return GF_JS_EXCEPTION(ctx);

	if (argc>1) {
		use_gz = JS_ToBool(ctx, argv[1]);
	}

	if (is_decomp) {
		e = gf_gz_decompress_payload_ex((u8*) data, (u32) data_size, &out_ptr, &out_size, use_gz);
	} else {
		e = gf_gz_compress_payload_ex((u8 **)&data, (u32) data_size, &out_size, 0, GF_FALSE, &out_ptr, use_gz);
	}

	if (e) return js_throw_err(ctx, e);
	res = JS_NewArrayBuffer(ctx, out_ptr, out_size, js_gpac_free, NULL, 0);
	return res;
}
static JSValue js_sys_compress(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_compress_ex(ctx, this_val, argc, argv, GF_FALSE);
}
static JSValue js_sys_decompress(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_compress_ex(ctx, this_val, argc, argv, GF_TRUE);
}

static JSValue js_sys_url_cat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *parent;
	const char *src;
	char *url;
	JSValue res;
	if (argc<2) return GF_JS_EXCEPTION(ctx);
	parent = JS_ToCString(ctx, argv[0]);
	src = JS_ToCString(ctx, argv[1]);
	url = gf_url_concatenate(parent, src);
	if (url) {
		res = JS_NewString(ctx, url);
		gf_free(url);
	} else {
		res = JS_NewString(ctx, src);
	}
	JS_FreeCString(ctx, parent);
	JS_FreeCString(ctx, src);
	return res;
}


static JSValue js_sys_rect_union_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_inter)
{
	GF_Rect rc1, rc2;
	Double fval;
	JSValue v;
	int res;
	if (argc<2) return GF_JS_EXCEPTION(ctx);

#define GET_PROP(_obj, _n, _f)\
	_f = 0;\
	v = JS_GetPropertyStr(ctx, _obj, _n);\
	res = JS_ToFloat64(ctx, &fval, v);\
	if (!res) _f = FLT2FIX(fval);\
	JS_FreeValue(ctx, v);\

	GET_PROP(argv[0], "x", rc1.x)
	GET_PROP(argv[0], "y", rc1.y)
	GET_PROP(argv[0], "w", rc1.width)
	GET_PROP(argv[0], "h", rc1.height)
	GET_PROP(argv[1], "x", rc2.x)
	GET_PROP(argv[1], "y", rc2.y)
	GET_PROP(argv[1], "w", rc2.width)
	GET_PROP(argv[1], "h", rc2.height)

#undef GET_PROP

	if (is_inter) {
		gf_rect_intersect(&rc1, &rc2);
	} else {
		gf_rect_union(&rc1, &rc2);
	}
	v = JS_NewObject(ctx);
	JS_SetPropertyStr(ctx, v, "x", JS_NewFloat64(ctx, FIX2FLT(rc1.x) ));
	JS_SetPropertyStr(ctx, v, "y", JS_NewFloat64(ctx, FIX2FLT(rc1.y) ));
	JS_SetPropertyStr(ctx, v, "w", JS_NewFloat64(ctx, FIX2FLT(rc1.width) ));
	JS_SetPropertyStr(ctx, v, "h", JS_NewFloat64(ctx, FIX2FLT(rc1.height) ));
	return v;
}
static JSValue js_sys_rect_union(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_rect_union_ex(ctx, this_val, argc, argv, GF_FALSE);
}
static JSValue js_sys_rect_intersect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_rect_union_ex(ctx, this_val, argc, argv, GF_TRUE);
}

#include <gpac/xml.h>
#include <gpac/mpd.h>
static JSValue js_sys_mpd_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	if (argc<1) return GF_JS_EXCEPTION(ctx);
	size_t ab_size;
	const char *data = JS_GetArrayBuffer(ctx, &ab_size, argv[0]);
	if (!data || !gf_utf8_is_legal(data, (u32) ab_size)) {
		return JS_NULL;
	}
	char *str = gf_malloc(sizeof(char)*(ab_size+1));
	memcpy(str, data, ab_size);
	str[ab_size]=0;
	//HLS playlist
	if (strstr(str, "#EXTM3U")) {
		JSValue mpdo = JS_NewObject(ctx);
		Bool is_master = GF_FALSE;
		Bool is_live = GF_TRUE;
		JSValue reps = JS_NewArray(ctx);
		if (strstr(str, "#EXT-X-STREAM-INF")) {
			is_master = GF_TRUE;
			JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 1));
			//unused for HLS
			JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, 0) );
			//unknown until first variant is parsed ...
			JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, 0) );
			JS_SetPropertyStr(ctx, mpdo, "live", JS_FALSE);

			JSValue periods = JS_NewArray(ctx);
			JS_SetPropertyStr(ctx, mpdo, "periods", periods);
			JSValue po = JS_NewObject(ctx);
			JS_SetPropertyUint32(ctx, periods, 0, po);

			JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, 0) );
			JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, 0) );
			JS_SetPropertyStr(ctx, po, "ID", JS_NULL );

			JS_SetPropertyStr(ctx, po, "reps", reps);
		} else {
			JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx,2));
			JS_SetPropertyStr(ctx, mpdo, "segments", reps);
		}
		Double target_dur=0;
		Double cur_dur=0;
		u32 media_seq = 0;
		u32 vidx=0;
		u32 ASID = 1;
		u32 cur_bandwidth=0;
		char *cur = str;
		while (cur[0]) {
			while (cur[0] && strchr(" \n\t\r", cur[0]))
				cur++;

			char *sep = strchr(cur, '\n');
			if (cur[0] != '#') {
				if (sep) sep[0] = 0;
				JSValue var = JS_NewObject(ctx);
				JS_SetPropertyUint32(ctx, reps, vidx, var);
				if (is_master) {
					JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, cur) );
					JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(cur) ) );
					JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) );
					JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) );
					JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) );
					JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) );
					JS_SetPropertyStr(ctx, var, "template", JS_NULL );
					JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) );
					JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet
					JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, cur_bandwidth) );
					JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) );
				} else {
					JS_SetPropertyStr(ctx, var, "url", JS_NewString(ctx, cur) );
					JS_SetPropertyStr(ctx, var, "duration", JS_NewFloat64(ctx, cur_dur ? cur_dur : target_dur) );
				}
				vidx++;
				if (!sep) break;
				cur = sep+1;
				continue;
			}
			if (strstr(cur, "EXT-X-ENDLIST")) is_live = GF_FALSE;

			//for variant, extract target duration and segment duration
			if (!is_master) {
				if (sep) sep[0] = 0;
				char *tdur = strstr(cur, "EXT-X-TARGETDURATION:");
				if (tdur) target_dur = atof(tdur+21);

				char *mseq = strstr(cur, "EXT-X-MEDIA-SEQUENCE:");
				if (mseq) media_seq = atoi(mseq+21);

				char *extinf = strstr(cur, "EXT-INF:");
				if (extinf) {
					char *last_c = strchr(extinf, ',');
					if (last_c) last_c[0] = 0;
					cur_dur = atof(extinf+8);
					if (last_c) last_c[0] = ',';
				}

				if (!sep) break;
				if (sep) sep[0] = '\n';
				cur = sep+1;
				continue;
			}
			//for master, extract URI ad bandwidth
			if (sep) sep[0] = 0;

			char *bw = strstr(cur, "BANDWIDTH=");
			if (bw) {
				bw += 10;
				char *end = strchr(bw, ',');
				if (end) end[0] = 0;
				cur_bandwidth = atoi(bw);
				if (end) end[0] = ',';
			}

			char *uri = strstr(cur, "URI=\"");
			if (uri) {
				ASID++;
				uri += 5;
				char *end = strchr(uri, '\"');
				if (end) {
					end[0] = 0;
					JSValue var = JS_NewObject(ctx);
					JS_SetPropertyUint32(ctx, reps, vidx, var);
					vidx++;
					JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, uri) );
					JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(uri) ) );
					JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) );
					JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) );
					JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) );
					JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) );
					JS_SetPropertyStr(ctx, var, "template", JS_NULL );
					JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) );
					JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet
					JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, 0) );
					JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) );
					end[0] = '\n';
				}
			}
			//do we need to extract codec info ?
			if (!sep) break;
			sep[0] = '\n';
			cur = sep+1;
		}
		JS_SetPropertyStr(ctx, mpdo, "live", is_live ? JS_TRUE : JS_FALSE);
		if (media_seq) {
			JS_SetPropertyStr(ctx, mpdo, "seq_start", JS_NewInt32(ctx, media_seq) );
			JS_SetPropertyStr(ctx, mpdo, "live_seg_num", JS_NewInt32(ctx, media_seq+vidx) );
		}
		gf_free(str);
		return mpdo;
	} else if (!strstr(str, "<MPD ")) {
		gf_free(str);
		return JS_NULL;
	}
	GF_DOMParser *parser = gf_xml_dom_new();
	GF_Err e = gf_xml_dom_parse_string(parser, str);
	if (e) {
		gf_xml_dom_del(parser);
		gf_free(str);
		return JS_NULL;
	}
	GF_MPD *mpd = gf_mpd_new();
	e = gf_mpd_init_from_dom(gf_xml_dom_get_root(parser), mpd, NULL);
	gf_free(str);
	gf_xml_dom_del(parser);
	if (e) {
		gf_mpd_del(mpd);
		return JS_NULL;
	}

	u32 i, count = gf_list_count(mpd->periods);
	JSValue mpdo = JS_NewObject(ctx);
	JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 0));
	JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, mpd->availabilityStartTime) );
	JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, mpd->time_shift_buffer_depth) );
	JS_SetPropertyStr(ctx, mpdo, "live", JS_NewBool(ctx, mpd->type==GF_MPD_TYPE_DYNAMIC) );

	u64 now = gf_net_get_utc();
	JS_SetPropertyStr(ctx, mpdo, "live_utc", JS_NewInt64(ctx, now) );
	JSValue periods = JS_NewArray(ctx);
	JS_SetPropertyStr(ctx, mpdo, "periods", periods);
	u32 period_idx=0;
	u64 mpd_time = mpd->availabilityStartTime;
	for (i=0; i<count; i++) {
		u32 j, nb_sets, cur_rep=0;
		GF_MPD_Period *p = gf_list_get(mpd->periods, i);
		//closed period, don't add if end less than now
		if (mpd->type && p->duration) {
			//add 10s margin
			if (mpd_time + p->start + p->duration + 10000 < now) continue;
			//we could also supress period announcements ?
		}

		JSValue po = JS_NewObject(ctx);
		JS_SetPropertyUint32(ctx, periods, period_idx, po);
		period_idx++;

		JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, p->start) );
		JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, p->duration) );
		JS_SetPropertyStr(ctx, po, "ID", p->ID ? JS_NewString(ctx, p->ID) : JS_NULL );
		JSValue reps = JS_NewArray(ctx);
		JS_SetPropertyStr(ctx, po, "reps", reps );

		u32 mpd_timescale = 0;
		u64 seg_duration = 0;
		GF_MPD_SegmentTimeline *mpd_stl = NULL;
		if (p->segment_template) {
			if (p->segment_template->duration) seg_duration = p->segment_template->duration;
			if (p->segment_template->timescale) mpd_timescale = p->segment_template->timescale;
			if (p->segment_template->segment_timeline) mpd_stl = p->segment_template->segment_timeline;
		}

		nb_sets = gf_list_count(p->adaptation_sets);
		for (j=0; j<nb_sets; j++) {
			u32 k, nb_reps;
			GF_MPD_AdaptationSet *set = gf_list_get(p->adaptation_sets, j);

			if (set->segment_template) {
				if (set->segment_template->duration) seg_duration = set->segment_template->duration;
				if (set->segment_template->timescale) mpd_timescale = set->segment_template->timescale;
				if (set->segment_template->segment_timeline) mpd_stl = set->segment_template->segment_timeline;
			}

			nb_reps = gf_list_count(set->representations);
			for (k=0; k<nb_reps; k++) {
				u64 start_range, end_range, segdur_ms;
				u32 bidx;
				GF_MPD_Representation *rep = gf_list_get(set->representations, k);
				char *seg_url = NULL;

				if (rep->segment_template) {
					if (rep->segment_template->duration) seg_duration = rep->segment_template->duration;
					if (rep->segment_template->timescale) mpd_timescale = rep->segment_template->timescale;
					if (rep->segment_template->segment_timeline) mpd_stl = rep->segment_template->segment_timeline;
				}
				JSValue repo = JS_NewObject(ctx);
				JS_SetPropertyUint32(ctx, reps, cur_rep, repo);
				cur_rep++;

				JS_SetPropertyStr(ctx, repo, "ID", rep->id ? JS_NewString(ctx, rep->id) : JS_NULL);
				JS_SetPropertyStr(ctx, repo, "AS_ID", JS_NewInt32(ctx, set->id) );
				JS_SetPropertyStr(ctx, repo, "as_idx", JS_NewInt32(ctx, j+1) );
				JS_SetPropertyStr(ctx, repo, "xlink", JS_NULL);
				JSValue urls_o = JS_NewArray(ctx);
				JS_SetPropertyStr(ctx, repo, "URLs", urls_o );
				u32 el_idx, js_idx=0;
				for (el_idx=0;el_idx<4; el_idx++) {
					GF_List *urls = rep->base_URLs;
					if (el_idx==0) urls = mpd->base_URLs;
					else if (el_idx==1) urls = p->base_URLs;
					else if (el_idx==2) urls = set->base_URLs;

					for (bidx=0; bidx < gf_list_count(urls); bidx++) {
						GF_MPD_BaseURL *burl = gf_list_get(urls, bidx);
						if (burl->URL) {
							JS_SetPropertyUint32(ctx, urls_o, js_idx, JS_NewString(ctx, burl->URL) );
							js_idx++;
						}
					}
				}
				JS_SetPropertyStr(ctx, repo, "timescale", JS_NewInt32(ctx, mpd_timescale ? mpd_timescale : 1) );
				JS_SetPropertyStr(ctx, repo, "duration", JS_NewInt64(ctx, seg_duration) );
				JS_SetPropertyStr(ctx, repo, "bandwidth", JS_NewInt32(ctx, rep->bandwidth) );

				gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0);

				JS_SetPropertyStr(ctx, repo, "template", seg_url ? JS_NewString(ctx, seg_url) : JS_NULL );
				if (seg_url) gf_free(seg_url);

				u64 seg_idx = 0;
				if (seg_duration) {
					seg_idx = now - p->start - mpd->availabilityStartTime;
					seg_idx /= 1000;
					if (mpd_timescale) seg_idx *= mpd_timescale;
					seg_idx /= seg_duration;
				}
				JS_SetPropertyStr(ctx, repo, "live_seg_num", JS_NewInt64(ctx, seg_idx) );

				JSValue segs = JS_NewArray(ctx);
				JS_SetPropertyStr(ctx, repo, "segments", segs );
				if (mpd_stl) {
					u32 sidx, re, cur_seg=0;
					//u64 start = mpd->availabilityStartTime + p->start;
					for (sidx=0; sidx<gf_list_count(mpd_stl->entries); sidx++) {
						GF_MPD_SegmentTimelineEntry *e = gf_list_get(mpd_stl->entries, sidx);
						//if (e->start_time) start = e->start_time;
						for (re=0; re<e->repeat_count+1; re++) {
							JSValue sego = JS_NewObject(ctx);
							gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA, cur_seg, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0);

							JS_SetPropertyStr(ctx, sego, "url", JS_NewString(ctx, seg_url) );
							JS_SetPropertyStr(ctx, sego, "duration", JS_NewInt32(ctx, (u32) segdur_ms) );

							JS_SetPropertyUint32(ctx, segs, cur_seg, sego );
							cur_seg++;
							gf_free(seg_url);
						}
					}
				}
			}
		}
	}
	gf_mpd_del(mpd);
	return mpdo;
}


enum
{
	OPT_RMDIR,
	OPT_MKDIR,
	OPT_DIREXISTS,
	OPT_DIRCLEAN,
	OPT_FILEBASENAME,
	OPT_FILEEXT,
	OPT_FILEDEL,
	OPT_FILEMODTIME,
	OPT_FILEEXISTS,
	OPT_FILEMOVE,
};

static JSValue js_sys_file_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 file_opt)
{
	const char *dirname, *newfile;
	char *ext;
	GF_Err e;
	JSValue res;
	if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	dirname = JS_ToCString(ctx, argv[0]);
	if (!dirname) return GF_JS_EXCEPTION(ctx);

	res = JS_UNDEFINED;
	switch (file_opt) {
	case OPT_RMDIR:
		e = gf_rmdir(dirname);
		if (e) res = js_throw_err_msg(ctx, e, "Failed to remove dir %s", dirname);
		break;
	case OPT_MKDIR:
		e = gf_mkdir(dirname);
		if (e) res = js_throw_err_msg(ctx, e, "Failed to create dir %s", dirname);
		break;
	case OPT_DIREXISTS:
		res = JS_NewBool(ctx, gf_dir_exists(dirname) );
		break;
	case OPT_DIRCLEAN:
		e = gf_dir_cleanup(dirname);
		if (e) res = js_throw_err_msg(ctx, e, "Failed to clean dir %s", dirname);
		break;
	case OPT_FILEBASENAME:
		res = JS_NewString(ctx, gf_file_basename(dirname) );
		break;
	case OPT_FILEEXT:
		ext = gf_file_ext_start(dirname);
		res = ext ? JS_NewString(ctx, ext) : JS_NULL;
		break;
	case OPT_FILEDEL:
		e = gf_file_delete(dirname);
		if (e) res = js_throw_err_msg(ctx, e, "Failed to delete file %s", dirname);
		break;
	case OPT_FILEMODTIME:
		res = JS_NewInt64(ctx, gf_file_modification_time(dirname) );
		break;
	case OPT_FILEEXISTS:
		res = JS_NewBool(ctx, gf_file_exists(dirname) );
		break;
	case OPT_FILEMOVE:
		newfile = (argc<2) ? NULL : JS_ToCString(ctx, argv[1]);
		if (!newfile) res = js_throw_err_msg(ctx, GF_BAD_PARAM, "Missing new file name");
		else {
			e = gf_file_move(dirname, newfile);
			if (e) res = js_throw_err_msg(ctx, e, "Failed to move file %s to %s", dirname, newfile);
			JS_FreeCString(ctx, newfile);
		}
		break;
	}

	JS_FreeCString(ctx, dirname);
	return res;
}

static JSValue js_sys_rmdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_RMDIR);
}
static JSValue js_sys_mkdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_MKDIR);
}
static JSValue js_sys_dir_exists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_DIREXISTS);
}
static JSValue js_sys_dir_clean(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_DIRCLEAN);
}
static JSValue js_sys_basename(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEBASENAME);
}
static JSValue js_sys_file_ext(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEEXT);
}
static JSValue js_sys_file_exists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEEXISTS);
}
static JSValue js_sys_file_del(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEDEL);
}
static JSValue js_sys_file_modtime(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEMODTIME);
}
static JSValue js_sys_file_move(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEMOVE);
}


static JSValue js_sys_get_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *sec, *key, *val;
	JSValue res;
	if (argc!=2) return GF_JS_EXCEPTION(ctx);
	sec = JS_ToCString(ctx, argv[0]);
	if (!sec) return GF_JS_EXCEPTION(ctx);
	key = JS_ToCString(ctx, argv[1]);
	if (!key) {
		JS_FreeCString(ctx, sec);
		return GF_JS_EXCEPTION(ctx);
	}
	val = gf_opts_get_key(sec, key);
	res = val ? JS_NewString(ctx, val) : JS_NULL;
	JS_FreeCString(ctx, sec);
	JS_FreeCString(ctx, key);
	return res;
}

static JSValue js_sys_set_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *sec, *key, *val;
	if (argc<2) return GF_JS_EXCEPTION(ctx);
	sec = JS_ToCString(ctx, argv[0]);
	if (!sec) return GF_JS_EXCEPTION(ctx);
	key = JS_ToCString(ctx, argv[1]);
	if (!key) {
		JS_FreeCString(ctx, sec);
		return GF_JS_EXCEPTION(ctx);
	}
	val = NULL;
	if (argc>2)
		val = JS_ToCString(ctx, argv[2]);

	gf_opts_set_key(sec, key, val);

	JS_FreeCString(ctx, sec);
	JS_FreeCString(ctx, key);
	if (val)
		JS_FreeCString(ctx, val);
	return JS_UNDEFINED;
}
static JSValue js_sys_discard_opts(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	gf_opts_discard_changes();
	return JS_UNDEFINED;
}

static JSValue js_sys_basecode_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_dec, Bool is_16)
{
	u32 out_size=0;
	u8 *out_ptr = NULL;
	JSValue res;
	GF_Err e;
	if (!argc) return GF_JS_EXCEPTION(ctx);

	if (is_dec) {
		u32 len;
		const char *str = JS_ToCString(ctx, argv[0]);
		if (!str) return GF_JS_EXCEPTION(ctx);
		len = (u32) strlen(str);
		out_ptr = gf_malloc(sizeof(u8) * len);
		if (!out_ptr) {
			e = GF_OUT_OF_MEM;
		} else if (is_16) {
			out_size = gf_base16_decode((u8*) str, (u32) len, out_ptr, len);
			e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
		} else {
			out_size = gf_base64_decode((u8*) str, (u32) len, out_ptr, len);
			e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
		}
		JS_FreeCString(ctx, str);
	} else {
		const u8 *data;
		size_t data_size;
		data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
		if (!data) return GF_JS_EXCEPTION(ctx);
		u32 size64 = (u32) data_size * 2 + 3;
		out_ptr = gf_malloc(sizeof(char) * size64);
		if (!out_ptr) {
			e = GF_OUT_OF_MEM;
		} else if (is_16) {
			out_size = gf_base16_encode((u8*) data, (u32) data_size, out_ptr, size64);
			e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
		} else {
			out_size = gf_base64_encode((u8*) data, (u32) data_size, out_ptr, size64);
			e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
		}
	}

	if (e) return js_throw_err(ctx, e);
	if (is_dec) {
		res = JS_NewArrayBuffer(ctx, out_ptr, out_size, js_gpac_free, NULL, 0);
	} else {
		out_ptr[out_size] = 0;
		res = JS_NewString(ctx, out_ptr);
		gf_free(out_ptr);
	}
	return res;
}
static JSValue js_sys_base64enc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_FALSE, GF_FALSE);
}
static JSValue js_sys_base64dec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_TRUE, GF_FALSE);
}
static JSValue js_sys_base16enc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_FALSE, GF_TRUE);
}
static JSValue js_sys_base16dec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_TRUE, GF_TRUE);
}
static JSValue js_sys_ntohl(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_ntohl((u32) val));
}
static JSValue js_sys_ntohs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_ntohs((u16) val));
}
static JSValue js_sys_htonl(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_htonl((u32) val));
}
static JSValue js_sys_htons(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 val;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_htons((u16) val));
}

GF_Err jsf_ToProp_ex(GF_Filter *filter, JSContext *ctx, JSValue value, u32 p4cc, GF_PropertyValue *prop, u32 prop_type);

static JSValue js_pixfmt_size(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 w, h, osize;
	GF_PropertyValue prop;
	if (argc<3) return GF_JS_EXCEPTION(ctx);
	GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
	if (e) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &w, argv[1])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &h, argv[2])) return GF_JS_EXCEPTION(ctx);

	if (!gf_pixel_get_size_info(prop.value.uint, w, h, &osize, NULL, NULL, NULL, NULL))
		return js_throw_err_msg(ctx, GF_BAD_PARAM, "Unknown pixel format %d\n", prop.value.uint);
	return JS_NewInt32(ctx, osize);
}

static JSValue js_pixfmt_depth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_PropertyValue prop;
	if (argc<1) return GF_JS_EXCEPTION(ctx);
	GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
	if (e) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_pixel_is_wide_depth(prop.value.uint));
}
static JSValue js_pixfmt_transparent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_PropertyValue prop;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
	if (e) return GF_JS_EXCEPTION(ctx);

	return JS_NewBool(ctx, 	gf_pixel_fmt_is_transparent(prop.value.uint) );
}

static JSValue js_pixfmt_yuv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_PropertyValue prop;
	if (!argc) return GF_JS_EXCEPTION(ctx);
	GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
	if (e) return GF_JS_EXCEPTION(ctx);

	return JS_NewBool(ctx, gf_pixel_fmt_is_yuv(prop.value.uint) );
}

static JSValue js_pcmfmt_depth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	GF_PropertyValue prop;
	if (argc!=1) return GF_JS_EXCEPTION(ctx);
	GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PCMFMT);
	if (e) return GF_JS_EXCEPTION(ctx);
	return JS_NewInt32(ctx, gf_audio_fmt_bit_depth(prop.value.uint)/8 );
}

static JSValue js_sys_set_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *logs;
	Bool reset = GF_FALSE;
	if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	if (argc>1)
		reset = JS_ToBool(ctx, argv[1]);

	if (!js_rt->js_orig_logs)
		js_rt->js_orig_logs = gf_log_get_tools_levels();

	logs = JS_ToCString(ctx, argv[0]);
	if (!logs) {
		if (js_rt->js_orig_logs) {
			gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
			gf_free(js_rt->js_orig_logs);
			js_rt->js_orig_logs = NULL;
		} else {
			gf_log_set_tools_levels("all@warning", GF_TRUE);
		}
	} else {
		gf_log_set_tools_levels(logs, reset);
		JS_FreeCString(ctx, logs);
	}
	return JS_UNDEFINED;
}


static JSValue js_sys_get_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	if (argc && JS_ToBool(ctx, argv[0]) && js_rt->js_orig_logs) {
		return JS_NewString(ctx, js_rt->js_orig_logs);
	}
	char *logs = gf_log_get_tools_levels();
	JSValue res = JS_NewString(ctx, logs);
	gf_free(logs);
	return res;
}

#include <gpac/color.h>

static JSValue js_color_lerp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *str;
	u32 col1, col2;
	char szCol[12];
	Double interp, minterp, a1, r1, g1, b1, a2, r2, g2, b2;
	u8 r, g, b, a;

	if (argc!=3)
		return GF_JS_EXCEPTION(ctx);
	if (JS_ToFloat64(ctx, &interp, argv[0]))
		return GF_JS_EXCEPTION(ctx);

	if (interp<0) interp=0;
	else if (interp>1) interp=1;
	minterp = 1-interp;

	str = JS_ToCString(ctx, argv[1]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	col1 = gf_color_parse(str);
	JS_FreeCString(ctx, str);

	str = JS_ToCString(ctx, argv[2]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	col2 = gf_color_parse(str);
	JS_FreeCString(ctx, str);

	a1 = GF_COL_A(col1) / 255.0;
	a2 = GF_COL_A(col2) / 255.0;
	r1 = GF_COL_R(col1) / 255.0;
	r2 = GF_COL_R(col2) / 255.0;
	g1 = GF_COL_G(col1) / 255.0;
	g2 = GF_COL_G(col2) / 255.0;
	b1 = GF_COL_B(col1) / 255.0;
	b2 = GF_COL_B(col2) / 255.0;

	a = (u8) ((a1 * minterp + a2 * interp) * 255);
	r = (u8) ((r1 * minterp + r2 * interp) * 255);
	g = (u8) ((g1 * minterp + g2 * interp) * 255);
	b = (u8) ((b1 * minterp + b2 * interp) * 255);

	if (!a && !r && !g && !b)
		return JS_NewString(ctx, "none");

	sprintf(szCol, "0x%02X%02X%02X%02X", a, r, g, b);
	return JS_NewString(ctx, szCol);
}

static JSValue js_color_get_component(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *str;
	u32 comp_idx, color;
	Double comp;
	if (argc!=2)
		return GF_JS_EXCEPTION(ctx);

	str = JS_ToCString(ctx, argv[0]);
	if (!str) return GF_JS_EXCEPTION(ctx);
	color = gf_color_parse(str);
	JS_FreeCString(ctx, str);

	if (JS_ToInt32(ctx, &comp_idx, argv[1]))
		return GF_JS_EXCEPTION(ctx);

	switch (comp_idx) {
	case 0: comp = GF_COL_A(color); break;
	case 1: comp = GF_COL_R(color); break;
	case 2: comp = GF_COL_G(color); break;
	case 3: comp = GF_COL_B(color); break;
	default: return GF_JS_EXCEPTION(ctx);
	}
	comp /= 255.0;
	return JS_NewFloat64(ctx, comp);
}

typedef struct
{
	u64 last_sample_time, frame_ts;
	u32 samples_used, consumed, nb_samples, channels;
	u32 fade;
	JSValue jspid;
	Double volume;
	u8 *data;
	size_t data_size;
} PidMix;

typedef struct
{
	u32 channels, samples_gap, fade_len;
	Double *chan_buf;
	u32 max_pid_channels;
	Double *in_chan_buf;
	u32 nb_inputs;
	PidMix *inputs;

	u32 sample_size;
	Double (*get_sample)(u8 *data);
	void (*set_sample)(u8 *data, Double val);
} AMixCtx;

static JSValue js_audio_mix(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u32 nb_samples, i, j, k, nb_src, fade_length=0;
	u64 audio_time;
	JSValue v;
	s32 res;
	size_t ab_size;
	u8 *mix_ab;
	u32 max_chan = 0;
	PidMix *pids;
	AMixCtx *mix = JS_GetOpaque(this_val, amix_class_id);
	if (!mix) return GF_JS_EXCEPTION(ctx);

	if (argc != 3) return GF_JS_EXCEPTION(ctx);

	if (JS_ToInt64(ctx, &audio_time, argv[0])) return GF_JS_EXCEPTION(ctx);
	mix_ab = JS_GetArrayBuffer(ctx, &ab_size, argv[1]);
	if (!mix_ab) return GF_JS_EXCEPTION(ctx);
	nb_samples = (u32) (ab_size / mix->channels / mix->sample_size);

	v = JS_GetPropertyStr(ctx, argv[2], "length");
	if (JS_IsException(v)) return GF_JS_EXCEPTION(ctx);

	if (JS_ToInt32(ctx, &nb_src, v)) {
		JS_FreeValue(ctx, v);
		return GF_JS_EXCEPTION(ctx);
	}
	JS_FreeValue(ctx, v);

	if (mix->nb_inputs < nb_src) {
		mix->inputs = gf_realloc(mix->inputs, sizeof(PidMix) * nb_src);
		if (!mix->inputs) return js_throw_err(ctx, GF_OUT_OF_MEM);
		mix->nb_inputs = nb_src;
	}
	pids = mix->inputs;

	//update current input states
	for (i=0; i<nb_src; i++) {
		PidMix *pid = &pids[i];
		pid->jspid = JS_GetPropertyUint32(ctx, argv[2], i);

		v = JS_GetPropertyStr(ctx, pid->jspid, "last_sample_time");
		res = JS_ToInt64(ctx, &pid->last_sample_time, v);
		JS_FreeValue(ctx, v);
		//assume first sample if not set
		if (res) pid->last_sample_time = 0;

		v = JS_GetPropertyStr(ctx, pid->jspid, "frame_ts");
		res = JS_ToInt64(ctx, &pid->frame_ts, v);
		JS_FreeValue(ctx, v);
		//we must have a frame timestamp in audio timescale
		if (res) return GF_JS_EXCEPTION(ctx);

		v = JS_GetPropertyStr(ctx, pid->jspid, "samples_used");
		res = JS_ToInt32(ctx, &pid->samples_used, v);
		JS_FreeValue(ctx, v);
		//if not set, assume first sample
		if (res) pid->samples_used = 0;

		v = JS_GetPropertyStr(ctx, pid->jspid, "channels");
		res = JS_ToInt32(ctx, &pid->channels, v);
		JS_FreeValue(ctx, v);
		//we must have the number of channels for this source
		if (res || !pid->channels) return GF_JS_EXCEPTION(ctx);

		//we must have the audio data
		v = JS_GetPropertyStr(ctx, pid->jspid, "data");
		pid->data = JS_GetArrayBuffer(ctx, &pid->data_size, v);
		JS_FreeValue(ctx, v);

		v = JS_GetPropertyStr(ctx, pid->jspid, "nb_samples");
		res = JS_ToInt32(ctx, &pid->nb_samples, v);
		JS_FreeValue(ctx, v);
		//if not set, derive from data size
		if (res) {
			pid->nb_samples = (u32) (pid->data_size / pid->channels / mix->sample_size);
		}
		v = JS_GetPropertyStr(ctx, pid->jspid, "fade");
		res = JS_ToInt32(ctx, &pid->fade, v);
		JS_FreeValue(ctx, v);
		if (res) pid->fade = 0;
		if (pid->fade) fade_length++;

		v = JS_GetPropertyStr(ctx, pid->jspid, "volume");
		res = JS_ToFloat64(ctx, &pid->volume, v);
		JS_FreeValue(ctx, v);
		if (res) pid->volume = 1.0;

		if (pid->channels>max_chan)
			max_chan = pid->channels;

		pid->consumed = 0;
	}
	if (max_chan > mix->max_pid_channels) {
		mix->in_chan_buf = gf_realloc(mix->in_chan_buf, sizeof(Double) * max_chan);
		if (!mix->in_chan_buf) return js_throw_err(ctx, GF_OUT_OF_MEM);
		mix->max_pid_channels = max_chan;
	}
	if (fade_length) {
		fade_length = MIN(mix->fade_len, nb_samples);
	}

	for (i=0; i<nb_samples; i++) {
		u32 active=0;
		memset(mix->chan_buf, 0, sizeof(Double) * mix->channels);

		for (j=0; j<nb_src; j++) {
			PidMix *pid = &pids[j];

			//for the time being, as soon as we start writing audio we no longer check sync
			if (!pid->last_sample_time) {
				if (pid->frame_ts + pid->samples_used  > audio_time + i) {
					continue;
				}
				pid->last_sample_time = 1; //pid.frame_ts + pid.sample_used;
				JS_SetPropertyStr(ctx, pid->jspid, "last_sample_time", JS_NewInt64(ctx, pid->last_sample_time) );
			} else {
				//audio discontinuity
				if (pid->frame_ts + pid->samples_used  > audio_time + i + mix->samples_gap) {
					continue;
				}
			}

			u32 s_pos = pid->samples_used + pid->consumed;
			if (s_pos >= pid->nb_samples) {
				gf_assert(0);
				GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[AVMix] error mixing\n"));
			}
			u32 pos = s_pos * pid->channels;
			u8 *buf = pid->data + mix->sample_size * pos;
			for (k=0; k<pid->channels; k++) {
				mix->in_chan_buf[k] = mix->get_sample(buf);
				buf += mix->sample_size;
			}

			active += 1;

			Double fade_scale = 1;
			if (pid->fade) {
				//fade from 0 to 1 starting at pos=0 up to fade_length
				if (pid->fade==1) {
					if (s_pos<fade_length) {
						fade_scale = s_pos/fade_length;
					} else {
						pid->fade = 0;
						JS_SetPropertyStr(ctx, pid->jspid, "fade", JS_NewInt32(ctx, pid->fade) );
					}
				}
				//fade from 1 to 0 starting at pos=nb_samples-fade_length up to nb_samples
				else if (pid->fade==2) {
					if (s_pos>nb_samples-fade_length ) {
						fade_scale = (nb_samples - s_pos)/fade_length;
					}
				}
			}
			fade_scale *= pid->volume;

			//todo , proper down/up mix ...
			for (k=0; k<mix->channels; k++) {
				Double s_val;
				if (pid->channels <= k)
					s_val = mix->in_chan_buf[0];
				else
					s_val = mix->in_chan_buf[k];

				mix->chan_buf[k] += s_val * fade_scale;
			}
			pid->consumed ++;
		}

		u8 *dst = mix_ab + mix->sample_size * (i * mix->channels);
		for (j=0; j< mix->channels; j++) {
			Double sval = active ? (mix->chan_buf[j] / active) : 0;
			mix->set_sample(dst, sval);
			dst += mix->sample_size;
		}
	}

	//update pid values
	for (i=0; i<nb_src; i++) {
		PidMix *pid = &pids[i];
		pid->samples_used += pid->consumed;
		JS_SetPropertyStr(ctx, pid->jspid, "samples_used", JS_NewInt32(ctx, pid->samples_used));

		JS_FreeValue(ctx, pid->jspid);
	}
	return JS_UNDEFINED;
}

static Double amix_get_s16(u8 *data)
{
	u16 val = data[1];
	val <<= 8;
	val |= data[0];
	return ((Double) (s16) val) / 65535;

}
static void amix_set_s16(u8 *data, Double val)
{
	val *= 65535;
	u16 res = (u16) val;
	data[0] = res & 0xFF;
	data[1] = (res>>8) & 0xFF;
}
static Double amix_get_s32(u8 *data)
{
	u32 val = data[3];
	val <<= 8;
	val |= data[2];
	val <<= 8;
	val |= data[1];
	val <<= 8;
	val |= data[0];
	return ((Double) (s32) val) / 0xFFFFFFFF;

}
static void amix_set_s32(u8 *data, Double val)
{
	val *= 0xFFFFFFFF;
	u32 res = (u32) val;
	data[0] = res & 0xFF;
	data[1] = (res>>8) & 0xFF;
	data[2] = (res>>16) & 0xFF;
	data[3] = (res>>24) & 0xFF;
}
static Double amix_get_flt(u8 *data)
{
	return (Double) *(Float *)data;
}
static void amix_set_flt(u8 *data, Double val)
{
	*(Float *)data = (Float) val;
}
static Double amix_get_dbl(u8 *data)
{
	return *(Double *)data;
}
static void amix_set_dbl(u8 *data, Double val)
{
	*(Double *)data = val;
}


static void js_amix_finalize(JSRuntime *rt, JSValue obj)
{
	AMixCtx *mix = JS_GetOpaque(obj, amix_class_id);
	if (!mix) return;

	if (mix->chan_buf) gf_free(mix->chan_buf);
	if (mix->inputs) gf_free(mix->inputs);
	if (mix->in_chan_buf) gf_free(mix->in_chan_buf);
	gf_free(mix);
}

JSClassDef amixClass = {
	"FILE",
	.finalizer = js_amix_finalize,
};

static const JSCFunctionListEntry amix_funcs[] = {
	JS_CFUNC_DEF("mix", 0, js_audio_mix),
};

static JSValue amix_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
	u32 a_fmt=0;
	u32 channels, samples_gap, fade_len;
	AMixCtx *mix;
	JSValue anobj;

	if (argc != 4) return js_throw_err(ctx, GF_BAD_PARAM);

	const char *fmt = JS_ToCString(ctx, argv[0]);
	if (!fmt) return GF_JS_EXCEPTION(ctx);
	//we only support these formats for the time being, all in packed mode
	if (!strcmp(fmt, "s16")) a_fmt = 1;
	else if (!strcmp(fmt, "s32")) a_fmt = 2;
	else if (!strcmp(fmt, "flt")) a_fmt = 3;
	else if (!strcmp(fmt, "dbl")) a_fmt = 4;
	JS_FreeCString(ctx, fmt);
	if (!a_fmt) return GF_JS_EXCEPTION(ctx);

	if (JS_ToInt32(ctx, &channels, argv[1])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &samples_gap, argv[2])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &fade_len, argv[3])) return GF_JS_EXCEPTION(ctx);
	if (!channels) return js_throw_err(ctx, GF_BAD_PARAM);

	GF_SAFEALLOC(mix, AMixCtx);
	mix->channels = channels;
	mix->samples_gap = samples_gap;
	mix->fade_len = fade_len;
	mix->chan_buf = gf_malloc(sizeof(Double) * channels);
	if (!mix->chan_buf) {
		gf_free(mix);
		return js_throw_err(ctx, GF_OUT_OF_MEM);
	}

	switch (a_fmt) {
	case 1:
		mix->sample_size = 2;
		mix->get_sample = amix_get_s16;
		mix->set_sample = amix_set_s16;
		break;
	case 2:
		mix->sample_size = 4;
		mix->get_sample = amix_get_s32;
		mix->set_sample = amix_set_s32;
		break;
	case 3:
		mix->sample_size = 4;
		mix->get_sample = amix_get_flt;
		mix->set_sample = amix_set_flt;
		break;
	case 4:
		mix->sample_size = 8;
		mix->get_sample = amix_get_dbl;
		mix->set_sample = amix_set_dbl;
		break;
	}

	anobj = JS_NewObjectClass(ctx, amix_class_id);
	if (JS_IsException(anobj)) {
		gf_free(mix);
		return anobj;
	}
	JS_SetOpaque(anobj, mix);
	return anobj;
}

static const JSCFunctionListEntry sys_funcs[] = {
	JS_CGETSET_MAGIC_DEF_ENUM("nb_cores", js_sys_prop_get, NULL, JS_SYS_NB_CORES),
	JS_CGETSET_MAGIC_DEF_ENUM("sampling_period_duration", js_sys_prop_get, NULL, JS_SYS_SAMPLE_DUR),
	JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU),
	JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU),
	JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_DIFF),
	JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_DIFF),
	JS_CGETSET_MAGIC_DEF_ENUM("cpu_idle_time", js_sys_prop_get, NULL, JS_SYS_CPU_IDLE),
	JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_usage", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_USAGE),
	JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_usage", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_USAGE),
	JS_CGETSET_MAGIC_DEF_ENUM("pid", js_sys_prop_get, NULL, JS_SYS_PID),
	JS_CGETSET_MAGIC_DEF_ENUM("thread_count", js_sys_prop_get, NULL, JS_SYS_THREADS),
	JS_CGETSET_MAGIC_DEF_ENUM("process_memory", js_sys_prop_get, NULL, JS_SYS_PROCESS_MEM),
	JS_CGETSET_MAGIC_DEF_ENUM("physical_memory", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM),
	JS_CGETSET_MAGIC_DEF_ENUM("physical_memory_avail", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM_AVAIL),
	JS_CGETSET_MAGIC_DEF_ENUM("gpac_memory", js_sys_prop_get, NULL, JS_SYS_GPAC_MEM),

	JS_CGETSET_MAGIC_DEF_ENUM("last_wdir", js_sys_prop_get, js_sys_prop_set, JS_SYS_LAST_WORK_DIR),
	JS_CGETSET_MAGIC_DEF_ENUM("batteryOn", js_sys_prop_get, NULL, JS_SYS_BATTERY_ON),
	JS_CGETSET_MAGIC_DEF_ENUM("batteryCharging", js_sys_prop_get, NULL, JS_SYS_BATTERY_CHARGE),
	JS_CGETSET_MAGIC_DEF_ENUM("batteryPercent", js_sys_prop_get, NULL, JS_SYS_BATTERY_PERCENT),
	JS_CGETSET_MAGIC_DEF_ENUM("batteryLifeTime", js_sys_prop_get, NULL, JS_SYS_BATTERY_LIFETIME),
	JS_CGETSET_MAGIC_DEF_ENUM("batteryFullLifeTime", js_sys_prop_get, NULL, JS_SYS_BATTERY_LIFETIME_FULL),
	JS_CGETSET_MAGIC_DEF_ENUM("hostname", js_sys_prop_get, NULL, JS_SYS_HOSTNAME),
	JS_CGETSET_MAGIC_DEF_ENUM("username", js_sys_prop_get, NULL, JS_SYS_USERNAME),

	JS_CGETSET_MAGIC_DEF_ENUM("test_mode", js_sys_prop_get, NULL, JS_SYS_TEST_MODE),
	JS_CGETSET_MAGIC_DEF_ENUM("old_arch", js_sys_prop_get, NULL, JS_SYS_OLD_ARCH),
	JS_CGETSET_MAGIC_DEF_ENUM("log_color", js_sys_prop_get, NULL, JS_SYS_LOG_COLOR),

	JS_CGETSET_MAGIC_DEF_ENUM("quiet", js_sys_prop_get, NULL, JS_SYS_QUIET),
	JS_CGETSET_MAGIC_DEF_ENUM("timezone", js_sys_prop_get, NULL, JS_SYS_TIMEZONE),
	JS_CGETSET_MAGIC_DEF_ENUM("nb_files_open", js_sys_prop_get, NULL, JS_SYS_FILES_OPEN),
	JS_CGETSET_MAGIC_DEF_ENUM("cache_dir", js_sys_prop_get, NULL, JS_SYS_CACHE_DIR),
	JS_CGETSET_MAGIC_DEF_ENUM("shared_dir", js_sys_prop_get, NULL, JS_SYS_SHARED_DIR),
	JS_CGETSET_MAGIC_DEF_ENUM("version", js_sys_prop_get, NULL, JS_SYS_VERSION),
	JS_CGETSET_MAGIC_DEF_ENUM("version_full", js_sys_prop_get, NULL, JS_SYS_VERSION_FULL),
	JS_CGETSET_MAGIC_DEF_ENUM("copyright", js_sys_prop_get, NULL, JS_SYS_COPYRIGHT),
	JS_CGETSET_MAGIC_DEF_ENUM("version_major", js_sys_prop_get, NULL, JS_SYS_V_MAJOR),
	JS_CGETSET_MAGIC_DEF_ENUM("version_minor", js_sys_prop_get, NULL, JS_SYS_V_MINOR),
	JS_CGETSET_MAGIC_DEF_ENUM("version_micro", js_sys_prop_get, NULL, JS_SYS_V_MICRO),

	JS_CGETSET_MAGIC_DEF_ENUM("rmt_on_new_client", NULL, js_sys_prop_set, JS_SYS_RMT_ON_NEW_CLIENT),
	JS_CGETSET_MAGIC_DEF_ENUM("on_log", NULL, js_sys_prop_set, JS_SYS_ON_LOG),

	JS_CFUNC_DEF("set_arg_used", 0, js_sys_set_arg_used),
	JS_CFUNC_DEF("error_string", 0, js_sys_error_string),
	JS_CFUNC_DEF("prompt_input", 0, js_sys_prompt_input),
	JS_CFUNC_DEF("prompt_string", 0, js_sys_prompt_string),
	JS_CFUNC_DEF("prompt_echo_off", 0, js_sys_prompt_echo_off),
	JS_CFUNC_DEF("prompt_code", 0, js_sys_prompt_code),
	JS_CFUNC_DEF("prompt_size", 0, js_sys_prompt_size),

	JS_CFUNC_DEF("keyname", 0, js_sys_keyname),
	JS_CFUNC_DEF("get_event_type", 0, js_sys_evt_by_name),
	JS_CFUNC_DEF("gc", 0, js_sys_gc),

	JS_CFUNC_DEF("enum_directory", 0, js_sys_enum_directory),
	JS_CFUNC_DEF("clock_ms", 0, js_sys_clock),
	JS_CFUNC_DEF("clock_us", 0, js_sys_clock_high_res),
	JS_CFUNC_DEF("sleep", 0, js_sys_sleep),
	JS_CFUNC_DEF("exit", 0, js_sys_exit),
	JS_CFUNC_DEF("fcc_to_str", 0, js_sys_4cc_to_str),
	JS_CFUNC_DEF("rand_init", 0, js_sys_rand_init),
	JS_CFUNC_DEF("rand", 0, js_sys_rand),
	JS_CFUNC_DEF("rand64", 0, js_sys_rand64),
	JS_CFUNC_DEF("getenv", 0, js_sys_getenv),
	JS_CFUNC_DEF("get_utc", 0, js_sys_get_utc),
	JS_CFUNC_DEF("get_ntp", 0, js_sys_get_ntp),
	JS_CFUNC_DEF("ntp_shift", 0, js_sys_ntp_shift),

	JS_CFUNC_DEF("crc32", 0, js_sys_crc32),
	JS_CFUNC_DEF("sha1", 0, js_sys_sha1),
	JS_CFUNC_DEF("load_file", 0, js_sys_file_data),
	JS_CFUNC_DEF("load_script", 0, js_sys_load_script),
	JS_CFUNC_DEF("compress", 0, js_sys_compress),
	JS_CFUNC_DEF("decompress", 0, js_sys_decompress),
	JS_CFUNC_DEF("rmdir", 0, js_sys_rmdir),
	JS_CFUNC_DEF("mkdir", 0, js_sys_mkdir),
	JS_CFUNC_DEF("dir_exists", 0, js_sys_dir_exists),
	JS_CFUNC_DEF("dir_clean", 0, js_sys_dir_clean),
	JS_CFUNC_DEF("basename", 0, js_sys_basename),
	JS_CFUNC_DEF("file_ext", 0, js_sys_file_ext),
	JS_CFUNC_DEF("file_exists", 0, js_sys_file_exists),
	JS_CFUNC_DEF("del", 0, js_sys_file_del),
	JS_CFUNC_DEF("mod_time", 0, js_sys_file_modtime),
	JS_CFUNC_DEF("move", 0, js_sys_file_move),
	JS_CFUNC_DEF("get_opt", 0, js_sys_get_opt),
	JS_CFUNC_DEF("set_opt", 0, js_sys_set_opt),
	JS_CFUNC_DEF("discard_opts", 0, js_sys_discard_opts),

	JS_CFUNC_DEF("base64enc", 0, js_sys_base64enc),
	JS_CFUNC_DEF("base64dec", 0, js_sys_base64dec),
	JS_CFUNC_DEF("base16enc", 0, js_sys_base16enc),
	JS_CFUNC_DEF("base16dec", 0, js_sys_base16dec),

	JS_CFUNC_DEF("ntohl", 0, js_sys_ntohl),
	JS_CFUNC_DEF("ntohs", 0, js_sys_ntohs),
	JS_CFUNC_DEF("htonl", 0, js_sys_htonl),
	JS_CFUNC_DEF("htons", 0, js_sys_htons),

	JS_CFUNC_DEF("pixfmt_size", 0, js_pixfmt_size),
	JS_CFUNC_DEF("pixfmt_transparent", 0, js_pixfmt_transparent),
	JS_CFUNC_DEF("pixfmt_yuv", 0, js_pixfmt_yuv),
	JS_CFUNC_DEF("pixfmt_depth", 0, js_pixfmt_depth),
	JS_CFUNC_DEF("pcmfmt_depth", 0, js_pcmfmt_depth),
	JS_CFUNC_DEF("color_lerp", 0, js_color_lerp),
	JS_CFUNC_DEF("color_component", 0, js_color_get_component),
	JS_CFUNC_DEF("url_cat", 0, js_sys_url_cat),
	JS_CFUNC_DEF("rect_union", 0, js_sys_rect_union),
	JS_CFUNC_DEF("rect_intersect", 0, js_sys_rect_intersect),
	JS_CFUNC_DEF("mpd_parse", 0, js_sys_mpd_parse),

	JS_CFUNC_DEF("_avmix_audio", 0, js_audio_mix),

	JS_CFUNC_DEF("enable_rmtws", 0, js_sys_enable_rmtws),
	JS_CFUNC_DEF("set_logs", 0, js_sys_set_logs),
	JS_CFUNC_DEF("get_logs", 0, js_sys_get_logs),
};


static void js_sha1_finalize(JSRuntime *rt, JSValue obj)
{
	GF_SHA1Context *sha1 = JS_GetOpaque(obj, sha1_class_id);
	if (!sha1) return;
	gf_free(sha1);
}

JSClassDef sha1Class = {
	"SHA1",
	.finalizer = js_sha1_finalize,
};

static JSValue js_sha1_push(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const u8 *data;
	size_t data_size;
	GF_SHA1Context *sha1 = JS_GetOpaque(this_val, sha1_class_id);
	if (!sha1) {
		sha1 = gf_sha1_starts();
		if (!sha1) {
			return js_throw_err(ctx, GF_OUT_OF_MEM);
		}
		JS_SetOpaque(this_val, sha1);
	}
	if (!argc) return GF_JS_EXCEPTION(ctx);
	data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
	gf_sha1_update(sha1, (u8 *) data, (u32) data_size);

	return JS_UNDEFINED;
}
static JSValue js_sha1_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	u8 output[GF_SHA1_DIGEST_SIZE];
	GF_SHA1Context *sha1 = JS_GetOpaque(this_val, sha1_class_id);
	if (!sha1) return GF_JS_EXCEPTION(ctx);
	gf_sha1_finish(sha1, output);
	JS_SetOpaque(this_val, NULL);
	return JS_NewArrayBufferCopy(ctx, output, GF_SHA1_DIGEST_SIZE);
}

static const JSCFunctionListEntry sha1_funcs[] = {
	JS_CFUNC_DEF("push", 0, js_sha1_push),
	JS_CFUNC_DEF("get", 0, js_sha1_get),
};

static JSValue sha1_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
	JSValue anobj;
	GF_SHA1Context *sha1;

	sha1 = gf_sha1_starts();
	if (!sha1) {
		return js_throw_err(ctx, GF_OUT_OF_MEM);
	}
	anobj = JS_NewObjectClass(ctx, sha1_class_id);
	if (JS_IsException(anobj)) {
		gf_free(sha1);
		return anobj;
	}
	JS_SetOpaque(anobj, sha1);
	return anobj;
}


static void js_file_finalize(JSRuntime *rt, JSValue obj)
{
	FILE *f = JS_GetOpaque(obj, file_class_id);
	if (!f) return;
	gf_fclose(f);
}

JSClassDef fileClass = {
	"FILE",
	.finalizer = js_file_finalize,
};

enum
{
	JS_FILE_POS = 0,
	JS_FILE_EOF,
	JS_FILE_ERROR,
	JS_FILE_SIZE,
	JS_FILE_IS_GFIO,
};

static JSValue js_file_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);

	switch (magic) {
	case JS_FILE_POS:
		return JS_NewInt64(ctx, gf_ftell(f));
	case JS_FILE_EOF:
		return JS_NewBool(ctx, gf_feof(f));
	case JS_FILE_ERROR:
		return JS_NewInt32(ctx, gf_ferror(f) );
	case JS_FILE_SIZE:
		return JS_NewInt64(ctx, gf_fsize(f) );
	case JS_FILE_IS_GFIO:
		return JS_NewBool(ctx, gf_fileio_check(f));
	}
	return JS_UNDEFINED;
}

static JSValue js_file_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
	s64 lival;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);

	switch (magic) {
	case JS_FILE_POS:
		if (JS_ToInt64(ctx, &lival, value)) return GF_JS_EXCEPTION(ctx);
		gf_fseek(f, lival, SEEK_SET);
		break;
	}
	return JS_UNDEFINED;
}
static JSValue js_file_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);
	gf_fflush(f);
	return JS_UNDEFINED;
}
static JSValue js_file_close(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);
	gf_fclose(f);
	JS_SetOpaque(this_val, NULL);
	return JS_UNDEFINED;
}
static JSValue js_file_read(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const u8 *data;
	size_t size;
	u32 read;
	s32 nb_bytes=0;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);
	if (!argc) return GF_JS_EXCEPTION(ctx);

	data = JS_GetArrayBuffer(ctx, &size, argv[0]);
	if (!data) return GF_JS_EXCEPTION(ctx);
	if (argc>1) {
		if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
	}
	if (!nb_bytes) nb_bytes = (s32) size;
	else if (nb_bytes > (s32) size) nb_bytes = (s32) size;

	read = (u32) gf_fread((void *) data, nb_bytes, f);
	return JS_NewInt64(ctx, read);
}

static JSValue js_file_seek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	s64 pos;
	s32 whence, res;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);
	if (argc!=2) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt64(ctx, &pos, argv[0])) return GF_JS_EXCEPTION(ctx);
	if (JS_ToInt32(ctx, &whence, argv[1])) return GF_JS_EXCEPTION(ctx);

	res = gf_fseek(f, pos, whence);
	return JS_NewInt32(ctx, res);
}

static JSValue js_file_gets(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char *data=NULL;
	char temp[1025];
	JSValue res;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);

	temp[1024] = 0;
	while (1) {
		u32 rlen;
		temp[0] = 0;
		const char *read = gf_fgets(temp, 1024, f);
		if (!read) break;
		gf_dynstrcat(&data, temp, NULL);
		if (!data) return js_throw_err(ctx, GF_OUT_OF_MEM);
		rlen = (u32) strlen(temp);
		if (rlen <= 1024)
			break;
		if (temp[rlen-1] == '\n')
			break;
	}
	res = JS_NewString(ctx, data);
	gf_free(data);
	return res;
}

static JSValue js_file_getc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	char res[2];
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);

	res[0] = gf_fgetc(f);
	res[1] = 0;
	return JS_NewString(ctx, res);
}

static JSValue js_file_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const u8 *data;
	size_t size;
	u32 written;
	s32 nb_bytes=0;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f) return GF_JS_EXCEPTION(ctx);
	if (!argc) return GF_JS_EXCEPTION(ctx);

	data = JS_GetArrayBuffer(ctx, &size, argv[0]);
	if (!data) return GF_JS_EXCEPTION(ctx);
	if (argc>1) {
		if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
	}
	if (!nb_bytes) nb_bytes = (u32) size;
	else if (nb_bytes > (s32) size) nb_bytes = (u32) size;

	written = (u32) gf_fwrite((void *) data, nb_bytes, f);
	return JS_NewInt64(ctx, written);
}
static JSValue js_file_puts(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	const char *string;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f || !argc) return GF_JS_EXCEPTION(ctx);

	string = JS_ToCString(ctx, argv[0]);
	if (!string) return GF_JS_EXCEPTION(ctx);
	gf_fputs(string, f);
	JS_FreeCString(ctx, string);
	return JS_UNDEFINED;
}

static JSValue js_file_putc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	int val=0;
	FILE *f = JS_GetOpaque(this_val, file_class_id);
	if (!f || !argc) return GF_JS_EXCEPTION(ctx);

	if (JS_IsString(argv[0])) {
		const char *string = JS_ToCString(ctx, argv[0]);
		if (!string) return GF_JS_EXCEPTION(ctx);
		val = string[0];
		JS_FreeCString(ctx, string);
	} else {
		if (JS_ToInt32(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
	}
	gf_fputc(val, f);
	return JS_UNDEFINED;
}

static const JSCFunctionListEntry file_funcs[] = {
	JS_CGETSET_MAGIC_DEF_ENUM("pos", js_file_prop_get, js_file_prop_set, JS_FILE_POS),
	JS_CGETSET_MAGIC_DEF_ENUM("eof", js_file_prop_get, NULL, JS_FILE_EOF),
	JS_CGETSET_MAGIC_DEF_ENUM("error", js_file_prop_get, NULL, JS_FILE_ERROR),
	JS_CGETSET_MAGIC_DEF_ENUM("size", js_file_prop_get, NULL, JS_FILE_SIZE),
	JS_CGETSET_MAGIC_DEF_ENUM("gfio", js_file_prop_get, NULL, JS_FILE_IS_GFIO),

	JS_CFUNC_DEF("flush", 0, js_file_flush),
	JS_CFUNC_DEF("close", 0, js_file_close),
	JS_CFUNC_DEF("read", 0, js_file_read),
	JS_CFUNC_DEF("seek", 0, js_file_seek),
	JS_CFUNC_DEF("gets", 0, js_file_gets),
	JS_CFUNC_DEF("getc", 0, js_file_getc),
	JS_CFUNC_DEF("write", 0, js_file_write),
	JS_CFUNC_DEF("puts", 0, js_file_puts),
	JS_CFUNC_DEF("putc", 0, js_file_putc),
};

static JSValue file_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
	JSValue anobj;
	FILE *f=NULL;

	if (!argc) {
		f = gf_file_temp(NULL);
		if (!f) {
			return js_throw_err(ctx, GF_OUT_OF_MEM);
		}
	} else {
		GF_Err e = GF_OK;
		const char *name=NULL, *mode=NULL, *parent_io=NULL;
		name = JS_ToCString(ctx, argv[0] );
		if (argc>1) {
			mode = JS_ToCString(ctx, argv[1] );
			if (argc>2) {
				parent_io = JS_ToCString(ctx, argv[2] );
			}
		}

		if (!name || !mode) {
			e = GF_BAD_PARAM;
		} else {
			f = gf_fopen_ex(name, parent_io, mode, GF_FALSE);
			if (!f) e = GF_URL_ERROR;
		}
		if (name) JS_FreeCString(ctx, name);
		if (mode) JS_FreeCString(ctx, mode);
		if (parent_io) JS_FreeCString(ctx, parent_io);

		if (e) {
			return js_throw_err(ctx, e);
		}
	}

	anobj = JS_NewObjectClass(ctx, file_class_id);
	if (JS_IsException(anobj)) {
		gf_fclose(f);
		return anobj;
	}
	JS_SetOpaque(anobj, f);
	return anobj;
}


typedef struct
{
	JSValue open, close, read, write, tell, seek, eof, exists, factory_obj;
	JSContext *ctx;
	u32 all_refs;
	GF_List *url_pending;
	Bool lock;
	struct __jsf_fileio *root;
} JSFileIOFactoryCtx;

typedef struct __jsf_fileio
{
	JSFileIOFactoryCtx *factory;
	JSValue js_obj;
	GF_FileIO *gfio;
	u32 nb_refs;
} JSFileIOCtx;

static void js_fileio_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
	JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
	if (!ioctx) return;
	if (ioctx->factory->root == ioctx) {
		JSFileIOFactoryCtx *iofac = ioctx->factory;
		JS_MarkValue(rt, iofac->open, mark_func);
		JS_MarkValue(rt, iofac->close, mark_func);
		JS_MarkValue(rt, iofac->write, mark_func);
		JS_MarkValue(rt, iofac->read, mark_func);
		JS_MarkValue(rt, iofac->tell, mark_func);
		JS_MarkValue(rt, iofac->seek, mark_func);
		JS_MarkValue(rt, iofac->eof, mark_func);
		JS_MarkValue(rt, iofac->exists, mark_func);
		JS_MarkValue(rt, iofac->factory_obj, mark_func);
	}
	JS_MarkValue(rt, ioctx->js_obj, mark_func);
}

static void js_fileio_finalize(JSRuntime *rt, JSValue obj)
{
	FILE *f = JS_GetOpaque(obj, file_class_id);
	if (!f) return;
	gf_fclose(f);
}

JSClassDef fileioClass = {
	"FILEIO",
	.finalizer = js_fileio_finalize,
	.gc_mark = js_fileio_gc_mark
};


enum
{
	JS_FILEIO_URL = 0,
	JS_FILEIO_RES_URL,
	JS_FILEIO_PARENT,
};

static JSValue js_fileio_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
	JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
	if (!ioctx) return GF_JS_EXCEPTION(ctx);

	switch (magic) {
	case JS_FILEIO_URL:
		return JS_NewString(ctx, gf_fileio_url(ioctx->gfio) );
	case JS_FILEIO_RES_URL:
		return JS_NewString(ctx, gf_fileio_resource_url(ioctx->gfio) );
	case JS_FILEIO_PARENT:
		return JS_DupValue(ctx, ioctx->factory->factory_obj);
	}
	return JS_UNDEFINED;
}

static JSValue js_fileio_protect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
	if (!ioctx) return JS_UNDEFINED;
	if (!ioctx->factory->lock) {
		ioctx->factory->lock = GF_TRUE;
		ioctx->factory->all_refs++;
	}
	return JS_UNDEFINED;
}

static void js_fileio_factory_delete(JSContext *ctx, JSFileIOFactoryCtx *iofac, JSFileIOCtx *ioctx_deleted)
{
	while (gf_list_count(iofac->url_pending)) {
		JSFileIOCtx *ioctx = gf_list_pop_back(iofac->url_pending);
		gf_fileio_del(ioctx->gfio);
		gf_free(ioctx);
	}
	gf_list_del(iofac->url_pending);

	JS_SetOpaque(iofac->factory_obj, NULL);
	JS_FreeValue(ctx, iofac->factory_obj);
	JS_FreeValue(ctx, iofac->open);
	JS_FreeValue(ctx, iofac->close);
	JS_FreeValue(ctx, iofac->read);
	JS_FreeValue(ctx, iofac->write);
	JS_FreeValue(ctx, iofac->seek);
	JS_FreeValue(ctx, iofac->tell);
	JS_FreeValue(ctx, iofac->eof);
	JS_FreeValue(ctx, iofac->exists);

	//detach root (might have been closed before)
	if (!ioctx_deleted || (ioctx_deleted->factory->root != ioctx_deleted) ) {
		JS_SetOpaque(iofac->root->js_obj, NULL);
		JS_FreeValue(ctx, iofac->root->js_obj);
		gf_fileio_del(iofac->root->gfio);
		gf_free(iofac->root);
	}
	gf_free(iofac);
}

static JSValue js_fileio_destroy(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
	JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
	if (!ioctx) return JS_UNDEFINED;
	if (!ioctx->factory->lock) return JS_UNDEFINED;

	ioctx->factory->lock = GF_FALSE;
	ioctx->factory->all_refs--;
	if (!ioctx->factory->all_refs) {
		js_fileio_factory_delete(ctx, ioctx->factory, NULL);
	}
	return JS_UNDEFINED;
}

static const JSCFunctionListEntry fileio_funcs[] = {
	JS_CGETSET_MAGIC_DEF_ENUM("url", js_fileio_prop_get, NULL, JS_FILEIO_URL),
	JS_CGETSET_MAGIC_DEF_ENUM("resource_url", js_fileio_prop_get, NULL, JS_FILEIO_RES_URL),
	JS_CGETSET_MAGIC_DEF_ENUM("parent", js_fileio_prop_get, NULL, JS_FILEIO_PARENT),
	JS_CFUNC_DEF("protect", 0, js_fileio_protect),
	JS_CFUNC_DEF("destroy", 0, js_fileio_destroy),
};

static GF_Err jsfio_seek(GF_FileIO *fileio, u64 offset, s32 whence)
{
	JSValue argv[2], res;
	s32 ret;
	JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
	if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
	JSContext *ctx = ioctx->factory->ctx;

	gf_js_lock(ctx, GF_TRUE);

	argv[0] = JS_NewInt64(ctx, offset);
	argv[1] = JS_NewInt64(ctx, whence);
	res = JS_Call(ctx, ioctx->factory->seek, ioctx->js_obj, 2, argv);
	JS_ToInt32(ctx, &ret, res);
	JS_FreeValue(ctx, res);
	gf_js_lock(ctx, GF_FALSE);
	return ret;
}
static u32 jsfio_read(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
	JSValue argv[1], res;
	u32 ret;
	JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
	if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
	JSContext *ctx = ioctx->factory->ctx;
	gf_js_lock(ctx, GF_TRUE);
	argv[0] = JS_NewArrayBuffer(ctx, buffer, bytes, NULL, 0, 0);
	res = JS_Call(ctx, ioctx->factory->read, ioctx->js_obj, 1, argv);
	JS_FreeValue(ctx, argv[0]);
	JS_ToInt32(ctx, &ret, res);
	JS_FreeValue(ctx, res);
	gf_js_lock(ctx, GF_FALSE);
	return ret;
}
static u32 jsfio_write(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
	JSValue argv[1], res;
	u32 ret;
	JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
	if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
	JSContext *ctx = ioctx->factory->ctx;

	gf_js_lock(ctx, GF_TRUE);
	argv[0] = JS_NewArrayBuffer(ctx, buffer, bytes, NULL, 0, 0);
	res = JS_Call(ctx, ioctx->factory->write, ioctx->js_obj, 1, argv);
	JS_FreeValue(ctx, argv[0]);
	JS_ToInt32(ctx, &ret, res);
	JS_FreeValue(ctx, res);
	gf_js_lock(ctx, GF_FALSE);
	return ret;
}

static s64 jsfio_tell(GF_FileIO *fileio)
{
	JSValue res;
	s64 ret;
	JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
	if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
	JSContext *ctx = ioctx->factory->ctx;
	gf_js_lock(ctx, GF_TRUE);
	res = JS_Call(ctx, ioctx->factory->tell, ioctx->js_obj, 0, NULL);
	JS_ToInt64(ctx, &ret, res);
	JS_FreeValue(ctx, res);
	gf_js_lock(ctx, GF_FALSE);
	return ret;
}

static Bool jsfio_eof(GF_FileIO *fileio)
{
	JSValue res;
	s32 ret;
	JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
	if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
	JSContext *ctx = ioctx->factory->ctx;
	gf_js_lock(ctx, GF_TRUE);
	res = JS_Call(ctx, ioctx->factory->eof, ioctx->js_obj, 0, NULL);
	JS_ToInt32(ctx, &ret, res);
	JS_FreeValue(ctx, res);
	gf_js_lock(ctx, GF_FALSE);
	return ret ? GF_TRUE : GF_FALSE;
}

static GF_FileIO *jsfio_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_err)
{
	JSValue args[2], res;
	Bool do_delete = GF_FALSE;
	JSFileIOCtx *ioctx_ref = gf_fileio_get_udta(fileio_ref);
	if (!ioctx_ref || !ioctx_ref->gfio) return NULL;
	JSContext *ctx = ioctx_ref->factory->ctx;
	gf_js_lock(ctx, GF_TRUE);

	*out_err = GF_OK;

	if (!strcmp(mode, "url")) {
		JSFileIOCtx *ioctx;
		if (!url) return NULL;
		GF_SAFEALLOC(ioctx, JSFileIOCtx);
		if (!ioctx) return NULL;
		char *path = gf_url_concatenate(gf_fileio_resource_url(ioctx_ref->gfio), url);

		if (JS_IsUndefined(ioctx_ref->factory->write)) {
			ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
		} else if (JS_IsUndefined(ioctx_ref->factory->read)) {
			ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
		} else {
			ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
		}
		gf_free(path);
		if (!ioctx->gfio) {
			gf_free(ioctx);
			gf_js_lock(ctx, GF_FALSE);
			return NULL;
		}
		ioctx->factory = ioctx_ref->factory;
		ioctx->js_obj = JS_UNDEFINED;
		gf_list_add(ioctx->factory->url_pending, ioctx);
		ioctx_ref->factory->all_refs++;
		gf_js_lock(ctx, GF_FALSE);
		return ioctx->gfio;
	}

	if (!strcmp(mode, "probe")) {
		args[0] = JS_NewString(ctx, url);
		res = JS_Call(ctx, ioctx_ref->factory->exists, ioctx_ref->factory->factory_obj, 1, args);
		JS_FreeValue(ctx, args[0]);
		if (!JS_ToBool(ctx, res)) *out_err = GF_URL_ERROR;
		JS_FreeValue(ctx, res);
		gf_js_lock(ctx, GF_FALSE);
		return NULL;
	}

	if (!strcmp(mode, "ref")) {
		ioctx_ref->nb_refs++;
		ioctx_ref->factory->all_refs++;
		gf_js_lock(ctx, GF_FALSE);
		return fileio_ref;
	}
	if (!strcmp(mode, "unref")) {
		if (!ioctx_ref->nb_refs) return NULL;
		ioctx_ref->nb_refs--;
		ioctx_ref->factory->all_refs--;
		if (ioctx_ref->nb_refs) {
			gf_js_lock(ctx, GF_FALSE);
			return fileio_ref;
		}
		//close
		url = NULL;
		mode = NULL;
		do_delete = GF_TRUE;
	}


	if (mode && !strcmp(mode, "close")) {
		if (!JS_IsUndefined(ioctx_ref->js_obj)) {
			ioctx_ref->factory->all_refs--;
			res = JS_Call(ctx, ioctx_ref->factory->close, ioctx_ref->js_obj, 0, NULL);
			JS_FreeValue(ctx, res);
		}
		if (ioctx_ref->nb_refs) {
			gf_js_lock(ctx, GF_FALSE);
			return NULL;
		}
		do_delete = GF_TRUE;
	}
	if (do_delete) {
		JS_SetOpaque(ioctx_ref->js_obj, NULL);
		JS_FreeValue(ctx, ioctx_ref->js_obj);
		ioctx_ref->js_obj = JS_UNDEFINED;

		//closing the root object
		if (!ioctx_ref->factory->all_refs) {
			js_fileio_factory_delete(ctx, ioctx_ref->factory, ioctx_ref);

			gf_fileio_del(ioctx_ref->gfio);
			gf_free(ioctx_ref);
		} else if (ioctx_ref->factory->root != ioctx_ref) {
			gf_fileio_del(ioctx_ref->gfio);
			gf_free(ioctx_ref);
		}

		gf_js_lock(ctx, GF_FALSE);
		return NULL;
	}

	JSFileIOCtx *ioctx = NULL;
	Bool created = GF_FALSE;
	//open, create a new obj if underlying file object is not undefined (ie, it is open)
	if (JS_IsUndefined(ioctx_ref->js_obj) ) {
		ioctx = ioctx_ref;
		if (gf_list_del_item(ioctx_ref->factory->url_pending, ioctx)>=0) {
			ioctx_ref->factory->all_refs--;
		}
	}
	if (!ioctx) {
		u32 i, count = gf_list_count(ioctx_ref->factory->url_pending);
		for (i=0; i<count; i++) {
			ioctx = gf_list_get(ioctx_ref->factory->url_pending, i);
			const char *a_url = gf_fileio_resource_url(ioctx->gfio);
			if (!strcmp(url, a_url)) {
				ioctx_ref->factory->all_refs--;
				gf_list_rem(ioctx_ref->factory->url_pending, i);
				break;
			}
			ioctx = NULL;
		}
	}

	if (!ioctx) {
		GF_SAFEALLOC(ioctx, JSFileIOCtx);
		if (!ioctx) {
			*out_err = GF_OUT_OF_MEM;
			gf_js_lock(ctx, GF_FALSE);
			return NULL;
		}
		ioctx->factory = ioctx_ref->factory;
		created = GF_TRUE;
	}

	ioctx->js_obj = JS_NewObjectClass(ctx, fileio_class_id);
	if (JS_IsException(ioctx->js_obj)) {
		JS_FreeValue(ctx, ioctx->js_obj);
		ioctx->js_obj = JS_UNDEFINED;
		if (created) gf_free(ioctx);
		*out_err = GF_OUT_OF_MEM;
		gf_js_lock(ctx, GF_FALSE);
		return NULL;
	}
	JS_SetOpaque(ioctx->js_obj, ioctx);

	char *path = NULL;
	if (!strnicmp(url, "gfio://", 7)) {
		url = gf_fileio_translate_url(url);
	} else if (created) {
		path = gf_url_concatenate( gf_fileio_resource_url(ioctx_ref->gfio), url);
		url = path;
	}

	if (created) {
		if (JS_IsUndefined(ioctx->factory->write)) {
			ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
		} else if (JS_IsUndefined(ioctx->factory->read)) {
			ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
		} else {
			ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
		}
	}

	args[0] = JS_NewString(ctx, url);
	args[1] = JS_NewString(ctx, mode);
	res = JS_Call(ctx, ioctx_ref->factory->open, ioctx->js_obj, 2, args);

	JS_FreeValue(ctx, args[0]);
	JS_FreeValue(ctx, args[1]);
	if (path) gf_free(path);

	if (JS_IsBool(res) && JS_ToBool(ctx, res) ) {
		ioctx_ref->factory->all_refs++;
		gf_js_lock(ctx, GF_FALSE);
		return ioctx->gfio;
	}
	JS_ToInt32(ctx, (int32_t *) out_err, res);

	JS_SetOpaque(ioctx->js_obj, NULL);
	JS_FreeValue(ctx, ioctx->js_obj);
	ioctx->js_obj = JS_UNDEFINED;
	if (created) {
		gf_fileio_del(ioctx->gfio);
		gf_free(ioctx);
	}
	gf_js_lock(ctx, GF_FALSE);
	return NULL;
}


static JSValue fileio_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
	JSValue anobj;
	JSFileIOCtx *ioctx;
	char *full_url;
	const char *parent;

	if (argc != 9) return GF_JS_EXCEPTION(ctx);
	if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
	//open
	if (!JS_IsFunction(ctx, argv[1]) || JS_IsNull(argv[1])) return GF_JS_EXCEPTION(ctx);
	//close
	if (!JS_IsFunction(ctx, argv[2]) || JS_IsNull(argv[2])) return GF_JS_EXCEPTION(ctx);
	//write, can be null
	if (!JS_IsFunction(ctx, argv[3]) || JS_IsNull(argv[3])) {
		if (!JS_IsFunction(ctx, argv[4]) || JS_IsNull(argv[4])) return GF_JS_EXCEPTION(ctx);
	}
	//read, can be null
	if (!JS_IsFunction(ctx, argv[4]) || JS_IsNull(argv[4])) {
		if (!JS_IsFunction(ctx, argv[3]) || JS_IsNull(argv[3])) return GF_JS_EXCEPTION(ctx);
	}
	//seek
	if (!JS_IsFunction(ctx, argv[5]) || JS_IsNull(argv[5])) return GF_JS_EXCEPTION(ctx);
	//tell
	if (!JS_IsFunction(ctx, argv[6]) || JS_IsNull(argv[6])) return GF_JS_EXCEPTION(ctx);
	//eos
	if (!JS_IsFunction(ctx, argv[7]) || JS_IsNull(argv[7])) return GF_JS_EXCEPTION(ctx);
	//exists
	if (!JS_IsFunction(ctx, argv[8]) || JS_IsNull(argv[8])) return GF_JS_EXCEPTION(ctx);

	const char *url = JS_ToCString(ctx, argv[0]);
	if (!url) return GF_JS_EXCEPTION(ctx);

	GF_SAFEALLOC(ioctx, JSFileIOCtx);
	if (!ioctx) return js_throw_err(ctx, GF_OUT_OF_MEM);
	GF_SAFEALLOC(ioctx->factory, JSFileIOFactoryCtx);
	if (!ioctx->factory) {
		gf_free(ioctx);
		return js_throw_err(ctx, GF_OUT_OF_MEM);
	}
	ioctx->factory->url_pending = gf_list_new();
	if (!ioctx->factory->url_pending) {
		gf_free(ioctx->factory);
		gf_free(ioctx);
		return js_throw_err(ctx, GF_OUT_OF_MEM);
	}

	anobj = JS_NewObjectClass(ctx, fileio_class_id);
	if (JS_IsException(anobj)) {
		gf_free(ioctx);
		return anobj;
	}
	JS_SetOpaque(anobj, ioctx);
	ioctx->factory->open = JS_DupValue(ctx, argv[1]);
	ioctx->factory->close = JS_DupValue(ctx, argv[2]);
	ioctx->factory->write = JS_DupValue(ctx, argv[3]);
	ioctx->factory->read = JS_DupValue(ctx, argv[4]);
	ioctx->factory->seek = JS_DupValue(ctx, argv[5]);
	ioctx->factory->tell = JS_DupValue(ctx, argv[6]);
	ioctx->factory->eof = JS_DupValue(ctx, argv[7]);
	ioctx->factory->exists = JS_DupValue(ctx, argv[8]);
	ioctx->factory->factory_obj = JS_DupValue(ctx, anobj);
	ioctx->factory->ctx = ctx;
	ioctx->js_obj = JS_UNDEFINED;
	ioctx->factory->root = ioctx;

	parent = jsf_get_script_filename(ctx);
	full_url = gf_url_concatenate(parent, url);

	if (JS_IsUndefined(ioctx->factory->write)) {
		ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
	} else if (JS_IsUndefined(ioctx->factory->read)) {
		ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
	} else {
		ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
	}
	JS_FreeCString(ctx, url);
	gf_free(full_url);
	return anobj;
}

static void js_core_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
	if (js_rt->log_ctx) {
		JS_MarkValue(rt, js_rt->log_fun, mark_func);
		JS_MarkValue(rt, js_rt->log_obj, mark_func);
	}
}
JSClassDef coreClass = {
	"Core",
	.gc_mark = js_core_gc_mark
};


static int js_gpaccore_init(JSContext *ctx, JSModuleDef *m)
{
	JSValue proto, ctor;
	if (!bitstream_class_id) {
		JS_NewClassID(&bitstream_class_id);
		JS_NewClass(JS_GetRuntime(ctx), bitstream_class_id, &bitstreamClass);

		JS_NewClassID(&sha1_class_id);
		JS_NewClass(JS_GetRuntime(ctx), sha1_class_id, &sha1Class);

		JS_NewClassID(&file_class_id);
		JS_NewClass(JS_GetRuntime(ctx), file_class_id, &fileClass);

		JS_NewClassID(&amix_class_id);
		JS_NewClass(JS_GetRuntime(ctx), amix_class_id, &amixClass);

		JS_NewClassID(&fileio_class_id);
		JS_NewClass(JS_GetRuntime(ctx), fileio_class_id, &fileioClass);

		JS_NewClassID(&core_class_id);
		JS_NewClass(JS_GetRuntime(ctx), core_class_id, &coreClass);
	}

	//JSValue core_o = JS_NewObject(ctx);
	JSValue core_o = JS_NewObjectClass(ctx, core_class_id);
	JS_SetPropertyFunctionList(ctx, core_o, sys_funcs, countof(sys_funcs));
	JS_SetModuleExport(ctx, m, "Sys", core_o);

	JSValue args = JS_NewArray(ctx);
	u32 i, nb_args = gf_sys_get_argc();
	for (i=0; i<nb_args; i++) {
		JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, gf_sys_get_arg(i)));
	}
	JS_SetPropertyStr(ctx, core_o, "args", args);

#define DEF_CONST( _val ) \
	JS_SetPropertyStr(ctx, core_o, #_val, JS_NewInt32(ctx, _val));

	DEF_CONST(GF_CONSOLE_RESET)
	DEF_CONST(GF_CONSOLE_RED)
	DEF_CONST(GF_CONSOLE_GREEN)
	DEF_CONST(GF_CONSOLE_BLUE)
	DEF_CONST(GF_CONSOLE_YELLOW)
	DEF_CONST(GF_CONSOLE_CYAN)
	DEF_CONST(GF_CONSOLE_WHITE)
	DEF_CONST(GF_CONSOLE_MAGENTA)
	DEF_CONST(GF_CONSOLE_CLEAR)
	DEF_CONST(GF_CONSOLE_SAVE)
	DEF_CONST(GF_CONSOLE_RESTORE)
	DEF_CONST(GF_CONSOLE_BOLD)
	DEF_CONST(GF_CONSOLE_ITALIC)
	DEF_CONST(GF_CONSOLE_UNDERLINED)
	DEF_CONST(GF_CONSOLE_STRIKE)
	DEF_CONST(SEEK_SET)
	DEF_CONST(SEEK_CUR)
	DEF_CONST(SEEK_END)

#undef DEF_CONST


	//bitstream constructor
	proto = JS_NewObjectClass(ctx, bitstream_class_id);
	JS_SetPropertyFunctionList(ctx, proto, bitstream_funcs, countof(bitstream_funcs));
	JS_SetClassProto(ctx, bitstream_class_id, proto);
	ctor = JS_NewCFunction2(ctx, bitstream_constructor, "Bitstream", 1, JS_CFUNC_constructor, 0);
	JS_SetModuleExport(ctx, m, "Bitstream", ctor);

	//sha1 constructor
	proto = JS_NewObjectClass(ctx, sha1_class_id);
	JS_SetPropertyFunctionList(ctx, proto, sha1_funcs, countof(sha1_funcs));
	JS_SetClassProto(ctx, sha1_class_id, proto);
	ctor = JS_NewCFunction2(ctx, sha1_constructor, "SHA1", 1, JS_CFUNC_constructor, 0);
	JS_SetModuleExport(ctx, m, "SHA1", ctor);

	//FILE constructor
	proto = JS_NewObjectClass(ctx, file_class_id);
	JS_SetPropertyFunctionList(ctx, proto, file_funcs, countof(file_funcs));
	JS_SetClassProto(ctx, file_class_id, proto);
	ctor = JS_NewCFunction2(ctx, file_constructor, "File", 1, JS_CFUNC_constructor, 0);
	JS_SetModuleExport(ctx, m, "File", ctor);

	//amix constructor
	proto = JS_NewObjectClass(ctx, amix_class_id);
	JS_SetPropertyFunctionList(ctx, proto, amix_funcs, countof(amix_funcs));
	JS_SetClassProto(ctx, amix_class_id, proto);
	ctor = JS_NewCFunction2(ctx, amix_constructor, "AudioMixer", 1, JS_CFUNC_constructor, 0);
	JS_SetModuleExport(ctx, m, "AudioMixer", ctor);

	//FILEIO constructor
	proto = JS_NewObjectClass(ctx, fileio_class_id);
	JS_SetPropertyFunctionList(ctx, proto, fileio_funcs, countof(fileio_funcs));
	JS_SetClassProto(ctx, fileio_class_id, proto);
	ctor = JS_NewCFunction2(ctx, fileio_constructor, "FileIO", 1, JS_CFUNC_constructor, 0);
	JS_SetModuleExport(ctx, m, "FileIO", ctor);

	//RMTClient constructor
	JS_NewClassID(&js_sys_rmt_client_class_id);
	JS_NewClass(JS_GetRuntime(ctx), js_sys_rmt_client_class_id, &js_sys_rmt_client_class);
	proto = JS_NewObjectClass(ctx, js_sys_rmt_client_class_id);
	JS_SetPropertyFunctionList(ctx, proto, js_sys_rmt_client_funcs, countof(js_sys_rmt_client_funcs));
	JS_SetClassProto(ctx, js_sys_rmt_client_class_id, proto);

	return 0;
}


void qjs_module_init_gpaccore(JSContext *ctx)
{
	JSModuleDef *m;
	m = JS_NewCModule(ctx, "gpaccore", js_gpaccore_init);
	if (!m) return;

	JS_AddModuleExport(ctx, m, "Sys");
	JS_AddModuleExport(ctx, m, "Bitstream");
	JS_AddModuleExport(ctx, m, "SHA1");
	JS_AddModuleExport(ctx, m, "File");
	JS_AddModuleExport(ctx, m, "FileIO");
	JS_AddModuleExport(ctx, m, "AudioMixer");
	return;
}

void qjs_module_init_scenejs(JSContext *ctx);
void qjs_module_init_storage(JSContext *ctx);
void qjs_module_init_xhr(JSContext *c);
void qjs_module_init_evg(JSContext *c);
void qjs_module_init_webgl(JSContext *c);
void qjs_module_init_gpaccore(JSContext *c);

#ifndef GPAC_DISABLE_QJS_LIBC
#include "../quickjs/quickjs-libc.h"
void qjs_module_init_qjs_libc(JSContext *ctx)
{
#ifdef CONFIG_BIGNUM
	if (bignum_ext) {
		JS_AddIntrinsicBigFloat(ctx);
		JS_AddIntrinsicBigDecimal(ctx);
		JS_AddIntrinsicOperators(ctx);
		JS_EnableBignumExt(ctx, TRUE);
	}
#endif
	/* system modules */
	js_init_module_std(ctx, "std");
	js_init_module_os(ctx, "os");
}
#endif // GPAC_DISABLE_QJS_LIBC


void qjs_init_all_modules(JSContext *ctx, Bool no_webgl, Bool for_vrml)
{
	//init modules
	qjs_module_init_gpaccore(ctx);

#ifndef GPAC_DISABLE_QJS_LIBC
	qjs_module_init_qjs_libc(ctx);
#endif

	//vrml, init scene JS but do not init xhr (defined in DOM JS)
	if (for_vrml) {
#if !defined(GPAC_DISABLE_COMPOSITOR)
		qjs_module_init_scenejs(ctx);
#endif
	} else {
		qjs_module_init_xhr(ctx);
	}
	qjs_module_init_evg(ctx);
	qjs_module_init_storage(ctx);

	if (!no_webgl && !for_vrml)
		qjs_module_init_webgl(ctx);
}


int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, JS_BOOL use_realpath, JS_BOOL is_main);


#ifndef GPAC_STATIC_BIN

#if defined(WIN32) || defined(_WIN32_WCE)
#include <windows.h>
#else
#include <dlfcn.h>
#endif

typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, const char *module_name);

static JSModuleDef *qjs_module_loader_dyn_lib(JSContext *ctx,
                                        const char *module_name)
{
	JSModuleDef *m=NULL;
	void *hd;
	JSInitModuleFunc *init;
	char *filename;

	if (!strchr(module_name, '/') || !strchr(module_name, '\\')) {
		/* must add a '/' so that the DLL is not searched in the system library paths */
		filename = gf_malloc(strlen(module_name) + 2 + 1);
		if (!filename) return NULL;
		strcpy(filename, "./");
		strcpy(filename + 2, module_name);
	} else {
		filename = (char *)module_name;
	}

	/* load dynamic lib */
#ifdef WIN32
	hd = LoadLibrary(filename);
#else
	hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
#endif

	if (filename != module_name)
		gf_free(filename);

	if (!hd) {
		JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", module_name);
		return NULL;
	}

#ifdef WIN32
	init = (JSInitModuleFunc *) GetProcAddress(hd, "js_init_module");
#else
	init = (JSInitModuleFunc *) dlsym(hd, "js_init_module");
#endif

	if (!init) {
		JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", module_name);
	} else {
		m = init(ctx, module_name);
		if (!m) {
			JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", module_name);
		}
	}
#ifdef WIN32
	FreeLibrary(hd);
#else
	dlclose(hd);
#endif

	return m;
}

#endif // GPAC_STATIC_BIN

JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque)
{
	JSModuleDef *m;
	const char *fext = gf_file_ext_start(module_name);

	if (fext && (!strcmp(fext, ".so") || !strcmp(fext, ".dll") || !strcmp(fext, ".dylib")) )  {
#ifndef GPAC_STATIC_BIN
		m = qjs_module_loader_dyn_lib(ctx, module_name);
#else
		JS_ThrowReferenceError(ctx, "could not load module filename '%s', dynamic library loading disabled in build", module_name);
		m = NULL;
#endif
	} else {
		u32 buf_len;
		u8 *buf;
		JSValue func_val;
		char *url;
		GF_Err e;
		const char *par_url = jsf_get_script_filename(ctx);
		url = gf_url_concatenate(par_url, module_name);

		if (gf_file_exists(url ? url : module_name)) {
			e = gf_file_load_data(url ? url : module_name, &buf, &buf_len);
		} else {
			e = GF_URL_ERROR;
		}
		if (url) gf_free(url);

		if (e != GF_OK) {
			JS_ThrowReferenceError(ctx, "could not load module filename '%s': %s", module_name, gf_error_to_string(e) );
			return NULL;
		}
		/* compile the module */
		func_val = JS_Eval(ctx, buf ? (char *) buf : "", buf_len, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
		gf_free(buf);
		if (JS_IsException(func_val))
			return NULL;
		/* XXX: could propagate the exception */
		js_module_set_import_meta(ctx, func_val, GF_TRUE, GF_FALSE);
		/* the module is already referenced, so we must free it */
		m = JS_VALUE_GET_PTR(func_val);
		JS_FreeValue(ctx, func_val);
	}
	return m;
}

#ifndef GPAC_DISABLE_QJS_LIBC

static JSContext *JS_NewWorkerContext(JSRuntime *rt)
{
	JSContext *ctx = JS_NewContext(rt);
	if (!ctx)
		return NULL;

	JSValue global_obj = JS_GetGlobalObject(ctx);
	js_load_constants(ctx, global_obj);
	JS_FreeValue(ctx, global_obj);

	//disable WebGL and scene.js modules for workers
	qjs_init_all_modules(ctx, GF_TRUE, GF_FALSE);
	return ctx;
}

void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque)
{
	if (!is_handled) {
		GF_LOG(GF_LOG_WARNING, GF_LOG_CONSOLE, ("Possibly unhandled promise rejection: "));
		js_dump_error_exc(ctx, reason);
	}
}
#endif

static void qjs_init_runtime_libc(JSRuntime *rt)
{
 	if (gf_opts_get_bool("core", "no-js-mods"))
		return;

	/* module loader */
	JS_SetModuleLoaderFunc(rt, NULL, qjs_module_loader, NULL);

#ifndef GPAC_DISABLE_QJS_LIBC

	js_std_set_worker_new_context_func(JS_NewWorkerContext);
	js_std_init_handlers(rt);


	if (gf_opts_get_bool("core", "unhandled-rejection")) {
		JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, NULL);
	}
#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		js_promise_rejection_tracker(NULL, JS_NULL, JS_NULL, 1, NULL);
	}
#endif

#endif

}

static void qjs_uninit_runtime_libc(JSRuntime *rt)
{
 	if (gf_opts_get_bool("core", "no-js-mods"))
		return;

#ifndef GPAC_DISABLE_QJS_LIBC
	js_std_free_handlers(rt);
#endif

}

#endif
