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

	*out_err = GF_OK;

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

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

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

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


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

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

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

		gf_js_lock(ctx, GF_FALSE);
		return NULL;
	}

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

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

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

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

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

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

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

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

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