	/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2021-2025
 *					All rights reserved
 *
 *  This file is part of GPAC / NodeJS module
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terfsess of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "gpac_napi.h"

#include <gpac/thread.h>
#include <gpac/dash.h>
#include <gpac/mpd.h>
#include <gpac/network.h>

#ifndef GPAC_DISABLE_SVG
#include <gpac/scenegraph_svg.h>
#endif


typedef struct
{
	Bool is_init;
	u32 str_buf_alloc;
	char *str_buf;

	u32 nb_args;
	char **argv;

	u32 main_thid;

	napi_async_context rmt_ctx;
	GF_List *rmt_new_clients;
	GF_List* rmt_task_calls;
	GF_List* rmt_delete_calls;
	struct _napi_session *fs_rmt_handler;


} GPAC_NAPI;

#define GF_SETUP_ERROR	0
#define GF_NOTIF_ERROR	1
#define GF_NOTIF_ERROR_AND_DISCONNECT	2

typedef struct _napi_session
{
	GF_FilterSession *fs;
	napi_async_context async_ctx;
	napi_env env;
	napi_ref ref;
	GF_List *defer_filters;
	Bool blocking;
} NAPI_Session;


typedef struct
{
	napi_ref ref;
	GF_FilterPacket *pck;
	u8 is_src, readonly, dangling;
	napi_ref shared_ab_ref, shared_destructor_ref;
	GF_List *pid_refs;
} NAPI_FilterPacket;

typedef struct
{
	GF_Filter *f;
	GF_FilterSession *fs;
	napi_ref ref;
	napi_env env;

	Bool is_custom;
	napi_async_context async_ctx;

	napi_ref binding_ref;
	void *binding_stack;
} NAPI_Filter;

/*for custom filters only*/
typedef struct
{
	GF_Filter *f;
	GF_FilterPid *pid;

	napi_ref ref;

	NAPI_FilterPacket *napi_pck;
	GF_List *pck_refs;
} NAPI_FilterPid;

typedef struct
{
	napi_ref ref;
	napi_env env;
	napi_ref groups;
} NAPI_Dash;


#define NAPI_GPAC \
	GPAC_NAPI *gpac;\
	if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) { \
		napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance"); \
		return NULL;\
	}

napi_value filter_enum_pid_props(napi_env env, napi_value cbk_fun, GF_FilterPid *pid);
napi_value filter_get_pid_property(napi_env env, char *pname, GF_FilterPid *pid, Bool is_info);
napi_value fs_wrap_filter(napi_env env, GF_FilterSession *fs, GF_Filter *filter);
napi_status prop_from_napi(napi_env env, napi_value prop, u32 p4cc, char *pname, u32 custom_type, GF_PropertyValue *p);
napi_value prop_to_napi(napi_env env, const GF_PropertyValue *p, u32 p4cc);
napi_value frac_to_napi(napi_env env, const GF_Fraction *frac);
napi_value frac64_to_napi(napi_env env, const GF_Fraction64 *frac);

NAPI_FilterPacket *wrap_filter_packet(napi_env env, GF_FilterPacket *pck, Bool is_src);
napi_value wrap_filterevent(napi_env env, GF_FilterEvent *evt, napi_value *for_val);

napi_type_tag filtersession_tag = {0x475041434e4f4445, 0x2053455353494f4e};
napi_type_tag filter_tag = {0x475041434e4f4445, 0x2046494c54455220};
napi_type_tag filterpid_tag = {0x475041434e4f4445, 0x46696c7472506964};
napi_type_tag filterpck_tag = {0x475041434e4f4445, 0x47465041434b4554};
napi_type_tag filterevent_tag = {0x475041434e4f4445, 0x4746464556454e54};
napi_type_tag dashbind_tag = {0x475041434e4f4445, 0x44415348494e4a53};
napi_type_tag fileio_tag = {0x475041434e4f4445,   0x474646494c45494f};
napi_type_tag httpsess_tag = {0x475041434e4f4445, 0x474646494cf0df5c};

napi_type_tag rmtclient_tag = {0x475041434e4f4445, 0x174646494cf0df5c};

napi_value dashin_bind(napi_env env, napi_callback_info info);
void dashin_detach_binding(NAPI_Filter *napi_f);

napi_value httpout_bind(napi_env env, napi_callback_info info);
void httpout_detach_binding(NAPI_Filter *napi_f);


void dummy_finalize(napi_env env, void* finalize_data, void* finalize_hint) {
}

napi_value gpac_init(napi_env env, napi_callback_info info)
{
	NAPI_GPAC
	NARG_ARGS(2, 0)

	NARG_U32(mem_track, 0, 0);
	NARG_STR(profile, 1, NULL);

	if (!gpac->is_init) {
		//copy profile as we will destroy gpac->str_buf
		if (profile) profile = strdup(profile);
		if (gpac->str_buf) gf_free(gpac->str_buf);
		gpac->str_buf = NULL;
		gpac->str_buf_alloc = 0;
		gf_sys_close();
		gf_sys_init(mem_track, profile);
		gpac->is_init = GF_TRUE;
		if (profile) free(profile);
	} else {
		GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[NAPI] libgpac already initialized, ignoring init(%d, %s)\n", mem_track, profile ? profile : "NULL"));
	}
	return NULL;
}

napi_value gpac_e2s(napi_env env, napi_callback_info info)
{
	napi_value val;
	NARG_ARGS(1, 1)
	NARG_S32(e, 0, 0);
	NAPI_CALL( napi_create_string_utf8(env, gf_error_to_string(e), NAPI_AUTO_LENGTH, &val) );
	return val;
}

napi_value gpac_set_logs(napi_env env, napi_callback_info info)
{
	NARG_ARGS(2, 1)
	NARG_STR(logs, 0, NULL);
	NARG_BOOL(reset, 1, GF_FALSE);
	gf_log_set_tools_levels(logs, reset);
	return NULL;
}

napi_value gpac_sys_clock(napi_env env, napi_callback_info info)
{
	napi_status status;
	napi_value val;
	NAPI_CALL( napi_create_uint32(env, gf_sys_clock(), &val) );
	return val;
}

napi_value gpac_sys_clock_high_res(napi_env env, napi_callback_info info)
{
	napi_status status;
	napi_value val;
	NAPI_CALL( napi_create_bigint_uint64(env, gf_sys_clock_high_res(), &val) );
	return val;
}

napi_value gpac_sys_keyname(napi_env env, napi_callback_info info)
{
	napi_value val;
#ifndef GPAC_DISABLE_SVG
	const char *gf_dom_get_friendly_name(u32 key_identifier);

	NARG_ARGS(1, 1)
	NARG_S32(code, 0, 0);
	NAPI_CALL( napi_create_string_utf8(env, gf_dom_get_friendly_name(code), NAPI_AUTO_LENGTH, &val) );
#else
	NAPI_CALL( napi_create_string_utf8(env, "unknown", NAPI_AUTO_LENGTH, &val) );
#endif
	return val;
}


static u32 RMT_CLIENT_PROP_PEER_ADDRESS = 0;
static u32 RMT_CLIENT_PROP_ON_DATA = 1;
static u32 RMT_CLIENT_PROP_ON_CLOSE = 2;


#define RMT_CLIENT\
	RMT_ClientCtx* client=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &rmtclient_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a RMTClient object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &client) );\


napi_value rmt_client_getter(napi_env env, napi_callback_info info) {
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	RMT_CLIENT

	if (!client)
		return NULL;

	if (magic == &RMT_CLIENT_PROP_PEER_ADDRESS) {
		const char *peer = gf_rmt_get_peer_address(client);
		if (!peer) return NULL;
		NAPI_CALL( napi_create_string_utf8(env, peer, NAPI_AUTO_LENGTH, &ret) );
		return ret;
	}

	return NULL;

}

typedef struct {
	u32 type;

	napi_env env;
	napi_ref _this;
	napi_ref fun;

	void* client;

} NAPI_RMT_Task ;

typedef struct {

	NAPI_RMT_Task* task;

	napi_value arg;

} NAPI_RMT_Task_Call ;


void napi_rmt_task_del(napi_env env, NAPI_RMT_Task* task) {

	if (task && task->type == RMT_CALLBACK_NODE) {
		uint32_t res;
		if (task->_this) {
			napi_reference_unref(env, task->_this, &res);
			napi_delete_reference(env, task->_this);
		}
		task->_this = NULL;

		if (task->fun) {
			napi_reference_unref(env, task->fun, &res);
			napi_delete_reference(env, task->fun);
		}
		task->fun = NULL;

		gf_free(task);

	}

}



Bool fs_flush_rmt(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms);

void gpac_rmt_client_on_delete(void *udta) {

	NAPI_RMT_Task* task = udta;

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

	napi_env env = task->env;

	GPAC_NAPI *gpac;
	if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) {
		napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance");
		return;
	}

	if (task->client) {
		RMT_ClientCtx* client = task->client;
		NAPI_RMT_Task* datatask = gf_rmt_client_get_on_data_task(client);
		gf_rmt_client_set_on_data_cbk(client, NULL, NULL);
		napi_rmt_task_del(env, datatask);

		gf_rmt_client_set_on_del_cbk(client, NULL, NULL);
	}

	if (!gpac->rmt_delete_calls) gpac->rmt_delete_calls = gf_list_new();
	gf_list_add(gpac->rmt_delete_calls, task );

	if (gpac->fs_rmt_handler) {
		gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTClientOnData");
	}
	else {
		GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("gpac_rmt_client_on_delete with no fs_rmt_handler\n"));
	}



}

void gpac_rmt_client_on_data(void *udta, const u8* payload, u64 size, Bool is_binary) {

	NAPI_RMT_Task* task = udta;

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

	napi_env env = task->env;

	GPAC_NAPI *gpac;
	if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) {
		napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance");
		return;
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_client_on_data binary %d size %llu: %.*s\n", is_binary, size, size, payload));

	napi_value arg;
	// napi_status status; //TODO: check status
	if (is_binary) {
		void* abdata;
		/*status = */napi_create_arraybuffer(env, size, &abdata, &arg);
		memcpy(abdata, payload, size);
	}
	else {
		/*status = */napi_create_string_utf8(env, (const char*)payload, size, &arg);
	}

	NAPI_RMT_Task_Call* on_data_call;
	GF_SAFEALLOC(on_data_call, NAPI_RMT_Task_Call);
	on_data_call->task = task;
	on_data_call->arg = arg;


	if (!gpac->rmt_task_calls) gpac->rmt_task_calls = gf_list_new();
	gf_list_add(gpac->rmt_task_calls, on_data_call );

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_client_on_data added call %p\n", on_data_call));

	if (gpac->fs_rmt_handler) {

		gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTClientOnData");

	}
	else {
		GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("gpac_rmt_client_on_data with no fs_rmt_handler\n"));
	}



}

napi_value rmt_client_setter(napi_env env, napi_callback_info info) {

	NAPI_GPAC

	NARG_ARGS_THIS_MAGIC(1, 1)

	RMT_CLIENT

	if (magic == &RMT_CLIENT_PROP_ON_DATA) {

		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);

		if (rtype == napi_null || rtype == napi_undefined ) {

			NAPI_RMT_Task* task = gf_rmt_client_get_on_data_task(client);
			napi_rmt_task_del(env, task);

			gf_rmt_client_set_on_data_cbk(client, NULL, NULL);

		} else if (rtype == napi_function) {

			if (!gpac->rmt_ctx) {
				napi_value global, name;
				NAPI_CALL( napi_get_global(env, &global) );
				NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) );
				NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) )
			}


			NAPI_RMT_Task *task;
			GF_SAFEALLOC(task, NAPI_RMT_Task);

			task->type = RMT_CALLBACK_NODE;
			task->env = env;
			task->client = client;
			napi_create_reference(env, this_val, 1, &task->_this);
			napi_create_reference(env, argv[0], 1, &task->fun);

			gf_rmt_client_set_on_data_cbk(client, task, gpac_rmt_client_on_data);

		}

	}
	else if (magic == &RMT_CLIENT_PROP_ON_CLOSE) {

		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);

		if (rtype == napi_null || rtype == napi_undefined ) {

			NAPI_RMT_Task* task = gf_rmt_client_get_on_del_task(client);
			if (task && task->type == RMT_CALLBACK_NODE) {
				// reset the js function but keep the ref to the client for on_delete
				uint32_t res;
				napi_reference_unref(env, task->fun, &res);
				napi_delete_reference(env, task->fun);
				task->fun = NULL;
			}



		} else if (rtype == napi_function) {

			if (!gpac->rmt_ctx) {
				napi_value global, name;
				NAPI_CALL( napi_get_global(env, &global) );
				NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) );
				NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) )
			}


			NAPI_RMT_Task *task = gf_rmt_client_get_on_del_task(client);
			if (!task)
				GF_SAFEALLOC(task, NAPI_RMT_Task);

			task->type = RMT_CALLBACK_NODE;
			task->env = env;
			task->client = client;
			napi_create_reference(env, this_val, 1, &task->_this);
			napi_create_reference(env, argv[0], 1, &task->fun);

			gf_rmt_client_set_on_del_cbk(client, task, gpac_rmt_client_on_delete);

		}

	}

	return NULL;

}

napi_value rmt_client_send(napi_env env, napi_callback_info info) {


	NARG_ARGS_THIS(1,1)
	RMT_CLIENT

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got client %p\n", client));

	bool is_arraybuffer = 0;
	NAPI_CALL( napi_is_arraybuffer(env, argv[0], &is_arraybuffer) );

	if (is_arraybuffer) {

		u8* data=NULL;
		size_t len=0;
		NAPI_CALL(napi_get_arraybuffer_info(env, argv[0], (void **)&data, &len) );

		GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got bin data of size %d\n", len));

		gf_rmt_client_send_to_ws(client, (const char*)data, len, GF_TRUE);

		//free(data); //freed elsewhere???
	}
    else {
		NARG_STR_ALLOC(data, 0, NULL);

		GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("rmt_client_send got string data %s\n", data));

		gf_rmt_client_send_to_ws(client, data, strlen(data), GF_FALSE);

		gf_free(data);
	}


	return NULL;

}

napi_value grap_wrap_rmt_new_client(napi_env env, RMT_ClientCtx* client) {
	napi_status status;
	napi_value obj;

	napi_property_descriptor rmt_client_properties[] = {
		{ "peer_address", NULL, NULL, rmt_client_getter, NULL, NULL, napi_enumerable, &RMT_CLIENT_PROP_PEER_ADDRESS},
		{ "on_data", NULL, NULL, NULL, rmt_client_setter, NULL, napi_enumerable, &RMT_CLIENT_PROP_ON_DATA},
		{ "on_close", NULL, NULL, NULL, rmt_client_setter, NULL, napi_enumerable, &RMT_CLIENT_PROP_ON_CLOSE},
		{ "send", NULL, rmt_client_send, NULL, NULL, NULL, napi_enumerable, NULL},
	};

	NAPI_CALL( napi_create_object(env, &obj) );

	NAPI_CALL( napi_define_properties(env, obj, sizeof(rmt_client_properties)/sizeof(napi_property_descriptor), rmt_client_properties) );

	NAPI_CALL( napi_type_tag_object(env, obj, &rmtclient_tag) );

	NAPI_CALL( napi_wrap(env, obj, client, dummy_finalize, NULL, NULL) );

	NAPI_RMT_Task *task;
	GF_SAFEALLOC(task, NAPI_RMT_Task);

	task->type = RMT_CALLBACK_NODE;
	task->env = env;
	task->client = client;
	napi_create_reference(env, obj, 1, &task->_this);

	gf_rmt_client_set_on_del_cbk(client, task, gpac_rmt_client_on_delete);

	return obj;
}


Bool fs_flush_rmt(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms)
{
	NAPI_Session *napi_fs = callback;
	napi_env env = napi_fs->env;
	GPAC_NAPI *gpac=NULL;

	napi_get_instance_data(env, (void **) &gpac);
	if (!gpac) return GF_FALSE;


	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d fs_flush_rmt clients %d tasks %d deletes %d gpac->rmt_ctx %p\n", __FILE__, __LINE__, gf_list_count(gpac->rmt_new_clients), gf_list_count(gpac->rmt_task_calls), gf_list_count(gpac->rmt_delete_calls), gpac->rmt_ctx));

	while (gf_list_count(gpac->rmt_delete_calls)) {

		napi_value res;

		NAPI_RMT_Task* task = gf_list_pop_front(gpac->rmt_delete_calls);

		napi_value _this;
		napi_get_reference_value(env, task->_this, &_this);

		RMT_ClientCtx* client;
		napi_remove_wrap(env, _this, (void**) &client);

		napi_wrap(env, _this, NULL, NULL, NULL, NULL);

		if (task->fun) {
			napi_value fun;
			napi_get_reference_value(env, task->fun, &fun);
			napi_make_callback(env, gpac->rmt_ctx, _this, fun, 0, NULL, &res);
		}

		napi_rmt_task_del(env, task);


	}

	while (gf_list_count(gpac->rmt_new_clients)) {

		void* client = gf_list_pop_front(gpac->rmt_new_clients);

		RMT_WS* rmt = gf_rmt_client_get_rmt((RMT_ClientCtx*) client);
		NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt);
		if (task && gpac->rmt_ctx) {

			napi_value res, _this, fun;
			// napi_status status; //TODO: check status

			napi_get_reference_value(env, task->_this, &_this);
			napi_get_reference_value(env, task->fun, &fun);

			napi_value jsclient = grap_wrap_rmt_new_client(env, client);
			napi_valuetype rtype;
			napi_typeof(env, jsclient, &rtype);

			/*status = */napi_make_callback(env, gpac->rmt_ctx, _this, fun, 1, &jsclient, &res);
		}


	}


	while (gf_list_count(gpac->rmt_task_calls)) {

		NAPI_RMT_Task_Call* call = gf_list_pop_front(gpac->rmt_task_calls);

		if (call && call->task) {

			if (gpac->rmt_ctx) {

				napi_value res, _this, fun;
				// napi_status status; //TODO: check status

				napi_get_reference_value(env, call->task->_this, &_this);
				napi_get_reference_value(env, call->task->fun, &fun);

				/*status = */napi_make_callback(env, gpac->rmt_ctx, _this, fun, 1, &call->arg, &res);
				// else {
				// 	napi_extended_error_info* error_info = NULL;
				// 	napi_get_last_error_info(env, &error_info);
				// 	const char* err_message = error_info->error_message;
				// 	fprintf(stderr, "ERR cb status %d err: %s\n", status, err_message);
				// }
			}

			gf_free(call);
		}


	}

	if (!gf_list_count(gpac->rmt_task_calls) && !gf_list_count(gpac->rmt_new_clients)) {
		return GF_FALSE;
	}
	*reschedule_ms = 10;
	return GF_TRUE;
}

napi_value gpac_enable_rmtws(napi_env env, napi_callback_info info)
{
	NARG_ARGS(1, 1)
	NARG_BOOL(enable, 0, GF_TRUE);
	NAPI_GPAC
	gpac->is_init = GF_TRUE;
	gf_sys_enable_rmtws(enable);
	return NULL;
}
napi_value gpac_enable_userws(napi_env env, napi_callback_info info)
{
	NARG_ARGS(1, 1)
	NARG_BOOL(enable, 0, GF_TRUE);
	NAPI_GPAC
	gpac->is_init = GF_TRUE;
	gf_sys_enable_userws(enable);
	return NULL;
}


napi_value gpac_set_args(napi_env env, napi_callback_info info)
{
	NAPI_GPAC
	bool is_array;
	u32 i, nb_items=0;
	napi_value val;
	NARG_ARGS(1, 1)

	gpac->is_init = GF_TRUE;

	NAPI_CALL( napi_is_array(env, argv[0], &is_array) );
	if (!is_array) {
		napi_throw_error(env, NULL, "NAPI call failed");
		return NULL;
	}
	NAPI_CALL( napi_get_array_length(env, argv[0], &nb_items) );

	if (!nb_items) return NULL;
	if (gpac->argv) {
		for (i=0; i<gpac->nb_args; i++) {
			gf_free(gpac->argv[i]);
		}
		gf_free(gpac->argv);
	}
	gpac->argv = gf_malloc(sizeof(char *) * nb_items);
	gpac->nb_args = nb_items;
	for (i=0; i<gpac->nb_args; i++) {
		size_t size;
		NAPI_CALL( napi_get_element(env, argv[0], i, &val) );
		NAPI_CALL( napi_get_value_string_utf8(env, val, NULL, 0, &size) );
		gpac->argv[i] = gf_malloc(sizeof(char) * (size+1) );
		NAPI_CALL( napi_get_value_string_utf8(env, val, gpac->argv[i], size+1, &size) );
		gpac->argv[i][size]=0;
	}
	gf_sys_set_args(gpac->nb_args, (const char **) gpac->argv);
	return NULL;
}


static void gpac_napi_finalize(napi_env env, void* finalize_data, void* finalize_hint)
{

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_napi_finalize() \n"));
	GPAC_NAPI *inst = finalize_data;

	if (inst->nb_args) {
		u32 i;
		for (i=0; i<inst->nb_args; i++) {
			gf_free(inst->argv[i]);
		}
		gf_free(inst->argv);
	}

	if (inst->str_buf) gf_free(inst->str_buf);

	RMT_WS* rmt = (RMT_WS*) gf_sys_get_rmtws();
	NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt);
	gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);
	napi_rmt_task_del(env, task);

	rmt = (RMT_WS*) gf_sys_get_userws();
	task = gf_rmt_get_on_new_client_task(rmt);
	gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);
	napi_rmt_task_del(env, task);

	if (inst->rmt_new_clients) {
		gf_list_del(inst->rmt_new_clients);
		inst->rmt_new_clients = NULL;
	}
	if (inst->rmt_delete_calls) {
		gf_list_del(inst->rmt_delete_calls);
		inst->rmt_delete_calls = NULL;
	}
	if (inst->rmt_task_calls) {
		while (gf_list_count(inst->rmt_task_calls)) {
			NAPI_RMT_Task_Call *call = gf_list_pop_back(inst->rmt_task_calls);
			gf_free(call);
		}
		gf_list_del(inst->rmt_task_calls);
	}
	if (inst->rmt_ctx) {
		napi_async_destroy(env, inst->rmt_ctx);
		inst->rmt_ctx = NULL;
	}

	//close gpac
	gf_sys_close();

#if defined(GPAC_MEMORY_TRACKING)
	if (gf_memory_size() || gf_file_handles_count()) {
		gf_log_set_tools_levels("mem@info", GF_FALSE);
		gf_memory_print();
	}
#endif

	//not allocated using gf_malloc !!
	free(inst);
}

#define FILTERPCK\
	NAPI_FilterPacket *napi_pck=NULL;\
	GF_FilterPacket *pck=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &filterpck_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a FilterPacket object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &napi_pck) );\
	pck = napi_pck->pck;\
	if (!pck) {\
		napi_throw_error(env, NULL, napi_pck->is_src ? "Detached packet, already drop" : "Detached packet, already sent");\
		return NULL;\
	}\


static u32 FS_PROP_PCK_DTS = 0;
static u32 FS_PROP_PCK_CTS = 1;
static u32 FS_PROP_PCK_SAP = 2;
static u32 FS_PROP_PCK_DUR = 3;
static u32 FS_PROP_PCK_SIZE = 4;
static u32 FS_PROP_PCK_DATA = 5;
static u32 FS_PROP_PCK_START = 6;
static u32 FS_PROP_PCK_END = 7;
static u32 FS_PROP_PCK_TIMESCALE = 8;
static u32 FS_PROP_PCK_INTERLACED = 9;
static u32 FS_PROP_PCK_CORRUPTED = 10;
static u32 FS_PROP_PCK_SEEK = 11;
static u32 FS_PROP_PCK_BYTE_OFFSET = 12;
static u32 FS_PROP_PCK_ROLL = 13;
static u32 FS_PROP_PCK_CRYPT = 14;
static u32 FS_PROP_PCK_CLOCK = 15;
static u32 FS_PROP_PCK_CAROUSEL = 16;
static u32 FS_PROP_PCK_SEQNUM = 17;
static u32 FS_PROP_PCK_DEPS = 18;
static u32 FS_PROP_PCK_FRAME_IFCE = 19;
static u32 FS_PROP_PCK_BLOCKING = 20;

