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

#include <gpac/setup.h>

#ifndef GPAC_DISABLE_SVG

#ifdef GPAC_HAS_SPIDERMONKEY

#ifdef GPAC_CONFIG_ANDROID
#ifndef XP_UNIX
#define XP_UNIX
#endif /* XP_UNIX */
#endif

#include <gpac/html5_mse.h>

#define JSVAL_CHECK_STRING(_v) (JSVAL_IS_STRING(_v) || JSVAL_IS_NULL(_v))

typedef enum
{
	/* MediaSource properties */
	HTML_MEDIASOURCE_PROP_SOURCEBUFFERS         = -1,
	HTML_MEDIASOURCE_PROP_ACTIVESOURCEBUFFERS   = -2,
	HTML_MEDIASOURCE_PROP_DURATION              = -3,
	HTML_MEDIASOURCE_PROP_READYSTATE            = -4,
	/* SourceBuffer properties */
	HTML_SOURCEBUFFER_PROP_MODE					= -5,
	HTML_SOURCEBUFFER_PROP_UPDATING             = -6,
	HTML_SOURCEBUFFER_PROP_BUFFERED             = -7,
	HTML_SOURCEBUFFER_PROP_TIMESTAMPOFFSET      = -8,
	HTML_SOURCEBUFFER_PROP_TIMESCALE            = -9,
	HTML_SOURCEBUFFER_PROP_AUDIOTRACKS          = -10,
	HTML_SOURCEBUFFER_PROP_VIDEOTRACKS          = -11,
	HTML_SOURCEBUFFER_PROP_TEXTTRACKS           = -12,
	HTML_SOURCEBUFFER_PROP_APPENDWINDOWSTART    = -13,
	HTML_SOURCEBUFFER_PROP_APPENDWINDOWEND      = -14,
	/* SourceBufferList properties */
	HTML_SOURCEBUFFERLIST_PROP_LENGTH           = -15,
} GF_HTML_MediaSourcePropEnum;

static GF_HTML_MediaRuntime *html_media_rt = NULL;

Bool gf_mse_is_mse_object(JSContext *c, JSObject *obj) {
	if (GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) return GF_TRUE;
	if (GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferClass, NULL) ) return GF_TRUE;
	if (GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferListClass, NULL) ) return GF_TRUE;
	return GF_FALSE;
}

static GFINLINE GF_SceneGraph *mediasource_get_scenegraph(JSContext *c)
{
	GF_SceneGraph *scene;
	JSObject *global = JS_GetGlobalObject(c);
	assert(global);
	scene = (GF_SceneGraph *)SMJS_GET_PRIVATE(c, global);
	assert(scene);
	return scene;
}

void gf_mse_get_event_target(JSContext *c, JSObject *obj, GF_DOMEventTarget **target, GF_SceneGraph **sg) {
	if (!sg || !target) return;
	*sg = mediasource_get_scenegraph(c);
	if (GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
		GF_HTML_MediaSource *ms = NULL;
		ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
		*target = ms->evt_target;
	} else if (GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferClass, NULL) ) {
		GF_HTML_SourceBuffer *sb = NULL;
		sb = (GF_HTML_SourceBuffer *)SMJS_GET_PRIVATE(c, obj);
		*target = sb->evt_target;
	} else if (GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferListClass, NULL) ) {
		GF_HTML_SourceBufferList *sbl = NULL;
		sbl = (GF_HTML_SourceBufferList *)SMJS_GET_PRIVATE(c, obj);
		*target = sbl->evt_target;
	} else {
		*target = NULL;
		*sg = NULL;
	}
}

static void mediasource_sourceBuffer_initjs(JSContext *c, JSObject *ms_obj, GF_HTML_SourceBuffer *sb)
{
	sb->_this = JS_NewObject(c, &html_media_rt->sourceBufferClass._class, 0, 0);
	//gf_js_add_root(c, &sb->_this, GF_JSGC_OBJECT);
	SMJS_SET_PRIVATE(c, sb->_this, sb);
	sb->buffered->_this = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, sb->_this);
	SMJS_SET_PRIVATE(c, sb->buffered->_this, sb->buffered);
}