napi_value filterpck_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTERPCK

	if (magic == &FS_PROP_PCK_DTS) {
		u64 dts = gf_filter_pck_get_dts(pck);
		if (dts==GF_FILTER_NO_TS) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			NAPI_CALL( napi_create_int64(env, (s64) dts, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PCK_CTS) {
		u64 cts = gf_filter_pck_get_cts(pck);
		if (cts==GF_FILTER_NO_TS) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			NAPI_CALL( napi_create_int64(env, (s64) cts, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PCK_SAP) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_sap(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_DUR) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_duration(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_SIZE) {
		u32 size;
		gf_filter_pck_get_data(pck, &size);
		NAPI_CALL( napi_create_uint32(env, size, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_DATA) {
		u32 size;
		u8 *data = (u8 *) gf_filter_pck_get_data(pck, &size);
		if (!data) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			//TODO, mark as readonly using napi_pck->readonly !!
			NAPI_CALL(napi_create_external_arraybuffer(env, data, size, dummy_finalize, NULL, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PCK_START) {
		Bool s, e;
		gf_filter_pck_get_framing(pck, &s, &e);
		NAPI_CALL( napi_get_boolean(env, s, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_END) {
		Bool s, e;
		gf_filter_pck_get_framing(pck, &s, &e);
		NAPI_CALL( napi_get_boolean(env, e, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_TIMESCALE) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_timescale(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_INTERLACED) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pck_get_interlaced(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_CORRUPTED) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pck_get_corrupted(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_SEEK) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pck_get_seek_flag(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_BYTE_OFFSET) {
		u64 bo = gf_filter_pck_get_byte_offset(pck);
		if (bo==GF_FILTER_NO_BO) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			NAPI_CALL( napi_create_int64(env, (s64) bo, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PCK_ROLL) {
		NAPI_CALL( napi_create_int32(env, gf_filter_pck_get_roll_info(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_CRYPT) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_crypt_flags(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_CLOCK) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_clock_type(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_CAROUSEL) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_carousel_version(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_SEQNUM) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_seq_num(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_DEPS) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pck_get_dependency_flags(pck), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_FRAME_IFCE) {
		NAPI_CALL( napi_get_boolean(env, (gf_filter_pck_get_frame_interface(pck)!=NULL), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PCK_BLOCKING) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pck_is_blocking_ref(pck), &ret) );
		return ret;
	}
	return NULL;
}

napi_value filterpck_setter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS_MAGIC(1, 1)
	FILTERPCK

	if (magic && napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot set property on input packet");
		return NULL;
	}
	if (magic == &FS_PROP_PCK_DTS) {
		u64 dts;
		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);
		if (rtype == napi_null) {
			dts = GF_FILTER_NO_TS;
		} else {
			NAPI_CALL( napi_get_value_int64(env, argv[0], (s64 *) &dts) );
		}
		gf_filter_pck_set_dts(pck, dts);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_CTS) {
		u64 cts;
		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);
		if (rtype == napi_null) {
			cts = GF_FILTER_NO_TS;
		} else {
			NAPI_CALL( napi_get_value_int64(env, argv[0], (s64 *) &cts) );
		}
		gf_filter_pck_set_dts(pck, cts);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_SAP) {
		NARG_U32(sap, 0, 0)
		gf_filter_pck_set_sap(pck, sap);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_DUR) {
		NARG_U32(dur, 0, 0)
		gf_filter_pck_set_duration(pck, dur);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_SIZE) {
		napi_throw_error(env, NULL, "Cannot set size property");
		return NULL;
	}
	if (magic == &FS_PROP_PCK_DATA) {
		napi_throw_error(env, NULL, "Cannot set data property");
		return NULL;
	}
	if (magic == &FS_PROP_PCK_START) {
		NARG_BOOL(start, 0, 0);
		Bool s, e;
		gf_filter_pck_get_framing(pck, &s, &e);
		gf_filter_pck_set_framing(pck, start, e);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_END) {
		NARG_BOOL(end, 0, 0);
		Bool s, e;
		gf_filter_pck_get_framing(pck, &s, &e);
		gf_filter_pck_set_framing(pck, s, end);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_TIMESCALE) {
		napi_throw_error(env, NULL, "Cannot set timescale on output packet");
		return NULL;
	}
	if (magic == &FS_PROP_PCK_INTERLACED) {
		NARG_BOOL(ilced, 0, 0);
		gf_filter_pck_set_interlaced(pck, ilced);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_CORRUPTED) {
		NARG_BOOL(corr, 0, 0);
		gf_filter_pck_set_interlaced(pck, corr);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_SEEK) {
		NARG_BOOL(seek, 0, 0);
		gf_filter_pck_set_interlaced(pck, seek);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_BYTE_OFFSET) {
		u64 bo;
		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);
		if (rtype == napi_null) {
			bo = GF_FILTER_NO_BO;
		} else {
			NAPI_CALL( napi_get_value_int64(env, argv[0], (s64 *) &bo) );
		}
		gf_filter_pck_set_byte_offset(pck, bo);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_ROLL) {
		NARG_U32(roll, 0, 0);
		gf_filter_pck_set_interlaced(pck, roll);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_CRYPT) {
		NARG_U32(crypt, 0, 0);
		gf_filter_pck_set_interlaced(pck, crypt);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_CLOCK) {
		NARG_U32(clock, 0, 0);
		gf_filter_pck_set_clock_type(pck, clock);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_CAROUSEL) {
		NARG_U32(carv, 0, 0);
		gf_filter_pck_set_carousel_version(pck, carv);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_SEQNUM) {
		NARG_U32(seqnum, 0, 0);
		gf_filter_pck_set_seq_num(pck, seqnum);
		return NULL;
	}
	if (magic == &FS_PROP_PCK_DEPS) {
		NARG_U32(deps, 0, 0);
		gf_filter_pck_set_dependency_flags(pck, deps);
		return NULL;
	}
	return NULL;
}


napi_value filterpck_enum_props(napi_env env, napi_callback_info info)
{
	napi_value global;
	NARG_ARGS_THIS(1, 1)
	FILTERPCK

	napi_valuetype rtype;
	napi_typeof(env, argv[0], &rtype);
	if (rtype != napi_function) {
		napi_throw_error(env, NULL, "property enumerator is not a function");
		return NULL;
	}

	NAPI_CALL( napi_get_global(env, &global) );
	u32 prop_idx=0;
	while (1) {
		napi_handle_scope scope_h;
		u32 p4cc=0;
		napi_value fun_args[3], res;
		const char *pname = NULL;
		const GF_PropertyValue *prop = gf_filter_pck_enum_properties(pck, &prop_idx, &p4cc, &pname);
		if (!prop) break;

		NAPI_CALL(napi_open_handle_scope(env, &scope_h) );

		if (p4cc) {
			pname = gf_props_4cc_get_name(p4cc);
			if (!pname) pname = gf_4cc_to_str(p4cc);
		}

		NAPI_CALL( napi_create_string_utf8(env, pname, NAPI_AUTO_LENGTH, &fun_args[0]) );
		fun_args[1] = prop_to_napi(env, prop, p4cc);
		NAPI_CALL( napi_create_int32(env, prop->type, &fun_args[2]) );

		NAPI_CALL(napi_call_function(env, global, argv[0], 3, fun_args, &res) );
		NAPI_CALL(napi_close_handle_scope(env, scope_h) );
	}
	return NULL;
}


napi_value filterpck_get_prop(napi_env env, napi_callback_info info)
{
	const GF_PropertyValue *prop = NULL;
	NARG_ARGS_THIS(1, 1)
	FILTERPCK

	NARG_STR(pname, 0, NULL);

	u32 p4cc = gf_props_get_id(pname);

	if (p4cc)
		prop = gf_filter_pck_get_property(pck, p4cc);
	else
		prop = gf_filter_pck_get_property_str(pck, pname);

	if (!prop) {
		napi_value res;
		NAPI_CALL(napi_get_null(env, &res) );
		return res;
	}
	return prop_to_napi(env, prop, p4cc);
}

napi_value filterpck_ref(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPCK
	//if dangling, no ref
	if (napi_pck->dangling) return NULL;
	napi_pck->pck = gf_filter_pck_ref_ex(napi_pck->pck);
	//add ref
	u32 nbrefs;
	napi_reference_ref(env, napi_pck->ref, &nbrefs);
	return NULL;
}
napi_value filterpck_unref(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPCK
	//if dangling, no ref
	if (napi_pck->dangling) return NULL;
	gf_filter_pck_unref(pck);
	//remove ref
	u32 nbrefs;
	napi_reference_unref(env, napi_pck->ref, &nbrefs);
	if (!nbrefs)
		napi_pck->pck = NULL;
	return NULL;
}
napi_value filterpck_discard(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPCK

	//if dangling, accept discard as a way to free memory
	if (napi_pck->is_src && !napi_pck->dangling) {
		napi_throw_error(env, NULL, "Cannot discard an input packet");
		return NULL;
	}
	gf_filter_pck_discard(pck);

	//detach
	u32 rcount;
	napi_reference_unref(env, napi_pck->ref, &rcount);
	napi_delete_reference(env, napi_pck->ref);
	napi_pck->ref = NULL;
	napi_pck->pck = NULL;
	return NULL;
}

napi_value filterpck_clone(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_ARGS_THIS(1, 0)
	FILTERPCK
	GF_FilterPacket *cached_pck=NULL;
	NAPI_FilterPacket *napi_cached_pck=NULL;
	GF_FilterPacket *clone=NULL;

	if (argc>=1) {
		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);
		if (rtype == napi_object) {
			NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpck_tag, &_tag) );
			if (!_tag) {
				napi_throw_error(env, NULL, "Cached packet is not a FilterPacket object");
				return NULL;
			}
			NAPI_CALL( napi_unwrap(env, argv[0], (void**) &napi_cached_pck) );
			cached_pck = napi_cached_pck->pck;
		}
	}

	clone = gf_filter_pck_dangling_copy(pck, cached_pck);
	if (!clone) {
		napi_throw_error(env, NULL, "Failed to clone packet");
		return NULL;
	}
	//same packet used, return same object
	if (clone == cached_pck) {
		NAPI_CALL( napi_get_reference_value(env, napi_cached_pck->ref, &ret) );
		return ret;
	}
	//discard previous napi_cached_pck
	if (napi_cached_pck) {
		u32 nbrefs;
		napi_reference_unref(env, napi_cached_pck->ref, &nbrefs);
		napi_delete_reference(env, napi_cached_pck->ref);
		napi_cached_pck->ref = NULL;
		//napi_cached_pck will be freed when GC collects previous cached_pck
	}

	NAPI_FilterPacket *napi_cloned = wrap_filter_packet(env, clone, GF_FALSE);
	if (!napi_cloned) {
		napi_throw_error(env, NULL, "Failed to clone packet");
		return NULL;
	}
	napi_cloned->readonly = 0;
	napi_cloned->dangling = 1;
	NAPI_CALL( napi_get_reference_value(env, napi_cloned->ref, &ret) );
	return ret;
}

napi_value filterpck_readonly(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPCK
	if (napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot set readonly mode for input packet");
		return NULL;
	}
	gf_filter_pck_set_readonly(pck);
	return NULL;
}
napi_value filterpck_send(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPCK
	if (napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot send an input packet");
		return NULL;
	}
	gf_filter_pck_send(pck);

	//detach
	u32 rcount;
	napi_reference_unref(env, napi_pck->ref, &rcount);
	if (!rcount) {
		napi_delete_reference(env, napi_pck->ref);
		napi_pck->ref = NULL;
	}
	napi_pck->pck = NULL;
	return NULL;
}


napi_value filterpck_copy_props(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTERPCK
	GF_FilterPacket *from_pck=NULL;
	NAPI_FilterPacket *napi_from_pck=NULL;
	if (napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot copy properties on input packet");
		return NULL;
	}

	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpck_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "No packet to copy properties from");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &napi_from_pck) );
	from_pck = napi_from_pck->pck;

	gf_filter_pck_merge_properties(from_pck, pck);
	return NULL;
}


napi_value filterpck_set_prop(napi_env env, napi_callback_info info)
{
	Bool is_null = GF_FALSE;
	u32 p4cc;
	GF_Err e;
	GF_PropertyValue p;
	NARG_ARGS_THIS(3, 1)
	FILTERPCK
	if (napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot set property on input packet");
		return NULL;
	}

	NARG_STR(pname, 0, NULL);
	if (!pname) return NULL;

	p4cc = gf_props_get_id(pname);

	if (argc==1) {
		is_null = GF_TRUE;
	} else {
		napi_valuetype rtype;
		napi_typeof(env, argv[1], &rtype);
		if (rtype == napi_null)
			is_null = GF_TRUE;
		else {
			NARG_U32(custom_type,2,0)
			status = prop_from_napi(env, argv[1], p4cc, pname, custom_type, &p);
			if (status != napi_ok) {
				napi_throw_error(env, NULL, "Cannot set packet property, invalid property value");
				return NULL;
			}
		}
	}
	if (p4cc) {
		e = gf_filter_pck_set_property(pck, p4cc, is_null ? NULL : &p);
	} else {
		e = gf_filter_pck_set_property_dyn(pck, pname, is_null ? NULL : &p);
	}
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Cannot set packet property");
	}
	if (!is_null) {
		gf_props_reset_single(&p);
	}
	return NULL;
}

napi_value filterpck_truncate(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTERPCK

	if (napi_pck->is_src) {
		napi_throw_error(env, NULL, "Cannot truncate an input packet");
		return NULL;
	}
	NARG_U32(size, 0, 0)
	gf_filter_pck_truncate(pck, size);
	return NULL;
}


static u32 FS_PROP_PID_NAME = 0;
static u32 FS_PROP_PID_FILTERNAME = 1;
static u32 FS_PROP_PID_EOS = 2;
static u32 FS_PROP_PID_HAS_SEEN_EOS = 3;
static u32 FS_PROP_PID_HAS_RECEIVED = 4;
static u32 FS_PROP_PID_WOULD_BLOCK = 5;
static u32 FS_PROP_PID_MAX_BUFFER = 6;
static u32 FS_PROP_PID_BUFFER = 7;
static u32 FS_PROP_PID_BUFFER_FULL = 8;
static u32 FS_PROP_PID_FIRST_EMPTY = 9;
static u32 FS_PROP_PID_FIRST_CTS = 10;
static u32 FS_PROP_PID_NB_PCK_QUEUED = 11;
static u32 FS_PROP_PID_TIMESCALE = 12;
static u32 FS_PROP_PID_MIN_PCK_DUR = 13;
static u32 FS_PROP_PID_PLAYING = 14;
static u32 FS_PROP_PID_NEXT_TS = 15;
static u32 FS_PROP_PID_SPARSE = 16;
static u32 FS_PROP_PID_HAS_DECODER = 17;

#define FILTERPID\
	GF_FilterPid *pid=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &filterpid_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a FilterPid object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &pid) );\

napi_value filterpid_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTERPID

	if (magic == &FS_PROP_PID_NAME) {
		const char *fname = gf_filter_pid_get_name(pid);
		if (fname) {
			NAPI_CALL( napi_create_string_utf8(env, fname, NAPI_AUTO_LENGTH, &ret) );
		} else {
			NAPI_CALL( napi_get_null(env, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PID_FILTERNAME) {
		const char *fname = gf_filter_pid_get_filter_name(pid);
		if (fname) {
			NAPI_CALL( napi_create_string_utf8(env, fname, NAPI_AUTO_LENGTH, &ret) );
		} else {
			NAPI_CALL( napi_get_null(env, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PID_EOS) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_eos(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_HAS_SEEN_EOS) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_has_seen_eos(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_HAS_RECEIVED) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_eos_received(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_WOULD_BLOCK) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_would_block(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_SPARSE) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_sparse(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_MAX_BUFFER) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pid_get_max_buffer(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_BUFFER) {
		NAPI_CALL( napi_create_int64(env, (s64) gf_filter_pid_query_buffer_duration(pid, GF_FALSE), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_BUFFER_FULL) {
		u64 dur = gf_filter_pid_query_buffer_duration(pid, GF_TRUE);
		NAPI_CALL( napi_get_boolean(env, (dur ? 1 : 0), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_FIRST_EMPTY) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_first_packet_is_empty(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_FIRST_CTS) {
		u64 cts;
		if (!gf_filter_pid_get_first_packet_cts(pid, &cts)) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else if (cts == GF_FILTER_NO_TS) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			NAPI_CALL( napi_create_int64(env, (s64) cts, &ret) );
		}
		return ret;
	}
	if (magic == &FS_PROP_PID_NB_PCK_QUEUED) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pid_get_packet_count(pid), &ret) );
        return ret;
	}
	if (magic == &FS_PROP_PID_TIMESCALE) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pid_get_timescale(pid), &ret) );
        return ret;
	}
	if (magic == &FS_PROP_PID_MIN_PCK_DUR) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_pid_get_min_pck_duration(pid), &ret) );
        return ret;
	}
	if (magic == &FS_PROP_PID_PLAYING) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_playing(pid), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_PID_NEXT_TS) {
		u64 cts = gf_filter_pid_get_next_ts(pid);
		if (cts == GF_FILTER_NO_TS) {
			NAPI_CALL( napi_get_null(env, &ret) );
		} else {
			NAPI_CALL( napi_create_int64(env, (s64) cts, &ret) );
		}
        return ret;
	}
	if (magic == &FS_PROP_PID_HAS_DECODER) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_pid_has_decoder(pid), &ret) );
        return ret;
	}
	return NULL;
}

napi_value filterpid_setter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS_MAGIC(1, 1)
	FILTERPID

	if (magic == &FS_PROP_PID_NAME) {
		NARG_STR(name, 0, NULL)
		gf_filter_pid_set_name(pid, name);
		return NULL;
	}
	if (magic == &FS_PROP_PID_EOS) {
		NARG_BOOL(val, 0, GF_FALSE)
		if (val) gf_filter_pid_set_eos(pid);
		return NULL;
	}
	if (magic == &FS_PROP_PID_MAX_BUFFER) {
		NARG_U32(val, 0, 0)
		gf_filter_pid_set_max_buffer(pid, val);
		return NULL;
	}
	if (magic == &FS_PROP_PID_BUFFER) {
		NARG_U32(val, 0, 0)
		gf_filter_pid_set_max_buffer(pid, val);
		return NULL;
	}
	return NULL;
}


napi_value filterpid_remove(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	gf_filter_pid_remove(pid);
	return NULL;
}

napi_value filterpid_enum_props(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	return filter_enum_pid_props(env, argv[0], pid);
}

napi_value filterpid_get_prop(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_STR(pname, 0, NULL)
	return filter_get_pid_property(env, pname, pid, GF_FALSE);
}
napi_value filterpid_get_info(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_STR(pname, 0, NULL)
	return filter_get_pid_property(env, pname, pid, GF_TRUE);
}

napi_value filterpid_copy_props(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NARG_ARGS_THIS(1,1)
	FILTERPID
	GF_FilterPid *ipid=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpid_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterPid object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &ipid) );

	e = gf_filter_pid_copy_properties(pid, ipid);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Cannot copy PID properties");
	}
	return NULL;
}

napi_value filterpid_reset_props(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NARG_ARGS_THIS(1,1)
	FILTERPID
	e = gf_filter_pid_reset_properties(pid);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Cannot reset PID properties");
	}
	return NULL;
}

napi_value filterpid_set_prop_internal(napi_env env, napi_callback_info info, Bool is_info)
{
	GF_PropertyValue p;
	GF_Err e;
	u32 p4cc=0;
	Bool is_null=GF_FALSE;
	NARG_ARGS_THIS(3,1)
	FILTERPID

	NARG_STR(pname, 0, NULL);
	if (!pname) return NULL;

	p4cc = gf_props_get_id(pname);

	if (argc==1) {
		is_null = GF_TRUE;
	} else {
		napi_valuetype rtype;
		napi_typeof(env, argv[1], &rtype);
		if (rtype == napi_null)
			is_null = GF_TRUE;
		else {
			NARG_U32(custom_type,2,0)
			status = prop_from_napi(env, argv[1], p4cc, pname, custom_type, &p);
			if (status != napi_ok) {
				napi_throw_error(env, NULL, "Cannot set PID property, invalid property value");
				return NULL;
			}
		}
	}
	if (p4cc) {
		if (is_info) {
			e = gf_filter_pid_set_info(pid, p4cc, is_null ? NULL : &p);
		} else {
			e = gf_filter_pid_set_property(pid, p4cc, is_null ? NULL : &p);
		}
	} else {
		if (is_info) {
			e = gf_filter_pid_set_info_dyn(pid, pname, is_null ? NULL : &p);
		} else {
			e = gf_filter_pid_set_property_dyn(pid, pname, is_null ? NULL : &p);
		}
	}
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Cannot set PID property");
	}
	if (!is_null) {
		gf_props_reset_single(&p);
	}
	return NULL;
}

napi_value filterpid_set_prop(napi_env env, napi_callback_info info)
{
	return filterpid_set_prop_internal(env, info, GF_FALSE);
}
napi_value filterpid_set_info(napi_env env, napi_callback_info info)
{
	return filterpid_set_prop_internal(env, info, GF_TRUE);
}

napi_value filterpid_clear_eos(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,0)
	FILTERPID
	NARG_BOOL(all_pids, 0, GF_FALSE);
	gf_filter_pid_clear_eos(pid, all_pids);
	return NULL;
}
napi_value filterpid_check_caps(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	gf_filter_pid_check_caps(pid);
	return NULL;
}
napi_value filterpid_discard_block(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	gf_filter_pid_discard_block(pid);
	return NULL;
}
napi_value filterpid_allow_direct_dispatch(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	gf_filter_pid_allow_direct_dispatch(pid);
	return NULL;
}
napi_value filterpid_get_clock_type(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_THIS
	FILTERPID
	NAPI_CALL( napi_create_uint32(env, gf_filter_pid_get_clock_info(pid, NULL, NULL), &ret) );
	return ret;
}

napi_value filterpid_get_clock_timestamp(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	u64 ts;
	u32 scale;
	gf_filter_pid_get_clock_info(pid, &ts, &scale);
	GF_Fraction64 frac = {ts, scale};
	return frac64_to_napi(env, &frac);
}

napi_value filterpid_is_filter_in_parents(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_ARGS_THIS(1,1)
	FILTERPID
	GF_Filter *f=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filter_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a Filter object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &f) );
	NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_filter_in_parents(pid, f), &ret) );
	return ret;
}


napi_value filterpid_get_buffer_occupancy(napi_env env, napi_callback_info info)
{
	napi_property_descriptor p_desc = {.attributes=napi_enumerable};
	napi_value obj;
	NARG_THIS
	FILTERPID

	u32 max_units = 0, nb_pck = 0, max_dur = 0, dur = 0;
	Bool in_final_flush = gf_filter_pid_get_buffer_occupancy(pid, &max_units, &nb_pck, &max_dur, &dur);
	in_final_flush = ! in_final_flush;

	NAPI_CALL(napi_create_object(env, &obj) );

#define SET_VAL(_val) \
	p_desc.utf8name = #_val; \
	NAPI_CALL(napi_create_uint32(env, _val, &p_desc.value) ); \
	NAPI_CALL(napi_define_properties(env, obj, 1, &p_desc) ); \

	SET_VAL(max_units);
	SET_VAL(nb_pck);
	SET_VAL(max_dur);
	SET_VAL(dur);

	p_desc.utf8name = "is_final_flush";
	NAPI_CALL(napi_get_boolean(env, in_final_flush, &p_desc.value) );
	NAPI_CALL(napi_define_properties(env, obj, 1, &p_desc) );
	return obj;
}

napi_value filterpid_loose_connect(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	gf_filter_pid_set_loose_connect(pid);
	return NULL;
}

napi_value filterpid_set_framing(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,0)
	FILTERPID
	NARG_BOOL(framed, 0, GF_TRUE);
	gf_filter_pid_set_framing_mode(pid, framed);
	return NULL;
}
napi_value filterpid_set_clock_mode(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_U32(ck_mode, 0, 0);
	gf_filter_pid_set_clock_mode(pid, ck_mode);
	return NULL;
}
napi_value filterpid_set_discard(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_BOOL(do_discard, 0, GF_TRUE);
	gf_filter_pid_set_discard(pid, do_discard);
	return NULL;
}
napi_value filterpid_require_source_id(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NARG_THIS
	FILTERPID
	e = gf_filter_pid_require_source_id(pid);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to require sourceID for PID");
	}
	return NULL;
}
napi_value filterpid_recompute_dts(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_BOOL(do_recompute, 0, GF_TRUE);
	gf_filter_pid_recompute_dts(pid, do_recompute);
	return NULL;
}

napi_value filterpid_resolve_template(napi_env env, napi_callback_info info)
{
	GF_Err e;
	char szTpl[GF_MAX_PATH];
	NARG_ARGS_THIS(3,1)
	napi_value ret;
	FILTERPID

	NARG_U32(file_idx, 1, 0);
	NARG_STR(suffix, 2, NULL);
	if (suffix) suffix = gf_strdup(suffix);

	NARG_STR(template, 0, NULL);
	if (!template) {
		napi_throw_error(env, NULL, "No template provided");
		if (suffix) gf_free(suffix);
		return NULL;
	}

	e = gf_filter_pid_resolve_file_template(pid, szTpl, template, file_idx, suffix);
	if (suffix) gf_free(suffix);

	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to resolve template for PID");
	}
	NAPI_CALL( napi_create_string_utf8(env, szTpl, NAPI_AUTO_LENGTH, &ret) );
	return ret;
}

napi_value filterpid_query_cap(napi_env env, napi_callback_info info)
{
	napi_value ret;
	u32 p4cc;
	const GF_PropertyValue *cap=NULL;
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NARG_STR(pname, 0, NULL);
	if (!pname) return NULL;

	p4cc = gf_props_get_id(pname);
	if (p4cc) {
		cap = gf_filter_pid_caps_query(pid, p4cc);
	} else {
		cap = gf_filter_pid_caps_query_str(pid, pname);
	}
	if (cap) return prop_to_napi(env, cap, p4cc);

	NAPI_CALL(napi_get_null(env, &ret));
	return ret;
}

napi_value filterpid_negotiate_cap(napi_env env, napi_callback_info info)
{
	u32 p4cc;
	GF_Err e;
	GF_PropertyValue p;
	NARG_ARGS_THIS(3,2)
	FILTERPID
	NARG_STR(pname, 0, NULL);
	if (!pname) return NULL;
	p4cc = gf_props_get_id(pname);
	NARG_U32(custom_type, 2, 0);

	NAPI_CALL( prop_from_napi(env, argv[1], p4cc, pname, custom_type, &p) );

	if (p4cc) {
		e = gf_filter_pid_negotiate_property(pid, p4cc, &p);
	} else {
		e = gf_filter_pid_negotiate_property_dyn(pid, pname, &p);
	}
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to negotiate cap for PID");
	}
	gf_props_reset_single(&p);
	return NULL;
}

void napi_pck_del(napi_env env, void *finalize_data, void* finalize_hint)
{
	NAPI_FilterPacket *napi_pck = (NAPI_FilterPacket *)finalize_data;
	//output packet not sent or dangling packet, discard it
	if (napi_pck->pck && (!napi_pck->is_src || napi_pck->dangling) )
		gf_filter_pck_discard(napi_pck->pck);

	//we still have a ref and a packet, unref - this cleans up calls to pck.ref() without pck.unref()
	//not true for dangling copy
	if (!napi_pck->dangling && napi_pck->ref && napi_pck->pck) {
		gf_filter_pck_unref(napi_pck->pck);
	}
	//if pid is still alive, remove ourselves
	//this is needed because the GC for this packet might happen before the GC for the session/custom filter
	//so we need to make sure we won't call the shared destructor callback
	if (napi_pck->pid_refs) {
		gf_list_del_item(napi_pck->pid_refs, napi_pck);
	}
	gf_free(napi_pck);
}

NAPI_FilterPacket *wrap_filter_packet(napi_env env, GF_FilterPacket *pck, Bool is_src)
{
	napi_value pck_obj;
	NAPI_FilterPacket *napi_pck;

	GF_SAFEALLOC(napi_pck, NAPI_FilterPacket);
	if (!napi_pck) {
		return NULL;
	}
	napi_pck->pck = pck;
	napi_pck->is_src = is_src ? 1 : 0;
	//all packets are read-only by default
	napi_pck->readonly = 1;
	napi_create_object(env, &pck_obj);
	napi_create_reference(env, pck_obj, 1, &napi_pck->ref);
	napi_type_tag_object(env, pck_obj, &filterpck_tag);

	//define all props for custom filter
	napi_property_descriptor filterpck_properties[] = {
		{ "dts", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_DTS},
		{ "cts", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_CTS},
		{ "sap", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_SAP},
		{ "dur", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_DUR},
		{ "size", NULL, NULL, filterpck_getter, NULL, NULL, napi_enumerable, &FS_PROP_PCK_SIZE},
		{ "data", NULL, NULL, filterpck_getter, NULL, NULL, napi_enumerable, &FS_PROP_PCK_DATA},
		{ "start", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_START},
		{ "end", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_END},
		{ "timescale", NULL, NULL, filterpck_getter, NULL, NULL, napi_enumerable, &FS_PROP_PCK_TIMESCALE},
		{ "interlaced", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_INTERLACED},
		{ "corrupted", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_CORRUPTED},
		{ "seek", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_SEEK},
		{ "byte_offset", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_BYTE_OFFSET},
		{ "roll", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_ROLL},
		{ "crypt", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_CRYPT},
		{ "clock", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_CLOCK},
		{ "carousel", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_CAROUSEL},
		{ "seqnum", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_SEQNUM},
		{ "deps", NULL, NULL, filterpck_getter, filterpck_setter, NULL, napi_enumerable, &FS_PROP_PCK_DEPS},
		{ "frame_ifce", NULL, NULL, filterpck_getter, NULL, NULL, napi_enumerable, &FS_PROP_PCK_FRAME_IFCE},
		{ "blocking", NULL, NULL, filterpck_getter, NULL, NULL, napi_enumerable, &FS_PROP_PCK_BLOCKING},

		{ "enum_props", 0, filterpck_enum_props, 0, 0, 0, napi_enumerable, 0 },
		{ "get_prop", 0, filterpck_get_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "ref", 0, filterpck_ref, 0, 0, 0, napi_enumerable, 0 },
		{ "unref", 0, filterpck_unref, 0, 0, 0, napi_enumerable, 0 },
		{ "discard", 0, filterpck_discard, 0, 0, 0, napi_enumerable, 0 },
		{ "clone", 0, filterpck_clone, 0, 0, 0, napi_enumerable, 0 },
		{ "readonly", 0, filterpck_readonly, 0, 0, 0, napi_enumerable, 0 },
		{ "send", 0, filterpck_send, 0, 0, 0, napi_enumerable, 0 },
		{ "copy_props", 0, filterpck_copy_props, 0, 0, 0, napi_enumerable, 0 },
		{ "set_prop", 0, filterpck_set_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "truncate", 0, filterpck_truncate, 0, 0, 0, napi_enumerable, 0 },
	};

	napi_define_properties(env, pck_obj, sizeof(filterpck_properties)/sizeof(napi_property_descriptor), filterpck_properties);

	napi_wrap(env, pck_obj, napi_pck, napi_pck_del, NULL, NULL);
	return napi_pck;
}

napi_value filterpid_get_packet(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_THIS
	FILTERPID
	GF_FilterPacket *pck;
	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);

	if (napi_pid->napi_pck) {
		NAPI_CALL( napi_get_reference_value(env, napi_pid->napi_pck->ref, &ret) );
		return ret;
	}
	pck = gf_filter_pid_get_packet(pid);
	if (!pck) {
		NAPI_CALL(napi_get_null(env, &ret));
		return ret;
	}
	//create pck object
	napi_pid->napi_pck = wrap_filter_packet(env, pck, GF_TRUE);
	NAPI_CALL( napi_get_reference_value(env, napi_pid->napi_pck->ref, &ret) );
	return ret;
}

napi_value filterpid_drop_packet(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTERPID
	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);

	if (!napi_pid->napi_pck) {
		return NULL;
	}
	u32 rcount;
	napi_reference_unref(env, napi_pid->napi_pck->ref, &rcount);
	if (!rcount) {
		napi_delete_reference(env, napi_pid->napi_pck->ref);
		napi_pid->napi_pck->ref = NULL;
		napi_pid->napi_pck->pck = NULL;
	}
	napi_pid->napi_pck = NULL;

	gf_filter_pid_drop_packet(pid);
	return NULL;
}


napi_value filterpid_send_event(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	GF_FilterEvent *evt=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterevent_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterEvent object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &evt) );
	evt->base.on_pid = pid;
	gf_filter_pid_send_event(pid, evt);
	return NULL;
}

napi_value filterpid_forward(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTERPID
	NAPI_FilterPacket *napi_pck=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpck_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterPacket  object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &napi_pck) );
	if (napi_pck->pck) {
        GF_Err e = gf_filter_pck_forward(napi_pck->pck, pid);
		if (e) {
			napi_throw_error(env, gf_error_to_string(e), "Failed to forward packet");
		}
	}
	return NULL;
}

napi_value filterpid_new_pck(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_ARGS_THIS(1,0)
	FILTERPID

	NARG_U32(size, 0, 0)
	GF_FilterPacket *pck = gf_filter_pck_new_alloc(pid, size, NULL);
	if (!pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	NAPI_FilterPacket *napi_pck = wrap_filter_packet(env, pck, GF_FALSE);
	if (!napi_pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	napi_pck->readonly = 0;
	NAPI_CALL( napi_get_reference_value(env, napi_pck->ref, &ret) );
	return ret;
}

napi_value filterpid_new_pck_ref(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_ARGS_THIS(3,1)
	FILTERPID

	NAPI_FilterPacket *napi_pck=NULL;
	GF_FilterPacket *ipck=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpck_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterPacket object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &napi_pck) );
	ipck = napi_pck->pck;
	if (!ipck) {
		napi_throw_error(env, NULL, napi_pck->is_src ? "Detached packet, already drop" : "Detached packet, already sent");
		return NULL;
	}

	NARG_U32(size, 1, 0)
	NARG_U32(offset, 2, 0)

	GF_FilterPacket *pck = gf_filter_pck_new_ref(pid, offset, size, ipck);
	if (!pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	napi_pck = wrap_filter_packet(env, pck, GF_FALSE);
	if (!napi_pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	NAPI_CALL( napi_get_reference_value(env, napi_pck->ref, &ret) );
	return ret;
}

void filter_cbk_release_packet(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck)
{
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);
	NAPI_FilterPacket *napi_pck = NULL;
	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);

	napi_pck = gf_list_pop_front(napi_pid->pck_refs);
	if (!napi_pck) return;

	if (napi_pck->shared_destructor_ref) {
		napi_value fun, obj, res;
		napi_get_reference_value(napi_f->env, napi_pck->shared_destructor_ref, &fun);
		napi_get_global(napi_f->env, &obj);
		napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun, 0, NULL, &res);
	}

	u32 rcount;

	//unref arraybuffer
	napi_reference_unref(napi_f->env, napi_pck->shared_ab_ref, &rcount);
	napi_delete_reference(napi_f->env, napi_pck->shared_ab_ref);

	//unref destructor
	if (napi_pck->shared_destructor_ref) {
		napi_reference_unref(napi_f->env, napi_pck->shared_ab_ref, &rcount);
		napi_delete_reference(napi_f->env, napi_pck->shared_ab_ref);
	}

	//unref packet
	napi_reference_unref(napi_f->env, napi_pck->ref, &rcount);
	if (rcount==0) {
		napi_delete_reference(napi_f->env, napi_pck->ref);
		napi_pck->ref = NULL;
		napi_pck->pck = NULL;
	}

}