static JSBool SMJS_FUNCTION(mediasource_is_type_supported)
{
	SMJS_ARGS
#ifdef FILTER_FIXME
	GF_SceneGraph *sg;
	GF_JSAPIParam par;
#endif
	Bool isSupported = GF_TRUE;
	char *mime;
	if (!argc || !JSVAL_CHECK_STRING(argv[0])) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	mime = SMJS_CHARS(c, argv[0]);
#ifdef FILTER_FIXME
	sg = mediasource_get_scenegraph(c);
	assert(sg);
#endif
	if (!strlen(mime)) {
		isSupported = GF_FALSE;
	} else {
#ifdef FILTER_FIXME
		sg->script_action(sg->script_action_cbck, GF_JSAPI_OP_GET_COMPOSITOR, NULL, &par);
		isSupported = gf_term_is_type_supported((GF_Terminal *)par.compositor, mime);
#else
	isSupported = GF_FALSE;
#endif
	}
	SMJS_SET_RVAL(BOOLEAN_TO_JSVAL(isSupported ? JS_TRUE : JS_FALSE));
	SMJS_FREE(c, mime);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(mediasource_addSourceBuffer)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_HTML_SourceBuffer    *sb;
	GF_HTML_MediaSource     *ms;
	const char              *mime;
	GF_Err					e;
	u32						exception = 0;


	if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	if (!argc || !JSVAL_CHECK_STRING(argv[0]))
	{
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	mime = SMJS_CHARS(c, argv[0]);
	if (!strlen(mime))     {
		exception = GF_DOM_EXC_INVALID_ACCESS_ERR;
		goto exit;
	}
	ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
	if (!ms) {
		exception = GF_DOM_EXC_INVALID_ACCESS_ERR;
		goto exit;
	} else if (ms->readyState != MEDIA_SOURCE_READYSTATE_OPEN) {
		exception = GF_DOM_EXC_INVALID_STATE_ERR;
		goto exit;
	}
	assert(ms->service);
	/*
	if (gf_list_count(ms->sourceBuffers.list) > 0) {
		dom_throw_exception(c, GF_DOM_EXC_QUOTA_EXCEEDED_ERR);
		e = GF_BAD_PARAM;
		goto exit;
	}
	*/
	sb = gf_mse_source_buffer_new(ms);
	assert(sb);
	e = gf_mse_source_buffer_load_parser(sb, mime);
	if (e == GF_OK) {
		gf_mse_mediasource_add_source_buffer(ms, sb);
		mediasource_sourceBuffer_initjs(c, obj, sb);
		SMJS_SET_RVAL( OBJECT_TO_JSVAL(sb->_this) );
	} else {
		gf_mse_source_buffer_del(sb);
		exception = GF_DOM_EXC_NOT_SUPPORTED_ERR;
	}
exit:
	if (mime) {
		SMJS_FREE(c, (void *)mime);
	}
	if (exception) {
		return dom_throw_exception(c, exception);
	} else {
		return JS_TRUE;
	}
}

static JSBool SMJS_FUNCTION(mediasource_removeSourceBuffer)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_HTML_MediaSource *ms;
	GF_HTML_SourceBuffer *sb;
	JSObject *sb_obj;
	if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
	if (!ms) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	if (!argc || JSVAL_IS_NULL(argv[0]) || !JSVAL_IS_OBJECT(argv[0])) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	sb_obj = JSVAL_TO_OBJECT(argv[0]);
	if (!GF_JS_InstanceOf(c, sb_obj, &html_media_rt->sourceBufferClass, NULL) ) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	sb = (GF_HTML_SourceBuffer *)SMJS_GET_PRIVATE(c, sb_obj);
	if (!sb) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	} else {
		GF_Err e = gf_mse_remove_source_buffer(ms, sb);
		if (e == GF_NOT_FOUND) {
			return dom_throw_exception(c, GF_DOM_EXC_NOT_FOUND_ERR);
		}
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(mediasource_endOfStream)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_HTML_MediaSource *ms;
	u32 i;
	if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
	if (!ms) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	if (ms->readyState != MEDIA_SOURCE_READYSTATE_OPEN) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
	}
	for (i = 0; i < gf_list_count(ms->sourceBuffers.list); i++) {
		GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)gf_list_get(ms->sourceBuffers.list, i);
		if (sb->updating) {
			return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
		}
	}
	if (argc > 0)  {
		char *error = NULL;
		if (!JSVAL_CHECK_STRING(argv[0])) {
			return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
		}
		error = SMJS_CHARS(c, argv[0]);
		if (strcmp(error, "decode") && strcmp(error, "network")) {
			SMJS_FREE(c, error);
			return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
		}
		SMJS_FREE(c, error);
	}
	gf_mse_mediasource_end(ms);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(media_source_constructor)
{
	GF_HTML_MediaSource *p;
	SMJS_OBJ_CONSTRUCTOR(&html_media_rt->mediaSourceClass)

	if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) return JS_TRUE;

	p = gf_mse_media_source_new();
	p->c = c;
	p->_this = obj;
	SMJS_SET_PRIVATE(c, obj, p);

	p->sourceBuffers._this = JS_NewObject(c, &html_media_rt->sourceBufferListClass._class, 0, 0);
	SMJS_SET_PRIVATE(c, p->sourceBuffers._this, &p->sourceBuffers);

	p->activeSourceBuffers._this = JS_NewObject(c, &html_media_rt->sourceBufferListClass._class, 0, 0);
	SMJS_SET_PRIVATE(c, p->activeSourceBuffers._this, &p->activeSourceBuffers);

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