napi_value filterpid_new_pck_shared(napi_env env, napi_callback_info info)
{
	Bool has_destructor=GF_FALSE;
	napi_value ret;
	GF_Err e;
	NARG_ARGS_THIS(2,1)
	FILTERPID

	if (argc>1) {
		napi_valuetype rtype;
		napi_typeof(env, argv[1], &rtype);
		if (rtype != napi_function) {
			napi_throw_error(env, NULL, "destructor is not a function");
			return NULL;
		}
		has_destructor = GF_TRUE;
	}

	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);

	Bool is_ab=GF_FALSE;
	NAPI_CALL(napi_is_arraybuffer(env, argv[0], (bool*) &is_ab) );
	if (!is_ab) {
		napi_throw_error(env, NULL, "not an array buffer");
		return NULL;
	}

	u8 *data_p=NULL;
	size_t data_len=0;
	NAPI_CALL(napi_get_arraybuffer_info(env, argv[0], (void **)&data_p, &data_len) );

	GF_FilterPacket *pck = gf_filter_pck_new_shared(pid, data_p, data_len, filter_cbk_release_packet);
	if (!pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	NAPI_FilterPacket *napi_pck = wrap_filter_packet(env, pck, GF_FALSE);
	if (!napi_pck) goto err_exit;

	if (!napi_pid->pck_refs) {
		napi_pid->pck_refs = gf_list_new();
		if (!napi_pid->pck_refs) goto err_exit;
	}
	e = gf_list_add(napi_pid->pck_refs, napi_pck);
	if (e) goto err_exit;

	//ref packet to avoid destruction
	u32 nbrefs;
	status = napi_reference_ref(env, napi_pck->ref, &nbrefs);
	if (status!=napi_ok) goto err_exit;
	//ref array buffer
	status = napi_create_reference(env, argv[0], 1, &napi_pck->shared_ab_ref);
	if (status!=napi_ok) goto err_exit;
	if (has_destructor) {
		status = napi_create_reference(env, argv[1], 1, &napi_pck->shared_destructor_ref);
		if (status!=napi_ok) goto err_exit;
	}

	napi_pck->readonly = 0;
	status = napi_get_reference_value(env, napi_pck->ref, &ret);
	if (status == napi_ok) {
		napi_pck->pid_refs = napi_pid->pck_refs;
		return ret;
	}

err_exit:
	if (pck) gf_filter_pck_discard(pck);
	if (napi_pck) gf_list_del_item(napi_pid->pck_refs, napi_pck);

	napi_throw_error(env, NULL, "Failed to create new packet");
	return NULL;

}

napi_value filterpid_new_pck_copy_clone(napi_env env, napi_callback_info info, Bool is_clone)
{
	napi_value ret;
	NARG_ARGS_THIS(1,1)
	FILTERPID

	NAPI_FilterPacket *napi_pck=NULL;
	GF_FilterPacket *ipck=NULL;
	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterpck_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterPacket object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &napi_pck) );
	ipck = napi_pck ? napi_pck->pck : NULL;
	if (!ipck) {
		napi_throw_error(env, NULL, napi_pck->is_src ? "Detached packet, already drop" : "Detached packet, already sent");
		return NULL;
	}

	GF_FilterPacket *pck;
	if (is_clone)
		pck = gf_filter_pck_new_clone(pid, ipck, NULL);
	else
		pck = gf_filter_pck_new_copy(pid, ipck, NULL);

	if (!pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	napi_pck = wrap_filter_packet(env, pck, GF_FALSE);
	if (!napi_pck) {
		napi_throw_error(env, NULL, "Failed to create new packet");
		return NULL;
	}
	napi_pck->readonly = 0;
	NAPI_CALL( napi_get_reference_value(env, napi_pck->ref, &ret) );
	return ret;
}
napi_value filterpid_new_pck_copy(napi_env env, napi_callback_info info)
{
	return filterpid_new_pck_copy_clone(env, info, GF_FALSE);
}
napi_value filterpid_new_pck_clone(napi_env env, napi_callback_info info)
{
	return filterpid_new_pck_copy_clone(env, info, GF_TRUE);
}


static u32 FILTER_PROP_NAME = 0;
static u32 FILTER_PROP_ID = 1;
static u32 FILTER_PROP_IPIDS = 2;
static u32 FILTER_PROP_OPIDS = 3;
static u32 FILTER_PROP_STATUS = 4;
static u32 FILTER_PROP_BYTES_DONE = 5;


#define FILTER\
	GF_Filter *f=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &filter_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a Filter object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &f) );\

#define FILTER_CUST\
	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(f);\
	if (!napi_f->is_custom) {\
		napi_throw_error(env, NULL, "Not a custom Filter object");\
		return NULL;\
	}\


#define FILTER_ARG(_name, _argidx)\
	GF_Filter *_name=NULL;\
	bool _fa_tag;\
	NAPI_CALL( napi_check_object_type_tag(env, argv[_argidx], &filter_tag, &_fa_tag) );\
	if (!_fa_tag) {\
		napi_throw_error(env, NULL, "Not a Filter object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, argv[_argidx], (void**) &_name) );\

napi_value filter_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTER

	if (magic == &FILTER_PROP_NAME) {
		const char *fname = gf_filter_get_name(f);
		if (!fname) return NULL;
		NAPI_CALL( napi_create_string_utf8(env, fname, NAPI_AUTO_LENGTH, &ret) );
		return ret;
	}
	if (magic == &FILTER_PROP_ID) {
		const char *fid = gf_filter_get_name(f);
		if (!fid) return NULL;
		NAPI_CALL( napi_create_string_utf8(env, fid, NAPI_AUTO_LENGTH, &ret) );
		return ret;
	}
	if (magic == &FILTER_PROP_IPIDS) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_get_ipid_count(f), &ret) );
		return ret;
	}
	if (magic == &FILTER_PROP_OPIDS) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_get_opid_count(f), &ret) );
		return ret;
	}

	if (magic == &FILTER_PROP_STATUS) {
		const char* statusstr = gf_filter_get_status(f);
		NAPI_CALL( napi_create_string_utf8(env, statusstr, NAPI_AUTO_LENGTH, &ret) );
		return ret;
	}

	if (magic == &FILTER_PROP_BYTES_DONE) {
		u64 bytes_dones = gf_filter_get_bytes_done(f);
		NAPI_CALL( napi_create_int64(env, bytes_dones, &ret) );
		return ret;
	}

	return NULL;
}

napi_value filter_setter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS_MAGIC(1, 1)
	FILTER

	if (magic == &FILTER_PROP_NAME) {
		NARG_STR(name, 0, NULL);
		gf_filter_set_name(f, name);
		return NULL;
	}
	return NULL;
}

napi_value filter_remove(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER
	gf_filter_remove(f);
	return NULL;
}
napi_value filter_update(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(3, 2)
	FILTER
	NARG_STR(name, 0, NULL);
	NARG_STR_ALLOC(value, 1, NULL);
	NARG_U32(mask, 2, 0)

	gf_fs_send_update(NULL, NULL, f, name, value, mask);
	if (value) gf_free(value);
	return NULL;
}
napi_value filter_set_source_internal(napi_env env, napi_callback_info info, Bool is_restricted)
{
	GF_Err e;
	NARG_ARGS_THIS(2, 1)
	FILTER
	FILTER_ARG(src_f, 0)

	NARG_STR(link_ext, 1, NULL);

	if (is_restricted)
		e = gf_filter_set_source_restricted(f, src_f, link_ext);
	else
		e = gf_filter_set_source(f, src_f, link_ext);

	if (e)
		napi_throw_error(env, gf_error_to_string(e), "Failed to set source");
	return NULL;
}
napi_value filter_set_source(napi_env env, napi_callback_info info)
{
	return filter_set_source_internal(env, info, GF_FALSE);
}
napi_value filter_set_source_restricted(napi_env env, napi_callback_info info)
{
	return filter_set_source_internal(env, info, GF_TRUE);
}
napi_value filter_insert(napi_env env, napi_callback_info info)
{
	GF_FilterPid *opid=NULL;
	s32 offset=1;
	GF_Err e;
	NARG_ARGS_THIS(3, 1)
	FILTER
	FILTER_ARG(ins_f, 0)

	//check if 2nd param is int, get opid
	if (argc>1) {
		s32 idx = -1;
		if (napi_get_value_int32(env, argv[1], &idx) == napi_ok) {
			offset = 2;
			if (idx>=0) {
				opid = gf_filter_get_opid(f, (u32) idx);
				if (!opid) {
					napi_throw_error(env, NULL, "Invalid output PID index");
					return NULL;
				}
			}
		}
	}
	NARG_STR(link_ext, (u32)offset, NULL);

	e = gf_filter_set_source(f, ins_f, link_ext);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to set source");
		return NULL;
	}
	e = gf_filter_reconnect_output(f, opid);
	if (e)
		napi_throw_error(env, gf_error_to_string(e), "Failed to reconnect output");
	return NULL;
}

napi_value filter_reconnect(napi_env env, napi_callback_info info)
{
	GF_FilterPid *opid=NULL;
	GF_Err e;
	NARG_ARGS_THIS(1, 0)
	FILTER

	//check if 2nd param is int, get opid
	if (argc) {
		s32 idx = -1;
		if (napi_get_value_int32(env, argv[0], &idx) == napi_ok) {
			if (idx>=0) {
				opid = gf_filter_get_opid(f, (u32) idx);
				if (!opid) {
					napi_throw_error(env, NULL, "Invalid output PID index");
					return NULL;
				}
			}
		}
	}
	e = gf_filter_reconnect_output(f, opid);
	if (e)
		napi_throw_error(env, gf_error_to_string(e), "Failed to reconnect output");
	return NULL;
}

napi_value filter_probe_link(napi_env env, napi_callback_info info)
{
	GF_Err e;
	char *res=NULL;
	s32 idx = -1;
	s32 offset=1;
	NARG_ARGS_THIS(2, 1)
	FILTER

	//check if 2nd param is int, get opid
	if (argc>1) {
		if (napi_get_value_int32(env, argv[0], &idx) == napi_ok) {
			offset=2;
		}
	}
	NARG_STR(fdesc, (u32)offset, NULL);

	e = gf_filter_probe_link(f, idx, fdesc, &res);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to probe links");
		return NULL;
	}
	napi_value val;
	NAPI_CALL( napi_create_string_utf8(env, res, NAPI_AUTO_LENGTH, &val) );
	gf_free(res);
	return val;
}

napi_value filter_get_destinations(napi_env env, napi_callback_info info)
{
	GF_Err e;
	char *res=NULL;
	s32 idx = -1;
	NARG_ARGS_THIS(1, 0)
	FILTER

	//check if 2nd param is int, get opid
	if (argc && (napi_get_value_int32(env, argv[0], &idx) == napi_ok)) {
	}

	e = gf_filter_get_possible_destinations(f, idx, &res);
	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to get destinations");
		return NULL;
	}
	napi_value val;
	NAPI_CALL( napi_create_string_utf8(env, res, NAPI_AUTO_LENGTH, &val) );
	gf_free(res);
	return val;
}

napi_value filter_require_source_id(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER
	gf_filter_require_source_id(f);
	return NULL;
}


napi_value filter_bind(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1,1)
	FILTER

	napi_valuetype rtype;
	napi_typeof(env, argv[0], &rtype);
	if (rtype != napi_object) {
		napi_throw_error(env, NULL, "Binding not an object");
		return NULL;
	}

	const char *fname = gf_filter_get_name(f);
	if (fname && !strcmp(fname, "dashin")) {
		return dashin_bind(env, info);
	}
	if (fname && !strcmp(fname, "httpout")) {
		return httpout_bind(env, info);
	}
	napi_throw_error(env, NULL, "Failed to bind to filter, not dashin filter");
	return NULL;
}


napi_value frac_to_napi(napi_env env, const GF_Fraction *frac)
{
	napi_value val, res;
	napi_status status;
	NAPI_CALL(napi_create_object(env, &res) );
	NAPI_CALL(napi_create_int32(env, frac->num, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "num", val) );
	NAPI_CALL(napi_create_uint32(env, frac->den, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "den", val) );
	return res;
}
napi_value frac64_to_napi(napi_env env, const GF_Fraction64 *frac)
{
	napi_value val, res;
	napi_status status;
	NAPI_CALL(napi_create_object(env, &res) );
	NAPI_CALL(napi_create_int64(env, frac->num, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "num", val) );
	NAPI_CALL(napi_create_int64(env, frac->den, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "den", val) );
	return res;
}
napi_status frac_from_napi(napi_env env, napi_value prop, GF_Fraction *frac)
{
	napi_status status;
	Bool has_p;
	napi_value v;
	GET_PROP_FIELD_S32("num", "Fraction", frac->num)
	GET_PROP_FIELD_U32("den", "Fraction", frac->den)
	return napi_ok;
}

napi_status frac64_from_napi(napi_env env, napi_value prop, GF_Fraction64 *frac)
{
	napi_status status;
	napi_value v;
	Bool has_p;
	GET_PROP_FIELD_S64("num", "Fraction64", frac->num)
	GET_PROP_FIELD_U64("den", "Fraction64", frac->den)
	return napi_ok;
}

#define SET_BOOL(_val) \
	NAPI_CALL(napi_get_boolean(env, stats._val ? true : false, &val) ); \
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

#define SET_U32(_val) \
	NAPI_CALL(napi_create_uint32(env, stats._val, &val) ); \
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

#define SET_U64(_val) \
	NAPI_CALL(napi_create_int64(env, stats._val, &val) ); \
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

#define SET_S64(_val) \
	NAPI_CALL(napi_create_int64(env, stats._val, &val) ); \
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

#define SET_S32(_val) \
	NAPI_CALL(napi_create_int32(env, stats._val, &val) ); \
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

#define SET_STR(_val) \
	if (stats._val) {\
		NAPI_CALL( napi_create_string_utf8(env, stats._val, NAPI_AUTO_LENGTH, &val) );\
	} else {\
		napi_get_null(env, &val);\
	}\
	NAPI_CALL(napi_set_named_property(env, res, #_val, val) );

napi_value filter_get_statistics(napi_env env, napi_callback_info info)
{
	GF_Err e;
	GF_FilterStats stats;
	napi_value res, val;
	NARG_THIS
	FILTER

	e = gf_filter_get_stats(f, &stats);
 	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to get filter stats");
		return NULL;
	}
	NAPI_CALL(napi_create_object(env, &res) );


	NAPI_CALL(napi_get_boolean(env, stats.filter_alias ? true : false, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "filter_alias", val) );
	if (stats.filter_alias) {
		return res;
	}
	SET_U64(nb_tasks_done)
	SET_U64(nb_pck_processed)
	SET_U64(nb_bytes_processed)
	SET_U64(nb_pck_sent)
	SET_U64(nb_hw_pck_sent)
	SET_U32(nb_errors)
	SET_U32(nb_current_errors)
	SET_U64(nb_bytes_sent)
	SET_U64(time_process)
	SET_S32(percent)
	SET_STR(status)
	SET_BOOL(report_updated)
	SET_STR(name)
	SET_STR(reg_name)
	SET_STR(filter_id)
	SET_BOOL(done)
	SET_U32(nb_pid_in)
	SET_U64(nb_in_pck)
	SET_U32(nb_pid_out)
	SET_U64(nb_out_pck)
	SET_BOOL(in_eos)
	SET_U32(type)

	NAPI_CALL( napi_create_string_utf8(env, gf_stream_type_name(stats.stream_type), NAPI_AUTO_LENGTH, &val) );
	NAPI_CALL(napi_set_named_property(env, res, "streamtype", val) );

	char *sep = NULL;
	const char *codecid = gf_codecid_file_ext(stats.codecid);
	if (codecid) sep = strchr(codecid, '|');
	if (sep) {
		NAPI_CALL( napi_create_string_utf8(env, codecid, sep-codecid, &val) );
	} else {
		NAPI_CALL( napi_create_string_utf8(env, codecid, NAPI_AUTO_LENGTH, &val) );
	}
	NAPI_CALL(napi_set_named_property(env, res, "codecid", val) );

	NAPI_CALL(napi_set_named_property(env, res, "last_ts_sent", frac64_to_napi(env, &stats.last_ts_sent)) );
	NAPI_CALL(napi_set_named_property(env, res, "last_ts_drop", frac64_to_napi(env, &stats.last_ts_drop)) );
	return res;
}


napi_value prop_to_napi(napi_env env, const GF_PropertyValue *p, u32 p4cc)
{
	napi_value res, val;
	u32 i;
	napi_status status;


	if (gf_props_type_is_enum(p->type)) {
		NAPI_CALL( napi_create_string_utf8(env, gf_props_enum_name(p->type, p->value.uint), NAPI_AUTO_LENGTH, &res) );
		return res;
	}

	switch (p->type) {
	case GF_PROP_BOOL:
		NAPI_CALL(napi_get_boolean(env, p->value.boolean ? true : false, &res) );
		return res;
	case GF_PROP_SINT:
		NAPI_CALL(napi_create_int32(env, p->value.sint, &res) );
		return res;
	case GF_PROP_UINT:
        if (p4cc== GF_PROP_PID_STREAM_TYPE) {
			NAPI_CALL( napi_create_string_utf8(env, gf_stream_type_name(p->value.uint), NAPI_AUTO_LENGTH, &res) );
			return res;
		}
        if (p4cc== GF_PROP_PID_PIXFMT) {
			NAPI_CALL( napi_create_string_utf8(env, gf_pixel_fmt_sname(p->value.uint), NAPI_AUTO_LENGTH, &res) );
			return res;
		}
        if (p4cc== GF_PROP_PID_CODECID) {
			char *sep=NULL;
			const char *codecid = gf_codecid_file_ext(p->value.uint);
			if (codecid) sep = strchr(codecid, '|');
			if (sep) {
				NAPI_CALL( napi_create_string_utf8(env, codecid, sep-codecid, &res) );
			} else {
				NAPI_CALL( napi_create_string_utf8(env, codecid, NAPI_AUTO_LENGTH, &res) );
			}
			return res;
		}
		NAPI_CALL(napi_create_uint32(env, p->value.uint, &res) );
		return res;
	case GF_PROP_LSINT:
		NAPI_CALL(napi_create_int64(env, p->value.longsint, &res) );
		return res;
	case GF_PROP_LUINT:
		NAPI_CALL(napi_create_int64(env, p->value.longuint, &res) );
		return res;
	case GF_PROP_FRACTION:
		return frac_to_napi(env, &p->value.frac);
	case GF_PROP_FRACTION64:
		return frac64_to_napi(env, &p->value.lfrac);
	case GF_PROP_FLOAT:
		NAPI_CALL(napi_create_double(env, (double) FIX2FLT(p->value.fnumber), &res) );
		return res;
	case GF_PROP_DOUBLE:
		NAPI_CALL(napi_create_double(env, p->value.number, &res) );
		return res;
	case GF_PROP_STRING:
	case GF_PROP_NAME:
		if (p->value.string) {
			NAPI_CALL( napi_create_string_utf8(env, p->value.string, NAPI_AUTO_LENGTH, &res) );
		} else {
			napi_get_null(env, &res);
		}
		return res;
	case GF_PROP_4CC:
		NAPI_CALL( napi_create_string_utf8(env, gf_4cc_to_str(p->value.uint), NAPI_AUTO_LENGTH, &res) );
		return res;

	case GF_PROP_VEC2I:
		NAPI_CALL(napi_create_object(env, &res) );
		NAPI_CALL(napi_create_int32(env, p->value.vec2i.x, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "x", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec2i.y, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "y", val) );
		return res;
	case GF_PROP_VEC2:
		NAPI_CALL(napi_create_object(env, &res) );
		NAPI_CALL(napi_create_double(env, p->value.vec2.x, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "x", val) );
		NAPI_CALL(napi_create_double(env, p->value.vec2.y, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "y", val) );
		return res;
	case GF_PROP_VEC3I:
		NAPI_CALL(napi_create_object(env, &res) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.x, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "x", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.y, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "y", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.z, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "z", val) );
		return res;
	case GF_PROP_VEC4I:
		NAPI_CALL(napi_create_object(env, &res) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.x, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "x", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.y, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "y", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.z, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "z", val) );
		NAPI_CALL(napi_create_int32(env, p->value.vec3i.z, &val) );
		NAPI_CALL(napi_set_named_property(env, res, "w", val) );
		return res;
	case GF_PROP_DATA:
	case GF_PROP_DATA_NO_COPY:
	case GF_PROP_CONST_DATA:
		if (p->value.data.ptr) {
#if 0
			NAPI_CALL(napi_create_external_arraybuffer(env, p->value.data.ptr, p->value.data.size, dummy_finalize, NULL, &res) );
#else
			u8 *output;
			NAPI_CALL(napi_create_buffer(env, p->value.data.size, (void**) &output, &res) );
			memcpy(output, p->value.data.ptr, p->value.data.size);
#endif
		} else {
			napi_get_null(env, &res);
		}
		return res;
	case GF_PROP_POINTER:
		NAPI_CALL( napi_create_string_utf8(env, "INTERNAL_POINTER", NAPI_AUTO_LENGTH, &res) );
		return res;
	case GF_PROP_UINT_LIST:
		NAPI_CALL(napi_create_array_with_length(env, p->value.uint_list.nb_items, &res) );
		for (i=0; i<p->value.uint_list.nb_items; i++) {
			NAPI_CALL(napi_create_uint32(env, p->value.uint_list.vals[i], &val) );
			NAPI_CALL(napi_set_element(env, res, i, val) );
		}
		return res;
	case GF_PROP_SINT_LIST:
		NAPI_CALL(napi_create_array_with_length(env, p->value.sint_list.nb_items, &res) );
		for (i=0; i<p->value.uint_list.nb_items; i++) {
			NAPI_CALL(napi_create_int32(env, p->value.sint_list.vals[i], &val) );
			NAPI_CALL(napi_set_element(env, res, i, val) );
		}
		return res;
	case GF_PROP_STRING_LIST:
		NAPI_CALL(napi_create_array_with_length(env, p->value.string_list.nb_items, &res) );
		for (i=0; i<p->value.string_list.nb_items; i++) {
			if (p->value.string_list.vals[i]) {
				NAPI_CALL( napi_create_string_utf8(env, p->value.string_list.vals[i], NAPI_AUTO_LENGTH, &val) );
			} else {
				napi_get_null(env, &val);
			}
			NAPI_CALL(napi_set_element(env, res, i, val) );
		}
		return res;
	case GF_PROP_4CC_LIST:
		NAPI_CALL(napi_create_array_with_length(env, p->value.uint_list.nb_items, &res) );
		for (i=0; i<p->value.uint_list.nb_items; i++) {
			NAPI_CALL( napi_create_string_utf8(env, gf_4cc_to_str(p->value.uint_list.vals[i]), NAPI_AUTO_LENGTH, &val) );
			NAPI_CALL(napi_set_element(env, res, i, val) );
		}
		return res;
	case GF_PROP_VEC2I_LIST:
		NAPI_CALL(napi_create_array_with_length(env, p->value.v2i_list.nb_items, &res) );
		for (i=0; i<p->value.v2i_list.nb_items; i++) {
			napi_value item;
			NAPI_CALL(napi_create_object(env, &item) );
			NAPI_CALL(napi_create_int32(env, p->value.v2i_list.vals[i].x, &val) );
			NAPI_CALL(napi_set_named_property(env, item, "x", val) );
			NAPI_CALL(napi_create_int32(env, p->value.v2i_list.vals[i].y, &val) );
			NAPI_CALL(napi_set_named_property(env, item, "y", val) );
			NAPI_CALL(napi_set_element(env, res, i, item) );
		}
		return res;
	default:
		napi_throw_error(env, NULL, "unknown property type");
		return NULL;
	}
}

napi_status napi_get_str(napi_env env, napi_value val, char **ostr)
{
	GPAC_NAPI *gpac;
	size_t s;
	napi_status status;
	status = napi_get_instance_data(env, (void **) &gpac);
	if (status != napi_ok) return status;

	napi_valuetype vtype;
	status = napi_typeof(env, val, &vtype);
	if (status != napi_ok) return status;
	if (vtype == napi_null) {
		*ostr = NULL;
		return napi_ok;
	}

	status = napi_get_value_string_utf8(env, val, NULL, 0, &s);
	if (status != napi_ok) return status;

	if (s+1 > gpac->str_buf_alloc) {
		gpac->str_buf = gf_realloc(gpac->str_buf, sizeof(char) * (s+1));
		if (!gpac->str_buf) {
			napi_throw_error(env, NULL, "Out of memory");
			return napi_generic_failure;
		}
		gpac->str_buf_alloc = s+1;
	}
	status = napi_get_value_string_utf8(env, val, gpac->str_buf, gpac->str_buf_alloc, &s);
	if (status != napi_ok) return status;
	gpac->str_buf[s]=0;
	*ostr = gpac->str_buf;
	return napi_ok;
}

napi_status prop_from_napi(napi_env env, napi_value prop, u32 p4cc, char *pname, u32 custom_type, GF_PropertyValue *p)
{
	char *str;
	napi_status status;
	Bool has_p;
	napi_value v;

	memset(p, 0, sizeof(GF_PropertyValue));
	if (!p4cc) {
		if (!custom_type) {
            p->type = GF_PROP_STRING;
            status = napi_get_str(env, prop, &str);
			if (status != napi_ok) return status;
            p->value.string = str ? gf_strdup(str) : NULL;
            return napi_ok;
		}
        p->type = custom_type;
	} else {
		p->type = gf_props_4cc_get_type(p4cc);
	}

    if (p4cc == GF_PROP_PID_STREAM_TYPE) {
		status = napi_get_str(env, prop, &str);
		if (status != napi_ok) return status;
		if (!str) {
			napi_throw_error(env, NULL, "Invalid StreamType");
			return napi_generic_failure;
		}
        p->value.uint = gf_stream_type_by_name( str );
        return napi_ok;
	}
    if (p4cc == GF_PROP_PID_CODECID) {
		status = napi_get_str(env, prop, &str);
		if (status != napi_ok) return status;
		if (!str) {
			napi_throw_error(env, NULL, "Invalid CodecID");
			return napi_generic_failure;
		}
        p->value.uint = gf_codecid_parse( str );
        return napi_ok;
	}
	if (gf_props_type_is_enum(p->type)) {
		status = napi_get_str(env, prop, &str);
		if (status != napi_ok) return status;
		if (!str) {
			napi_throw_error(env, NULL, "Invalid Enum");
			return napi_generic_failure;
		}
		p->value.uint = gf_props_parse_enum(p->type, str);
        return napi_ok;
	}
	if (p->type==GF_PROP_SINT) {
		return napi_get_value_int32(env, prop, &p->value.sint);
	}
	if (p->type==GF_PROP_UINT) {
		return napi_get_value_uint32(env, prop, &p->value.uint);
	}
	if (p->type==GF_PROP_4CC) {
		status = napi_get_str(env, prop, &str);
		if (status != napi_ok) return status;
		if (!str) {
			napi_throw_error(env, NULL, "Invalid 4CC");
			return napi_generic_failure;
		}
		p->value.uint = gf_4cc_parse(str);
		return napi_ok;
	}
	if (p->type==GF_PROP_LSINT) {
		return napi_get_value_int64(env, prop, &p->value.longsint);
	}
	if (p->type==GF_PROP_LUINT) {
		return napi_get_value_int64(env, prop, (s64 *) &p->value.longuint);
	}
	if (p->type==GF_PROP_BOOL) {
		bool val;
		status = napi_get_value_bool(env, prop, &val);
		p->value.boolean = val ? GF_TRUE : GF_FALSE;
		return status;
	}

	if (p->type==GF_PROP_FRACTION) {
		GET_PROP_FIELD_S32("num", "Fraction", p->value.frac.num)
		GET_PROP_FIELD_U32("den", "Fraction", p->value.frac.den)
		return napi_ok;
	}
	if (p->type==GF_PROP_FRACTION64) {
		GET_PROP_FIELD_S64("num", "Fraction64", p->value.lfrac.num)
		GET_PROP_FIELD_U64("den", "Fraction64", p->value.lfrac.den)
		return napi_ok;
	}
	if (p->type==GF_PROP_FLOAT) {
		Double n;
		status = napi_get_value_double(env, prop, &n);
		p->value.fnumber = FLT2FIX(n);
		return status;
	}
	if (p->type==GF_PROP_DOUBLE) {
		return napi_get_value_double(env, prop, &p->value.number);
	}
	if (p->type==GF_PROP_VEC2I) {
		GET_PROP_FIELD_S32("x", "Vec2i", p->value.vec2i.x)
		GET_PROP_FIELD_S32("y", "Vec2i", p->value.vec2i.y)
		return napi_ok;
	}
	if (p->type==GF_PROP_VEC2) {
		GET_PROP_FIELD_FLT("x", "Vec2", p->value.vec2.x)
		GET_PROP_FIELD_FLT("y", "Vec2", p->value.vec2.y)
		return napi_ok;
	}
	if (p->type==GF_PROP_VEC3I) {
		GET_PROP_FIELD_S32("x", "Vec3i", p->value.vec3i.x)
		GET_PROP_FIELD_S32("y", "Vec3i", p->value.vec3i.y)
		GET_PROP_FIELD_S32("z", "Vec3i", p->value.vec3i.z)
		return napi_ok;
	}
	if (p->type==GF_PROP_VEC4I) {
		GET_PROP_FIELD_S32("x", "Vec4i", p->value.vec4i.x)
		GET_PROP_FIELD_S32("y", "Vec4i", p->value.vec4i.y)
		GET_PROP_FIELD_S32("z", "Vec4i", p->value.vec4i.z)
		GET_PROP_FIELD_S32("w", "Vec4i", p->value.vec4i.w)
		return napi_ok;
	}
	if ((p->type==GF_PROP_STRING) || (p->type==GF_PROP_STRING_NO_COPY) || (p->type==GF_PROP_NAME)) {
		status = napi_get_str(env, prop, &str);
		if (status != napi_ok) return status;
		p->value.string = str ? gf_strdup(p->value.string) : NULL;
		return napi_ok;
	}
	if ((p->type==GF_PROP_DATA) || (p->type==GF_PROP_DATA_NO_COPY) || (p->type==GF_PROP_CONST_DATA)) {
		napi_throw_error(env, NULL, "Setting data property from JS not yet implemented");
		return napi_generic_failure;
	}
	if (p->type==GF_PROP_POINTER) {
		napi_throw_error(env, NULL, "Setting pointer property from JS not supported");
		return napi_generic_failure;
	}

	if ((p->type==GF_PROP_UINT_LIST) || (p->type==GF_PROP_4CC_LIST)
		|| (p->type==GF_PROP_SINT_LIST) || (p->type==GF_PROP_VEC2I_LIST)
 		|| (p->type==GF_PROP_STRING_LIST)
	) {
		Bool is_array;
		u32 i, nb_items;
		size_t size;
		status = napi_is_array(env, prop, (bool *) &is_array);
		if (!is_array) {
			napi_throw_error(env, NULL, "Property value not an array");
			return napi_generic_failure;
		}
		//use uint list for nb_items
		status = napi_get_array_length(env, prop, &nb_items);
		p->value.uint_list.nb_items = nb_items;
		if (!p->value.uint_list.nb_items) {
			p->value.uint_list.vals = NULL;
			return status;
		}
		if (p->type==GF_PROP_STRING_LIST) size = sizeof(char *) * nb_items;
		else if (p->type==GF_PROP_VEC2I_LIST) size = sizeof(GF_PropVec2i) * nb_items;
		else size = sizeof(u32) * nb_items;

		p->value.uint_list.vals = gf_malloc(size);
		if (!p->value.uint_list.vals) {
			napi_throw_error(env, NULL, "Failed to allocate property");
			return napi_generic_failure;
		}
		memset(p->value.uint_list.vals, 0, size);

		for (i=0; i<nb_items; i++) {
			napi_value val;
			status = napi_get_element(env, prop, i, &val);
			if (status != napi_ok) return status;

			if (p->type==GF_PROP_STRING_LIST) {
				napi_valuetype vtype;
				napi_typeof(env, val, &vtype);
				if (vtype == napi_null) {
					p->value.string_list.vals[i] = NULL;
				} else {
					status = napi_get_value_string_utf8(env, val, NULL, 0, &size);
					if (status != napi_ok) return status;
					p->value.string_list.vals[i] = gf_malloc(sizeof(char) * (size+1) );
					if (!p->value.string_list.vals[i]) return napi_generic_failure;
					status = napi_get_value_string_utf8(env, val, p->value.string_list.vals[i], size+1, &size);
					if (status != napi_ok) return status;
					p->value.string_list.vals[i] [size]=0;
				}
			}
			else if (p->type==GF_PROP_VEC2I_LIST) {
				GET_PROP_FIELD_S32("x", "Vec2i", p->value.v2i_list.vals[i].x)
				GET_PROP_FIELD_S32("y", "Vec2i", p->value.v2i_list.vals[i].y)
			} else if (p->type==GF_PROP_SINT_LIST) {
				status = napi_get_value_int32(env, val, &p->value.sint_list.vals[i]);
				if (status != napi_ok) return status;
			} else if (p->type==GF_PROP_UINT_LIST) {
				status = napi_get_value_uint32(env, val, &p->value.uint_list.vals[i]);
				if (status != napi_ok) return status;
			} else if (p->type==GF_PROP_4CC_LIST) {
				status = napi_get_str(env, prop, &str);
				if (status != napi_ok) return status;
				if (str && (strlen(str)==4)) {
					p->value.uint_list.vals[i] = GF_4CC(str[0], str[1], str[2], str[3]);
				} else {
					napi_throw_error(env, NULL, "Invalid 4CC, needs 4 characters");
					return napi_generic_failure;
				}
				if (status != napi_ok) return status;
			}
		}
	}

	napi_throw_error(env, NULL, "Unsupported property type");
	return napi_generic_failure;
}

napi_value filter_enum_pid_props(napi_env env, napi_value cbk_fun, GF_FilterPid *pid)
{
	napi_value res, global;
	napi_status status;
	napi_valuetype rtype;
	napi_typeof(env, cbk_fun, &rtype);
	if (rtype != napi_function) {
		napi_throw_error(env, NULL, "property enumerator is not a function");
		return NULL;
	}

	NAPI_CALL( napi_get_global(env, &global) );
	u32 prop_idx=0;
	while (1) {
		napi_handle_scope scope_h;
		u32 p4cc=0;
		napi_value fun_args[3];
		const char *pname = NULL;
		const GF_PropertyValue *prop = gf_filter_pid_enum_properties(pid, &prop_idx, &p4cc, &pname);
		if (!prop) break;

		NAPI_CALL(napi_open_handle_scope(env, &scope_h) );

		if (p4cc) {
			pname = gf_props_4cc_get_name(p4cc);
			if (!pname) pname = gf_4cc_to_str(p4cc);
		}

		NAPI_CALL( napi_create_string_utf8(env, pname, NAPI_AUTO_LENGTH, &fun_args[0]) );
		fun_args[1] = prop_to_napi(env, prop, p4cc);
		NAPI_CALL( napi_create_int32(env, prop->type, &fun_args[2]) );

		NAPI_CALL(napi_call_function(env, global, cbk_fun, 3, fun_args, &res) );
		NAPI_CALL(napi_close_handle_scope(env, scope_h) );
	}
	return NULL;
}

napi_value filter_iopid_enum_props(napi_env env, napi_callback_info info, Bool is_opid)
{
	GF_FilterPid *pid=NULL;
	NARG_ARGS_THIS(2, 2)
	FILTER
	NARG_U32(idx, 0, 0)

	if (is_opid) {
		pid = gf_filter_get_opid(f, idx);
	} else {
		pid = gf_filter_get_ipid(f, idx);
	}
	if (!pid) {
		napi_throw_error(env, NULL, "Invalid PID index");
		return NULL;
	}
	return filter_enum_pid_props(env, argv[1], pid);
}

napi_value filter_ipid_enum_props(napi_env env, napi_callback_info info)
{
	return filter_iopid_enum_props(env, info, GF_FALSE);
}
napi_value filter_opid_enum_props(napi_env env, napi_callback_info info)
{
	return filter_iopid_enum_props(env, info, GF_TRUE);
}

napi_value filter_get_pid_property(napi_env env, char *pname, GF_FilterPid *pid, Bool is_info)
{
	napi_status status;
	napi_value res;
	GF_PropertyEntry *pe=NULL;
	u32 p4cc = gf_props_get_id(pname);
	const GF_PropertyValue *prop = NULL;

	if (is_info) {
		if (p4cc)
			prop = gf_filter_pid_get_info(pid, p4cc, &pe);
		else
			prop = gf_filter_pid_get_info_str(pid, pname, &pe);
	} else {
		if (p4cc)
			prop = gf_filter_pid_get_property(pid, p4cc);
		else
			prop = gf_filter_pid_get_property_str(pid, pname);
	}
	if (!prop) {

		if (!strcmp(pname, "buffer")) {
			NAPI_CALL( napi_create_int64(env, (s64) gf_filter_pid_query_buffer_duration(pid, GF_FALSE), &res) );
			return res;
		}
		if (!strcmp(pname, "buffer_total")) {
			NAPI_CALL( napi_create_int64(env, (s64) gf_filter_pid_query_buffer_duration(pid, GF_TRUE), &res) );
			return res;
		}
		if (!strcmp(pname, "name")) {
			const char *fname = gf_filter_pid_get_name(pid);
			if (fname) {
				NAPI_CALL( napi_create_string_utf8(env, fname, NAPI_AUTO_LENGTH, &res) );
			} else {
				NAPI_CALL( napi_get_null(env, &res) );
			}
			return res;
		}
		if (!strcmp(pname, "eos")) {
			NAPI_CALL( napi_get_boolean(env, gf_filter_pid_is_eos(pid), &res) );
			return res;
		}

		napi_value res;
		NAPI_CALL(napi_get_null(env, &res) );
		return res;
	}
	res = prop_to_napi(env, prop, p4cc);
	gf_filter_release_property(pe);
	return res;
}

napi_value filter_iopid_prop(napi_env env, napi_callback_info info, Bool is_opid)
{
	GF_FilterPid *pid=NULL;
	NARG_ARGS_THIS(2, 2)
	FILTER
	NARG_U32(idx, 0, 0)
	NARG_STR(name, 1, 0)

	if (is_opid) {
		pid = gf_filter_get_opid(f, idx);
	} else {
		pid = gf_filter_get_ipid(f, idx);
	}
	if (!pid) {
		napi_throw_error(env, NULL, "Invalid PID index");
		return NULL;
	}
	return filter_get_pid_property(env, name, pid, GF_FALSE);
}
napi_value filter_ipid_prop(napi_env env, napi_callback_info info)
{
	return filter_iopid_prop(env, info, GF_FALSE);
}
napi_value filter_opid_prop(napi_env env, napi_callback_info info)
{
	return filter_iopid_prop(env, info, GF_TRUE);
}

napi_value filter_iopid_stats(napi_env env, napi_callback_info info, Bool is_opid)
{
	GF_Err e;
	napi_value res, val;
	GF_FilterPidStatistics stats;
	GF_FilterPid *pid=NULL;
	NARG_ARGS_THIS(2, 1)
	FILTER
	NARG_U32(idx, 0, 0)
	NARG_U32(mode, 1, 0)

	if (is_opid) {
		pid = gf_filter_get_opid(f, idx);
	} else {
		pid = gf_filter_get_ipid(f, idx);
	}
	if (!pid) {
		napi_throw_error(env, NULL, "Invalid PID index");
		return NULL;
	}

	e = gf_filter_pid_get_statistics(pid, &stats, mode);
 	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to get filter stats");
		return NULL;
	}
	NAPI_CALL(napi_create_object(env, &res) );
	SET_BOOL(disconnected)
	SET_U32(average_process_rate)
	SET_U32(max_process_rate)
	SET_U32(average_bitrate)
	SET_U32(max_bitrate)
	SET_U32(nb_processed)
	SET_U32(max_process_time)
	SET_U64(total_process_time)
	SET_U64(first_process_time)
	SET_U64(last_process_time)
	SET_U32(min_frame_dur)
	SET_U32(nb_saps)
	SET_U32(max_sap_process_time)
	SET_U64(total_sap_process_time)
	SET_U64(max_buffer_time)
	SET_U64(max_playout_time)
	SET_U64(min_playout_time)
	SET_U64(buffer_time)
	SET_U32(nb_buffer_units)
	SET_U64(last_rt_report)
	SET_U32(rtt)
	SET_U32(jitter)
	SET_U32(loss_rate)
	NAPI_CALL(napi_set_named_property(env, res, "last_ts_sent", frac64_to_napi(env, &stats.last_ts_sent)) );
	NAPI_CALL(napi_set_named_property(env, res, "last_ts_drop", frac64_to_napi(env, &stats.last_ts_drop)) );
	return res;
}
napi_value filter_ipid_stats(napi_env env, napi_callback_info info)
{
	return filter_iopid_stats(env, info, GF_FALSE);
}
napi_value filter_opid_stats(napi_env env, napi_callback_info info)
{
	return filter_iopid_stats(env, info, GF_TRUE);
}

napi_value filter_ipid_source(napi_env env, napi_callback_info info)
{
	GF_FilterPid *pid=NULL;
	NARG_ARGS_THIS(1, 1)
	FILTER
	NARG_U32(idx, 0, 0)

	pid = gf_filter_get_ipid(f, idx);
	if (!pid) {
		napi_throw_error(env, NULL, "Invalid PID index");
		return NULL;
	}
	GF_Filter *f_src = gf_filter_pid_get_source_filter(pid);

	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(f);
	return fs_wrap_filter(env, napi_f->fs, f_src);
}

napi_value filter_opid_sinks(napi_env env, napi_callback_info info)
{
	napi_value res;
	u32 i;
	GF_FilterPid *pid=NULL;
	NARG_ARGS_THIS(1, 1)
	FILTER
	NARG_U32(idx, 0, 0)

	pid = gf_filter_get_opid(f, idx);
	if (!pid) {
		napi_throw_error(env, NULL, "Invalid PID index");
		return NULL;
	}
	NAPI_CALL(napi_create_array(env, &res) );

	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(f);
	i=0;
	while (1) {
		GF_Filter *f_dst = gf_filter_pid_enum_destinations(pid, i);
		if (!f_dst) break;

		NAPI_CALL(napi_set_element(env, res, i, fs_wrap_filter(env, napi_f->fs, f_dst) ) );
		i++;
	}
	return res;
}

napi_value filter_get_info(napi_env env, napi_callback_info info)
{
	napi_value res;
	u32 p4cc;
	NARG_ARGS_THIS(1, 1)
	FILTER
	NARG_STR(prop_name, 0, NULL);

	const GF_PropertyValue *prop;
	GF_PropertyEntry *pe=NULL;
	p4cc =gf_props_get_id(prop_name);
	if (p4cc) {
		prop = gf_filter_get_info(f, p4cc, &pe);
	} else {
		prop = gf_filter_get_info_str(f, prop_name, &pe);
	}
	if (prop) {
		res = prop_to_napi(env, prop, p4cc);
	} else {
		NAPI_CALL( napi_get_null(env, &res) );
	}
	gf_filter_release_property(pe);
	return res;
}

napi_value filter_all_args(napi_env env, napi_callback_info info)
{
	napi_value res;
	u32 a_idx;
	NARG_THIS
	FILTER

	NAPI_CALL(napi_create_array(env, &res) );
	a_idx=0;
	while (1) {
		napi_value n_arg, val;
		const GF_FilterArgs *arg = gf_filter_enumerate_args(f, a_idx);
		if (!arg) break;

		NAPI_CALL(napi_create_object(env, &n_arg) );

		NAPI_CALL( napi_create_string_utf8(env, arg->arg_name, NAPI_AUTO_LENGTH, &val) );
		NAPI_CALL(napi_set_named_property(env, n_arg, "name", val) );
		NAPI_CALL( napi_create_string_utf8(env, arg->arg_desc, NAPI_AUTO_LENGTH, &val) );
		NAPI_CALL(napi_set_named_property(env, n_arg, "desc", val) );
		NAPI_CALL( napi_create_int32(env, arg->arg_type, &val) );
		NAPI_CALL(napi_set_named_property(env, n_arg, "type", val) );

		if (arg->arg_default_val) {
			NAPI_CALL( napi_create_string_utf8(env, arg->arg_default_val, NAPI_AUTO_LENGTH, &val) );
		} else {
			NAPI_CALL( napi_get_null(env, &val) );
		}
		NAPI_CALL(napi_set_named_property(env, n_arg, "default", val) );

		if (arg->min_max_enum) {
			NAPI_CALL( napi_create_string_utf8(env, arg->min_max_enum, NAPI_AUTO_LENGTH, &val) );
		} else {
			NAPI_CALL( napi_get_null(env, &val) );
		}
		NAPI_CALL(napi_set_named_property(env, n_arg, "min_max_enum", val) );

		NAPI_CALL( napi_create_int32(env, arg->flags, &val) );
		NAPI_CALL(napi_set_named_property(env, n_arg, "flags", val) );

		char argval[GF_PROP_DUMP_ARG_SIZE];
		gf_filter_get_arg_str(f, arg->arg_name, argval);
		NAPI_CALL( napi_create_string_utf8(env, argval, NAPI_AUTO_LENGTH, &val) );
		NAPI_CALL(napi_set_named_property(env, n_arg, "value", val) );



		NAPI_CALL(napi_set_element(env, res, a_idx, n_arg) );
		a_idx++;
	}
	return res;
}

/* custom filters*/
static GF_Err napi_filter_cbk_process(GF_Filter *filter)
{
	napi_status status;
	napi_value obj, fun_val, res;
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);
	if (!napi_f) return GF_SERVICE_ERROR;

	napi_get_reference_value(napi_f->env, napi_f->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_f->env, obj, "process", (bool *) &has_fun);
	if (!has_fun) return GF_EOS;

	status = napi_get_named_property(napi_f->env, obj, "process", &fun_val);
	if (status != napi_ok) return GF_BAD_PARAM;

	status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 0, NULL, &res);

	GF_Err e = GF_OK;
	napi_get_value_int32(napi_f->env, res, &e);
	return e;
}

void filter_inputpid_finalize(napi_env env, void* finalize_data, void* finalize_hint)
{
	//finalize_hint contains napi_pid
	NAPI_FilterPid *napi_pid = (NAPI_FilterPid *)finalize_hint;
	gf_free(napi_pid);
}

static NAPI_FilterPid *wrap_filter_pid(napi_env env, GF_Filter *filter, GF_FilterPid *pid, Bool is_input)
{
	NAPI_FilterPid *napi_pid;
	napi_value pid_obj;

	GF_SAFEALLOC(napi_pid, NAPI_FilterPid);
	if (!napi_pid) {
		return NULL;
	}
	gf_filter_pid_set_udta(pid, napi_pid);
	napi_pid->f = filter;
	napi_pid->pid = pid;

	napi_create_object(env, &pid_obj);
	napi_create_reference(env, pid_obj, 1, &napi_pid->ref);
	napi_type_tag_object(env, pid_obj, &filterpid_tag);

	napi_wrap(env, pid_obj, pid, is_input ? filter_inputpid_finalize : dummy_finalize, napi_pid, NULL);

	//define all props for custom filter
	napi_property_descriptor filterpid_properties[] = {
		{ "name", NULL, NULL, filterpid_getter, filterpid_setter, NULL, napi_enumerable, &FS_PROP_PID_NAME},
		{ "filter_name", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_FILTERNAME},
		{ "eos", NULL, NULL, filterpid_getter, filterpid_setter, NULL, napi_enumerable, &FS_PROP_PID_EOS},
		{ "has_seen_eos", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_HAS_SEEN_EOS},
		{ "eos_received", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_HAS_RECEIVED},
		{ "would_block", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_WOULD_BLOCK},
		{ "sparse", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_SPARSE},
		{ "max_buffer", NULL, NULL, filterpid_getter, filterpid_setter, NULL, napi_enumerable, &FS_PROP_PID_MAX_BUFFER},
		{ "buffer", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_BUFFER},
		{ "buffer_full", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_BUFFER_FULL},
		{ "first_empty", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_FIRST_EMPTY},
		{ "first_cts", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_FIRST_CTS},
		{ "nb_pck_queued", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_NB_PCK_QUEUED},
		{ "timescale", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_TIMESCALE},
		{ "min_pck_dur", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_MIN_PCK_DUR},
		{ "playing", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_PLAYING},
		{ "next_ts", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_NEXT_TS},
		{ "has_decoder", NULL, NULL, filterpid_getter, NULL, NULL, napi_enumerable, &FS_PROP_PID_HAS_DECODER},

		{ "remove", 0, filterpid_remove, 0, 0, 0, napi_enumerable, 0 },
		{ "enum_props", 0, filterpid_enum_props, 0, 0, 0, napi_enumerable, 0 },
		{ "get_prop", 0, filterpid_get_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "get_info", 0, filterpid_get_info, 0, 0, 0, napi_enumerable, 0 },
		{ "copy_props", 0, filterpid_copy_props, 0, 0, 0, napi_enumerable, 0 },
		{ "reset_props", 0, filterpid_reset_props, 0, 0, 0, napi_enumerable, 0 },
		{ "set_prop", 0, filterpid_set_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "set_info", 0, filterpid_set_info, 0, 0, 0, napi_enumerable, 0 },
		{ "clear_eos", 0, filterpid_clear_eos, 0, 0, 0, napi_enumerable, 0 },
		{ "check_caps", 0, filterpid_check_caps, 0, 0, 0, napi_enumerable, 0 },
		{ "discard_block", 0, filterpid_discard_block, 0, 0, 0, napi_enumerable, 0 },
		{ "allow_direct_dispatch", 0, filterpid_allow_direct_dispatch, 0, 0, 0, napi_enumerable, 0 },
		{ "get_clock_type", 0, filterpid_get_clock_type, 0, 0, 0, napi_enumerable, 0 },
		{ "get_clock_timestamp", 0, filterpid_get_clock_timestamp, 0, 0, 0, napi_enumerable, 0 },
		{ "is_filter_in_parents", 0, filterpid_is_filter_in_parents, 0, 0, 0, napi_enumerable, 0 },
		{ "get_buffer_occupancy", 0, filterpid_get_buffer_occupancy, 0, 0, 0, napi_enumerable, 0 },
		{ "loose_connect", 0, filterpid_loose_connect, 0, 0, 0, napi_enumerable, 0 },
		{ "set_framing", 0, filterpid_set_framing, 0, 0, 0, napi_enumerable, 0 },
		{ "set_clock_mode", 0, filterpid_set_clock_mode, 0, 0, 0, napi_enumerable, 0 },
		{ "set_discard", 0, filterpid_set_discard, 0, 0, 0, napi_enumerable, 0 },
		{ "require_source_id", 0, filterpid_require_source_id, 0, 0, 0, napi_enumerable, 0 },
		{ "recompute_dts", 0, filterpid_recompute_dts, 0, 0, 0, napi_enumerable, 0 },
		{ "resolve_template", 0, filterpid_resolve_template, 0, 0, 0, napi_enumerable, 0 },
		{ "query_cap", 0, filterpid_query_cap, 0, 0, 0, napi_enumerable, 0 },
		{ "negotiate_cap", 0, filterpid_negotiate_cap, 0, 0, 0, napi_enumerable, 0 },
		{ "get_packet", 0, filterpid_get_packet, 0, 0, 0, napi_enumerable, 0 },
		{ "drop_packet", 0, filterpid_drop_packet, 0, 0, 0, napi_enumerable, 0 },
		{ "send_event", 0, filterpid_send_event, 0, 0, 0, napi_enumerable, 0 },
		{ "forward", 0, filterpid_forward, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pck", 0, filterpid_new_pck, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pck_ref", 0, filterpid_new_pck_ref, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pck_shared", 0, filterpid_new_pck_shared, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pck_copy", 0, filterpid_new_pck_copy, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pck_clone", 0, filterpid_new_pck_clone, 0, 0, 0, napi_enumerable, 0 },
	};

	napi_define_properties(env, pid_obj, sizeof(filterpid_properties)/sizeof(napi_property_descriptor), filterpid_properties);

	return napi_pid;
}
static GF_Err napi_filter_cbk_configure(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
{
	GF_Err e=GF_OK;
	napi_value args[2];
	napi_status status;
	napi_value obj, fun_val, res;
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);
	Bool has_fun = GF_FALSE;
	if (!napi_f) return GF_SERVICE_ERROR;

	napi_get_reference_value(napi_f->env, napi_f->ref, &obj);

	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);

	napi_has_named_property(napi_f->env, obj, "configure_pid", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_f->env, obj, "configure_pid", &fun_val);
		if (status != napi_ok) has_fun = GF_FALSE;
	}

	if (is_remove) {
		if (napi_pid) {
			if (has_fun) {
				napi_get_reference_value(napi_f->env, napi_pid->ref, &args[0]);
				napi_get_boolean(napi_f->env, (bool) 1, &args[1]);
				status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 2, args, &res);
				status = napi_get_value_int32(napi_f->env, res, &e);
			}
			u32 rcount;
			napi_reference_unref(napi_f->env, napi_pid->ref, &rcount);
			napi_delete_reference(napi_f->env, napi_pid->ref);
			napi_pid->pid = NULL;
			gf_filter_pid_set_udta(pid, NULL);
		}
		return e;
	}
	if (!napi_pid) {
		napi_pid = wrap_filter_pid(napi_f->env, filter, pid, GF_TRUE);
		if (!napi_pid) return GF_OUT_OF_MEM;
	}

	if (has_fun) {
		napi_get_reference_value(napi_f->env, napi_pid->ref, &args[0]);
		napi_get_boolean(napi_f->env, (bool) 0, &args[1]);
		status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 2, args, &res);
		status = napi_get_value_int32(napi_f->env, res, &e);
	}

	return e;
}
static GF_Err napi_filter_cbk_reconfigure_output(GF_Filter *filter, GF_FilterPid *pid)
{
	GF_Err e=GF_OK;
	napi_value args[1];
	napi_status status;
	napi_value obj, fun_val, res;
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);
	Bool has_fun = GF_FALSE;
	if (!napi_f) return GF_SERVICE_ERROR;

	napi_get_reference_value(napi_f->env, napi_f->ref, &obj);

	NAPI_FilterPid *napi_pid = gf_filter_pid_get_udta(pid);
	if (!napi_pid) return GF_SERVICE_ERROR;

	napi_has_named_property(napi_f->env, obj, "reconfigure_output", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_f->env, obj, "reconfigure_output", &fun_val);
		if (status != napi_ok) has_fun = GF_FALSE;
	}
	//by default return not supported
	e = GF_NOT_SUPPORTED;
	if (has_fun) {
		napi_get_reference_value(napi_f->env, napi_pid->ref, &args[0]);
		status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 1, args, &res);
		status = napi_get_value_int32(napi_f->env, res, &e);
	}
	return e;
}

static Bool napi_filter_cbk_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
{
	napi_status status;
	napi_value obj, fun_val, arg, res;
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);
	if (!napi_f) return GF_FALSE;

	napi_get_reference_value(napi_f->env, napi_f->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_f->env, obj, "process_event", (bool *) &has_fun);
	if (!has_fun) return GF_FALSE;

	status = napi_get_named_property(napi_f->env, obj, "process_event", &fun_val);
	if (status != napi_ok) return GF_FALSE;

	arg = wrap_filterevent(napi_f->env, (GF_FilterEvent *)evt, NULL);
	status = napi_make_callback(napi_f->env, napi_f->async_ctx, obj, fun_val, 1, &arg, &res);

	bool ret = GF_FALSE;
	napi_get_value_bool(napi_f->env, res, &ret);
	return ret ? GF_TRUE : GF_FALSE;
}

static u32 FS_PROP_CUST_BLOCK_ENABLED = 0;
static u32 FS_PROP_CUST_OUTPUT_BUFFER = 1;
static u32 FS_PROP_CUST_PLAYOUT_BUFFER = 2;
static u32 FS_PROP_CUST_SINKS_DONE = 3;
static u32 FS_PROP_CUST_EVTS_QUEUED = 4;
static u32 FS_PROP_CUST_CK_HINT_TIME = 5;
static u32 FS_PROP_CUST_CK_HINT_MEDIATIME = 6;
static u32 FS_PROP_CUST_CONNECTIONS_PENDING = 7;