static DECL_FINALIZE(media_source_finalize)

GF_HTML_MediaSource *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) return;
p = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
gf_mse_mediasource_del(p, GF_TRUE);
}

static SMJS_FUNC_PROP_GET(media_source_get_source_buffers)
GF_HTML_MediaSource *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
p = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
if (!p) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
} else {
	*vp = OBJECT_TO_JSVAL(p->sourceBuffers._this);
	return JS_TRUE;
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(media_source_get_active_source_buffers)
GF_HTML_MediaSource *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
	return JS_TRUE;
}
p = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
if (p) {
	*vp = OBJECT_TO_JSVAL(p->activeSourceBuffers._this);
	return JS_TRUE;
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(media_source_get_ready_state)
GF_HTML_MediaSource *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
p = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
if (!p) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
} else {
	switch (p->readyState)
	{
	case MEDIA_SOURCE_READYSTATE_CLOSED:
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "closed"));
		break;
	case MEDIA_SOURCE_READYSTATE_OPEN:
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "open"));
		break;
	case MEDIA_SOURCE_READYSTATE_ENDED:
		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "ended"));
		break;
	}
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(media_source_get_duration)
GF_HTML_MediaSource *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL) ) {
	return JS_TRUE;
}
p = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
if (p) {
	if (p->readyState == MEDIA_SOURCE_READYSTATE_CLOSED || p->durationType == DURATION_NAN) {
		*vp = JS_GetNaNValue(c);
	} else if (p->durationType == DURATION_INFINITY) {
		*vp = JS_GetPositiveInfinityValue(c);
	} else {
		*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, p->duration));
	}
	return JS_TRUE;
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(media_source_set_duration)
GF_HTML_MediaSource *ms;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->mediaSourceClass, NULL)) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, obj);
if (!ms) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
} else {
	if (ms->readyState != MEDIA_SOURCE_READYSTATE_OPEN) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
	} else if (!JSVAL_IS_NUMBER(*vp)) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	} else {
		u32 i, count;
		count = gf_list_count(ms->sourceBuffers.list);
		for (i = 0; i < count; i++) {
			GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)gf_list_get(ms->sourceBuffers.list, i);
			if (sb->updating) {
				return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
			}
		}
		{
			jsdouble durationValue;
			JS_ValueToNumber(c, *vp, &durationValue);
			if (durationValue < 0) {
				return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
			} else {
				ms->duration = durationValue;
				ms->durationType = DURATION_VALUE;
				/* TODO: call the run duration algorithm */
			}
		}
	}
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET( sourcebufferlist_getProperty)
GF_HTML_SourceBufferList *p;
u32 count;
s32 idx;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferListClass, NULL) )
{
	return JS_TRUE;
}
p = (GF_HTML_SourceBufferList *)SMJS_GET_PRIVATE(c, obj);
if (p) {
	count = gf_list_count(p->list);
	if (!SMJS_ID_IS_INT(id)) return JS_TRUE;

	switch (SMJS_ID_TO_INT(id)) {
	case HTML_SOURCEBUFFERLIST_PROP_LENGTH:
		*vp = INT_TO_JSVAL(count);
		break;
	default:
		idx = SMJS_ID_TO_INT(id);
		if ((idx<0) || ((u32) idx>=count)) {
			*vp = JSVAL_VOID;
			return JS_TRUE;
		} else {
			GF_HTML_SourceBuffer *sb;
			sb = (GF_HTML_SourceBuffer *)gf_list_get(p->list, idx);
			*vp = OBJECT_TO_JSVAL(sb->_this);
			return JS_TRUE;
		}

	}
}
return JS_TRUE;

}