napi_value filter_cust_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTER
	FILTER_CUST

	if (magic == &FS_PROP_CUST_BLOCK_ENABLED) {
		NAPI_CALL( napi_get_boolean(env, (bool) gf_filter_block_enabled(f), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_OUTPUT_BUFFER) {
		u32 val;
		gf_filter_get_output_buffer_max(f, &val, NULL);
		NAPI_CALL( napi_create_uint32(env, val, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_PLAYOUT_BUFFER) {
		u32 val;
		gf_filter_get_output_buffer_max(f, NULL, &val);
		NAPI_CALL( napi_create_uint32(env, val, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_SINKS_DONE) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_all_sinks_done(f), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_EVTS_QUEUED) {
		NAPI_CALL( napi_create_uint32(env, gf_filter_get_num_events_queued(f), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_CK_HINT_TIME) {
        u64 val;
        gf_filter_get_clock_hint(f, &val, NULL);
		NAPI_CALL( napi_create_int64(env, val, &ret) );
		return ret;
	}
	if (magic == &FS_PROP_CUST_CK_HINT_MEDIATIME) {
        GF_Fraction64 val;
        gf_filter_get_clock_hint(f, NULL, &val);
        ret = frac64_to_napi(env, &val);
		return ret;
	}
	if (magic == &FS_PROP_CUST_CONNECTIONS_PENDING) {
		NAPI_CALL( napi_get_boolean(env, gf_filter_connections_pending(f), &ret) );
		return ret;
	}
	return NULL;
}

napi_value filter_push_cap(napi_env env, napi_callback_info info)
{
	GF_Err e;
	char *pname=NULL;
	u32 p4cc=0;
	GF_PropertyValue prop;
	NARG_ARGS_THIS(5, 3)
	FILTER
	FILTER_CUST

	napi_valuetype rtype;
	napi_typeof(env, argv[0], &rtype);
	if (rtype == napi_string) {
		NARG_STR(prop_name, 0, NULL)
		pname = prop_name;
		p4cc = gf_props_get_id(pname);
	} else {
		NARG_U32(pcode, 0, 0)
		p4cc = pcode;
	}
	NARG_U32(flags, 2, 0)
	NARG_U32(priority, 3, 0)
	NARG_U32(custom_type, 4, 0)

	if (p4cc) pname = NULL;

	//clone prop name as prop from napi may reuse the global arg buffer
	if (pname) pname = gf_strdup(pname);
	//convert napi_prop to prop
	status = prop_from_napi(env, argv[1], p4cc, pname, custom_type, &prop);
	if (status == napi_ok) {
		e = gf_filter_push_caps(f, p4cc, &prop, pname, flags, priority);
	} else {
		e = GF_BAD_PARAM;
	}
	if (pname) gf_free(pname);
	//do not reset property, it is now owned by filter

 	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Error assigning cap");
	}
	return NULL;
}

napi_value filter_update_status(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(2, 1)
	FILTER
	FILTER_CUST

	NARG_STR(fstatus, 0, NULL)
	NARG_U32(percent, 1, 0)
	gf_filter_update_status(f, percent, fstatus);
	return NULL;
}

napi_value filter_reschedule(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 0)
	FILTER
	FILTER_CUST
	NARG_U32(when, 0, 0)

	if (when)
		gf_filter_ask_rt_reschedule(f, when);
	else
		gf_filter_post_process_task(f);
	return NULL;
}

napi_value filter_notify_failure(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(2, 1)
	FILTER
	FILTER_CUST
	NARG_S32(err, 0, 0)
	NARG_U32(type, 1, GF_SETUP_ERROR)

	if (type==GF_NOTIF_ERROR)
		gf_filter_notification_failure(f, err, GF_FALSE);
	else if (type==GF_NOTIF_ERROR_AND_DISCONNECT)
		gf_filter_notification_failure(f, err, GF_TRUE);
	else
		gf_filter_setup_failure(f, err);
	return NULL;
}


napi_value filter_make_sticky(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER
	FILTER_CUST
	gf_filter_make_sticky(f);
	return NULL;
}

napi_value filter_prevent_blocking(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER
	FILTER_CUST
	NARG_BOOL(enable, 0, 0)
	gf_filter_prevent_blocking(f, enable);
	return NULL;
}
napi_value filter_block_eos(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER
	FILTER_CUST
	NARG_BOOL(enable, 0, 0)
	gf_filter_block_eos(f, enable);
	return NULL;
}
napi_value filter_set_max_pids(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER
	FILTER_CUST
	NARG_U32(max_extra_pids, 0, 0)
	gf_filter_set_max_extra_input_pids(f, max_extra_pids);
	return NULL;
}
napi_value filter_hint_clock(napi_env env, napi_callback_info info)
{
	GF_Fraction64 frac;
	NARG_ARGS_THIS(2, 2)
	FILTER
	FILTER_CUST
	NARG_U64(clock_us, 0, 0)
	NAPI_CALL(frac64_from_napi(env, argv[1], &frac) );

	gf_filter_hint_single_clock(f, clock_us, frac);
	return NULL;
}

napi_value filter_new_pid(napi_env env, napi_callback_info info)
{
	napi_value ret;
	NARG_THIS
	FILTER
	FILTER_CUST

	GF_FilterPid *opid = gf_filter_pid_new(f);
	if (!opid) {
		napi_throw_error(env, NULL, "Failed to create new output PID");
		return NULL;
	}

	NAPI_FilterPid *napi_pid = wrap_filter_pid(env, f, opid, GF_FALSE);
	if (!napi_pid) {
		napi_throw_error(env, NULL, "Failed to create new output PID");
		return NULL;
	}
	napi_get_reference_value(env, napi_pid->ref, &ret);
	return ret;
}

napi_value filter_print_connections(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER
	FILTER_CUST
	gf_filter_print_all_connections(f, NULL);
	return NULL;
}


#define FILTER_SESSION\
	GF_FilterSession *fs;\
	bool _tag;\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &fs) );\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &filtersession_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a FilterSession object");\
		return NULL;\
	}\


static u32 FS_PROP_LAST_TASK = 0;
static u32 FS_PROP_NB_FILTERS = 1;
static u32 FS_PROP_HTTP_RATE = 2;
static u32 FS_PROP_MAX_HTTP_RATE = 3;


napi_value fs_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTER_SESSION

	if (magic == &FS_PROP_LAST_TASK) {
		NAPI_CALL( napi_get_boolean(env, (bool) gf_fs_is_last_task(fs), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_NB_FILTERS) {
		NAPI_CALL( napi_create_uint32(env, gf_fs_get_filters_count(fs), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_HTTP_RATE) {
		NAPI_CALL( napi_create_uint32(env, gf_fs_get_http_rate(fs), &ret) );
		return ret;
	}
	if (magic == &FS_PROP_MAX_HTTP_RATE) {
		NAPI_CALL( napi_create_uint32(env, gf_fs_get_http_max_rate(fs), &ret) );
		return ret;
	}
	return NULL;
}

napi_value fs_setter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS_MAGIC(1, 1)
	FILTER_SESSION

	if (magic == &FS_PROP_MAX_HTTP_RATE) {
		NARG_U32(new_rate, 0, 0);
		gf_fs_set_http_max_rate(fs, new_rate);
		return NULL;
	}
	return NULL;
}

napi_value fs_run(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NARG_THIS
	FILTER_SESSION
	NAPI_GPAC
	NAPI_Session *napi_fs = gf_fs_get_rt_udta(fs);

	gpac->fs_rmt_handler = napi_fs;
	if (gf_list_count(gpac->rmt_new_clients) || gf_list_count(gpac->rmt_task_calls) || gf_list_count(gpac->rmt_delete_calls)) {
		gf_fs_post_user_task(fs, fs_flush_rmt, napi_fs, "RMTFlush");
	}

	e = gf_fs_run(fs);

	//TODO: run flush rmt again if not blocking?

	if (e>=GF_OK) {
		e = gf_fs_get_last_connect_error(fs);
		if (e>=GF_OK)
			e = gf_fs_get_last_process_error(fs);
	}
	if (e<0)
		napi_throw_error(env, gf_error_to_string(e), "Error running session");\


	while (gf_list_count(napi_fs->defer_filters)) {
		GF_Filter *filter = gf_list_pop_front(napi_fs->defer_filters);
		const char *fname = "on_filter_new";
		Bool has_fun = GF_FALSE;
		napi_has_named_property(env, this_val, fname, (bool *) &has_fun);
		if (has_fun) {
			napi_value fun_val;
			status = napi_get_named_property(env, this_val, fname, &fun_val);
			if (status == napi_ok) {
				napi_value res, arg = fs_wrap_filter(env, napi_fs->fs, filter);
				status = napi_make_callback(env, napi_fs->async_ctx, this_val, fun_val, 1, &arg, &res);
			}
		}
	}
	return NULL;
}

napi_value fs_wrap_filter(napi_env env, GF_FilterSession *fs, GF_Filter *filter)
{
	napi_status status;
	napi_value obj;
	NAPI_Filter *napi_f = gf_filter_get_rt_udta(filter);

	if (!filter) {
		NAPI_CALL( napi_get_null(env, &obj) );
		return obj;
	}

	napi_property_descriptor filter_properties[] = {
		{ "name", NULL, NULL, filter_getter, filter_setter, NULL, napi_enumerable, &FILTER_PROP_NAME},
		{ "ID", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_ID},
		{ "nb_ipid", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_IPIDS},
		{ "nb_opid", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_OPIDS},
		{ "status", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_STATUS},
		{ "bytes_done", NULL, NULL, filter_getter, NULL, NULL, napi_enumerable, &FILTER_PROP_BYTES_DONE},
		{ "remove", 0, filter_remove, 0, 0, 0, napi_enumerable, 0 },
		{ "update", 0, filter_update, 0, 0, 0, napi_enumerable, 0 },
		{ "set_source", 0, filter_set_source, 0, 0, 0, napi_enumerable, 0 },
		{ "set_source_restricted", 0, filter_set_source_restricted, 0, 0, 0, napi_enumerable, 0 },
		{ "insert", 0, filter_insert, 0, 0, 0, napi_enumerable, 0 },
		{ "reconnect", 0, filter_reconnect, 0, 0, 0, napi_enumerable, 0 },
		{ "ipid_prop", 0, filter_ipid_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "ipid_enum_props", 0, filter_ipid_enum_props, 0, 0, 0, napi_enumerable, 0 },
		{ "opid_prop", 0, filter_opid_prop, 0, 0, 0, napi_enumerable, 0 },
		{ "opid_enum_props", 0, filter_opid_enum_props, 0, 0, 0, napi_enumerable, 0 },
		{ "ipid_source", 0, filter_ipid_source, 0, 0, 0, napi_enumerable, 0 },
		{ "opid_sinks", 0, filter_opid_sinks, 0, 0, 0, napi_enumerable, 0 },
		{ "all_args", 0, filter_all_args, 0, 0, 0, napi_enumerable, 0 },
		{ "get_info", 0, filter_get_info, 0, 0, 0, napi_enumerable, 0 },
		{ "get_statistics", 0, filter_get_statistics, 0, 0, 0, napi_enumerable, 0 },
		{ "require_source_id", 0, filter_require_source_id, 0, 0, 0, napi_enumerable, 0 },
		{ "bind", 0, filter_bind, 0, 0, 0, napi_enumerable, 0 },
		{ "ipid_stats", 0, filter_ipid_stats, 0, 0, 0, napi_enumerable, 0 },
		{ "opid_stats", 0, filter_opid_stats, 0, 0, 0, napi_enumerable, 0 },
		{ "probe_link", 0, filter_probe_link, 0, 0, 0, napi_enumerable, 0 },
		{ "get_destinations", 0, filter_get_destinations, 0, 0, 0, napi_enumerable, 0 },
	};

	if (napi_f) {
		NAPI_CALL ( napi_get_reference_value(env, napi_f->ref, &obj) );
		return obj;
	}

	GF_SAFEALLOC(napi_f, NAPI_Filter);
	napi_f->f = filter;
	napi_f->fs = fs;
	napi_f->env = env;

	NAPI_CALL( napi_create_object(env, &obj) );
	NAPI_CALL( napi_define_properties(env, obj, sizeof(filter_properties)/sizeof(napi_property_descriptor), filter_properties) );
	NAPI_CALL( napi_create_reference(env, obj, 1, &napi_f->ref) );
	NAPI_CALL( napi_type_tag_object(env, obj, &filter_tag) );

	NAPI_CALL( napi_wrap(env, obj, filter, dummy_finalize, NULL, NULL) );

	gf_filter_set_rt_udta(filter, napi_f);
	return obj;
}


napi_value fs_load_src_dst(napi_env env, napi_callback_info info, Bool is_source)
{
	GF_Err e;
	NARG_ARGS_THIS(2, 1)
	FILTER_SESSION
	NARG_STR(url, 0, NULL)

	if (!url) {
		napi_throw_error(env, NULL, "Missing URL");
		return NULL;
	}
	NARG_STR_ALLOC(parent_url, 1, NULL);

	GF_Filter *filter;
	if (is_source)
		filter = gf_fs_load_source(fs, url, NULL, parent_url, &e);
	else
		filter = gf_fs_load_destination(fs, url, NULL, parent_url, &e);

	if (parent_url) gf_free(parent_url);

	if (e<0)
		napi_throw_error(env, gf_error_to_string(e), is_source ? "Failed to load source filter" : "Failed to load destination filter");

	return fs_wrap_filter(env, fs, filter);
}

napi_value fs_load_src(napi_env env, napi_callback_info info)
{
	return fs_load_src_dst(env, info, GF_TRUE);
}
napi_value fs_load_dst(napi_env env, napi_callback_info info)
{
	return fs_load_src_dst(env, info, GF_FALSE);
}

napi_value fs_load_filter(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NARG_ARGS_THIS(1, 1)
	FILTER_SESSION
	NARG_STR(url, 0, NULL)

	GF_Filter *filter = gf_fs_load_filter(fs, url, &e);

	if (e<0)
		napi_throw_error(env, gf_error_to_string(e), "Failed to load filter");

	return fs_wrap_filter(env, fs, filter);
}

typedef struct
{
	napi_async_context ctx;
	napi_ref ref;
	napi_env env;
	NAPI_Session *napi_fs;
} NAPI_Task;

Bool fs_task_exec(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms)
{
	Bool ret = GF_TRUE;
	napi_status status;
	napi_value res, obj_val, fun_val;
	NAPI_Task *task = (NAPI_Task *) callback;
	napi_env env = (napi_env) task->env;

	status = napi_get_reference_value(env, task->ref, &obj_val);
	if (status != napi_ok) goto exit;
	status = napi_get_named_property(env, obj_val, "execute", &fun_val);
	if (status != napi_ok) goto exit;

	status = napi_make_callback(env, task->napi_fs->async_ctx, obj_val, fun_val, 0, NULL, &res);
	if (status != napi_ok) goto exit;

	napi_valuetype rtype;
	napi_typeof(env, res, &rtype);
	if (rtype == napi_boolean) {
		bool val;
		status = napi_get_value_bool(env, res, &val);
		ret = val ? GF_TRUE : GF_FALSE;
	} else if ((rtype==napi_number) || (rtype==napi_bigint)) {
		status = napi_get_value_uint32(env, res, reschedule_ms);
		if (status == napi_ok)
			ret = GF_TRUE;
	} else {
		ret = GF_FALSE;
	}

exit:
	if (status != napi_ok) {
		napi_throw_error(env, NULL, "Failed to execute task");
	}

	if (gf_fs_in_final_flush(fsess))
		ret = GF_FALSE;

	if (!ret) {
		u32 rcount;
		napi_reference_unref(env, task->ref, &rcount);
		napi_delete_reference(env, task->ref);
		gf_free(task);
	}
	return ret;
}

napi_value fs_post_task(napi_env env, napi_callback_info info)
{
	napi_value global, fun_name, fun_val;
	NARG_ARGS_THIS(2, 1)
	FILTER_SESSION
	NARG_STR(log_name, 1, "NapiTask");

	NAPI_CALL( napi_create_string_utf8(env, "execute", NAPI_AUTO_LENGTH, &fun_name) );
	status = napi_get_property(env, argv[0], fun_name, &fun_val);
	if (status != napi_ok) {
		napi_throw_error(env, NULL, "No execute method on object");
		return NULL;
	}
	napi_valuetype rtype;
	napi_typeof(env, fun_val, &rtype);
	if (rtype != napi_function) {
		napi_throw_error(env, NULL, "property execute is not a function");
		return NULL;
	}

	NAPI_Task *task;
	GF_SAFEALLOC(task, NAPI_Task);
	if (!task) {
		napi_throw_error(env, NULL, "Failed to allocate task");
		return NULL;
	}
	task->napi_fs = gf_fs_get_rt_udta(fs);

	NAPI_CALL( napi_get_global(env, &global) );
	NAPI_CALL( napi_create_string_utf8(env, "TaskExecute", NAPI_AUTO_LENGTH, &fun_name) );

	status = napi_create_reference(env, argv[0], 1, &task->ref);
	task->env = env;
	gf_fs_post_user_task(fs, fs_task_exec, task, log_name);

	return NULL;
}

napi_value fs_abort(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 0)
	FILTER_SESSION
	NARG_U32(flush, 0, 0);
	gf_fs_abort(fs, flush);
	return NULL;
}

napi_value fs_lock(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER_SESSION
	NARG_BOOL(lock, 0, 0);
	gf_fs_lock_filters(fs, lock);
	return NULL;
}
napi_value fs_reporting(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER_SESSION
	NARG_BOOL(do_report, 0, 0);
	gf_fs_enable_reporting(fs, do_report);
	return NULL;
}

napi_value fs_print_stats(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER_SESSION
	gf_fs_print_stats(fs);
	return NULL;
}

napi_value fs_print_graph(napi_env env, napi_callback_info info)
{
	NARG_THIS
	FILTER_SESSION
	gf_fs_print_connections(fs);
	return NULL;
}
napi_value fs_get_filter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS(1, 1)
	FILTER_SESSION
	NARG_U32(fidx, 0, 0);

	GF_Filter *f = gf_fs_get_filter(fs, fidx);
	if (!f) {
		napi_throw_error(env, NULL, "Invalid filter index");
		return NULL;
	}
	return fs_wrap_filter(env, fs, f);
}

napi_value fs_fire_event(napi_env env, napi_callback_info info)
{
	Bool res;
	napi_value ret;
	GF_Filter *f = NULL;
	GF_FilterEvent *evt = NULL;
	NARG_ARGS_THIS(3, 1)
	FILTER_SESSION


	NAPI_CALL( napi_check_object_type_tag(env, argv[0], &filterevent_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a FilterEvent object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, argv[0], (void**) &evt) );

	if (argc>1) {
		NAPI_CALL( napi_check_object_type_tag(env, argv[1], &filter_tag, &_tag) );
		if (!_tag) {
			napi_throw_error(env, NULL, "Not a Filter object");
			return NULL;
		}
		NAPI_CALL( napi_unwrap(env, argv[1], (void**) &f) );
	}

	NARG_BOOL(upstream, 2, GF_FALSE);

	res = gf_fs_fire_event(fs, f, evt, upstream);
	NAPI_CALL(napi_get_boolean(env, res, &ret) );
	return ret;
}


napi_value fs_is_supported_mime(napi_env env, napi_callback_info info)
{
	napi_value val;
	NARG_ARGS_THIS(1, 1)
	FILTER_SESSION
	NARG_STR(mime, 0, NULL);

	NAPI_CALL( napi_get_boolean(env, gf_fs_is_supported_mime(fs, mime), &val) );
	return val;
}

napi_value fs_is_supported_source(napi_env env, napi_callback_info info)
{
	bool is_ok;
	napi_value val;
	NARG_ARGS_THIS(2, 1)
	FILTER_SESSION

	NARG_STR(url, 0, NULL);
	NARG_STR_ALLOC(parent_url, 1, NULL);
	is_ok = gf_fs_is_supported_source(fs, url, parent_url);
	if (parent_url) gf_free(parent_url);

	NAPI_CALL( napi_get_boolean(env, is_ok, &val) );
	return val;
}


napi_value fs_new_filter(napi_env env, napi_callback_info info)
{
	NAPI_Filter *napi_f=NULL;
	GF_Err e=GF_OK;
	napi_value obj, async_name;
	NARG_ARGS_THIS(2, 0)
	FILTER_SESSION
	NARG_STR(fname, 0, "Custom");
	NARG_U32(flags, 1, 0);

	GF_Filter *new_f = gf_fs_new_filter(fs, fname, flags | GF_FS_REG_MAIN_THREAD, &e);
	if (!new_f) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to create custom filter");
		return NULL;
	}
	//inherit from regular filter
	obj = fs_wrap_filter(env, fs, new_f);
	napi_f = gf_filter_get_rt_udta(new_f);

	napi_f->is_custom = GF_TRUE;

	//define all props for custom filter
	napi_property_descriptor curstom_filter_properties[] = {
		{ "block_enabled", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_BLOCK_ENABLED},
		{ "output_buffer", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_OUTPUT_BUFFER},
		{ "playout_buffer", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_PLAYOUT_BUFFER},
		{ "sinks_done", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_SINKS_DONE},
		{ "nb_evts_queued", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_EVTS_QUEUED},
		{ "clock_hint_time", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_CK_HINT_TIME},
		{ "clock_hint_mediatime", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_CK_HINT_MEDIATIME},
		{ "connections_pending", NULL, NULL, filter_cust_getter, NULL, NULL, napi_enumerable, &FS_PROP_CUST_CONNECTIONS_PENDING},

		{ "push_cap", 0, filter_push_cap, 0, 0, 0, napi_enumerable, 0 },
		{ "update_status", 0, filter_update_status, 0, 0, 0, napi_enumerable, 0 },
		{ "reschedule", 0, filter_reschedule, 0, 0, 0, napi_enumerable, 0 },
		{ "notify_failure", 0, filter_notify_failure, 0, 0, 0, napi_enumerable, 0 },
		{ "make_sticky", 0, filter_make_sticky, 0, 0, 0, napi_enumerable, 0 },
		{ "prevent_blocking", 0, filter_prevent_blocking, 0, 0, 0, napi_enumerable, 0 },
		{ "block_eos", 0, filter_block_eos, 0, 0, 0, napi_enumerable, 0 },
		{ "set_max_pids", 0, filter_set_max_pids, 0, 0, 0, napi_enumerable, 0 },
		{ "hint_clock", 0, filter_hint_clock, 0, 0, 0, napi_enumerable, 0 },
		{ "new_pid", 0, filter_new_pid, 0, 0, 0, napi_enumerable, 0 },
		{ "print_connections", 0, filter_print_connections, 0, 0, 0, napi_enumerable, 0 },

	};

	NAPI_CALL(napi_define_properties(env, obj, sizeof(curstom_filter_properties)/sizeof(napi_property_descriptor), curstom_filter_properties) );

	//setup callbacks
	e = gf_filter_set_configure_ckb(new_f, napi_filter_cbk_configure);
	if (!e) e = gf_filter_set_process_ckb(new_f, napi_filter_cbk_process);
	if (!e) e = gf_filter_set_process_event_ckb(new_f, napi_filter_cbk_process_event);
	if (!e) e = gf_filter_set_reconfigure_output_ckb(new_f, napi_filter_cbk_reconfigure_output);

	if (e) {
		napi_throw_error(env, gf_error_to_string(e), "Failed to assign custom filter callbacks");
		return NULL;
	}

	NAPI_CALL( napi_create_string_utf8(env, "AsyncFilterTask", NAPI_AUTO_LENGTH, &async_name) );
	NAPI_CALL( napi_async_init(env, obj, async_name, &napi_f->async_ctx) );
	return obj;
}



void del_fs(napi_env env, void* finalize_data, void* finalize_hint)
{
	GF_FilterSession *fs = (GF_FilterSession *)finalize_data;
	NAPI_Session *napi_fs = gf_fs_get_rt_udta(fs);

	if (napi_fs) {
		u32 rcount;
		napi_reference_unref(env, napi_fs->ref, &rcount);
		napi_delete_reference(env, napi_fs->ref);
		napi_async_destroy(env, napi_fs->async_ctx);
		napi_fs->ref = NULL;
		napi_fs->async_ctx = NULL;
	}

	gf_fs_del(fs);

	if (napi_fs) {
		if (napi_fs->defer_filters) gf_list_del(napi_fs->defer_filters);
		gf_free(napi_fs);
	}
}

void fs_on_filter_creation(void *udta, GF_Filter *filter, Bool is_destroy)
{
	napi_value fs_val, fun_val;
	napi_status status;
	NAPI_Session *napi_fs = (NAPI_Session *)udta;
	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(filter);
	napi_env env = napi_fs->env;

	//set to NULL means final nodeJS addon shutdown, do not notify

	if (napi_fs->async_ctx != NULL) {
		GPAC_NAPI *gpac;
		if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) {
			return;
		}
		//creation callback are not guaranteed to run in main thread, defer notif to end of fs.run()
		if (!is_destroy && (gpac->main_thid != gf_th_id() ) ) {
			if (!napi_fs->defer_filters) napi_fs->defer_filters = gf_list_new();
			gf_list_add(napi_fs->defer_filters, filter);
			return;
		}
		//destroy callbacks are always scheduled on main thread, but remove filter if in pending list
		if (is_destroy)
			gf_list_del_item(napi_fs->defer_filters, filter);

		status = napi_get_reference_value(env, napi_fs->ref, &fs_val);
		if (status == napi_ok) {
			const char *fname = is_destroy ? "on_filter_del" : "on_filter_new";
			Bool has_fun = GF_FALSE;
			napi_has_named_property(env, fs_val, fname, (bool *) &has_fun);
			if (has_fun) {
				status = napi_get_named_property(env, fs_val, fname, &fun_val);
				if (status == napi_ok) {
					napi_value res, arg = fs_wrap_filter(env, napi_fs->fs, filter);
					status = napi_make_callback(env, napi_fs->async_ctx, fs_val, fun_val, 1, &arg, &res);
				}
			}
		}
	}

	if (is_destroy && napi_f) {
		u32 rcount;
		gf_filter_set_rt_udta(filter, NULL);
		napi_reference_unref(napi_f->env, napi_f->ref, &rcount);
		napi_delete_reference(napi_f->env, napi_f->ref);

		//extenral object was bound, destroy ref
		if (napi_f->binding_ref != NULL) {
			const char *fname = gf_filter_get_name(filter);
			if (fname && !strcmp(fname, "dashin"))
				dashin_detach_binding(napi_f);

			if (fname && !strcmp(fname, "httpour"))
				httpout_detach_binding(napi_f);

			napi_reference_unref(napi_f->env, napi_f->binding_ref, &rcount);
			napi_delete_reference(napi_f->env, napi_f->binding_ref);
		}

		//cleanup our bindings, we don't use finalizer as we don't know the order of destruction
		if (napi_f->is_custom) {
			u32 i, pids = gf_filter_get_opid_count(filter);
			for (i=0; i<pids; i++) {
				GF_FilterPid *pid = gf_filter_get_opid(filter, i);
				NAPI_FilterPid *napi_pid = (NAPI_FilterPid *) gf_filter_pid_get_udta(pid);
				if (napi_pid) {
					if (napi_pid->pck_refs) {
						while (gf_list_count(napi_pid->pck_refs)) {
							NAPI_FilterPacket *napi_pck = gf_list_pop_back(napi_pid->pck_refs);
							napi_pck->pid_refs = NULL;
						}
						gf_list_del(napi_pid->pck_refs);
					}
					gf_free(napi_pid);
				}
			}
		}
		gf_free(napi_f);
	}
}

Bool napi_gpac_event_proc(void *udta, GF_Event *event)
{
	napi_status status;
	bool has_fun;
	GF_FilterEvent fevt;
	napi_value fs_val, fun_val, evt, res;
	GPAC_NAPI *gpac;
	NAPI_Session *napi_fs = (NAPI_Session *) udta;


	status = napi_get_instance_data(napi_fs->env, (void **) &gpac);
	if (status != napi_ok) return GF_FALSE;

	if (gpac->main_thid != gf_th_id()) {
#ifndef GPAC_DISABLE_SVG
		GF_LOG(GF_LOG_WARNING, GF_LOG_SCRIPT, ("[NodeJS] GPAC main event callback from different thread not implemented, ignoring event %s\n", gf_dom_event_get_name(event->type) ));
#else
		GF_LOG(GF_LOG_WARNING, GF_LOG_SCRIPT, ("[NodeJS] GPAC main event callback from different thread not implemented, ignoring event %d\n", event->type));
#endif

		return GF_FALSE;
	}

	status = napi_get_reference_value(napi_fs->env, napi_fs->ref, &fs_val);
	if (status != napi_ok) return GF_FALSE;
	napi_has_named_property(napi_fs->env, fs_val, "on_event", (bool *) &has_fun);
	if (!has_fun) return GF_FALSE;
	status = napi_get_named_property(napi_fs->env, fs_val, "on_event", &fun_val);
	if (status != napi_ok) return GF_FALSE;

	fevt.base.type = GF_FEVT_USER;
	fevt.base.on_pid = NULL;
	fevt.user_event.event = *event;
	evt = wrap_filterevent(napi_fs->env, &fevt, NULL);

	//make event
	status = napi_make_callback(napi_fs->env, napi_fs->async_ctx, fs_val, fun_val, 1, &evt, &res);
	if (status != napi_ok) return GF_FALSE;

	napi_get_value_bool(napi_fs->env, res, &has_fun);
	return has_fun ? GF_TRUE : GF_FALSE;
}

napi_value new_fs(napi_env env, napi_callback_info info)
{
	NAPI_Session *napi_fs;
	napi_value fun_name;
	NARG_ARGS_THIS(1, 0);

	NARG_U32(flags, 0, 0);

	NAPI_GPAC
	gpac->is_init = GF_TRUE;

	GF_SAFEALLOC(napi_fs, NAPI_Session);
	if (!napi_fs) {
		napi_throw_error(env, NULL, "Failed to allocate filter session");
		return NULL;
	}

	GF_FilterSession *fs = gf_fs_new_defaults(flags);
	if (!fs) {
		gf_free(napi_fs);
		napi_throw_error(env, NULL, "Failed to allocate filter session");
		return NULL;
	}
	NAPI_CALL( napi_wrap(env, this_val, fs, del_fs, NULL, NULL) );

	NAPI_CALL( napi_type_tag_object(env, this_val, &filtersession_tag) );

	napi_fs->fs = fs;
	napi_fs->env = env;
	//we force all setup tasks on main thread, async IO with node is not implemented yet here
	gf_fs_set_filter_creation_callback(fs, fs_on_filter_creation, napi_fs, GF_TRUE);

	NAPI_CALL( napi_create_reference(env, this_val, 1, &napi_fs->ref) );

	NAPI_CALL( napi_create_string_utf8(env, "AsyncFilterSessionTask", NAPI_AUTO_LENGTH, &fun_name) );
	NAPI_CALL( napi_async_init(env, this_val, fun_name, &napi_fs->async_ctx) );


	gf_fs_set_ui_callback(fs, napi_gpac_event_proc, napi_fs);

	if (! (flags & GF_FS_FLAG_NON_BLOCKING))
		napi_fs->blocking = GF_TRUE;

	return this_val;
}

napi_status init_fs_class(napi_env env, napi_value exports, GPAC_NAPI *inst)
{
	napi_status status;
	napi_property_descriptor fs_properties[] = {
		{ "last_task", NULL, NULL, fs_getter, NULL, NULL, napi_enumerable, &FS_PROP_LAST_TASK},
		{ "nb_filters", NULL, NULL, fs_getter, NULL, NULL, napi_enumerable, &FS_PROP_NB_FILTERS},
		{ "http_bitrate", NULL, NULL, fs_getter, NULL, NULL, napi_enumerable, &FS_PROP_HTTP_RATE},
		{ "http_max_bitrate", NULL, NULL, fs_getter, fs_setter, NULL, napi_enumerable, &FS_PROP_MAX_HTTP_RATE},

		{ "run", 0, fs_run, 0, 0, 0, napi_enumerable, 0 },
		{ "load_src", 0, fs_load_src, 0, 0, 0, napi_enumerable, 0 },
		{ "load_dst", 0, fs_load_dst, 0, 0, 0, napi_enumerable, 0 },
		{ "load", 0, fs_load_filter, 0, 0, 0, napi_enumerable, 0 },
		{ "post_task", 0, fs_post_task, 0, 0, 0, napi_enumerable, 0 },
		{ "abort", 0, fs_abort, 0, 0, 0, napi_enumerable, 0 },
		{ "lock", 0, fs_lock, 0, 0, 0, napi_enumerable, 0 },
		//TODO move as attrib
		{ "reporting", 0, fs_reporting, 0, 0, 0, napi_enumerable, 0 },
		{ "print_stats", 0, fs_print_stats, 0, 0, 0, napi_enumerable, 0 },
		{ "print_graph", 0, fs_print_graph, 0, 0, 0, napi_enumerable, 0 },
		{ "get_filter", 0, fs_get_filter, 0, 0, 0, napi_enumerable, 0 },
		{ "fire_event", 0, fs_fire_event, 0, 0, 0, napi_enumerable, 0 },
		{ "is_supported_mime", 0, fs_is_supported_mime, 0, 0, 0, napi_enumerable, 0 },
		{ "is_supported_source", 0, fs_is_supported_source, 0, 0, 0, napi_enumerable, 0 },
		{ "new_filter", 0, fs_new_filter, 0, 0, 0, napi_enumerable, 0 },
	};

	napi_value cons;
	status = napi_define_class(env, "FilterSession", NAPI_AUTO_LENGTH , new_fs, NULL, sizeof(fs_properties)/sizeof(napi_property_descriptor), fs_properties, &cons);
	if (status != napi_ok) return status;

	status = napi_set_named_property(env, exports, "FilterSession", cons);
	if (status != napi_ok) return status;

	return napi_ok;
}

static u32 FEVT_PROP_TYPE = 0;
static u32 FEVT_PROP_START_RANGE = 1;
static u32 FEVT_PROP_END_RANGE = 2;
static u32 FEVT_PROP_SPEED = 3;
static u32 FEVT_PROP_FROM_PCK = 4;
static u32 FEVT_PROP_HW_BUFFER_RESET = 5;
static u32 FEVT_PROP_INITIAL_BCAST = 6;
static u32 FEVT_PROP_TS_BASED = 7;
static u32 FEVT_PROP_FULL_FILE_ONLY = 8;
static u32 FEVT_PROP_FORCED_DASH_SWITCH = 9;
static u32 FEVT_PROP_DROP_NON_REF = 10;
static u32 FEVT_PROP_NO_BYTERANGE = 11;
static u32 FEVT_PROP_START_OFFSET = 12;
static u32 FEVT_PROP_END_OFFSET = 13;
static u32 FEVT_PROP_HINT_BLOCK_SIZE = 14;
static u32 FEVT_PROP_SOURCE_SWITCH = 15;
static u32 FEVT_PROP_SWITCH_IS_INIT_SEG = 16;
static u32 FEVT_PROP_SKIP_CACHE_EXP = 17;
static u32 FEVT_PROP_SSIZE_URL = 18;
static u32 FEVT_PROP_SSIZE_MEDIA_START = 19;
static u32 FEVT_PROP_SSIZE_MEDIA_END = 20;
static u32 FEVT_PROP_SSIZE_IDX_START = 21;
static u32 FEVT_PROP_SSIZE_IDX_END = 22;
static u32 FEVT_PROP_SSIZE_IS_INIT = 23;
static u32 FEVT_PROP_SSIZE_IS_SHIFT = 24;
static u32 FEVT_PROP_QS_UP = 25;
static u32 FEVT_PROP_QS_DEP_GROUP_IDX = 26;
static u32 FEVT_PROP_QS_QIDX = 27;
static u32 FEVT_PROP_QS_TILEMODE = 28;
static u32 FEVT_PROP_QS_DEGRADATION = 29;
static u32 FEVT_PROP_MIN_X = 30;
static u32 FEVT_PROP_MIN_Y = 31;
static u32 FEVT_PROP_MAX_X = 32;
static u32 FEVT_PROP_MAX_Y = 33;
static u32 FEVT_PROP_IS_GAZE = 34;
static u32 FEVT_PROP_MAX_BUFFER = 35;
static u32 FEVT_PROP_MAX_PLAYOUT = 36;
static u32 FEVT_PROP_MIN_PLAYOUT = 37;
static u32 FEVT_PROP_PID_ONLY = 38;
static u32 FEVT_PROP_UI_MOUSE_X = 39;
static u32 FEVT_PROP_UI_MOUSE_Y = 40;
static u32 FEVT_PROP_UI_MOUSE_WHEEL = 41;
static u32 FEVT_PROP_UI_MOUSE_BUTTON = 42;
static u32 FEVT_PROP_UI_MT_ROTATE = 43;
static u32 FEVT_PROP_UI_MT_PINCH = 44;
static u32 FEVT_PROP_UI_MT_FINGERS = 45;
static u32 FEVT_PROP_UI_KEY_CODE = 46;
static u32 FEVT_PROP_UI_KEY_NAME = 47;
static u32 FEVT_PROP_UI_KEY_MODS = 48;
static u32 FEVT_PROP_UI_KEY_HW = 49;
static u32 FEVT_PROP_UI_SHOWTYPE = 50;
static u32 FEVT_PROP_UI_CLIPBOARD = 51;
static u32 FEVT_PROP_UI_WIDTH = 52;
static u32 FEVT_PROP_UI_HEIGHT = 53;
static u32 FEVT_PROP_UI_MOVE_X = 54;
static u32 FEVT_PROP_UI_MOVE_Y = 55;
static u32 FEVT_PROP_UI_MOVE_RELATIVE = 56;
static u32 FEVT_PROP_UI_MOVE_ALIGNX = 57;
static u32 FEVT_PROP_UI_MOVE_ALIGNY = 58;
static u32 FEVT_PROP_UI_CAPTION = 59;
static u32 FEVT_PROP_FILE_DEL = 60;
static u32 FEVT_PROP_UI_TYPE = 61;
static u32 FEVT_PROP_NTP_REF = 62;
static u32 FEVT_PROP_NAME = 63;
static u32 FEVT_PROP_UI_NAME = 64;
static u32 FEVT_PROP_PID = 65;
static u32 FEVT_PROP_TO_PCK = 66;
static u32 FEVT_PROP_ORIG_DELAY = 67;
static u32 FEVT_PROP_HINT_FIRST_DTS = 68;
static u32 FEVT_PROP_HINT_START_OFFSET = 69;
static u32 FEVT_PROP_HINT_END_OFFSET = 70;


#define FILTEREVENT\
	GF_FilterEvent *evt=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &filterevent_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a FilterEvent object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &evt) );\


#define ALL_EVENT_FIELDS	\
	EVT_DBL(FEVT_PROP_START_RANGE, evt->play.start_range);\
	EVT_DBL(FEVT_PROP_END_RANGE, evt->play.end_range);\
	EVT_DBL(FEVT_PROP_SPEED, evt->play.speed);\
	EVT_U32(FEVT_PROP_FROM_PCK, evt->play.from_pck);\
	EVT_BOOL(FEVT_PROP_HW_BUFFER_RESET, evt->play.hw_buffer_reset);\
	EVT_BOOL(FEVT_PROP_INITIAL_BCAST, evt->play.initial_broadcast_play);\
	EVT_U32(FEVT_PROP_TS_BASED, evt->play.timestamp_based);\
	EVT_BOOL(FEVT_PROP_FULL_FILE_ONLY, evt->play.full_file_only);\
	EVT_BOOL(FEVT_PROP_FORCED_DASH_SWITCH, evt->play.forced_dash_segment_switch);\
	EVT_BOOL(FEVT_PROP_DROP_NON_REF, evt->play.drop_non_ref);\
	EVT_BOOL(FEVT_PROP_NO_BYTERANGE, evt->play.no_byterange_forward);\
	EVT_U32(FEVT_PROP_TO_PCK, evt->play.to_pck);\
	EVT_U32(FEVT_PROP_ORIG_DELAY, evt->play.orig_delay);\
	EVT_U64(FEVT_PROP_HINT_FIRST_DTS, evt->play.hint_first_dts);\
	EVT_U64(FEVT_PROP_HINT_START_OFFSET, evt->play.hint_start_offset);\
	EVT_U64(FEVT_PROP_HINT_END_OFFSET, evt->play.hint_end_offset);\
	EVT_U64(FEVT_PROP_START_OFFSET, evt->seek.start_offset);\
	EVT_U64(FEVT_PROP_END_OFFSET, evt->seek.end_offset);\
	EVT_U32(FEVT_PROP_HINT_BLOCK_SIZE, evt->seek.hint_block_size);\
	EVT_STR(FEVT_PROP_SOURCE_SWITCH, evt->seek.source_switch);\
	EVT_BOOL(FEVT_PROP_SWITCH_IS_INIT_SEG, evt->seek.is_init_segment);\
	EVT_BOOL(FEVT_PROP_SKIP_CACHE_EXP, evt->seek.skip_cache_expiration);\
	EVT_STR(FEVT_PROP_SSIZE_URL, evt->seg_size.seg_url);\
	EVT_U64(FEVT_PROP_SSIZE_MEDIA_START, evt->seg_size.media_range_start);\
	EVT_U64(FEVT_PROP_SSIZE_MEDIA_END, evt->seg_size.media_range_end);\
	EVT_U64(FEVT_PROP_SSIZE_IDX_START, evt->seg_size.idx_range_start);\
	EVT_U64(FEVT_PROP_SSIZE_IDX_END, evt->seg_size.idx_range_end);\
	EVT_BOOL(FEVT_PROP_SSIZE_IS_INIT, evt->seg_size.is_init);\
	EVT_BOOL(FEVT_PROP_SSIZE_IS_SHIFT, evt->seg_size.is_shift);\
	EVT_BOOL(FEVT_PROP_QS_UP, evt->quality_switch.up);\
	EVT_U32(FEVT_PROP_QS_DEP_GROUP_IDX, evt->quality_switch.dependent_group_index);\
	EVT_S32(FEVT_PROP_QS_QIDX, evt->quality_switch.q_idx);\
	EVT_U32(FEVT_PROP_QS_TILEMODE, evt->quality_switch.set_tile_mode_plus_one);\
	EVT_U32(FEVT_PROP_QS_DEGRADATION, evt->quality_switch.quality_degradation);\
	EVT_U32(FEVT_PROP_MIN_X, evt->visibility_hint.min_x);\
	EVT_U32(FEVT_PROP_MIN_Y, evt->visibility_hint.min_y);\
	EVT_U32(FEVT_PROP_MAX_X, evt->visibility_hint.max_x);\
	EVT_U32(FEVT_PROP_MAX_Y, evt->visibility_hint.max_y);\
	EVT_BOOL(FEVT_PROP_IS_GAZE, evt->visibility_hint.is_gaze);\
	EVT_U32(FEVT_PROP_MAX_BUFFER, evt->buffer_req.max_buffer_us);\
	EVT_U32(FEVT_PROP_MAX_PLAYOUT, evt->buffer_req.max_playout_us);\
	EVT_U32(FEVT_PROP_MIN_PLAYOUT, evt->buffer_req.min_playout_us);\
	EVT_BOOL(FEVT_PROP_PID_ONLY, evt->buffer_req.pid_only);\
	EVT_STR(FEVT_PROP_FILE_DEL, evt->file_del.url);\
	EVT_U64(FEVT_PROP_NTP_REF, evt->ntp.ntp);\
	EVT_U32(FEVT_PROP_UI_TYPE, evt->user_event.event.type);\
	if (evt->user_event.event.type==GF_EVENT_MULTITOUCH) {\
		EVT_FIX(FEVT_PROP_UI_MOUSE_X, evt->user_event.event.mtouch.x);\
		EVT_FIX(FEVT_PROP_UI_MOUSE_Y, evt->user_event.event.mtouch.y);\
	} else {\
		EVT_S32(FEVT_PROP_UI_MOUSE_X, evt->user_event.event.mouse.x);\
		EVT_S32(FEVT_PROP_UI_MOUSE_Y, evt->user_event.event.mouse.y);\
	}\
	EVT_FIX(FEVT_PROP_UI_MOUSE_WHEEL, evt->user_event.event.mouse.wheel_pos);\
	EVT_U32(FEVT_PROP_UI_MOUSE_BUTTON, evt->user_event.event.mouse.button);\
	if (evt->user_event.event.type<=GF_EVENT_MOUSEWHEEL) {\
		EVT_U32(FEVT_PROP_UI_KEY_MODS, evt->user_event.event.mouse.key_states);\
	} else {\
		EVT_U32(FEVT_PROP_UI_KEY_MODS, evt->user_event.event.key.flags);\
	}\
	EVT_FIX(FEVT_PROP_UI_MT_ROTATE, evt->user_event.event.mtouch.rotation);\
	EVT_FIX(FEVT_PROP_UI_MT_PINCH, evt->user_event.event.mtouch.pinch);\
	EVT_U32(FEVT_PROP_UI_MT_FINGERS, evt->user_event.event.mtouch.num_fingers);\
	EVT_U32(FEVT_PROP_UI_KEY_CODE, evt->user_event.event.key.key_code);\
	EVT_U32(FEVT_PROP_UI_KEY_HW, evt->user_event.event.key.hw_code);\
	EVT_STR(FEVT_PROP_UI_CLIPBOARD, evt->user_event.event.clipboard.text);\
	EVT_U32(FEVT_PROP_UI_WIDTH, evt->user_event.event.size.width);\
	EVT_U32(FEVT_PROP_UI_HEIGHT, evt->user_event.event.size.height);\
	EVT_U32(FEVT_PROP_UI_SHOWTYPE, evt->user_event.event.show.show_type);\
	EVT_S32(FEVT_PROP_UI_MOVE_X, evt->user_event.event.move.x);\
	EVT_S32(FEVT_PROP_UI_MOVE_Y, evt->user_event.event.move.y);\
	EVT_U32(FEVT_PROP_UI_MOVE_RELATIVE, evt->user_event.event.move.relative);\
	EVT_U32(FEVT_PROP_UI_MOVE_ALIGNX, evt->user_event.event.move.align_x);\
	EVT_U32(FEVT_PROP_UI_MOVE_ALIGNY, evt->user_event.event.move.align_y);\
	EVT_STR(FEVT_PROP_UI_CAPTION, evt->user_event.event.caption.caption);\

napi_value fevt_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILTEREVENT

#define EVT_DBL(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_create_double(env, _field, &ret) );\
		return ret;\
	}

#define EVT_FIX(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_create_double(env, FIX2FLT(_field), &ret) );\
		return ret;\
	}

#define EVT_U32(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_create_uint32(env, (u32) _field, &ret) );\
		return ret;\
	}

#define EVT_S32(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_create_int32(env, (s32) _field, &ret) );\
		return ret;\
	}

#define EVT_STR(_magic_val, _field) \
	if (magic == &_magic_val) {\
		if (_field) {\
			NAPI_CALL( napi_create_string_utf8(env, _field, NAPI_AUTO_LENGTH, &ret) );\
		} else {\
			NAPI_CALL( napi_get_null(env, &ret) );\
		}\
		return ret;\
	}

#define EVT_U64(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_create_int64(env, (s64) _field, &ret) );\
		return ret;\
	}

#define EVT_BOOL(_magic_val, _field) \
	if (magic == &_magic_val) {\
		NAPI_CALL( napi_get_boolean(env, (bool) _field, &ret) );\
		return ret;\
	}

	//read-only props
	EVT_U32(FEVT_PROP_TYPE, evt->base.type);
	EVT_STR(FEVT_PROP_NAME, gf_filter_event_name(evt->base.type) );
#ifndef GPAC_DISABLE_SVG
	EVT_STR(FEVT_PROP_UI_NAME, gf_dom_event_get_name(evt->user_event.event.type) );
#endif

	//all read/write props
	ALL_EVENT_FIELDS

	//custom fileds
#ifndef GPAC_DISABLE_SVG
	EVT_STR(FEVT_PROP_UI_KEY_NAME, gf_dom_get_key_name(evt->user_event.event.key.key_code));
#endif

	if (magic == &FEVT_PROP_PID) {
		NAPI_FilterPid *napi_pid = evt->base.on_pid ? gf_filter_pid_get_udta(evt->base.on_pid) : NULL;

		if (napi_pid) {
			napi_get_reference_value(env, napi_pid->ref, &ret);
		} else {
			NAPI_CALL( napi_get_null(env, &ret) );
		}
		return ret;
	}


#undef EVT_DBL
#undef EVT_FIX
#undef EVT_U32
#undef EVT_S32
#undef EVT_STR
#undef EVT_U64
#undef EVT_BOOL

	return NULL;
}

napi_value fevt_setter(napi_env env, napi_callback_info info)
{
	NARG_ARGS_THIS_MAGIC(1, 1)
	FILTEREVENT

#define	EVT_U32(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_U32(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

#define	EVT_S32(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_S32(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

#define	EVT_U64(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_U64(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

#define	EVT_BOOL(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_BOOL(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

#define	EVT_DBL(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_DBL(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

#define	EVT_FIX(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_DBL(val, 0, 0);\
		_field = FLT2FIX(val);\
		return NULL;\
	}

#define	EVT_STR(_magic_val, _field)\
	if (magic == &_magic_val) {\
		NARG_STR(val, 0, 0);\
		_field = val;\
		return NULL;\
	}

	//all read/write props
	ALL_EVENT_FIELDS

	//custom fields
#ifndef GPAC_DISABLE_SVG
	if (magic == &FEVT_PROP_UI_KEY_NAME) {
		NARG_STR(val, 0, 0);
		evt->user_event.event.key.key_code = gf_dom_get_key_type(val);
		return NULL;
	}
#endif
	return NULL;
}


void del_evt(napi_env env, void* finalize_data, void* finalize_hint)
{
	GF_FilterEvent *fevt = (GF_FilterEvent *)finalize_data;
	gf_free(fevt);
}