#define SB_BASIC_CHECK \
    GF_HTML_SourceBuffer *sb; \
    if (!GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferClass, NULL) )  \
    { \
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
    }\
    sb = (GF_HTML_SourceBuffer *)SMJS_GET_PRIVATE(c, obj);\
    if (!sb)\
    {\
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
    }

#define SB_UPDATING_CHECK \
    SB_BASIC_CHECK \
    /* check if this source buffer is still in the list of source buffers */\
    if (gf_list_find(sb->mediasource->sourceBuffers.list, sb) < 0)\
    {\
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR); \
    } \
    if (sb->updating)\
    {\
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR); \
    }

/* FIXME : Function not used, generates warning on debian
static DECL_FINALIZE(sourcebuffer_finalize)

    GF_HTML_SourceBuffer *sb;
    if (!GF_JS_InstanceOf(c, obj, &html_media_rt->sourceBufferClass, NULL) ) return;
    sb = (GF_HTML_SourceBuffer *)SMJS_GET_PRIVATE(c, obj);
    if (sb) {
         TODO delete lists, remove functions
        gf_free(sb);
    }
}
*/

static JSBool SMJS_FUNCTION(sourcebuffer_appendBuffer)
{
	SMJS_OBJ
	SMJS_ARGS
	GF_HTML_ArrayBuffer  *ab;
	JSObject             *js_ab;

	SB_UPDATING_CHECK
	if (sb->mediasource->readyState == MEDIA_SOURCE_READYSTATE_CLOSED) {
		return JS_TRUE;
	} else if (sb->mediasource->readyState == MEDIA_SOURCE_READYSTATE_ENDED) {
		gf_mse_mediasource_open(sb->mediasource, NULL);
	}

	if (!argc || JSVAL_IS_NULL(argv[0]) || !JSVAL_IS_OBJECT(argv[0])) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	js_ab = JSVAL_TO_OBJECT(argv[0]);
	if (!GF_JS_InstanceOf(c, js_ab, &html_media_rt->arrayBufferClass, NULL) )
	{
		return dom_throw_exception(c, GF_DOM_EXC_TYPE_MISMATCH_ERR);
	}
	ab = (GF_HTML_ArrayBuffer *)SMJS_GET_PRIVATE(c, js_ab);
	if (!ab->length)
	{
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	/* TODO: handle buffer full flag case */
	/* TODO: run the coded frame eviction algo */
	gf_mse_source_buffer_append_arraybuffer(sb, ab);
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(sourcebuffer_appendStream)
{
	SMJS_OBJ
	SB_UPDATING_CHECK
	/* TODO */
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(sourcebuffer_abort)
{
	SMJS_OBJ
	SB_BASIC_CHECK
	if (gf_list_find(sb->mediasource->sourceBuffers.list, sb) < 0) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
	}
	if (sb->mediasource->readyState != MEDIA_SOURCE_READYSTATE_OPEN) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
	}
	if (gf_mse_source_buffer_abort(sb) != GF_OK) {
		return JS_TRUE;
	}
	return JS_TRUE;
}

static JSBool SMJS_FUNCTION(sourcebuffer_remove)
{
	SMJS_OBJ
	SMJS_ARGS
	jsdouble start, end;
	SB_UPDATING_CHECK
	if (argc < 2 || !JSVAL_IS_NUMBER(argv[0]) || !JSVAL_IS_NUMBER(argv[1])) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	JS_ValueToNumber(c, argv[0], &start);
	JS_ValueToNumber(c, argv[1], &end);
	if (start < 0 /* || start > sb->duration */ || start >= end) {
		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
	}
	if (sb->mediasource->readyState != MEDIA_SOURCE_READYSTATE_OPEN) {
		gf_mse_mediasource_open(sb->mediasource, NULL);
	}
	gf_mse_remove(sb, start, end);
	return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_mode)
SB_BASIC_CHECK
if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEGMENTS) {
	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "segments"));
} else if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEQUENCE) {
	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "sequence"));
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(sourceBuffer_set_mode)
char *smode = NULL;
GF_HTML_MediaSource_AppendMode mode = MEDIA_SOURCE_APPEND_MODE_SEGMENTS;
SB_BASIC_CHECK
if (!JSVAL_CHECK_STRING(*vp)) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
smode = SMJS_CHARS(c, *vp);
if (stricmp(smode, "segments") && stricmp(smode, "sequence")) {
	SMJS_FREE(c, smode);
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
if (!stricmp(smode, "segments")) {
	mode = MEDIA_SOURCE_APPEND_MODE_SEGMENTS;
} else if (!stricmp(smode, "sequence")) {
	mode = MEDIA_SOURCE_APPEND_MODE_SEQUENCE;
}
SMJS_FREE(c, smode);
if (gf_list_find(sb->mediasource->sourceBuffers.list, sb) < 0) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
}
if (sb->updating) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
}
if (sb->mediasource->readyState == MEDIA_SOURCE_READYSTATE_ENDED) {
	gf_mse_mediasource_open(sb->mediasource, NULL);
}
if (sb->append_state == MEDIA_SOURCE_APPEND_STATE_PARSING_MEDIA_SEGMENT) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
}
sb->append_mode = mode;
if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEQUENCE) {
	sb->group_start_timestamp_flag = GF_TRUE;
	sb->group_start_timestamp = sb->group_end_timestamp;
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_updating)
SB_BASIC_CHECK
*vp = BOOLEAN_TO_JSVAL(sb->updating ? JS_TRUE : JS_FALSE);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_timestampOffset)
SB_BASIC_CHECK
*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, sb->timestampOffset*1.0/sb->timescale));
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(sourceBuffer_set_timestampOffset)
jsdouble d;
SB_UPDATING_CHECK
if (sb->mediasource->readyState == MEDIA_SOURCE_READYSTATE_ENDED) {
	gf_mse_mediasource_open(sb->mediasource, NULL);
}
if (sb->append_state == MEDIA_SOURCE_APPEND_STATE_PARSING_MEDIA_SEGMENT) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_STATE_ERR);
}
JS_ValueToNumber(c, *vp, &d);
gf_mse_source_buffer_set_timestampOffset(sb, d);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_timescale)
SB_BASIC_CHECK
*vp = INT_TO_JSVAL(sb->timescale);
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(sourceBuffer_set_timescale)
SB_BASIC_CHECK
gf_mse_source_buffer_set_timescale(sb, JSVAL_TO_INT(*vp));
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_appendWindowStart)
SB_BASIC_CHECK
*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, sb->appendWindowStart));
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(sourceBuffer_set_appendWindowStart)
jsdouble d;
SB_UPDATING_CHECK
if (!JSVAL_IS_NUMBER(*vp)) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
JS_ValueToNumber(c, *vp, &d);
if (d < 0 || d >= sb->appendWindowEnd) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
sb->appendWindowStart = d;
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_appendWindowEnd)
SB_BASIC_CHECK
if (sb->appendWindowEnd == GF_MAX_DOUBLE) {
	*vp = JS_GetPositiveInfinityValue(c);
} else {
	*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, sb->appendWindowEnd));
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(sourceBuffer_set_appendWindowEnd)
jsdouble d;
SB_UPDATING_CHECK
if (!JSVAL_IS_NUMBER(*vp)) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
JS_ValueToNumber(c, *vp, &d);
if (d <= sb->appendWindowStart) {
	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
sb->appendWindowEnd = d;
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_buffered)
SB_BASIC_CHECK
gf_mse_source_buffer_update_buffered(sb);
*vp = OBJECT_TO_JSVAL(sb->buffered->_this);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(sourceBuffer_get_tracks)
SB_BASIC_CHECK
/* TODO */
return JS_TRUE;
}