napi_value wrap_filterevent(napi_env env, GF_FilterEvent *evt, napi_value *for_val)
{
	napi_status status;
	napi_value obj;
	if (for_val) {
		obj = *for_val;
	} else {
		NAPI_CALL(napi_create_object(env, &obj) );
	}
	napi_callback evt_set = for_val ? fevt_setter : NULL;

	napi_property_descriptor fevt_base_properties[] = {
		{ "type", NULL, NULL, fevt_getter, NULL, NULL, napi_enumerable, &FEVT_PROP_TYPE},
		{ "pid", NULL, NULL, fevt_getter, NULL, NULL, napi_enumerable, &FEVT_PROP_PID},
		{ "name", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_NAME},
	};
	NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_base_properties)/sizeof(napi_property_descriptor), fevt_base_properties) );


	//define properties based on event type
	if (evt->base.type == GF_FEVT_PLAY) {
		napi_property_descriptor fevt_properties[] = {
			{ "start_range", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_START_RANGE},
			{ "end_range", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_END_RANGE},
			{ "speed", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SPEED},
			{ "from_pck", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FROM_PCK},
			{ "hw_buffer_reset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_HW_BUFFER_RESET},
			{ "initial_broadcast_play", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_INITIAL_BCAST},
			{ "timestamp_based", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_TS_BASED},
			{ "full_file_only", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FULL_FILE_ONLY},
			{ "forced_dash_segment_switch", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FORCED_DASH_SWITCH},
			{ "drop_non_ref", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_DROP_NON_REF},
			{ "no_byterange_forward", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_NO_BYTERANGE},
			{ "to_pck", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_TO_PCK},
			{ "orig_delay", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_ORIG_DELAY},
			{ "hint_first_dts", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_HINT_FIRST_DTS},
			{ "hint_start_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_HINT_START_OFFSET},
			{ "hint_end_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_HINT_END_OFFSET},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_SET_SPEED) {
		napi_property_descriptor fevt_properties[] = {
			{ "speed", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SPEED},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if ((evt->base.type == GF_FEVT_STOP) || (evt->base.type == GF_FEVT_PLAY_HINT)) {
		napi_property_descriptor fevt_properties[] = {
			{ "forced_dash_segment_switch", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FORCED_DASH_SWITCH},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_SOURCE_SEEK) {
		napi_property_descriptor fevt_properties[] = {
			{ "start_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_START_OFFSET},
			{ "end_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_END_OFFSET},
			{ "hint_block_size", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_HINT_BLOCK_SIZE},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_SOURCE_SWITCH) {
		napi_property_descriptor fevt_properties[] = {
			{ "start_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_START_OFFSET},
			{ "end_offset", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_END_OFFSET},
			{ "source_switch", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SOURCE_SWITCH},
			{ "is_init_segment", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SWITCH_IS_INIT_SEG},
			{ "skip_cache_expiration", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SKIP_CACHE_EXP},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_SEGMENT_SIZE) {
		napi_property_descriptor fevt_properties[] = {
			{ "seg_url", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_URL},
			{ "media_range_start", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_MEDIA_START},
			{ "media_range_end", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_MEDIA_END},
			{ "idx_range_start", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_IDX_START},
			{ "idx_range_end", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_IDX_END},
			{ "is_init", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_IS_INIT},
			{ "is_shift", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_SSIZE_IS_SHIFT},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_QUALITY_SWITCH) {
		napi_property_descriptor fevt_properties[] = {
			{ "up", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_QS_UP},
			{ "dependent_group_index", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_QS_DEP_GROUP_IDX},
			{ "q_idx", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_QS_QIDX},
			{ "set_tile_mode_plus_one", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_QS_TILEMODE},
			{ "quality_degradation", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_QS_DEGRADATION},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_VISIBILITY_HINT) {
		napi_property_descriptor fevt_properties[] = {
			{ "min_x", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MIN_X},
			{ "max_x", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MIN_Y},
			{ "min_y", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MAX_X},
			{ "max_y", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MAX_Y},
			{ "is_gaze", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_IS_GAZE},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_BUFFER_REQ) {
		napi_property_descriptor fevt_properties[] = {
			{ "max_buffer_us", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MAX_BUFFER},
			{ "max_playout_us", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MAX_PLAYOUT},
			{ "min_playout_us", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_MIN_PLAYOUT},
			{ "pid_only", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_PID_ONLY},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_USER) {
		u32 ui_type = evt->user_event.event.type;

		napi_property_descriptor fevt_ui_properties[] = {
			{ "ui_type", NULL, NULL, fevt_getter, NULL, NULL, napi_enumerable, &FEVT_PROP_UI_TYPE},
#ifndef GPAC_DISABLE_SVG
			{ "ui_name", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_NAME},
#endif
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_ui_properties)/sizeof(napi_property_descriptor), fevt_ui_properties) );

		switch (ui_type) {
		case GF_EVENT_CLICK:
		case GF_EVENT_MOUSEUP:
		case GF_EVENT_MOUSEDOWN:
		case GF_EVENT_MOUSEOVER:
		case GF_EVENT_MOUSEOUT:
		case GF_EVENT_MOUSEMOVE:
		case GF_EVENT_MOUSEWHEEL:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "mouse_x", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_X},
				{ "mouse_y", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_Y},
				{ "wheel", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_WHEEL},
				{ "button", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_BUTTON},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		case GF_EVENT_MULTITOUCH:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "mt_x", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_X},
				{ "mt_y", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOUSE_Y},
				{ "mt_rotate", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MT_ROTATE},
				{ "mt_pinch", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MT_PINCH},
				{ "mt_fingers", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MT_FINGERS},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;

		case GF_EVENT_KEYUP:
		case GF_EVENT_KEYDOWN:
		case GF_EVENT_LONGKEYPRESS:
		case GF_EVENT_TEXTINPUT:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "keycode", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_KEY_CODE},
#ifndef GPAC_DISABLE_SVG
				{ "keyname", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_KEY_NAME},
#endif
				{ "keymods", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_KEY_MODS},
				{ "hwkey", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_KEY_HW},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		case GF_EVENT_PASTE_TEXT:
		case GF_EVENT_COPY_TEXT:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "clipboard", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_CLIPBOARD},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		case GF_EVENT_SIZE:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "width", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_WIDTH},
				{ "height", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_HEIGHT},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		case GF_EVENT_MOVE:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "move_x", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOVE_X},
				{ "move_y", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOVE_Y},
				{ "showtype", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_SHOWTYPE},
				{ "move_relative", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOVE_RELATIVE},
				{ "move_alignx", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOVE_ALIGNX},
				{ "move_aligny", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_MOVE_ALIGNY},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		case GF_EVENT_SET_CAPTION:
		{
			napi_property_descriptor fevt_properties[] = {
				{ "caption", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_UI_CAPTION},
			};
			NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
		}
			break;
		default:
			break;
		}
	}
	else if (evt->base.type == GF_FEVT_PLAY_HINT) {
		napi_property_descriptor fevt_properties[] = {
			{ "full_file_only", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FULL_FILE_ONLY},
			{ "forced_dash_segment_switch", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FORCED_DASH_SWITCH},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_FILE_DELETE) {
		napi_property_descriptor fevt_properties[] = {
			{ "url", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_FILE_DEL},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	else if (evt->base.type == GF_FEVT_NTP_REF) {
		napi_property_descriptor fevt_properties[] = {
			{ "ntp", NULL, NULL, fevt_getter, evt_set, NULL, napi_enumerable, &FEVT_PROP_NTP_REF},
		};
		NAPI_CALL( napi_define_properties(env, obj, sizeof(fevt_properties)/sizeof(napi_property_descriptor), fevt_properties) );
	}
	//other events don't have any attributes

	NAPI_CALL( napi_wrap(env, obj, evt, for_val ? del_evt : NULL, NULL, NULL) );
	NAPI_CALL( napi_type_tag_object(env, obj, &filterevent_tag) );

	return obj;
}
napi_value new_fevt(napi_env env, napi_callback_info info)
{
	GF_FilterEvent *evt;
	NARG_ARGS_THIS(2, 1);
	NARG_U32(type, 0, 0);

	NAPI_GPAC
	gpac->is_init = GF_TRUE;

	if (type==GF_FEVT_USER) {
		if (argc<2) {
			napi_throw_error(env, NULL, "User event type must be specified");
			return NULL;
		}
	}
	GF_SAFEALLOC(evt, GF_FilterEvent)
	if (!evt) {
		napi_throw_error(env, NULL, "Failed to allocate filter event");
		return NULL;
	}
	NAPI_CALL( napi_get_value_uint32(env, argv[0], &evt->base.type) );
	if (type==GF_FEVT_USER) {
		u32 val;
		NAPI_CALL( napi_get_value_uint32(env, argv[1], &val) );
		evt->user_event.event.type = (u8) val;
	}

	return wrap_filterevent(env, evt, &this_val);
}


napi_status init_fevt_class(napi_env env, napi_value exports, GPAC_NAPI *inst)
{
	napi_status status;
	napi_value cons;
	status = napi_define_class(env, "FilterEvent", NAPI_AUTO_LENGTH , new_fevt, NULL, 0, NULL, &cons);
	if (status != napi_ok) return status;
	return napi_set_named_property(env, exports, "FilterEvent", cons);
}


static napi_status InitConstants(napi_env env, napi_value exports);
napi_status init_fio_class(napi_env env, napi_value exports, GPAC_NAPI *inst);


static u32 NODEGPAC_PROP_RMT_ON_NEW_CLIENT = 0;
static u32 NODEGPAC_PROP_USERWS_ON_NEW_CLIENT = 1;



void gpac_rmt_on_new_client(void *udta, void* new_client) {

	NAPI_RMT_Task* task = udta;

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

	napi_env env = task->env;

	GPAC_NAPI *gpac;
	if ( napi_get_instance_data(env, (void **) &gpac) != napi_ok) {
		napi_throw_error(env, NULL, "Cannot get GPAC NAPI instance");
		return;
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("gpac_rmt_on_new_client %p\n", new_client));


	if (!gpac->rmt_new_clients) gpac->rmt_new_clients = gf_list_new();
	gf_list_add(gpac->rmt_new_clients, new_client );

	if (gpac->fs_rmt_handler) {

		gf_fs_post_user_task(gpac->fs_rmt_handler->fs, fs_flush_rmt, gpac->fs_rmt_handler, "RMTOnNewClient");
	}

}


napi_value nodegpac_setter(napi_env env, napi_callback_info info)
{
	NAPI_GPAC
	NARG_ARGS_THIS_MAGIC(1, 1)

	gpac->is_init = GF_TRUE;

	if (magic == &NODEGPAC_PROP_RMT_ON_NEW_CLIENT || magic == &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT) {

		napi_valuetype rtype;
		napi_typeof(env, argv[0], &rtype);

		RMT_WS* rmt = NULL;
		if (magic == &NODEGPAC_PROP_RMT_ON_NEW_CLIENT) 		rmt = (RMT_WS*) gf_sys_get_rmtws();
		if (magic == &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT) 	rmt = (RMT_WS*) gf_sys_get_userws();

		if (!rmt)
			return NULL;

		if (rtype == napi_null || rtype == napi_undefined ) {

			if (gpac->rmt_ctx) {
				napi_async_destroy(env, gpac->rmt_ctx);
				gpac->rmt_ctx = NULL;
			}
			NAPI_RMT_Task* task = gf_rmt_get_on_new_client_task(rmt);
			napi_rmt_task_del(env, task);

			gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);


		} else if (rtype == napi_function) {

			if (!gpac->rmt_ctx) {
				napi_value global, name;
				NAPI_CALL( napi_get_global(env, &global) );
				NAPI_CALL( napi_create_string_utf8(env, "GPAC_RMTCallback", NAPI_AUTO_LENGTH, &name) );
				NAPI_CALL( napi_async_init(env, global, name, &gpac->rmt_ctx) )
			}

			NAPI_RMT_Task *task;
			GF_SAFEALLOC(task, NAPI_RMT_Task);
			task->type = RMT_CALLBACK_NODE;
			task->env = env;
			napi_create_reference(env, this_val, 1, &task->_this);
			napi_create_reference(env, argv[0], 1, &task->fun);

			GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("setting on new client cb %p\n", gpac_rmt_on_new_client));
			gf_rmt_set_on_new_client_cbk(rmt, task, gpac_rmt_on_new_client);

		}

	}

	return NULL;
}

napi_value Init(napi_env env, napi_value exports)
{
	GPAC_NAPI *inst;
	napi_status status;
	napi_property_descriptor p_desc = {.attributes=napi_enumerable};

	//don't use gf_malloc here !!
	inst = malloc(sizeof(GPAC_NAPI));
	if (!inst) {
		napi_throw_error(env, NULL, "Failed to allocate GPAC NAPI context");
		return NULL;
	}
	memset(inst, 0, sizeof(GPAC_NAPI));
	NAPI_CALL( napi_set_instance_data(env, inst, gpac_napi_finalize, NULL) );

	NAPI_CALL(InitConstants(env, exports) );

#define DEF_FUN(_name, _fun) \
	p_desc.utf8name = _name; \
	NAPI_CALL( napi_create_function(env, NULL, 0, _fun, NULL, &p_desc.value) ); \
	NAPI_CALL( napi_define_properties(env, exports, 1, &p_desc) );

	/*function exports*/
	DEF_FUN("init", gpac_init);
	DEF_FUN("e2s", gpac_e2s);
	DEF_FUN("set_logs", gpac_set_logs);
	DEF_FUN("set_args", gpac_set_args);
	DEF_FUN("sys_clock", gpac_sys_clock);
	DEF_FUN("sys_clock_high_res", gpac_sys_clock_high_res);
	DEF_FUN("sys_keyname", gpac_sys_keyname);

	DEF_FUN("enable_rmtws", gpac_enable_rmtws);
	napi_property_descriptor rmtws_properties[] = {
		{ "rmt_on_new_client", NULL, NULL, NULL, nodegpac_setter, NULL, napi_enumerable, &NODEGPAC_PROP_RMT_ON_NEW_CLIENT},
	};
	NAPI_CALL( napi_define_properties(env, exports, sizeof(rmtws_properties)/sizeof(napi_property_descriptor), rmtws_properties) );

	DEF_FUN("enable_userws", gpac_enable_userws);
	napi_property_descriptor userws_properties[] = {
		{ "userws_on_new_client", NULL, NULL, NULL, nodegpac_setter, NULL, napi_enumerable, &NODEGPAC_PROP_USERWS_ON_NEW_CLIENT},
	};
	NAPI_CALL( napi_define_properties(env, exports, sizeof(userws_properties)/sizeof(napi_property_descriptor), userws_properties) );

	status = init_fs_class(env, exports, inst);
	if (status != napi_ok) return NULL;

	status = init_fevt_class(env, exports, inst);
	if (status != napi_ok) return NULL;

	status = init_fio_class(env, exports, inst);
	if (status != napi_ok) return NULL;


	inst->main_thid = gf_th_id();

	//default init
	gf_sys_init(0, NULL);
	return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)


static napi_status InitConstants(napi_env env, napi_value exports)
{
	napi_status status;
	napi_property_descriptor p_desc = {.attributes=napi_enumerable};

#define DEF_CONST_STR(_name, _fun) \
	p_desc.utf8name = _name; \
	status = napi_create_string_utf8(env, _fun, NAPI_AUTO_LENGTH, &p_desc.value);\
	if (status != napi_ok) return status;\
	status = napi_define_properties(env, exports, 1, &p_desc); \
	if (status != napi_ok) return status;\

#define DEF_CONST_INT(_name, _fun) \
	p_desc.utf8name = _name; \
	status = napi_create_int32(env, _fun, &p_desc.value); \
	if (status != napi_ok) return status;\
	status = napi_define_properties(env, exports, 1, &p_desc);\
	if (status != napi_ok) return status;\

#define DEF_CONST(_const) \
	p_desc.utf8name = #_const; \
	status = napi_create_int32(env, _const, &p_desc.value); \
	if (status != napi_ok) return status;\
	status = napi_define_properties(env, exports, 1, &p_desc);\
	if (status != napi_ok) return status;\


	/*symbol exports*/
	DEF_CONST_STR("version", gf_gpac_version() );
	DEF_CONST_STR("copyright", gf_gpac_copyright() );
	DEF_CONST_STR("copyright_cite", gf_gpac_copyright_cite() );

	DEF_CONST_INT("abi_major", gf_gpac_abi_major() );
	DEF_CONST_INT("abi_minor", gf_gpac_abi_minor() );
	DEF_CONST_INT("abi_micro", gf_gpac_abi_micro() );

	DEF_CONST(GF_LOG_ERROR)
	DEF_CONST(GF_LOG_WARNING)
	DEF_CONST(GF_LOG_INFO)
	DEF_CONST(GF_LOG_DEBUG)

	DEF_CONST(GF_PROP_FORBIDDEN)
	DEF_CONST(GF_PROP_BOOL)
	DEF_CONST(GF_PROP_UINT)
	DEF_CONST(GF_PROP_SINT)
	DEF_CONST(GF_PROP_LUINT)
	DEF_CONST(GF_PROP_LSINT)
	DEF_CONST(GF_PROP_FRACTION)
	DEF_CONST(GF_PROP_FRACTION64)
	DEF_CONST(GF_PROP_FLOAT)
	DEF_CONST(GF_PROP_DOUBLE)
	DEF_CONST(GF_PROP_VEC2I)
	DEF_CONST(GF_PROP_VEC2)
	DEF_CONST(GF_PROP_VEC3I)
	DEF_CONST(GF_PROP_VEC4I)
	DEF_CONST(GF_PROP_STRING)
	DEF_CONST(GF_PROP_STRING_NO_COPY)
	DEF_CONST(GF_PROP_DATA)
	DEF_CONST(GF_PROP_NAME)
	DEF_CONST(GF_PROP_DATA_NO_COPY)
	DEF_CONST(GF_PROP_CONST_DATA)
	DEF_CONST(GF_PROP_POINTER)
	DEF_CONST(GF_PROP_STRING_LIST)
	DEF_CONST(GF_PROP_UINT_LIST)
	DEF_CONST(GF_PROP_SINT_LIST)
	DEF_CONST(GF_PROP_VEC2I_LIST)
	DEF_CONST(GF_PROP_4CC)
	DEF_CONST(GF_PROP_4CC_LIST)

	DEF_CONST(GF_PROP_FIRST_ENUM)
	DEF_CONST(GF_PROP_PIXFMT)
	DEF_CONST(GF_PROP_PCMFMT)
	DEF_CONST(GF_PROP_CICP_COL_PRIM)
	DEF_CONST(GF_PROP_CICP_COL_TFC)
	DEF_CONST(GF_PROP_CICP_COL_MX)
	DEF_CONST(GF_PROP_CICP_LAYOUT)
	DEF_CONST(GF_PROP_FIRST_ENUM)

	DEF_CONST(GF_FEVT_PLAY)
	DEF_CONST(GF_FEVT_SET_SPEED)
	DEF_CONST(GF_FEVT_STOP)
	DEF_CONST(GF_FEVT_PAUSE)
	DEF_CONST(GF_FEVT_RESUME)
	DEF_CONST(GF_FEVT_SOURCE_SEEK)
	DEF_CONST(GF_FEVT_SOURCE_SWITCH)
	DEF_CONST(GF_FEVT_SEGMENT_SIZE)
	DEF_CONST(GF_FEVT_QUALITY_SWITCH)
	DEF_CONST(GF_FEVT_VISIBILITY_HINT)
	DEF_CONST(GF_FEVT_INFO_UPDATE)
	DEF_CONST(GF_FEVT_BUFFER_REQ)
	DEF_CONST(GF_FEVT_CAPS_CHANGE)
	DEF_CONST(GF_FEVT_CONNECT_FAIL)
	DEF_CONST(GF_FEVT_USER)
	DEF_CONST(GF_FEVT_PLAY_HINT)
	DEF_CONST(GF_FEVT_FILE_DELETE)

	DEF_CONST(GF_STATS_LOCAL)
	DEF_CONST(GF_STATS_LOCAL_INPUTS)
	DEF_CONST(GF_STATS_DECODER_SINK)
	DEF_CONST(GF_STATS_DECODER_SOURCE)
	DEF_CONST(GF_STATS_ENCODER_SINK)
	DEF_CONST(GF_STATS_ENCODER_SOURCE)
	DEF_CONST(GF_STATS_SINK)

	DEF_CONST(GF_FILTER_CLOCK_NONE)
	DEF_CONST(GF_FILTER_CLOCK_PCR)
	DEF_CONST(GF_FILTER_CLOCK_PCR_DISC)

	DEF_CONST(GF_FILTER_SAP_NONE)
	DEF_CONST(GF_FILTER_SAP_1)
	DEF_CONST(GF_FILTER_SAP_2)
	DEF_CONST(GF_FILTER_SAP_3)
	DEF_CONST(GF_FILTER_SAP_4)
	DEF_CONST(GF_FILTER_SAP_4_PROL)

	DEF_CONST(GF_EOS);
	DEF_CONST(GF_OK)
	DEF_CONST(GF_BAD_PARAM)
	DEF_CONST(GF_OUT_OF_MEM)
	DEF_CONST(GF_IO_ERR)
	DEF_CONST(GF_NOT_SUPPORTED)
	DEF_CONST(GF_CORRUPTED_DATA)
	DEF_CONST(GF_SG_UNKNOWN_NODE)
	DEF_CONST(GF_SG_INVALID_PROTO)
	DEF_CONST(GF_SCRIPT_ERROR)
	DEF_CONST(GF_BUFFER_TOO_SMALL)
	DEF_CONST(GF_NON_COMPLIANT_BITSTREAM)
	DEF_CONST(GF_FILTER_NOT_FOUND)
	DEF_CONST(GF_URL_ERROR)
	DEF_CONST(GF_SERVICE_ERROR)
	DEF_CONST(GF_REMOTE_SERVICE_ERROR)
	DEF_CONST(GF_STREAM_NOT_FOUND)
	DEF_CONST(GF_ISOM_INVALID_FILE)
	DEF_CONST(GF_ISOM_INCOMPLETE_FILE)
	DEF_CONST(GF_ISOM_INVALID_MEDIA)
	DEF_CONST(GF_ISOM_INVALID_MODE)
	DEF_CONST(GF_ISOM_UNKNOWN_DATA_REF)
	DEF_CONST(GF_ODF_INVALID_DESCRIPTOR)
	DEF_CONST(GF_ODF_FORBIDDEN_DESCRIPTOR)
	DEF_CONST(GF_ODF_INVALID_COMMAND)
	DEF_CONST(GF_BIFS_UNKNOWN_VERSION)
	DEF_CONST(GF_IP_ADDRESS_NOT_FOUND)
	DEF_CONST(GF_IP_CONNECTION_FAILURE)
	DEF_CONST(GF_IP_NETWORK_FAILURE)
	DEF_CONST(GF_IP_CONNECTION_CLOSED)
	DEF_CONST(GF_IP_NETWORK_EMPTY)
	DEF_CONST(GF_IP_UDP_TIMEOUT)
	DEF_CONST(GF_AUTHENTICATION_FAILURE)
	DEF_CONST(GF_NOT_READY)
	DEF_CONST(GF_INVALID_CONFIGURATION)
	DEF_CONST(GF_NOT_FOUND)
	DEF_CONST(GF_PROFILE_NOT_SUPPORTED)
	DEF_CONST(GF_REQUIRES_NEW_INSTANCE)
	DEF_CONST(GF_FILTER_NOT_SUPPORTED)


	DEF_CONST(GF_FILTER_UPDATE_DOWNSTREAM)
	DEF_CONST(GF_FILTER_UPDATE_UPSTREAM)

	DEF_CONST(GF_EVENT_CLICK)
	DEF_CONST(GF_EVENT_MOUSEUP)
	DEF_CONST(GF_EVENT_MOUSEDOWN)
	DEF_CONST(GF_EVENT_MOUSEMOVE)
	DEF_CONST(GF_EVENT_MOUSEWHEEL)
	DEF_CONST(GF_EVENT_DBLCLICK)
	DEF_CONST(GF_EVENT_MULTITOUCH)
	DEF_CONST(GF_EVENT_KEYUP)
	DEF_CONST(GF_EVENT_KEYDOWN)
	DEF_CONST(GF_EVENT_TEXTINPUT)
	DEF_CONST(GF_EVENT_DROPFILE)
	DEF_CONST(GF_EVENT_TIMESHIFT_DEPTH)
	DEF_CONST(GF_EVENT_TIMESHIFT_UPDATE)
	DEF_CONST(GF_EVENT_TIMESHIFT_OVERFLOW)
	DEF_CONST(GF_EVENT_TIMESHIFT_UNDERRUN)
	DEF_CONST(GF_EVENT_PASTE_TEXT)
	DEF_CONST(GF_EVENT_COPY_TEXT)
	DEF_CONST(GF_EVENT_SIZE)
	DEF_CONST(GF_EVENT_SHOWHIDE)
	DEF_CONST(GF_EVENT_MOVE)
	DEF_CONST(GF_EVENT_SET_CAPTION)
	DEF_CONST(GF_EVENT_REFRESH)
	DEF_CONST(GF_EVENT_QUIT)
	DEF_CONST(GF_EVENT_CODEC_SLOW)
	DEF_CONST(GF_EVENT_CODEC_OK)

	DEF_CONST(GF_KEY_MOD_SHIFT)
	DEF_CONST(GF_KEY_MOD_CTRL)
	DEF_CONST(GF_KEY_MOD_ALT)
	DEF_CONST(GF_KEY_EXT_NUMPAD)
	DEF_CONST(GF_KEY_EXT_LEFT)
	DEF_CONST(GF_KEY_EXT_RIGHT)

	DEF_CONST(GF_MemTrackerNone)
	DEF_CONST(GF_MemTrackerSimple)
	DEF_CONST(GF_MemTrackerBackTrace)

	DEF_CONST(GF_FS_SCHEDULER_LOCK_FREE)
	DEF_CONST(GF_FS_SCHEDULER_LOCK)
	DEF_CONST(GF_FS_SCHEDULER_LOCK_FREE_X)
	DEF_CONST(GF_FS_SCHEDULER_LOCK_FORCE)
	DEF_CONST(GF_FS_SCHEDULER_DIRECT)

	DEF_CONST(GF_FS_FLAG_LOAD_META)
	DEF_CONST(GF_FS_FLAG_NON_BLOCKING)
	DEF_CONST(GF_FS_FLAG_NO_GRAPH_CACHE)
	DEF_CONST(GF_FS_FLAG_NO_REGULATION)
	DEF_CONST(GF_FS_FLAG_NO_PROBE)
	DEF_CONST(GF_FS_FLAG_NO_REASSIGN)
	DEF_CONST(GF_FS_FLAG_PRINT_CONNECTIONS)
	DEF_CONST(GF_FS_FLAG_NO_ARG_CHECK)
	DEF_CONST(GF_FS_FLAG_NO_RESERVOIR)
	DEF_CONST(GF_FS_FLAG_FULL_LINK)
	DEF_CONST(GF_FS_FLAG_NO_IMPLICIT)
	DEF_CONST(GF_FS_FLAG_REQUIRE_SOURCE_ID)
	DEF_CONST(GF_FS_FLAG_FORCE_DEFER_LINK)
	DEF_CONST(GF_FS_FLAG_PREVENT_PLAY)

	DEF_CONST(GF_FS_ARG_HINT_NORMAL)
	DEF_CONST(GF_FS_ARG_HINT_ADVANCED)
	DEF_CONST(GF_FS_ARG_HINT_EXPERT)
	DEF_CONST(GF_FS_ARG_HINT_HIDE)
	DEF_CONST(GF_FS_ARG_UPDATE)
	DEF_CONST(GF_FS_ARG_META)
	DEF_CONST(GF_FS_ARG_META_ALLOC)
	DEF_CONST(GF_FS_ARG_SINK_ALIAS)

	DEF_CONST(GF_CAPFLAG_IN_BUNDLE)
	DEF_CONST(GF_CAPFLAG_INPUT)
	DEF_CONST(GF_CAPFLAG_OUTPUT)
	DEF_CONST(GF_CAPFLAG_EXCLUDED)
	DEF_CONST(GF_CAPFLAG_LOADED_FILTER)
	DEF_CONST(GF_CAPFLAG_STATIC)
	DEF_CONST(GF_CAPFLAG_OPTIONAL)
	DEF_CONST(GF_CAPFLAG_RECONFIG)
	DEF_CONST(GF_CAPS_INPUT)
	DEF_CONST(GF_CAPS_INPUT_OPT)
	DEF_CONST(GF_CAPS_INPUT_STATIC)
	DEF_CONST(GF_CAPS_INPUT_STATIC_OPT)
	DEF_CONST(GF_CAPS_INPUT_EXCLUDED)
	DEF_CONST(GF_CAPS_INPUT_LOADED_FILTER)
	DEF_CONST(GF_CAPS_OUTPUT)
	DEF_CONST(GF_CAPS_OUTPUT_LOADED_FILTER)
	DEF_CONST(GF_CAPS_OUTPUT_EXCLUDED)
	DEF_CONST(GF_CAPS_OUTPUT_STATIC)
	DEF_CONST(GF_CAPS_OUTPUT_STATIC_EXCLUDED)
	DEF_CONST(GF_CAPS_INPUT_OUTPUT)
	DEF_CONST(GF_CAPS_INPUT_OUTPUT_OPT)

	DEF_CONST(GF_FS_FLUSH_NONE)
	DEF_CONST(GF_FS_FLUSH_ALL)
	DEF_CONST(GF_FS_FLUSH_FAST)

	DEF_CONST(GF_SETUP_ERROR)
	DEF_CONST(GF_NOTIF_ERROR)
	DEF_CONST(GF_NOTIF_ERROR_AND_DISCONNECT)

	return napi_ok;
}


void dashbind_finalize(napi_env env, void* finalize_data, void* finalize_hint)
{
	NAPI_Dash *napi_dash = (NAPI_Dash *)finalize_data;
return;
	u32 rcount;
	napi_reference_unref(env, napi_dash->groups, &rcount);
	napi_delete_reference(env, napi_dash->groups);
	napi_dash->groups = NULL;

	napi_reference_unref(env, napi_dash->ref, &rcount);
	napi_delete_reference(env, napi_dash->ref);
	napi_dash->ref = NULL;

	/*destruction not done here*/
}

void dashbind_period_reset(void *udta, u32 type)
{
	napi_status status;
	napi_value obj, fun_val, res;
	NAPI_Dash *napi_dash = (NAPI_Dash *)udta;

	napi_get_reference_value(napi_dash->env, napi_dash->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_dash->env, obj, "on_period_reset", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_dash->env, obj, "on_period_reset", &fun_val);
		if (status!=napi_ok) has_fun = GF_FALSE;
	}

	if (!has_fun) return;

	napi_value arg;
	napi_create_uint32(napi_dash->env, type, &arg);
	napi_call_function(napi_dash->env, obj, fun_val, 1, &arg, &res);
}

void dashbind_new_group(void *udta, u32 group_idx, void *_dash)
{
	napi_property_descriptor p_desc = {.attributes=napi_enumerable};
	napi_status status;
	napi_value obj, group, groups, qualities, fun_val, res;
	NAPI_Dash *napi_dash = (NAPI_Dash *)udta;
	napi_env env = napi_dash->env;
	GF_DashClient *dash = (GF_DashClient *) _dash;

	napi_create_object(env, &group);

	napi_get_reference_value(napi_dash->env, napi_dash->groups, &groups);
	napi_set_element(env, groups, group_idx, group);

	p_desc.utf8name = "idx";
	napi_create_uint32(env, group_idx, &p_desc.value);
	napi_define_properties(env, group, 1, &p_desc);

	p_desc.utf8name = "duration";
	napi_create_int64(env, (s64) gf_dash_get_period_duration(dash), &p_desc.value);
	napi_define_properties(env, group, 1, &p_desc);

	p_desc.utf8name = "qualities";
	napi_create_array(env, &p_desc.value);
	napi_define_properties(env, group, 1, &p_desc);
	qualities = p_desc.value;

	u32 i, nb_qualities = gf_dash_group_get_num_qualities(dash, group_idx);
	for (i=0; i<nb_qualities; i++) {
		napi_value q, segs;
		GF_DASHQualityInfo qinfo;
		gf_dash_group_get_quality_info(dash, group_idx, i, &qinfo);

		napi_create_object(env, &q);


#define QSET_U32(_val) \
		p_desc.utf8name = #_val;\
		napi_create_uint32(env, qinfo._val, &p_desc.value); \
		napi_define_properties(env, q, 1, &p_desc);

#define QSET_BOOL(_val) \
		p_desc.utf8name = #_val;\
		napi_get_boolean(env, qinfo._val ? true : false, &p_desc.value); \
		napi_define_properties(env, q, 1, &p_desc);

#define QSET_STR(_val) \
		p_desc.utf8name = #_val;\
		if (qinfo._val) {\
			napi_create_string_utf8(env, qinfo._val, NAPI_AUTO_LENGTH, &p_desc.value);\
		} else {\
			napi_get_null(env, &p_desc.value);\
		}\
		napi_define_properties(env, q, 1, &p_desc);

#define QSET_DOUBLE(_val) \
		p_desc.utf8name = #_val;\
		napi_create_double(env, qinfo._val ? true : false, &p_desc.value); \
		napi_define_properties(env, q, 1, &p_desc);


		QSET_U32(bandwidth)
		QSET_STR(ID)
		QSET_STR(mime)
		QSET_STR(codec)
		QSET_U32(width)
		QSET_U32(height)
		QSET_BOOL(interlaced)

		p_desc.utf8name = "fps";
		GF_Fraction frac = {qinfo.fps_num, qinfo.fps_den};
		p_desc.value = frac_to_napi(env, &frac);
		napi_define_properties(env, q, 1, &p_desc);

		p_desc.utf8name = "sar";
		frac.num = qinfo.par_num;
		frac.den = qinfo.par_den;
		p_desc.value = frac_to_napi(env, &frac);
		napi_define_properties(env, q, 1, &p_desc);

		QSET_U32(sample_rate)
		QSET_U32(nb_channels)
		QSET_BOOL(disabled)
		QSET_BOOL(is_selected)
		QSET_DOUBLE(ast_offset)
		QSET_DOUBLE(average_duration)
		QSET_STR(hls_variant_url)
		QSET_U32(ssr)

		p_desc.utf8name = "sizes";
		napi_create_array(env, &p_desc.value);
		napi_define_properties(env, q, 1, &p_desc);
		segs = p_desc.value;

		/*! list of segmentURLs if known, NULL otherwise. Used for onDemand profile to get segment sizes*/
		if (qinfo.seg_urls) {
			u32 j, nb_segs=gf_list_count(qinfo.seg_urls);
			for (j=0; j<nb_segs; j++) {
				napi_value si;
				GF_MPD_SegmentURL *surl = gf_list_get((GF_List *) qinfo.seg_urls, j);

				napi_create_object(env, &si);
				napi_set_element(env, segs, j, si);
				p_desc.utf8name = "size";
				napi_create_int64(env, (1 + surl->media_range->end_range - surl->media_range->start_range), &p_desc.value);
				napi_define_properties(env, si, 1, &p_desc);

				p_desc.utf8name = "duration";
				napi_create_uint32(env, surl->duration, &p_desc.value);
				napi_define_properties(env, si, 1, &p_desc);
			}
		}
		napi_set_element(env, qualities, i, q);
	}

	//create srd
	p_desc.utf8name = "SRD";
	u32 id, x, y, w, h, fw, fh;
	if (gf_dash_group_get_srd_info(dash, group_idx, &id, &x, &y, &w, &h, &fw, &fh)) {
		napi_value srd, val;
		napi_create_object(env, &srd);
		p_desc.value = srd;

#define SRD_U32(_val) \
		napi_create_uint32(env, _val, &val);\
		napi_set_named_property(env, srd, #_val, val); \

		SRD_U32(id)
		SRD_U32(x)
		SRD_U32(y)
		SRD_U32(w)
		SRD_U32(h)
		SRD_U32(fw)
		SRD_U32(fh)

	} else {
		napi_get_null(env, &p_desc.value);
	}
	napi_define_properties(env, group, 1, &p_desc);

	napi_get_reference_value(napi_dash->env, napi_dash->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_dash->env, obj, "on_new_group", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_dash->env, obj, "on_new_group", &fun_val);
		if (status!=napi_ok) has_fun = GF_FALSE;
	}

	if (has_fun) {
		napi_call_function(napi_dash->env, obj, fun_val, 1, &group, &res);
	}
}

s32 dashbind_rate_adaptation(void *udta, u32 group_idx, u32 base_group_idx, Bool force_low_complex, void *_stats)
{
	napi_status status;
	napi_value obj, fun_val, groups, res, st, val;
	GF_DASHCustomAlgoInfo *stats = (GF_DASHCustomAlgoInfo *)_stats;
	NAPI_Dash *napi_dash = (NAPI_Dash *)udta;
	napi_env env = napi_dash->env;

	napi_get_reference_value(napi_dash->env, napi_dash->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_dash->env, obj, "on_rate_adaptation", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_dash->env, obj, "on_rate_adaptation", &fun_val);
		if (status!=napi_ok) has_fun = GF_FALSE;
	}

	if (!has_fun) return -1;

	u32 i, nb_groups;
	napi_get_reference_value(env, napi_dash->groups, &groups);
	napi_get_named_property(env, groups, "length", &res);
	napi_get_value_uint32(env, res, &nb_groups);

	napi_value args[4];
	napi_get_null(env, &args[0]);
	napi_get_null(env, &args[1]);
	for (i=0; i<nb_groups;i++) {
		u32 g_idx=0;
		napi_value group;
		napi_get_element(env, groups, i, &group);
		napi_get_named_property(env, group, "idx", &res);
		napi_get_value_uint32(env, res, &g_idx);
		if (g_idx==group_idx) args[0] = group;
		else if (g_idx==base_group_idx) args[1] = group;
	}
	napi_get_boolean(env, force_low_complex ? true : false, &args[2]);

	napi_create_object(env, &st);
	args[3] = st;


#define SSET_U32(_val) \
		napi_create_uint32(env, stats->_val, &val);\
		napi_set_named_property(env, st, #_val, val); \

#define SSET_DOUBLE(_val) \
		napi_create_double(env, stats->_val, &val);\
		napi_set_named_property(env, st, #_val, val); \

	SSET_U32(download_rate);
	SSET_U32(file_size);
	SSET_DOUBLE(speed);
	SSET_DOUBLE(max_available_speed);
	SSET_U32(display_width);
	SSET_U32(display_height);
	SSET_U32(active_quality_idx);
	SSET_U32(buffer_min_ms);
	SSET_U32(buffer_max_ms);
	SSET_U32(buffer_occupancy_ms);
	SSET_U32(quality_degradation_hint);
	SSET_U32(total_rate);
	napi_call_function(napi_dash->env, obj, fun_val, 4, args, &res);

	s32 ret_val=-1;
	napi_get_value_int32(env, res, &ret_val);
	return ret_val;
}

typedef struct
{
	u32 bits_per_sec;
	u64 total_bytes;
	u64 bytes_done;
	u64 us_since_start;
	u32 buffer_dur;
	u32 current_seg_dur;
} GF_DASHDownloadStats;

s32 dashbind_download_monitor(void *udta, u32 group_idx, void *_stats)
{
	napi_status status;
	napi_value obj, fun_val, groups, res, st, val;
	GF_DASHDownloadStats *stats = (GF_DASHDownloadStats *)_stats;
	NAPI_Dash *napi_dash = (NAPI_Dash *)udta;
	napi_env env = napi_dash->env;

	napi_get_reference_value(napi_dash->env, napi_dash->ref, &obj);
	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_dash->env, obj, "on_download_monitor", (bool *) &has_fun);
	if (has_fun) {
		status = napi_get_named_property(napi_dash->env, obj, "on_download_monitor", &fun_val);
		if (status!=napi_ok) has_fun = GF_FALSE;
	}

	if (!has_fun) return -1;

	u32 i, nb_groups;
	napi_get_reference_value(env, napi_dash->groups, &groups);
	napi_get_named_property(env, groups, "length", &res);
	napi_get_value_uint32(env, res, &nb_groups);

	napi_value args[2];
	napi_get_null(env, &args[0]);
	for (i=0; i<nb_groups;i++) {
		u32 g_idx=0;
		napi_value group;
		napi_get_element(env, groups, i, &group);
		napi_get_named_property(env, group, "idx", &res);
		napi_get_value_uint32(env, res, &g_idx);
		if (g_idx==group_idx) {
			args[0] = group;
			break;
		}
	}
	napi_create_object(env, &st);
	args[1] = st;

#define DSSET_U32(_val) \
		napi_create_uint32(env, stats->_val, &val);\
		napi_set_named_property(env, st, #_val, val); \

#define DSSET_U64(_val) \
		napi_create_int64(env, (s64) stats->_val, &val);\
		napi_set_named_property(env, st, #_val, val); \

	DSSET_U32(bits_per_sec);
	DSSET_U64(total_bytes);
	DSSET_U64(bytes_done);
	DSSET_U64(us_since_start);
	DSSET_U32(buffer_dur);
	DSSET_U32(current_seg_dur);

	napi_call_function(napi_dash->env, obj, fun_val, 2, args, &res);

	s32 ret_val=-1;
	napi_get_value_int32(env, res, &ret_val);
	return ret_val;
}
/*currently not exposed by filter.h*/
GF_Err gf_filter_bind_dash_algo_callbacks(GF_Filter *filter, void *udta,
		void (*period_reset)(void *rate_adaptation, u32 type),
		void (*new_group)(void *udta, u32 group_idx, void *dash),
		s32 (*rate_adaptation)(void *udta, u32 group_idx, u32 base_group_idx, Bool force_low_complex, void *stats),
		s32 (*download_monitor)(void *udta, u32 group_idx, void *stats)
	);

napi_value dashin_bind(napi_env env, napi_callback_info info)
{
	GF_Err e;
	napi_value val;
	NAPI_Dash *napi_dash;

	NARG_ARGS_THIS(1,1)
	FILTER

	GF_SAFEALLOC(napi_dash, NAPI_Dash);
	if (!napi_dash) {
		napi_throw_error(env, NULL, "Failed to allocate dashin binding");
		return NULL;
	}


	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(f);
	napi_create_reference(env, argv[0], 1, &napi_f->binding_ref);
	napi_dash->ref = napi_f->binding_ref;
	napi_dash->env = env;
	napi_f->binding_stack = napi_dash;

	napi_create_array(env, &val);
	napi_create_reference(env, val, 1, &napi_dash->groups);

	napi_type_tag_object(env, argv[0], &dashbind_tag);
	NAPI_CALL( napi_wrap(env, argv[0], napi_dash, dashbind_finalize, NULL, NULL) );

	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_f->env, argv[0], "on_download_monitor", (bool *) &has_fun);
	e = gf_filter_bind_dash_algo_callbacks(f, napi_dash, dashbind_period_reset, dashbind_new_group, dashbind_rate_adaptation, has_fun ? dashbind_download_monitor : NULL);
	if (e) {
		u32 rcount;
		napi_reference_unref(env, napi_f->binding_ref, &rcount);
		napi_delete_reference(env, napi_f->binding_ref);
		napi_f->binding_ref = NULL;
		napi_f->binding_stack = NULL;
		gf_free(napi_dash);

		napi_throw_error(env, gf_error_to_string(e), "Failed to bind dashin callbacks");
		return NULL;
	}
	//tag dasher filter to run on main thread only
	gf_filter_force_main_thread(napi_f->f, GF_TRUE);
	return NULL;
}

void dashin_detach_binding(NAPI_Filter *napi_f)
{
	NAPI_Dash *napi_dash = (NAPI_Dash *) napi_f->binding_stack;
	if (!napi_dash) return;
	gf_free(napi_dash);
}

typedef struct
{
	napi_ref ref;
	napi_env env;
} NAPI_HTTPOut;

void httpoutbind_finalize(napi_env env, void* finalize_data, void* finalize_hint)
{
}

typedef struct
{
	void *httpout_session;
	napi_env env;
	napi_ref req_ref;
} NAPI_HTTPSession;

void napi_http_del(napi_env env, void *finalize_data, void* finalize_hint)
{
	NAPI_HTTPSession *napi_http = (NAPI_HTTPSession *)finalize_data;
	if (napi_http) gf_free(napi_http);
}


static u32 httpout_throttle_cbk(void *udta, u64 done, u64 total)
{
	napi_value req;
	napi_value fun_val, args[2], res;
	u32 retval=0;
	NAPI_HTTPSession *napi_sess = (NAPI_HTTPSession *)udta;

	//call
	napi_get_reference_value(napi_sess->env, napi_sess->req_ref, &req);
	napi_get_named_property(napi_sess->env, req, "throttle", &fun_val);
	napi_create_int64(napi_sess->env, done, &args[0]);
	napi_create_int64(napi_sess->env, total, &args[1]);
	napi_call_function(napi_sess->env, req, fun_val, 2, args, &res);
	napi_get_value_uint32(napi_sess->env, res, &retval);

	return retval;
}
static s32 httpout_read_cbk(void *udta, u8 *buffer, u32 buffer_size)
{
	napi_value req;
	napi_value fun_val, arg, ab, res;
	u32 nb_bytes=0;
	napi_status status;
	NAPI_HTTPSession *napi_sess = (NAPI_HTTPSession *)udta;


	//call
	napi_get_reference_value(napi_sess->env, napi_sess->req_ref, &req);
	napi_get_named_property(napi_sess->env, req, "read", &fun_val);

	//create arg
#if NAPI_VERSION >= 7
	status = napi_create_external_arraybuffer(napi_sess->env, buffer, buffer_size, NULL, NULL, &ab);
#else
	void *buf_data=NULL;
	status = napi_create_arraybuffer(napi_sess->env, buffer_size, &buf_data, &ab);
#endif

	if (status==napi_ok)
		status = napi_create_typedarray(napi_sess->env, napi_uint8_array, buffer_size, ab, 0, &arg);

	//call
	if (status==napi_ok)
		status = napi_call_function(napi_sess->env, req, fun_val, 1, &arg, &res);

	if (status==napi_ok)
		status = napi_get_value_uint32(napi_sess->env, res, &nb_bytes);

#if NAPI_VERSION >= 7
	//detach array buffer
	napi_detach_arraybuffer(napi_sess->env, ab);
#else
	if ((status==napi_ok) && buf_data && nb_bytes)
		memcpy(buffer, buf_data, nb_bytes);
#endif
	return nb_bytes;
}
static u32 httpout_write_cbk(void *udta, const u8 *buffer, u32 buffer_size)
{
	napi_value req;
	napi_value fun_val, arg, ab, res;
	u32 nb_bytes=0;
	napi_status status;
	NAPI_HTTPSession *napi_sess = (NAPI_HTTPSession *)udta;

	//call
	napi_get_reference_value(napi_sess->env, napi_sess->req_ref, &req);
	napi_get_named_property(napi_sess->env, req, "write", &fun_val);

	//create arg
#if NAPI_VERSION >= 7
	status = napi_create_external_arraybuffer(napi_sess->env, (void *)buffer, buffer_size, NULL, NULL, &ab);
#else
	void *buf_data=NULL;
	status = napi_create_arraybuffer(napi_sess->env, buffer_size, &buf_data, &ab);
	if (status == napi_ok) {
		memcpy(buf_data, buffer, buffer_size);
	}
#endif

	if (status==napi_ok)
		status = napi_create_typedarray(napi_sess->env, napi_uint8_array, buffer_size, ab, 0, &arg);

	//call
	if (status==napi_ok)
		status = napi_call_function(napi_sess->env, req, fun_val, 1, &arg, &res);

	if (status==napi_ok)
		status = napi_get_value_uint32(napi_sess->env, res, &nb_bytes);

#if NAPI_VERSION >= 7
	//detach arraybuffer
	napi_detach_arraybuffer(napi_sess->env, ab);
#endif

	if (status!=napi_ok) return 0;
	return nb_bytes;
}
static void httpout_on_close_cbk(void *udta, GF_Err reason)
{
	napi_value req;
	NAPI_HTTPSession *napi_sess = (NAPI_HTTPSession *)udta;

	//call
	napi_get_reference_value(napi_sess->env, napi_sess->req_ref, &req);

	Bool has_p = GF_FALSE;
	napi_has_named_property(napi_sess->env, req, "close", (bool *) &has_p);
	if (has_p) {
		napi_value fun_val, arg, res;
		napi_get_named_property(napi_sess->env, req, "close", &fun_val);
		napi_create_uint32(napi_sess->env, reason, &arg);
		napi_call_function(napi_sess->env, req, fun_val, 1, &arg, &res);
	}
	napi_delete_reference(napi_sess->env, napi_sess->req_ref);
}

GF_Err gf_httpout_send_request(void *sess, void *udta,
	u32 reply,
	char *body_or_file,
	u32 nb_headers,
	const char **headers,
	u32 (*throttle)(void *udta, u64 done, u64 total),
	s32 (*read)(void *udta, u8 *buffer, u32 buffer_size),
	u32 (*write)(void *udta, const u8 *buffer, u32 buffer_size),
	void (*close)(void *udta, GF_Err reason)
);

napi_value httpout_send_cbk(napi_env env, napi_callback_info info)
{
	napi_value val;
	u32 i, reply=0;
	size_t size;
	char *body_or_file=NULL;
	u32 nb_headers=0;
	char **headers=NULL;
	Bool use_throttle=GF_FALSE;
	Bool use_read=GF_FALSE;
	Bool use_write=GF_FALSE;

	NARG_THIS

	NAPI_HTTPSession *napi_hsess;
	bool _tag;
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &httpsess_tag, &_tag) );
	if (!_tag) {
		napi_throw_error(env, NULL, "Not a HTTP session object");
		return NULL;
	}
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &napi_hsess) );

	napi_get_named_property(env, this_val, "reply", &val);
	napi_get_value_uint32(env, val, &reply);

	Bool has_p = GF_FALSE;
	napi_has_named_property(env, this_val, "body", (bool *) &has_p);
	if (has_p) {
		napi_get_named_property(env, this_val, "body", &val);
		napi_get_value_string_utf8(env, val, NULL, 0, &size);
		body_or_file = gf_malloc(sizeof(char) * (size+1) );
		napi_get_value_string_utf8(env, val, body_or_file, size+1, &size);
		body_or_file[size]=0;
	}

	napi_has_named_property(env, this_val, "headers_out", (bool *) &has_p);
	if (has_p) {
		napi_value hdrs;
		u32 nb_items;
		napi_get_named_property(env, this_val, "headers_out", &hdrs);
		napi_get_array_length(env, hdrs, &nb_items);
		for (i=0; i<nb_items; i++) {
			napi_value h;
			napi_get_element(env, hdrs, i, &h);
			napi_has_named_property(env, h, "name", (bool *) &has_p);
			if (!has_p) continue;
			napi_has_named_property(env, h, "value", (bool *) &has_p);
			if (!has_p) continue;

			headers = gf_realloc(headers, sizeof(char *) * (nb_headers+2));

			napi_get_named_property(env, h, "name", &val);
			napi_get_value_string_utf8(env, val, NULL, 0, &size);
			headers[nb_headers] = gf_malloc(sizeof(char) * (size+1) );
			napi_get_value_string_utf8(env, val, headers[nb_headers], size+1, &size);
			headers[nb_headers][size]=0;

			napi_get_named_property(env, h, "value", &val);
			napi_get_value_string_utf8(env, val, NULL, 0, &size);
			headers[nb_headers+1] = gf_malloc(sizeof(char) * (size+1) );
			napi_get_value_string_utf8(env, val, headers[nb_headers+1], size+1, &size);
			headers[nb_headers+1][size]=0;
			nb_headers += 2;
		}
	}

	napi_has_named_property(env, this_val, "throttle", (bool *) &has_p);
	if (has_p) use_throttle = GF_TRUE;
	napi_has_named_property(env, this_val, "read", (bool *) &has_p);
	if (has_p) use_read = GF_TRUE;
	napi_has_named_property(env, this_val, "write", (bool *) &has_p);
	if (has_p) use_write = GF_TRUE;

	gf_httpout_send_request(napi_hsess->httpout_session, napi_hsess, reply, body_or_file, nb_headers, (const char**) headers,
		use_throttle ? httpout_throttle_cbk : NULL,
		use_read ? httpout_read_cbk : NULL,
		use_write ? httpout_write_cbk : NULL,
		httpout_on_close_cbk);

	if (body_or_file) gf_free(body_or_file);
	if (headers) {
		for (i=0; i<nb_headers; i++) {
			gf_free(headers[i]);
		}
		gf_free(headers);
	}
	return NULL;
}

s32 httpout_on_request(void *udta, void *session, const char *method, const char *url, u32 auth_code, u32 nb_hdrs, const char **hdrs)
{
	napi_status status;
	napi_value req_obj;
	u32 i;
	napi_value obj, fun_val, res;
	NAPI_HTTPOut *napi_http = (NAPI_HTTPOut *)udta;
	NAPI_HTTPSession *napi_sess;

	//create request object
	GF_SAFEALLOC(napi_sess, NAPI_HTTPSession);
	if (!napi_sess) return 1;
	napi_sess->httpout_session = session;
	napi_sess->env = napi_http->env;

	napi_create_object(napi_http->env, &req_obj);
	napi_create_reference(napi_http->env, req_obj, 1, &napi_sess->req_ref);
	napi_type_tag_object(napi_http->env, req_obj, &httpsess_tag);
	napi_wrap(napi_http->env, req_obj, napi_sess, napi_http_del, NULL, NULL);

	//set properties
	napi_value val;
	status = napi_create_string_utf8(napi_http->env, method, NAPI_AUTO_LENGTH, &val);
	if (status==napi_ok) status = napi_set_named_property(napi_http->env, req_obj, "method", val);
	if (status!=napi_ok) return 1;

	status = napi_create_string_utf8(napi_http->env, url, NAPI_AUTO_LENGTH, &val);
	if (status==napi_ok) status = napi_set_named_property(napi_http->env, req_obj, "url", val);
	if (status!=napi_ok) return 1;

	status = napi_create_uint32(napi_http->env, auth_code, &val);
	if (status==napi_ok) status = napi_set_named_property(napi_http->env, req_obj, "auth_code", val);
	if (status!=napi_ok) return 1;

	status = napi_create_uint32(napi_http->env, 0, &val);
	if (status==napi_ok) status = napi_set_named_property(napi_http->env, req_obj, "reply", val);
	if (status!=napi_ok) return 1;

	status = napi_create_function(napi_http->env, "send", NAPI_AUTO_LENGTH, httpout_send_cbk, napi_http, &val);
	if (status==napi_ok) status = napi_set_named_property(napi_http->env, req_obj, "send", val);
	if (status!=napi_ok) return 1;

	napi_value in_headers;
	napi_create_array(napi_http->env, &in_headers);
	for (i=0; i<nb_hdrs;i+=2) {
		napi_value h;
		napi_create_object(napi_http->env, &h);
		napi_create_string_utf8(napi_http->env, hdrs[i], NAPI_AUTO_LENGTH, &val);
		napi_set_named_property(napi_http->env, h, "name", val);
		napi_create_string_utf8(napi_http->env, hdrs[i+1], NAPI_AUTO_LENGTH, &val);
		napi_set_named_property(napi_http->env, h, "value", val);
		napi_set_element(napi_http->env, in_headers, i/2, h);
	}
	napi_set_named_property(napi_http->env, req_obj, "headers_in", in_headers);

	napi_value out_headers;
	napi_create_array(napi_http->env, &out_headers);
	napi_set_named_property(napi_http->env, req_obj, "headers_out", out_headers);

	//call
	napi_get_reference_value(napi_http->env, napi_http->ref, &obj);
	status = napi_get_named_property(napi_http->env, obj, "on_request", &fun_val);
	if (status!=napi_ok) return 1;
	napi_call_function(napi_http->env, obj, fun_val, 1, &req_obj, &res);
	return 0;
}

GF_Err gf_filter_bind_httpout_callbacks(GF_Filter *filter, void *udta,
	s32 (*on_request)(void *udta, void *session, const char *method, const char *url, u32 auth_code, u32 nb_hdrs, const char **hdrs)
);

napi_value httpout_bind(napi_env env, napi_callback_info info)
{
	GF_Err e;
	NAPI_HTTPOut *napi_http;

	NARG_ARGS_THIS(1,1)
	FILTER

	NAPI_Filter *napi_f = (NAPI_Filter *) gf_filter_get_rt_udta(f);

	Bool has_fun = GF_FALSE;
	napi_has_named_property(napi_f->env, argv[0], "on_request", (bool *) &has_fun);
	if (!has_fun) {
		napi_throw_error(env, NULL, "Invalid httpout binding object, missing on_request callback");
		return NULL;
	}

	GF_SAFEALLOC(napi_http, NAPI_HTTPOut);
	if (!napi_http) {
		napi_throw_error(env, NULL, "Failed to allocate httpout binding");
		return NULL;
	}

	napi_create_reference(env, argv[0], 1, &napi_f->binding_ref);
	napi_http->ref = napi_f->binding_ref;
	napi_http->env = env;
	napi_f->binding_stack = napi_http;

	napi_type_tag_object(env, argv[0], &dashbind_tag);
	NAPI_CALL( napi_wrap(env, argv[0], napi_http, httpoutbind_finalize, NULL, NULL) );

	e = gf_filter_bind_httpout_callbacks(f, napi_http, httpout_on_request);
	if (e) {
		u32 rcount;
		napi_reference_unref(env, napi_f->binding_ref, &rcount);
		napi_delete_reference(env, napi_f->binding_ref);
		napi_f->binding_ref = NULL;
		napi_f->binding_stack = NULL;
		gf_free(napi_http);

		napi_throw_error(env, gf_error_to_string(e), "Failed to bind httpout callbacks");
		return NULL;
	}
	//tag httpout filter to run on main thread only
	gf_filter_force_main_thread(napi_f->f, GF_TRUE);
	return NULL;
}

void httpout_detach_binding(NAPI_Filter *napi_f)
{
	NAPI_HTTPOut *napi_http = (NAPI_HTTPOut *) napi_f->binding_stack;
	if (!napi_http) return;
	gf_free(napi_http);
}

typedef struct _napi_fio
{
	//points to factory object (passed in FileIO constructor)
	struct _napi_fio *factory;
	GF_FileIO *gfio;
	napi_ref ref;
	u32 nb_refs;

	//for root only
	napi_env env;
	GF_List *pending_urls;
	//GF_List *gc_exclude;
	u32 all_refs;
	napi_ref root_ref;
	napi_async_context async_ctx;
	Bool direct_mem;
} NAPI_FileIO;

static u32 FIO_PROP_URL = 0;

#define FILEIO\
	NAPI_FileIO *napi_fio=NULL;\
	bool _tag;\
	NAPI_CALL( napi_check_object_type_tag(env, this_val, &fileio_tag, &_tag) );\
	if (!_tag) {\
		napi_throw_error(env, NULL, "Not a FileIO object");\
		return NULL;\
	}\
	NAPI_CALL( napi_unwrap(env, this_val, (void**) &napi_fio) );\

napi_value fio_getter(napi_env env, napi_callback_info info)
{
	void *magic;
	napi_status status;
	napi_value this_val, ret;
	NAPI_CALL(napi_get_cb_info(env, info, NULL, NULL, &this_val, &magic) );
	FILEIO

	if (magic == &FIO_PROP_URL) {
		NAPI_CALL( napi_create_string_utf8(env, gf_fileio_url(napi_fio->gfio), NAPI_AUTO_LENGTH, &ret) );
		return ret;
	}
	return NULL;
}

static GF_Err gfio_seek(GF_FileIO *fileio, u64 offset, s32 whence)
{
	napi_status status;
	s32 rval;
	napi_value args[2], fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio);
	napi_env env = napi_fio->factory->env;

	//get fun and target this
	napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	status = napi_get_named_property(env, fact_obj, "seek", &fun_val);
	if (status!=napi_ok) {
		return GF_NOT_SUPPORTED;
	}
	napi_get_reference_value(env, napi_fio->ref, &obj);

	//create args
	status = napi_create_int64(env, (s64) offset, &args[0]);
	if (status != napi_ok) {
		return -1;
	}
	status = napi_create_uint32(env, whence, &args[1]);
	if (status != napi_ok) {
		return -1;
	}

	//call
	status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 2, args, &res);
	if (status!=napi_ok) {
		return -1;
	}
	status = napi_get_value_int32(env, res, &rval);
	if (status!=napi_ok) {
		return -1;
	}
    return rval;
}

/*
	ultimately we would like node to read/write directly to/from our buffer,
	however this currently crashes node
		https://github.com/nodejs/node/pull/33321#issuecomment-630905537

	"If you use napi_create_external_buffer() a second time on the same pointer without waiting for the buffer created from the first time to be released, you’ll get a crash"

	the same applies with napi_create_external_arraybuffer(), and we are usually called
	with the same internal buffer pointer for read or write methods

	until fixed, we create a different buffer and do a memcpy...
*/

static u32 gfio_read(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
	napi_status status;
	u32 nb_bytes;
	void *buf_data=NULL;

	napi_value arg, ab, fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio);
	napi_env env = napi_fio->factory->env;

	//get fun and target this
	status = napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	if (status!=napi_ok) return 0;
	status = napi_get_named_property(env, fact_obj, "read", &fun_val);
	if (status!=napi_ok) return 0;
	status = napi_get_reference_value(env, napi_fio->ref, &obj);
	if (status!=napi_ok) return 0;

	//create arg
#if NAPI_VERSION >= 7
	if (napi_fio->factory->direct_mem)
		status = napi_create_external_arraybuffer(env, buffer, bytes, NULL, NULL, &ab);
	else
#endif
		status = napi_create_arraybuffer(env, bytes, &buf_data, &ab);

	if (status==napi_ok)
		status = napi_create_typedarray(env, napi_uint8_array, bytes, ab, 0, &arg);

	//call
	if (status==napi_ok)
		status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 1, &arg, &res);

	if (status==napi_ok)
		status = napi_get_value_uint32(env, res, &nb_bytes);

	if ((status==napi_ok) && !napi_fio->factory->direct_mem && buf_data && nb_bytes)
		memcpy(buffer, buf_data, nb_bytes);

#if NAPI_VERSION >= 7
	//detach array buffer
	if (napi_fio->factory->direct_mem)
		napi_detach_arraybuffer(env, ab);
#endif

	if (status!=napi_ok) return 0;
    return nb_bytes;
}