static JSBool SMJS_FUNCTION(html_url_createObjectURL)
{
	SMJS_ARGS
	JSObject    *js_ms;
	GF_HTML_MediaSource *ms;
	char        blobURI[256];

	SMJS_SET_RVAL(JSVAL_NULL);
	if (!argc || JSVAL_IS_NULL(argv[0]) || !JSVAL_IS_OBJECT(argv[0])) {
		return JS_TRUE;
	}
	js_ms = JSVAL_TO_OBJECT(argv[0]);
	if (!GF_JS_InstanceOf(c, js_ms, &html_media_rt->mediaSourceClass, NULL) )
	{
		return JS_TRUE;
	}
	ms = (GF_HTML_MediaSource *)SMJS_GET_PRIVATE(c, js_ms);
	sprintf(blobURI, "blob:%p", ms);
	ms->blobURI = gf_strdup(blobURI);
	SMJS_SET_RVAL(STRING_TO_JSVAL( JS_NewStringCopyZ(c, blobURI)));
	return JS_TRUE;
}

GF_HTML_ArrayBuffer *gf_arraybuffer_new(char *data, u32 length)
{
	GF_HTML_ArrayBuffer *ab = NULL;
	GF_SAFEALLOC(ab, GF_HTML_ArrayBuffer);
	if (!ab) return NULL;
	
	if (length > 0) {
		ab->data = data;
		ab->length = length;
	}
	ab->reference_count = 0;
	return ab;
}