static u32 gfio_write(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
	napi_status status;
	u32 nb_bytes;

	napi_value arg, ab, fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio);
	napi_env env = napi_fio->factory->env;

	//get fun and target this
	status = napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	if (status!=napi_ok) return 0;
	status = napi_get_named_property(env, fact_obj, "write", &fun_val);
	if (status!=napi_ok) return 0;
	status = napi_get_reference_value(env, napi_fio->ref, &obj);
	if (status!=napi_ok) return 0;

	//create arg
#if NAPI_VERSION >= 7
	if (napi_fio->factory->direct_mem) {
		status = napi_create_external_arraybuffer(env, buffer, bytes, NULL, NULL, &ab);
	} else
#endif
	{
		void *buf_data=NULL;
		status = napi_create_arraybuffer(env, bytes, &buf_data, &ab);
		if (status == napi_ok) {
			memcpy(buf_data, buffer, bytes);
		}
	}

	if (status==napi_ok)
		status = napi_create_typedarray(env, napi_uint8_array, bytes, ab, 0, &arg);

	//call
	if (status==napi_ok)
		status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 1, &arg, &res);

	if (status==napi_ok)
		status = napi_get_value_uint32(env, res, &nb_bytes);

#if NAPI_VERSION >= 7
	//detach arraybuffer
	if (napi_fio->factory->direct_mem)
		napi_detach_arraybuffer(env, ab);
#endif

	if (status!=napi_ok) return 0;

    return nb_bytes;
}

static s64 gfio_tell(GF_FileIO *fileio)
{
	napi_status status;
	s64 pos;
	napi_value fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio);
	napi_env env = napi_fio->factory->env;

	//get fun and target this
	napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	status = napi_get_named_property(env, fact_obj, "tell", &fun_val);
	if (status!=napi_ok) {
		return -1;
	}
	napi_get_reference_value(env, napi_fio->ref, &obj);

	//call
	status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 0, NULL, &res);
	if (status!=napi_ok) {
		return -1;
	}
	status = napi_get_value_int64(env, res, &pos);
	if (status!=napi_ok) {
		return -1;
	}
    return pos;
}

static Bool gfio_eof(GF_FileIO *fileio)
{
	napi_status status;
	bool is_eof;
	napi_value fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio);
	napi_env env = napi_fio->factory->env;

	//get fun and target this
	napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	status = napi_get_named_property(env, fact_obj, "eof", &fun_val);
	if (status!=napi_ok) {
		return GF_FALSE;
	}
	napi_get_reference_value(env, napi_fio->ref, &obj);

	//call
	status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 0, NULL, &res);
	if (status!=napi_ok) {
		return GF_FALSE;
	}
	status = napi_get_value_bool(env, res, &is_eof);
	if (status!=napi_ok) {
		return -1;
	}
    return is_eof ? GF_TRUE : GF_FALSE;
}

static GF_FileIO *gfio_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_error)
{
	napi_status status;
	char *fio_url = NULL;
	Bool do_delete = GF_FALSE;
	napi_value args[3], fact_obj, obj, fun_val, res;
	NAPI_FileIO *napi_fio = gf_fileio_get_udta(fileio_ref);
	napi_env env = napi_fio->factory->env;
	NAPI_GPAC

    *out_error = GF_OK;

	//make sure we are called from main thread
	if (gpac->main_thid != gf_th_id()) {
		*out_error = GF_NOT_SUPPORTED;
		return NULL;
	}

    if (!strcmp(mode, "url")) {
        NAPI_FileIO *new_gfio;
        char *path = gf_url_concatenate(gf_fileio_resource_url(fileio_ref), url);
        GF_SAFEALLOC(new_gfio, NAPI_FileIO);
        if (!new_gfio) {
			*out_error = GF_OUT_OF_MEM;
			return NULL;
		}
		new_gfio->factory = napi_fio->factory;
		new_gfio->gfio = gf_fileio_new(path, new_gfio, gfio_open, gfio_seek, gfio_read, gfio_write, gfio_tell, gfio_eof, NULL);
		gf_free(path);

		if (!new_gfio->gfio) {
			gf_free(new_gfio);
			*out_error = GF_OUT_OF_MEM;
			return NULL;
		}
		gf_fileio_tag_main_thread(new_gfio->gfio);
		gf_list_add(napi_fio->factory->pending_urls, new_gfio);
        //fio_ref.factory.gc_exclude.append(new_gfio)
        napi_fio->factory->all_refs += 1;
        return new_gfio->gfio;
	}
	if (!strcmp(mode, "ref")) {
		napi_fio->nb_refs += 1;
		napi_fio->factory->all_refs += 1;
        return fileio_ref;
	}

	if (!strcmp(mode, "probe")) {
		bool exists;
		//get fun and target this
		napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
		status = napi_get_named_property(env, fact_obj, "exists", &fun_val);
		if (status!=napi_ok) return NULL;

		//create arg
		status = napi_create_string_utf8(env, url, NAPI_AUTO_LENGTH, &args[0]);
		if (status!=napi_ok) return NULL;

		//call
		status = napi_make_callback(env, napi_fio->factory->async_ctx, fact_obj, fun_val, 1, &args[0], &res);
		if (status!=napi_ok) return NULL;

		status = napi_get_value_bool(env, res, &exists);
		if (status!=napi_ok) return NULL;

		if (!exists) *out_error = GF_URL_ERROR;
		return NULL;
	}

	if (!strcmp(mode, "unref")) {
		napi_fio->nb_refs -= 1;
		napi_fio->factory->all_refs -= 1;
		if (napi_fio->nb_refs) return fileio_ref;
		do_delete = GF_TRUE;
	}

	if (!strcmp(mode, "close")) {
		if (napi_fio->ref != NULL) {
			//get fun and target this
			napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
			status = napi_get_named_property(env, fact_obj, "close", &fun_val);
			napi_get_reference_value(env, napi_fio->ref, &obj);
			status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 0, NULL, &res);
            napi_fio->factory->all_refs -= 1;
		}

        if (napi_fio->nb_refs) return NULL;
        do_delete = GF_TRUE;
	}

    if (do_delete) {
		uint32_t res;
		NAPI_FileIO *factory = napi_fio->factory;
        //file no longer used, can now be garbage collected
        //fio_ref.factory.gc_exclude.remove(fio_ref)

        if (napi_fio->factory != napi_fio) {
            gf_fileio_del(napi_fio->gfio);
			napi_get_reference_value(env, napi_fio->ref, &obj);
			napi_reference_unref(env, napi_fio->ref, &res);
			napi_delete_reference(env, napi_fio->ref);
			gf_free(napi_fio);
		}

		//final cleanup
        if (!factory->all_refs && !gf_list_count(factory->pending_urls)) { // and not len(fio_ref.factory.gc_exclude):
            gf_fileio_del(factory->gfio);
			napi_get_reference_value(env, factory->ref, &obj);
			napi_reference_unref(env, factory->ref, &res);
			napi_delete_reference(env, factory->ref);

			napi_get_reference_value(env, factory->root_ref, &obj);
			napi_wrap(env, obj, NULL, NULL, NULL, NULL);
			napi_reference_unref(env, factory->root_ref, &res);
			napi_delete_reference(env, factory->root_ref);
			gf_list_del(factory->pending_urls);
			gf_free(factory);
		}
        return NULL;
	}


	//this is an open() call, check if associated object exists, if not so we can use this instance
	NAPI_FileIO *fio = NULL;
	if (napi_fio->ref == NULL) {
		fio = napi_fio;
		if (gf_list_find(napi_fio->factory->pending_urls, napi_fio)>=0) {
			napi_fio->factory->all_refs -= 1;
			gf_list_del_item(napi_fio->factory->pending_urls, napi_fio);
		}
	}

	//check in pending urls
    if (fio == NULL) {
		u32 i, count = gf_list_count(napi_fio->factory->pending_urls);
		for (i=0; i<count; i++) {
			NAPI_FileIO *afio = gf_list_get(napi_fio->factory->pending_urls, i);
            const char *a_url = gf_fileio_resource_url(afio->gfio);
            if (!strcmp(a_url, url)) {
                napi_fio->factory->all_refs -= 1;
                gf_list_rem(napi_fio->factory->pending_urls, i);
                fio = afio;
                break;
			}
		}
	}
	//we need to create a new FileIO
	Bool created = (fio==NULL) ? GF_TRUE : GF_FALSE;

	//read url, translate
	if (!strncmp(url, "gfio://", 7)) {
		url = gf_fileio_translate_url(url);
	}
	//if we create a new FileIO, we need to resolve url, against parent URL
	else if (created) {
		fio_url = gf_url_concatenate(gf_fileio_resource_url(napi_fio->gfio), url);
		url = fio_url;
	}

	//create new FileIO
	if (fio==NULL) {
        GF_SAFEALLOC(fio, NAPI_FileIO);
        if (!fio) {
			*out_error = GF_OUT_OF_MEM;
			gf_free(fio_url);
			return NULL;
		}
		fio->factory = napi_fio->factory;
		fio->gfio = gf_fileio_new((char *) url, fio, gfio_open, gfio_seek, gfio_read, gfio_write, gfio_tell, gfio_eof, NULL);

		if (!fio->gfio) {
			gf_free(fio);
			gf_free(fio_url);
			*out_error = GF_OUT_OF_MEM;
			return NULL;
		}
		gf_fileio_tag_main_thread(fio->gfio);
	}

    //create object, direct copy of the object passed at construction
	napi_create_object(env, &obj);
	napi_create_reference(env, obj, 1, &fio->ref);


	//get fun and target this
	napi_get_reference_value(env, napi_fio->factory->root_ref, &fact_obj);
	status = napi_get_named_property(env, fact_obj, "open", &fun_val);
	if (status==napi_ok) {
	//create arg
	status = napi_create_string_utf8(env, url, NAPI_AUTO_LENGTH, &args[0]);
	}

	if (status==napi_ok) {
	status = napi_create_string_utf8(env, mode, NAPI_AUTO_LENGTH, &args[1]);
	}

	args[2] = fact_obj;

	//call
	if (status==napi_ok) {
	status = napi_make_callback(env, napi_fio->factory->async_ctx, obj, fun_val, 3, args, &res);
	}

	bool open_res = false;
	if (status==napi_ok) {
	status = napi_get_value_bool(env, res, &open_res);
	}

	if (fio_url) gf_free(fio_url);

	if (open_res) {
		//if not fio in fio.factory.gc_exclude:
		//    fio.factory.gc_exclude.append(fio)
		fio->factory->all_refs += 1;
		return fio->gfio;
	}

    //failure to open a new created object, remove FileIO and let the object be garbage collected
    if (created) {
		gf_fileio_del(fio->gfio);
        fio->gfio = NULL;
	}
	*out_error = GF_URL_ERROR;
	return NULL;
}

void del_fio(napi_env env, void* finalize_data, void* finalize_hint)
{
}

napi_value new_fio(napi_env env, napi_callback_info info)
{
	napi_value fact_obj, fun_val;
	NAPI_FileIO *napi_fio;
	NARG_ARGS_THIS(3, 2);
	NARG_STR(url, 0, NULL);
	NARG_BOOL(direct_mem, 2, GF_TRUE);

	NAPI_GPAC
	gpac->is_init = GF_TRUE;

#if NAPI_VERSION < 7
	direct_mem = GF_FALSE;
#endif

	if (!url) {
		napi_throw_error(env, NULL, "FileIO must be constructed with a non-null URL");
		return NULL;
	}
	napi_valuetype rtype;
	fact_obj = argv[1];
	napi_typeof(env, fact_obj, &rtype);
	if (rtype != napi_object) {
		napi_throw_error(env, NULL, "factory is not an object");
		return NULL;
	}
	status = napi_get_named_property(env, fact_obj, "open", &fun_val);
	if (status != napi_ok) {
		napi_throw_error(env, NULL, "factory has no open function");
		return NULL;
	}
	status = napi_get_named_property(env, fact_obj, "close", &fun_val);
	if (status != napi_ok) {
		napi_throw_error(env, NULL, "factory has no close function");
		return NULL;
	}

	GF_SAFEALLOC(napi_fio, NAPI_FileIO);
	if (!napi_fio) {
		napi_throw_error(env, NULL, "Failed to allocate FileIO");
		return NULL;
	}

	napi_fio->gfio = gf_fileio_new(url, napi_fio, gfio_open, gfio_seek, gfio_read, gfio_write, gfio_tell, gfio_eof, NULL);
	if (!napi_fio->gfio) {
		gf_free(napi_fio);
		napi_throw_error(env, NULL, "Failed to allocate FileIO");
		return NULL;
	}
	gf_fileio_tag_main_thread(napi_fio->gfio);

	napi_create_reference(env, fact_obj, 1, &napi_fio->root_ref);
	napi_fio->env = env;
	napi_fio->factory = napi_fio;
	napi_fio->pending_urls = gf_list_new();
	napi_fio->direct_mem = direct_mem;

	napi_value async_name;
	napi_create_string_utf8(env, "AsyncFileIO", NAPI_AUTO_LENGTH, &async_name);
	napi_async_init(env, this_val, async_name, &napi_fio->async_ctx);


	napi_property_descriptor fio_base_properties[] = {
		{ "url", NULL, NULL, fio_getter, NULL, NULL, napi_enumerable, &FIO_PROP_URL},
	};
	NAPI_CALL( napi_define_properties(env, this_val, sizeof(fio_base_properties)/sizeof(napi_property_descriptor), fio_base_properties) );


	NAPI_CALL( napi_wrap(env, this_val, napi_fio, del_fio, NULL, NULL) );
	NAPI_CALL( napi_type_tag_object(env, this_val, &fileio_tag) );

	return this_val;
}


napi_status init_fio_class(napi_env env, napi_value exports, GPAC_NAPI *inst)
{
	napi_status status;
	napi_value cons;
	status = napi_define_class(env, "FileIO", NAPI_AUTO_LENGTH , new_fio, NULL, 0, NULL, &cons);
	if (status != napi_ok) return status;
	return napi_set_named_property(env, exports, "FileIO", cons);
}