JSObject *gf_arraybuffer_js_new(JSContext *c, char *data, u32 length, JSObject *parent)
{
	JSObject *obj;
	GF_HTML_ArrayBuffer *p;
	if (!data) return NULL;
	p = gf_arraybuffer_new(data, length);
	obj = JS_NewObject(c, &html_media_rt->arrayBufferClass._class, NULL, parent);
	if (!parent) {
		p->reference_count = 0;
	}
	SMJS_SET_PRIVATE(c, obj, p);
	p->c = c;
	p->_this = obj;
	return obj;
}

void xhr_del_array_buffer(void *udta);

void gf_arraybuffer_del(GF_HTML_ArrayBuffer *buffer, Bool del_js)
{
	if (buffer) {
		if (del_js && buffer->_this) {
			/*if we have a JS object, make sure the parent XHR doesn't point to us ...*/
			JSObject *xhro = JS_GetParent(buffer->c, buffer->_this);
			if (xhro) {
				void *xhr_ctx = SMJS_GET_PRIVATE(buffer->c, xhro);
				xhr_del_array_buffer(xhr_ctx);
			}

			/* finalize the object from the JS perspective */
			buffer->c = NULL;
			buffer->_this = NULL;
		}
		/* but only delete if the data is not used elsewhere */
		if(!del_js && buffer->reference_count) {
			buffer->reference_count--;
		}
		if (!buffer->reference_count && !buffer->_this) {
			gf_free(buffer->data);
			if (buffer->url) gf_free(buffer->url);
			gf_free(buffer);
		}
	}
}

static JSBool SMJS_FUNCTION(arraybuffer_constructor)
{
	SMJS_ARGS
	u32 length = 0;
	SMJS_OBJ_CONSTRUCTOR(&html_media_rt->arrayBufferClass)

	if (!GF_JS_InstanceOf(c, obj, &html_media_rt->arrayBufferClass, NULL))
	{
		return JS_TRUE;
	}
	if (argc && !JSVAL_IS_NULL(argv[0]) && JSVAL_IS_INT(argv[0])) {
		length = JSVAL_TO_INT(argv[0]);
	}
	if (length > 0) {
		gf_arraybuffer_js_new(c, (char *)gf_malloc(length), length, NULL);
	}
	return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(arraybuffer_get_byteLength)
GF_HTML_ArrayBuffer *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->arrayBufferClass, NULL) ) {
	return JS_TRUE;
}
p = (GF_HTML_ArrayBuffer *)SMJS_GET_PRIVATE(c, obj);
if (p) {
	*vp = INT_TO_JSVAL(p->length);
}
return JS_TRUE;
}

static DECL_FINALIZE(arraybuffer_finalize)

GF_HTML_ArrayBuffer *p;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->arrayBufferClass, NULL) ) return;
p = (GF_HTML_ArrayBuffer *)SMJS_GET_PRIVATE(c, obj);
if (!p) return;
gf_arraybuffer_del(p, GF_TRUE);
}

static JSBool SMJS_FUNCTION(mse_event_add_listener)
{
	JSBool SMJS_FUNCTION_EXT(gf_sg_js_event_add_listener, GF_Node *vrml_node);
	return gf_sg_js_event_add_listener(SMJS_CALL_ARGS, NULL);
}

static JSBool SMJS_FUNCTION(mse_event_remove_listener)
{
	JSBool SMJS_FUNCTION_EXT(gf_sg_js_event_remove_listener, GF_Node *vrml_node);
	return gf_sg_js_event_remove_listener(SMJS_CALL_ARGS, NULL);
}

static JSBool SMJS_FUNCTION(mse_event_dispatch)
{
	/* TODO */
	return JS_TRUE;
}

void html_media_source_init_js_api(JSContext *js_ctx, JSObject *global, GF_HTML_MediaRuntime *_html_media_rt)
{
	/* HTML Media Element */
	if (_html_media_rt) {
		html_media_rt = _html_media_rt;

		JS_SETUP_CLASS(html_media_rt->arrayBufferClass, "ArrayBuffer", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, arraybuffer_finalize);
		{
			JSPropertySpec arrayBufferClassProps[] = {
				{"byteLength",       -1,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, arraybuffer_get_byteLength, 0},
				{0, 0, 0, 0, 0}
			};
			GF_JS_InitClass(js_ctx, global, 0, &html_media_rt->arrayBufferClass, arraybuffer_constructor, 0, arrayBufferClassProps, 0, 0, 0);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTML Media Source API] ArrayBuffer class initialized\n"));
		}

		JS_SETUP_CLASS(html_media_rt->mediaSourceClass, "MediaSource", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, media_source_finalize);
		{
			JSPropertySpec htmlMediaSourceClassProps[] = {
				{"sourceBuffers",       HTML_MEDIASOURCE_PROP_SOURCEBUFFERS,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, media_source_get_source_buffers, 0},
				{"activeSourceBuffers", HTML_MEDIASOURCE_PROP_ACTIVESOURCEBUFFERS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, media_source_get_active_source_buffers, 0},
				{"readyState",          HTML_MEDIASOURCE_PROP_READYSTATE,          JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, media_source_get_ready_state, 0},
				{"duration",            HTML_MEDIASOURCE_PROP_DURATION,            JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, media_source_get_duration, media_source_set_duration},
				{0, 0, 0, 0, 0}
			};
			JSFunctionSpec htmlMediaSourceClassFuncs[] = {
				SMJS_FUNCTION_SPEC("addSourceBuffer", mediasource_addSourceBuffer, 1),
				SMJS_FUNCTION_SPEC("removeSourceBuffer", mediasource_removeSourceBuffer, 1),
				SMJS_FUNCTION_SPEC("endOfStream", mediasource_endOfStream, 1),
				SMJS_FUNCTION_SPEC("addEventListener", mse_event_add_listener, 3),
				SMJS_FUNCTION_SPEC("removeEventListener", mse_event_remove_listener, 3),
				SMJS_FUNCTION_SPEC("dispatchEvent", mse_event_dispatch, 1),
				SMJS_FUNCTION_SPEC(0, 0, 0)
			};
			JSFunctionSpec htmlMediaSourceClassStaticFuncs[] = {
				SMJS_FUNCTION_SPEC("isTypeSupported", mediasource_is_type_supported, 1),
				SMJS_FUNCTION_SPEC(0, 0, 0)
			};
			GF_JS_InitClass(js_ctx, global, 0, &html_media_rt->mediaSourceClass, media_source_constructor, 0, htmlMediaSourceClassProps, htmlMediaSourceClassFuncs, 0, htmlMediaSourceClassStaticFuncs);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTML Media Source API] MediaSource class initialized\n"));
		}

		JS_SETUP_CLASS(html_media_rt->sourceBufferClass, "SourceBuffer", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_PropertyStub);
		{
			JSPropertySpec SourceBufferClassProps[] = {
				{"mode",			  HTML_SOURCEBUFFER_PROP_MODE,             JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, sourceBuffer_get_mode, sourceBuffer_set_mode},
				{"updating",          HTML_SOURCEBUFFER_PROP_UPDATING,         JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, sourceBuffer_get_updating, 0},
				{"buffered",          HTML_SOURCEBUFFER_PROP_BUFFERED,         JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, sourceBuffer_get_buffered, 0},
				{"timestampOffset",   HTML_SOURCEBUFFER_PROP_TIMESTAMPOFFSET,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, sourceBuffer_get_timestampOffset, sourceBuffer_set_timestampOffset},
				{"timescale",         HTML_SOURCEBUFFER_PROP_TIMESCALE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, sourceBuffer_get_timescale, sourceBuffer_set_timescale},
				{"appendWindowStart", HTML_SOURCEBUFFER_PROP_APPENDWINDOWSTART,JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, sourceBuffer_get_appendWindowStart, sourceBuffer_set_appendWindowStart},
				{"appendWindowEnd",   HTML_SOURCEBUFFER_PROP_APPENDWINDOWEND,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, sourceBuffer_get_appendWindowEnd, sourceBuffer_set_appendWindowEnd},
				{"audioTracks",       HTML_SOURCEBUFFER_PROP_AUDIOTRACKS,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, sourceBuffer_get_tracks, 0},
				{"videoTracks",       HTML_SOURCEBUFFER_PROP_VIDEOTRACKS,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, sourceBuffer_get_tracks, 0},
				{"textTracks",        HTML_SOURCEBUFFER_PROP_TEXTTRACKS,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, sourceBuffer_get_tracks, 0},
				{0, 0, 0, 0, 0}
			};
			JSFunctionSpec SourceBufferClassFuncs[] = {
				SMJS_FUNCTION_SPEC("appendBuffer", sourcebuffer_appendBuffer, 1),
				SMJS_FUNCTION_SPEC("appendStream", sourcebuffer_appendStream, 1),
				SMJS_FUNCTION_SPEC("abort",  sourcebuffer_abort, 0),
				SMJS_FUNCTION_SPEC("remove", sourcebuffer_remove, 2),
				SMJS_FUNCTION_SPEC("addEventListener", mse_event_add_listener, 3),
				SMJS_FUNCTION_SPEC("removeEventListener", mse_event_add_listener, 3),
				SMJS_FUNCTION_SPEC("dispatchEvent", mse_event_add_listener, 1),
				SMJS_FUNCTION_SPEC(0, 0, 0)
			};
			GF_JS_InitClass(js_ctx, global, 0, &html_media_rt->sourceBufferClass, 0, 0, SourceBufferClassProps, SourceBufferClassFuncs, 0, 0);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTML Media Source API] SourceBuffer class initialized\n"));
		}

		JS_SETUP_CLASS(html_media_rt->sourceBufferListClass, "SourceBufferList", JSCLASS_HAS_PRIVATE, sourcebufferlist_getProperty, JS_PropertyStub_forSetter, JS_PropertyStub);
		{
			JSPropertySpec SourceBufferListClassProps[] = {
				{"length",        HTML_SOURCEBUFFERLIST_PROP_LENGTH,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
				{0, 0, 0, 0, 0}
			};
			JSFunctionSpec SourceBufferListClassFuncs[] = {
				SMJS_FUNCTION_SPEC("addEventListener", mse_event_add_listener, 3),
				SMJS_FUNCTION_SPEC("removeEventListener", mse_event_add_listener, 3),
				SMJS_FUNCTION_SPEC("dispatchEvent", mse_event_add_listener, 1),
				SMJS_FUNCTION_SPEC(0, 0, 0)
			};
			GF_JS_InitClass(js_ctx, global, 0, &html_media_rt->sourceBufferListClass, 0, 0, SourceBufferListClassProps, SourceBufferListClassFuncs, 0, 0);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTML Media Source API] SourceBufferList class initialized\n"));
		}

		JS_SETUP_CLASS(html_media_rt->URLClass, "URL", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_PropertyStub);
		{
			JSPropertySpec URLClassProps[] = {
				{0, 0, 0, 0, 0}
			};
			JSFunctionSpec URLClassFuncs[] = {
				SMJS_FUNCTION_SPEC("createObjectURL", html_url_createObjectURL, 1),
				SMJS_FUNCTION_SPEC(0, 0, 0)
			};
			GF_JS_InitClass(js_ctx, global, 0, &html_media_rt->URLClass, 0, 0, URLClassProps, 0, 0, URLClassFuncs);
			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTML Media Source API] URL class initialized\n"));
		}
	}
}


#endif  /*GPAC_HAS_SPIDERMONKEY*/

#endif  /*GPAC_DISABLE_SVG*/
