root/src/scenegraph/html5_media_smjs.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. gf_html_media_object
  2. gf_html_track_init_js
  3. gf_html_media_element_init_js
  4. html_media_element_populate_tracks
  5. html_media_element_get_from_node
  6. SMJS_FUNCTION
  7. SMJS_FUNCTION
  8. SMJS_FUNCTION
  9. SMJS_FUNCTION
  10. html_get_element_class
  11. html_media_element_js_init
  12. html_media_element_js_finalize
  13. gf_html_media_get_event_target
  14. SMJS_FUNC_PROP_GET
  15. SMJS_FUNC_PROP_SET
  16. SMJS_FUNC_PROP_SET
  17. html_media_get_states
  18. GF_JS_InstanceOf
  19. SMJS_FUNC_PROP_SET
  20. ScriptAction
  21. SMJS_FUNC_PROP_SET
  22. SMJS_FUNC_PROP_SET
  23. JSVAL_IS_NUMBER
  24. SMJS_FUNC_PROP_SET
  25. SMJS_FUNC_PROP_SET
  26. SMJS_FUNC_PROP_SET
  27. SMJS_FUNC_PROP_GET
  28. SMJS_FUNC_PROP_SET
  29. SMJS_FUNC_PROP_SET
  30. html_media_get_audio_stack
  31. SMJS_FUNC_PROP_SET
  32. SMJS_FUNC_PROP_GET
  33. SMJS_FUNCTION
  34. SMJS_FUNCTION
  35. html_is_track_list
  36. SMJS_FUNC_PROP_GET
  37. SMJS_FUNCTION
  38. SMJS_FUNC_PROP_GET
  39. SMJS_FUNC_PROP_GET
  40. SMJS_FUNC_PROP_GET
  41. SMJS_FUNC_PROP_SET
  42. SMJS_FUNC_PROP_GET
  43. SMJS_FUNC_PROP_SET
  44. SMJS_FUNC_PROP_GET
  45. SMJS_FUNC_PROP_SET
  46. SMJS_FUNC_PROP_GET
  47. SMJS_FUNC_PROP_SET
  48. SMJS_FUNC_PROP_GET
  49. SMJS_FUNC_PROP_SET
  50. SMJS_FUNCTION
  51. SMJS_FUNCTION
  52. SMJS_FUNCTION
  53. html_media_js_api_del
  54. html_media_init_js_api

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


#include <gpac/internal/scenegraph_dev.h>

/*base SVG type*/
#include <gpac/nodes_svg.h>
/*dom events*/
#include <gpac/events.h>
/*dom text event*/
#include <gpac/utf.h>

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

#ifndef GPAC_DISABLE_SVG


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

#ifdef GPAC_HAS_SPIDERMONKEY

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

/***********************************************************
 *
 * DOM generic functions being used
 *
 ***********************************************************/
#ifdef USE_FFDEV_15
void dom_element_finalize(JSFreeOp *fop, JSObject *obj);
void dom_document_finalize(JSFreeOp *fop, JSObject *obj);
#else
void dom_element_finalize(JSContext *c, JSObject *obj);
void dom_document_finalize(JSContext *c, JSObject *obj);
#endif

void gf_svg_set_attributeNS(GF_Node *n, u32 ns_code, char *name, char *val);

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

/* including terminal dev for MediaObject */

#include <gpac/internal/terminal_dev.h>

/* including Compositor dev for TextureHandler and AudioInput */
#include <gpac/internal/compositor_dev.h>

static GF_HTML_MediaRuntime *html_media_rt = NULL;

/* Enumeration of the property codes for use in JS Bindings for the the HTML5 MediaError interface */
typedef enum
{
        MEDIA_ERROR_PROP_ABORTED            = -1,
        MEDIA_ERROR_PROP_NETWORK            = -2,
        MEDIA_ERROR_PROP_DECODE             = -3,
        MEDIA_ERROR_PROP_SRC_NOT_SUPPORTED  = -4,
        MEDIA_ERROR_PROP_CODE               = -5
} GF_HTML_MediaErrorPropEnum;

/* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLMediaElement interface */
typedef enum
{
        HTML_MEDIA_PROP_SRC                         = -1,
        HTML_MEDIA_PROP_CURRENTSRC                  = -2,
        HTML_MEDIA_PROP_CROSSORIGIN                 = -3,
        HTML_MEDIA_PROP_NETWORKSTATE                = -4,
        HTML_MEDIA_PROP_PRELOAD                     = -5,
        HTML_MEDIA_PROP_BUFFERED                    = -6,
        HTML_MEDIA_PROP_READYSTATE                  = -7,
        HTML_MEDIA_PROP_SEEKING                     = -8,
        HTML_MEDIA_PROP_CURRENTTIME                 = -9,
        HTML_MEDIA_PROP_DURATION                    = -10,
        HTML_MEDIA_PROP_STARTDATE                   = -11,
        HTML_MEDIA_PROP_PAUSED                      = -12,
        HTML_MEDIA_PROP_DEFAULTPLAYBACKRATE         = -13,
        HTML_MEDIA_PROP_PLAYBACKRATE                = -14,
        HTML_MEDIA_PROP_PLAYED                      = -15,
        HTML_MEDIA_PROP_SEEKABLE                    = -16,
        HTML_MEDIA_PROP_ENDED                       = -17,
        HTML_MEDIA_PROP_AUTOPLAY                    = -18,
        HTML_MEDIA_PROP_LOOP                        = -19,
        HTML_MEDIA_PROP_MEDIAGROUP                  = -20,
        HTML_MEDIA_PROP_CONTROLLER                  = -21,
        HTML_MEDIA_PROP_CONTROLS                    = -22,
        HTML_MEDIA_PROP_VOLUME                      = -23,
        HTML_MEDIA_PROP_MUTED                       = -24,
        HTML_MEDIA_PROP_DEFAULTMUTED                = -25,
        HTML_MEDIA_PROP_AUDIOTRACKS                 = -26,
        HTML_MEDIA_PROP_VIDEOTRACKS                 = -27,
        HTML_MEDIA_PROP_TEXTTRACKS                  = -28,
        HTML_MEDIA_PROP_NETWORK_EMPTY               = -29,
        HTML_MEDIA_PROP_NETWORK_IDLE                = -30,
        HTML_MEDIA_PROP_NETWORK_LOADING             = -31,
        HTML_MEDIA_PROP_NETWORK_NO_SOURCE           = -32,
        HTML_MEDIA_PROP_HAVE_NOTHING                = -33,
        HTML_MEDIA_PROP_HAVE_METADATA               = -34,
        HTML_MEDIA_PROP_HAVE_CURRENT_DATA           = -35,
        HTML_MEDIA_PROP_HAVE_FUTURE_DATA            = -36,
        HTML_MEDIA_PROP_HAVE_ENOUGH_DATA            = -37,
        HTML_MEDIA_PROP_ERROR                       = -38
} GF_HTML_MediaEltPropEnum;

/* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLVideoElement interface */
typedef enum {
        HTML_VIDEO_PROP_WIDTH                       = -1,
        HTML_VIDEO_PROP_HEIGHT                      = -2,
        HTML_VIDEO_PROP_VIDEOWIDTH                  = -3,
        HTML_VIDEO_PROP_VIDEOHEIGHT                 = -4,
        HTML_VIDEO_PROP_POSTER                      = -5
} GF_HTML_VideoEltPropEnum;

/* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLTrackElement interface */
typedef enum {
        HTML_TRACK_PROP_ID                          = -1,
        HTML_TRACK_PROP_KIND                        = -2,
        HTML_TRACK_PROP_LABEL                       = -3,
        HTML_TRACK_PROP_LANGUAGE                    = -4,
        HTML_TRACK_PROP_SELECTED                    = -5,
        HTML_TRACK_PROP_ENABLED                     = -6,
        HTML_TRACK_PROP_INBANDTYPE                  = -7
} GF_HTML_TrackPropEnum;

#define HTML_MEDIA_JS_CHECK     GF_HTML_MediaElement *me; \
                                GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj); \
                                if (!n || (n->sgprivate->tag != TAG_SVG_video && n->sgprivate->tag != TAG_SVG_audio)) \
                                { \
                                                                        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
                                } \
                                me = (GF_HTML_MediaElement *)html_media_element_get_from_node(c, n); \
                                if (!me) { \
                                                                        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
                                }

#define HTML_MEDIA_JS_START     HTML_MEDIA_JS_CHECK

/* Structure copied over from svg_media.c
   TODO : fix this - remove the copy */
typedef struct
{
        GF_TextureHandler txh;
        struct _drawable *graph;
        MFURL txurl;
        Bool first_frame_fetched;
        GF_Node *audio;
        Bool audio_dirty;
        Bool stop_requested;
} SVG_video_stack;

/* Structure copied over from svg_media.c
   TODO : fix this - remove the copy */
typedef struct
{
        GF_AudioInput input;
        Bool is_active, is_error;
        MFURL aurl;
} SVG_audio_stack;

/* Gets the GF_MediaObject associated with this node
 *
 *  \param n the node either an SVG/HTML5 video or audio element
 *  \return the GF_MediaObject attached to this node or null if none
 */
static GF_MediaObject *gf_html_media_object(GF_Node *n)
{
        GF_MediaObject *mo = NULL;
        if (n->sgprivate->tag == TAG_SVG_video)
        {
                SVG_video_stack *video_stack;
                video_stack = (SVG_video_stack *)n->sgprivate->UserPrivate;
                mo = video_stack->txh.stream;
        }
        else if (n->sgprivate->tag == TAG_SVG_audio)
        {
                SVG_audio_stack *audio_stack;
                audio_stack = (SVG_audio_stack *)n->sgprivate->UserPrivate;
                mo = audio_stack->input.stream;
        }
        return mo;
}

static void gf_html_track_init_js(GF_HTML_Track *track, JSContext *c, GF_HTML_TrackList *tracklist)
{
        JSObject *track_obj;
        track->c     = c;
        if (track->type == HTML_MEDIA_TRACK_TYPE_AUDIO)
        {
                track_obj = JS_NewObject(c, &html_media_rt->audioTrackClass._class, NULL, tracklist->_this);
        }
        else if (track->type == HTML_MEDIA_TRACK_TYPE_VIDEO)
        {
                track_obj = JS_NewObject(c, &html_media_rt->videoTrackClass._class, NULL, tracklist->_this);
        }
        else if (track->type == HTML_MEDIA_TRACK_TYPE_TEXT)
        {
                track_obj = JS_NewObject(c, &html_media_rt->textTrackClass._class, NULL, tracklist->_this);
        }
        else
        {
                return;
        }
        tracklist->onchange = JSVAL_NULL;
        tracklist->onaddtrack = JSVAL_NULL;
        tracklist->onremovetrack = JSVAL_NULL;
        track->_this = track_obj;
        SMJS_SET_PRIVATE(c, track->_this, track);
}

/* Not yet used, removed for GCC warning
static void gf_html_media_controller_init_js(GF_HTML_MediaController *mc, JSContext *c)
{
    mc->c            = c;
    mc->_this        = JS_NewObject(c, &html_media_rt->mediaControllerClass._class, NULL, NULL);
    SMJS_SET_PRIVATE(c, mc->_this, mc);
    mc->buffered->c              = c;
    mc->buffered->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
    SMJS_SET_PRIVATE(c, mc->buffered->_this, mc->buffered);
    mc->played->c                = c;
    mc->played->_this            = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
    SMJS_SET_PRIVATE(c, mc->played->_this, mc->played);
    mc->seekable->c              = c;
    mc->seekable->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
    SMJS_SET_PRIVATE(c, mc->seekable->_this, mc->seekable);
}
*/

static void gf_html_media_element_init_js(GF_HTML_MediaElement *me, JSContext *c, JSObject *node_obj)
{
        me->c                       = c;
        me->_this                   = JS_NewObject(c, &html_media_rt->htmlMediaElementClass._class, NULL, node_obj);
        SMJS_SET_PRIVATE(c, me->_this, me);

        me->error.c                 = c;
        me->error._this             = JS_NewObject(c, &html_media_rt->mediaErrorClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->error._this, &me->error);

        me->audioTracks.c           = c;
        me->audioTracks._this       = JS_NewObject(c, &html_media_rt->audioTrackListClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->audioTracks._this, &me->audioTracks);

        me->videoTracks.c           = c;
        me->videoTracks._this       = JS_NewObject(c, &html_media_rt->videoTrackListClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->videoTracks._this, &me->videoTracks);

        me->textTracks.c            = c;
        me->textTracks._this        = JS_NewObject(c, &html_media_rt->textTrackListClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->textTracks._this, &me->textTracks);

        me->buffered->c              = c;
        me->buffered->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->buffered->_this, me->buffered);

        me->played->c                = c;
        me->played->_this            = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->played->_this, me->played);

        me->seekable->c              = c;
        me->seekable->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
        SMJS_SET_PRIVATE(c, me->seekable->_this, me->seekable);
}

/* Function to browse the tracks in the MediaObject associated with the Media Element and to create appropriate HTML Track objects
 *
 * \param c The JavaScript Context to create the new JS object
 * \param me the HTMLMediaElement to update
 */
static void html_media_element_populate_tracks(JSContext *c, GF_HTML_MediaElement *me)
{
        GF_HTML_Track *track;
        GF_MediaObject *mo;
        GF_ClientService *service;
        GF_Scene *scene;
        u32 i, count;

        if (!me || !me->node) {
                return;
        }
        mo = gf_html_media_object(me->node);
        if (!mo || !mo->odm || !mo->odm->net_service || !mo->odm->parentscene) {
                return;
        }
        service = mo->odm->net_service;
        scene = mo->odm->parentscene;

        count = gf_list_count(scene->resources);
        for (i=0; i<count; i++) {
                u32 j;
                GF_ObjectManager *odm = (GF_ObjectManager *)gf_list_get(scene->resources, i);
                if (odm->net_service != service) continue;
                if (!odm->mo || !odm->OD) continue;
                for (j = 0; j < gf_list_count(odm->OD->ESDescriptors); j++)
                {
                        char id[256];
                        char *lang = "";
                        GF_ESD *esd = (GF_ESD *)gf_list_get(odm->OD->ESDescriptors, j);
                        sprintf(id, "track%d", esd->ESID);
                        if (esd->langDesc)
                        {
                                lang  = "";
                        }
                        switch(odm->mo->type)
                        {
                        case GF_MEDIA_OBJECT_VIDEO:
                                if (!html_media_tracklist_has_track(&me->videoTracks, id))
                                {
                                        track = html_media_add_new_track_to_list(&me->videoTracks, HTML_MEDIA_TRACK_TYPE_VIDEO,
                                                "video/mp4", GF_TRUE, id, "myKind", "myLabel", lang);
                                        gf_html_track_init_js(track, c, &me->videoTracks);
                                }
                                break;
                        case GF_MEDIA_OBJECT_AUDIO:
                                if (!html_media_tracklist_has_track(&me->audioTracks, id))
                                {
                                        track = html_media_add_new_track_to_list(&me->audioTracks, HTML_MEDIA_TRACK_TYPE_AUDIO,
                                                "audio/mp4", GF_TRUE, id, "myKind", "myLabel", lang);
                                        gf_html_track_init_js(track, c, &me->audioTracks);
                                }
                                break;
                        case GF_MEDIA_OBJECT_TEXT:
                                if (!html_media_tracklist_has_track(&me->textTracks, id))
                                {
                                        track = html_media_add_new_track_to_list(&me->textTracks, HTML_MEDIA_TRACK_TYPE_TEXT,
                                                "text/plain", GF_TRUE, id, "myKind", "myLabel", lang);
                                        gf_html_track_init_js(track, c, &me->textTracks);
                                }
                                break;
                        }
                }
        }
}

/* Used to retrieve the structure implementing the GF_HTML_MediaElement interface associated with this node
 * Usually this is done with the private stack of the node (see gf_node_get_private), but in this case,
 * the stack already contains the rendering stack SVG_video_stack.
 * So, we store the structure implementing the GF_HTML_MediaElement interface in the JavaScript context of this node,
 * as a non enumeratable property named 'gpac_me_impl'
 *
 *  \param c the global JavaScript context
 *  \param n the audio or video node
 *  \return the GF_HTML_MediaElement associated with this node in the given context
 */
static GF_HTML_MediaElement *html_media_element_get_from_node(JSContext *c, GF_Node *n)
{
        jsval vp;
        JSObject *me_obj;
        JSObject *node_obj;
        GF_HTML_MediaElement *me = NULL;

        if ((n->sgprivate->tag == TAG_SVG_video || n->sgprivate->tag == TAG_SVG_audio) && n->sgprivate->interact && n->sgprivate->interact->js_binding) {
                node_obj = (JSObject *)n->sgprivate->interact->js_binding->node;
                if (node_obj) {
                        JS_GetProperty(c, node_obj, "gpac_me_impl", &vp);
                        me_obj = JSVAL_TO_OBJECT(vp);
                        me = (GF_HTML_MediaElement *)SMJS_GET_PRIVATE(c, me_obj);
                }
        }
        return me;
}

static JSBool SMJS_FUNCTION(html_media_load)
{
        SMJS_OBJ
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
        {
                MFURL mfurl;
                GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
                GF_HTML_MediaElement *me = html_media_element_get_from_node(c, n);
                mfurl.count = 1;
                mfurl.vals = (SFURL *)gf_malloc(sizeof(SFURL));
                mfurl.vals[0].url = me->currentSrc;
                mfurl.vals[0].OD_ID = GF_MEDIA_EXTERNAL_ID;
                gf_mo_register(n, &mfurl, GF_FALSE, GF_FALSE);
                gf_free(mfurl.vals);
                return JS_TRUE;
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
}

static JSBool SMJS_FUNCTION(html_media_canPlayType)
{
        SMJS_OBJ
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
        {
                return JS_TRUE;
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
}

static JSBool SMJS_FUNCTION(html_media_fastSeek)
{
        SMJS_OBJ
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
        {
                return JS_TRUE;
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
}

static JSBool SMJS_FUNCTION(html_media_addTextTrack)
{
        SMJS_OBJ
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
        {
                return JS_TRUE;
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
}

void *html_get_element_class(GF_Node *n)
{
        if (!n || !html_media_rt)
        {
                return NULL;
        } else if (n->sgprivate->tag == TAG_SVG_video) {
                return &html_media_rt->htmlVideoElementClass;
        } else if (n->sgprivate->tag == TAG_SVG_audio) {
                return &html_media_rt->htmlAudioElementClass;
        } else {
                return NULL;
        }
}

/* Creates the GF_HTML_MediaElement structure for this node
 * Store it in the JavaScript context of this node, as a non enumeratable property named 'gpac_me_impl'
 * see \ref html_media_element_get_from_node for retrieving it
 *
 *  \param c the global JavaScript context
 *  \param n the audio or video node
 *  \param node_obj the JavaScript object representing the audio or video node
 */
void html_media_element_js_init(JSContext *c, JSObject *node_obj, GF_Node *n)
{
        if (n->sgprivate->tag == TAG_SVG_video || n->sgprivate->tag == TAG_SVG_audio) {
                GF_HTML_MediaElement *me;
                me = gf_html_media_element_new(n, NULL);
                gf_html_media_element_init_js(me, c, node_obj);
                /* To be able to retrieve the Media Element interface from the Node element */
                JS_DefineProperty(c, node_obj, "gpac_me_impl", OBJECT_TO_JSVAL(me->_this), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
        }
}

/* Deletes the GF_HTML_MediaElement structure for this node
 *
 *  \param c the global JavaScript context
 *  \param n the audio or video node
 */
void html_media_element_js_finalize(JSContext *c, GF_Node *n)
{
        GF_HTML_MediaElement *me = html_media_element_get_from_node(c, n);
        /* TODO: finalize child objects */
        gf_html_media_element_del(me);
}

/* Gets The EventTarget and SceneGraph from the GF_HTML_MediaElement structure for a audio/video JS node
 *
 *  \param c the global JavaScript context
 *  \param obj the JS object representing the HTMLAudioElement or HTMLVideoElement
 *  \param target the GF_DOMEventTarget object
 *  \param sg the scene graph
 */
void gf_html_media_get_event_target(JSContext *c, JSObject *obj, GF_DOMEventTarget **target, GF_SceneGraph **sg) {
        if (!sg || !target) return;
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
                GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ) {
                GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
                *target = gf_dom_event_get_target_from_node(n);
                *sg = n->sgprivate->scenegraph;
        } else {
                *target = NULL;
                *sg = NULL;
        }
}

static SMJS_FUNC_PROP_GET(html_media_error_get_code)
if (GF_JS_InstanceOf(c, obj, &html_media_rt->mediaErrorClass, NULL))
{
        GF_HTML_MediaError *error = (GF_HTML_MediaError*)SMJS_GET_PRIVATE(c, obj);
        if (!SMJS_ID_IS_INT(id)) {
                return JS_TRUE;
        }
        switch (SMJS_ID_TO_INT(id))
        {
        case MEDIA_ERROR_PROP_ABORTED:
                *vp = INT_TO_JSVAL(MEDIA_ERR_ABORTED);
                return JS_TRUE;
        case MEDIA_ERROR_PROP_NETWORK:
                *vp = INT_TO_JSVAL(MEDIA_ERR_NETWORK);
                return JS_TRUE;
        case MEDIA_ERROR_PROP_DECODE:
                *vp = INT_TO_JSVAL(MEDIA_ERR_DECODE);
                return JS_TRUE;
        case MEDIA_ERROR_PROP_SRC_NOT_SUPPORTED:
                *vp = INT_TO_JSVAL(MEDIA_ERR_SRC_NOT_SUPPORTED);
                return JS_TRUE;
        case MEDIA_ERROR_PROP_CODE:
                *vp = INT_TO_JSVAL(error->code);
                return JS_TRUE;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_error)
HTML_MEDIA_JS_START
*vp = OBJECT_TO_JSVAL(me->error._this);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_src)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "href", GF_XMLNS_XLINK, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_src)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_XLINK, "href", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_cors)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "crossorigin", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_cors)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "crossorigin", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

void media_event_collect_info(GF_ClientService *net, GF_ObjectManager *odm, GF_DOMMediaEvent *media_event, u32 *min_time, u32 *min_buffer);

static void html_media_get_states(GF_MediaObject *mo, GF_HTML_MediaReadyState *readyState, GF_HTML_NetworkState *networkState)
{
        GF_DOMMediaEvent media_event;
        u32 min_time;
        u32 min_buffer;
        if (!mo) return;

        if (!mo->odm) {
                *readyState = HAVE_NOTHING;
                *networkState = NETWORK_EMPTY;
                return;
        }

        memset(&media_event, 0, sizeof(GF_DOMMediaEvent));
        media_event_collect_info(mo->odm->net_service, mo->odm, &media_event, &min_time, &min_buffer);
        //GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] min_time: %d, min_buffer: %d loaded: "LLD"/"LLD"\n",
        //                                                    min_time, min_buffer, media_event.loaded_size, media_event.total_size));
        if (min_buffer == 0)
        {
                *readyState = HAVE_NOTHING;
                *networkState = NETWORK_EMPTY;
        }
        else
        {
                if (media_event.loaded_size != 0)
                {
                        *readyState = HAVE_METADATA;
                }
                else if (media_event.loaded_size == media_event.total_size)
                {
                        *readyState = HAVE_ENOUGH_DATA;
                }
        }
}

static SMJS_FUNC_PROP_GET(html_media_get_ready_state)
GF_HTML_MediaReadyState readyState = HAVE_NOTHING;
GF_HTML_NetworkState networkState = NETWORK_EMPTY;
GF_MediaObject *mo = NULL;
HTML_MEDIA_JS_START
html_media_element_populate_tracks(c, me);
mo = gf_html_media_object(n);
html_media_get_states(mo, &readyState, &networkState);
*vp = INT_TO_JSVAL( readyState );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_network_state)
GF_HTML_MediaReadyState readyState = HAVE_NOTHING;
GF_HTML_NetworkState networkState = NETWORK_EMPTY;
GF_MediaObject *mo = NULL;
HTML_MEDIA_JS_START
mo = gf_html_media_object(n);
html_media_get_states(mo, &readyState, &networkState);
*vp = INT_TO_JSVAL( networkState );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_const)
u32 v = 0;
if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL) ||
        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL))
{
        if (!SMJS_ID_IS_INT(id)) {
                return JS_TRUE;
        }
        switch (SMJS_ID_TO_INT(id)) {
        case HTML_MEDIA_PROP_NETWORK_EMPTY:
                v = NETWORK_EMPTY;
                break;
        case HTML_MEDIA_PROP_NETWORK_IDLE:
                v = NETWORK_IDLE;
                break;
        case HTML_MEDIA_PROP_NETWORK_LOADING:
                v = NETWORK_LOADING;
                break;
        case HTML_MEDIA_PROP_NETWORK_NO_SOURCE:
                v = NETWORK_NO_SOURCE;
                break;
        case HTML_MEDIA_PROP_HAVE_NOTHING:
                v = HAVE_NOTHING;
                break;
        case HTML_MEDIA_PROP_HAVE_METADATA:
                v = HAVE_METADATA;
                break;
        case HTML_MEDIA_PROP_HAVE_CURRENT_DATA:
                v = HAVE_CURRENT_DATA;
                break;
        case HTML_MEDIA_PROP_HAVE_FUTURE_DATA:
                v = HAVE_FUTURE_DATA;
                break;
        case HTML_MEDIA_PROP_HAVE_ENOUGH_DATA:
                v = HAVE_ENOUGH_DATA;
                break;
        default:
                return JS_TRUE;
        }
        *vp = INT_TO_JSVAL( v );
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_preload)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "preload", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_preload)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "preload", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_buffered)
HTML_MEDIA_JS_START
*vp = OBJECT_TO_JSVAL( me->buffered->_this );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_seeking)
HTML_MEDIA_JS_START
/* TODO: implement it for real */
*vp = BOOLEAN_TO_JSVAL( (me->seeking ? JS_TRUE : JS_FALSE) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_current_time)
u32 time_ms = 0;
Double time_s;
GF_MediaObject *mo = NULL;
HTML_MEDIA_JS_START
mo = gf_html_media_object(n);
gf_mo_get_object_time(mo, &time_ms);
time_s = time_ms / 1000.0;
*vp = JS_MAKE_DOUBLE(c, time_s );
return JS_TRUE;
}

static GFINLINE Bool ScriptAction(GF_SceneGraph *scene, u32 type, GF_Node *node, GF_JSAPIParam *param)
{
        if (scene->script_action)
        {
                return scene->script_action(scene->script_action_cbck, type, node, param);
        }
        return GF_FALSE;
}

static SMJS_FUNC_PROP_SET(html_media_set_current_time)
double d;
GF_JSAPIParam par;
HTML_MEDIA_JS_START
if (!JSVAL_IS_NUMBER(*vp)) {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
JS_ValueToNumber(c, *vp, &d);
par.time = d;
ScriptAction(n->sgprivate->scenegraph, GF_JSAPI_OP_SET_TIME, (GF_Node *)n, &par);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_duration)
Double dur;
GF_MediaObject *mo = NULL;
HTML_MEDIA_JS_START
mo = gf_html_media_object(n);
dur = gf_mo_get_duration(mo);
if (dur <= 0) {
        *vp = JS_GetNaNValue(c);
} else {
        *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, dur));
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_start_date)
HTML_MEDIA_JS_START
// SpiderMonkey 1.8.5 JSObject * JS_NewDateObject(JSContext *cx, int year, int mon, int mday, int hour, int min, int sec);
*vp = JSVAL_NULL;
/* TODO */
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_paused)
HTML_MEDIA_JS_START
*vp =( BOOLEAN_TO_JSVAL( (me->paused ? JS_TRUE : JS_FALSE) ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_default_playback_rate)
HTML_MEDIA_JS_START
*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, me->defaultPlaybackRate));
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_default_playback_rate)
jsdouble d;
HTML_MEDIA_JS_START
if (!JSVAL_IS_NUMBER(*vp)) {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
JS_ValueToNumber(c, *vp, &d);
me->defaultPlaybackRate = d;
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_playback_rate)
Double speed;
GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
GF_MediaObject *mo = gf_html_media_object(n);
speed = gf_mo_get_current_speed(mo);
*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, speed));
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_playback_rate)
jsdouble d;
Fixed speed;
GF_MediaObject *mo;
HTML_MEDIA_JS_START
mo = gf_html_media_object(n);
if (!JSVAL_IS_NUMBER(*vp)) {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
JS_ValueToNumber(c, *vp, &d);
speed = FLT2FIX(d);
gf_mo_set_speed(mo, speed);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_played)
HTML_MEDIA_JS_START
*vp =( OBJECT_TO_JSVAL( me->played->_this ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_seekable)
HTML_MEDIA_JS_START
*vp =( OBJECT_TO_JSVAL( me->seekable->_this ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_ended)
GF_MediaObject *mo;
HTML_MEDIA_JS_START
mo = gf_html_media_object(n);
*vp = BOOLEAN_TO_JSVAL( gf_mo_is_done(mo) ? JS_TRUE : JS_FALSE);
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_autoplay)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "autoplay", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
                *vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
        } else {
                *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
        }
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_autoplay)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "autoplay", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_loop)
GF_FieldInfo info;
HTML_MEDIA_JS_START
/* TODO: gf_mo_get_loop */
if (gf_node_get_attribute_by_name(n, "loop", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
                *vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
        } else {
                *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
        }
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_loop)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "loop", str);
        //TODO: use gf_mo_get_loop
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_mediagroup)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "mediagroup", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        *vp =( STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) ) );
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_mediagroup)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "mediagroup", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_controller)
HTML_MEDIA_JS_START
if (me->controller) {
        *vp = OBJECT_TO_JSVAL( me->controller->_this );
} else {
        *vp = JSVAL_NULL;
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_controller)
HTML_MEDIA_JS_START
if (JSVAL_IS_OBJECT(*vp)) {
        me->controller = (GF_HTML_MediaController *)SMJS_GET_PRIVATE(c, JSVAL_TO_OBJECT(*vp));
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_controls)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "controls", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
                *vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
        } else {
                *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
        }
        if (szAtt) gf_free(szAtt);
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_controls)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "controls", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SVG_audio_stack *html_media_get_audio_stack(GF_Node *n) {
        SVG_video_stack *video_stack = NULL;
        SVG_audio_stack *audio_stack = NULL;
        if (n->sgprivate->tag == TAG_SVG_video) {
                video_stack = (SVG_video_stack *)n->sgprivate->UserPrivate;
                if (video_stack->audio) {
                        audio_stack = (SVG_audio_stack *)video_stack->audio->sgprivate->UserPrivate;
                }
        } else if (n->sgprivate->tag == TAG_SVG_audio) {
                audio_stack = (SVG_audio_stack *)n->sgprivate->UserPrivate;
        }
        return audio_stack;
}

static SMJS_FUNC_PROP_GET(html_media_get_volume)
Fixed volume = FIX_ONE;
SVG_audio_stack *audio_stack = NULL;
HTML_MEDIA_JS_CHECK
audio_stack = html_media_get_audio_stack(n);
if (audio_stack) {
        audio_stack->input.input_ifce.GetChannelVolume(&audio_stack->input.input_ifce.callback, &volume);
}
*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, FIX2FLT(volume)));
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_volume)
jsdouble volume;
SVG_audio_stack *audio_stack = NULL;
HTML_MEDIA_JS_CHECK
audio_stack = html_media_get_audio_stack(n);
if (audio_stack) {
        JS_ValueToNumber(c, *vp, &volume);
        audio_stack->input.intensity = FLT2FIX(volume);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_muted)
SVG_audio_stack *audio_stack = NULL;
HTML_MEDIA_JS_CHECK
audio_stack = html_media_get_audio_stack(n);
if (audio_stack) {
        *vp = BOOLEAN_TO_JSVAL(audio_stack->input.is_muted ? JS_TRUE : JS_FALSE);
} else {
        *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_muted)
SVG_audio_stack *audio_stack = NULL;
HTML_MEDIA_JS_CHECK
audio_stack = html_media_get_audio_stack(n);
if (audio_stack) {
        audio_stack->input.is_muted = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_default_muted)
GF_FieldInfo info;
HTML_MEDIA_JS_START
if (gf_node_get_attribute_by_name(n, "muted", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
        char *szAtt = gf_svg_dump_attribute(n, &info);
        if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
                *vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
        } else {
                *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
        }
        if (szAtt) gf_free(szAtt);
} else {
        *vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
}
return JS_TRUE;
}

static SMJS_FUNC_PROP_SET(html_media_set_default_muted)
HTML_MEDIA_JS_START
if (JSVAL_CHECK_STRING(*vp)) {
        char *str = SMJS_CHARS(c, *vp);
        gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "muted", str);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_media_get_audio_tracks)
HTML_MEDIA_JS_START
*vp =( OBJECT_TO_JSVAL( me->audioTracks._this ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_video_tracks)
HTML_MEDIA_JS_START
*vp =( OBJECT_TO_JSVAL( me->videoTracks._this ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_media_get_text_tracks)
HTML_MEDIA_JS_START
*vp =( OBJECT_TO_JSVAL( me->textTracks._this ) );
return JS_TRUE;
}

static SMJS_FUNC_PROP_GET(html_time_ranges_get_length)
GF_HTML_MediaTimeRanges *timeranges;
if (!GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
*vp = INT_TO_JSVAL( gf_list_count(timeranges->times)/2);
return JS_TRUE;
}

static JSBool SMJS_FUNCTION(html_time_ranges_start)
{
        GF_HTML_MediaTimeRanges *timeranges;
        SMJS_OBJ
        SMJS_ARGS
        if ((argc!=1) || !GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
        if (!timeranges) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        if (JSVAL_IS_INT(argv[0])) {
                u32 i = JSVAL_TO_INT(argv[0]);
                u64 *start_value = (u64 *)gf_list_get(timeranges->times, 2*i);
                if (!start_value) {
                        return dom_throw_exception(c, GF_DOM_EXC_INDEX_SIZE_ERR);
                } else {
                        SMJS_SET_RVAL(DOUBLE_TO_JSVAL(JS_NewDouble(c, (*start_value)*1.0/timeranges->timescale)));
                }
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        return JS_TRUE;
}

static JSBool SMJS_FUNCTION(html_time_ranges_end)
{
        GF_HTML_MediaTimeRanges *timeranges;
        SMJS_OBJ
        SMJS_ARGS
        if ((argc!=1) || !GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
        if (!timeranges) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        if (JSVAL_IS_INT(argv[0])) {
                u32 i = JSVAL_TO_INT(argv[0]);
                u64 *end_value = (u64 *)gf_list_get(timeranges->times, 2*i+1);
                if (!end_value) {
                        return dom_throw_exception(c, GF_DOM_EXC_INDEX_SIZE_ERR);
                } else {
                        SMJS_SET_RVAL(DOUBLE_TO_JSVAL(JS_NewDouble(c, (*end_value)*1.0/timeranges->timescale)));
                }
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        return JS_TRUE;
}

static Bool html_is_track_list(JSContext *c, JSObject *obj) {
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackListClass, NULL)) return GF_TRUE;
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackListClass, NULL)) return GF_TRUE;
        if (GF_JS_InstanceOf(c, obj, &html_media_rt->textTrackListClass, NULL)) return GF_TRUE;
        return GF_FALSE;
}

static SMJS_FUNC_PROP_GET(html_track_list_get_length)
GF_HTML_TrackList *tracklist;
if (html_is_track_list(c, obj)) {
        tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        if (tracklist) {
                *vp = INT_TO_JSVAL( gf_list_count(tracklist->tracks) );
                return JS_TRUE;
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static JSBool SMJS_FUNCTION(html_track_list_get_track_by_id)
{
        SMJS_OBJ
        SMJS_ARGS
        if (html_is_track_list(c, obj)) {
                GF_HTML_TrackList *tracklist;
                GF_HTML_Track *track;
                char *str;
                tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
                str = SMJS_CHARS_FROM_STRING(c, JS_ValueToString(c, argv[0]) );
                track = html_media_tracklist_get_track(tracklist, str);
                SMJS_FREE(c, str);
                if (track) {
                        SMJS_SET_RVAL(OBJECT_TO_JSVAL(track->_this));
                        return JS_TRUE;
                } else {
                        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
                }
        } else {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
}

static SMJS_FUNC_PROP_GET(html_track_list_get_property)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist;
        s32 index;

        tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        index = SMJS_ID_TO_INT(id);
        if (index >= 0 && (u32)index < gf_list_count(tracklist->tracks)) {
                GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(tracklist->tracks, (u32)index);
                *vp = OBJECT_TO_JSVAL(track->_this);
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_track_list_get_selected_index)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        *vp = INT_TO_JSVAL(tracklist->selected_index);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_track_list_get_onchange)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        if (! JSVAL_IS_NULL(tracklist->onchange)) {
                *vp = tracklist->onchange;
        } else {
                *vp = JSVAL_NULL;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

JSBool gf_set_js_eventhandler(JSContext *c, jsval vp, jsval *callbackfuncval);

static SMJS_FUNC_PROP_SET(html_track_list_set_onchange)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        gf_set_js_eventhandler(c, *vp, &tracklist->onchange);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_track_list_get_onaddtrack)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        if (! JSVAL_IS_NULL(tracklist->onaddtrack)) {
                *vp = tracklist->onaddtrack;
        } else {
                *vp = JSVAL_NULL;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_SET(html_track_list_set_onaddtrack)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        gf_set_js_eventhandler(c, *vp, &tracklist->onaddtrack);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_track_list_get_onremovetrack)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        if (! JSVAL_IS_NULL(tracklist->onremovetrack) ) {
                *vp = tracklist->onremovetrack;
        } else {
                *vp = JSVAL_NULL;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_SET(html_track_list_set_onremovetrack)
if (html_is_track_list(c, obj)) {
        GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
        gf_set_js_eventhandler(c, *vp, &tracklist->onremovetrack);
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_track_get_property)
if (html_is_track_list(c, obj)) {
        GF_HTML_Track *track = (GF_HTML_Track *)SMJS_GET_PRIVATE(c, obj);
        if (!SMJS_ID_IS_INT(id)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        switch (SMJS_ID_TO_INT(id)) {
        case HTML_TRACK_PROP_ID:
                *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->id ? track->id : "") );
                return JS_TRUE;
        case HTML_TRACK_PROP_KIND:
                *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->kind ? track->kind : "") );
                return JS_TRUE;
        case HTML_TRACK_PROP_LABEL:
                *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->label ? track->label : "") );
                return JS_TRUE;
        case HTML_TRACK_PROP_LANGUAGE:
                *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->language ? track->language  : "") );
                return JS_TRUE;
        case HTML_TRACK_PROP_INBANDTYPE:
                if (GF_JS_InstanceOf(c, obj, &html_media_rt->textTrackClass, NULL)) {
                        *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->mime ? track->mime  : "") );
                        return JS_TRUE;
                }
        case HTML_TRACK_PROP_SELECTED:
                if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackClass, NULL)) {
                        *vp = BOOLEAN_TO_JSVAL(track->enabled_or_selected ? JS_TRUE : JS_FALSE);
                }
                return JS_TRUE;
        case HTML_TRACK_PROP_ENABLED:
                if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackClass, NULL)) {
                        *vp = BOOLEAN_TO_JSVAL(track->enabled_or_selected ? JS_TRUE : JS_FALSE);
                }
                return JS_TRUE;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_SET(html_track_set_property)
if (html_is_track_list(c, obj)) {
        GF_HTML_Track *track = (GF_HTML_Track *)SMJS_GET_PRIVATE(c, obj);
        if (!SMJS_ID_IS_INT(id)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        switch (SMJS_ID_TO_INT(id)) {
        case HTML_TRACK_PROP_SELECTED:
                if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackClass, NULL))
                {
                        track->enabled_or_selected = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
                }
                return JS_TRUE;
        case HTML_TRACK_PROP_ENABLED:
                if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackClass, NULL))
                {
                        track->enabled_or_selected = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
                }
                return JS_TRUE;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_GET(html_video_get_property)
if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL))
{
        GF_FieldInfo    info;
        SVG_video_stack *video;
        GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
        if (!n) return JS_TRUE;

        video = (SVG_video_stack *)n->sgprivate->UserPrivate;
        if (!SMJS_ID_IS_INT(id)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        switch (SMJS_ID_TO_INT(id)) {
        case HTML_VIDEO_PROP_WIDTH:
                if (gf_node_get_attribute_by_tag(n, TAG_SVG_ATT_width, GF_TRUE, GF_TRUE, &info)==GF_OK) {
                        SVG_Length *length;
                        length = (SVG_Length *)info.far_ptr;
                        *vp = INT_TO_JSVAL( FIX2FLT(length->value) );
                }
                return JS_TRUE;
        case HTML_VIDEO_PROP_HEIGHT:
                if (gf_node_get_attribute_by_tag(n, TAG_SVG_ATT_height, GF_TRUE, GF_TRUE, &info)==GF_OK) {
                        SVG_Length *length;
                        length = (SVG_Length *)info.far_ptr;
                        *vp = INT_TO_JSVAL( FIX2FLT(length->value) );
                }
                return JS_TRUE;
        case HTML_VIDEO_PROP_VIDEOWIDTH:
                *vp = INT_TO_JSVAL( video->txh.width );
                return JS_TRUE;
        case HTML_VIDEO_PROP_VIDEOHEIGHT:
                *vp = INT_TO_JSVAL( video->txh.height );
                return JS_TRUE;
        case HTML_VIDEO_PROP_POSTER:
                if (gf_node_get_attribute_by_name(n, "poster", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
                        char *szAtt = gf_svg_dump_attribute(n, &info);
                        *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
                        if (szAtt) gf_free(szAtt);
                }
                return JS_TRUE;
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static SMJS_FUNC_PROP_SET(html_video_set_property)
if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL))
{
        GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
        if (!SMJS_ID_IS_INT(id)) {
                return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
        }
        switch (SMJS_ID_TO_INT(id)) {
        case HTML_VIDEO_PROP_WIDTH:
                /* TODO: change value and dirty node */
                return JS_TRUE;
        case HTML_VIDEO_PROP_HEIGHT:
                /* TODO: change value and dirty node */
                return JS_TRUE;
        case HTML_VIDEO_PROP_POSTER:
        {
                char *str = SMJS_CHARS_FROM_STRING(c, JS_ValueToString(c, *vp) );
                gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "poster", str);
                SMJS_FREE(c, str);
                return JS_TRUE;
        }
        }
        return JS_TRUE;
} else {
        return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
}
}

static JSBool SMJS_FUNCTION(html_media_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(html_media_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(html_media_event_dispatch)
{
        return JS_TRUE;
}

void html_media_source_init_js_api(JSContext *js_ctx, JSObject *global, GF_HTML_MediaRuntime *html_media_rt);

void html_media_js_api_del() {
        if (html_media_rt) {
                gf_free(html_media_rt);
                html_media_rt = NULL;
        }
}

void html_media_init_js_api(GF_SceneGraph *scene) {
        /* HTML Media Element */
        if (!html_media_rt) {
                GF_SAFEALLOC(html_media_rt, GF_HTML_MediaRuntime);

                /* Setting up MediaError class (no constructor, no finalizer) */
                JS_SETUP_CLASS(html_media_rt->mediaErrorClass, "MediaError", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec mediaErrorClassProps[] = {
                                {"MEDIA_ERR_ABORTED",           MEDIA_ERROR_PROP_ABORTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
                                {"MEDIA_ERR_NETWORK",           MEDIA_ERROR_PROP_NETWORK,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
                                {"MEDIA_ERR_DECODE",            MEDIA_ERROR_PROP_DECODE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
                                {"MEDIA_ERR_SRC_NOT_SUPPORTED", MEDIA_ERROR_PROP_SRC_NOT_SUPPORTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
                                {"code",                        MEDIA_ERROR_PROP_CODE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec mediaErrorClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->mediaErrorClass, 0, 0, mediaErrorClassProps, mediaErrorClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] MediaError class initialized\n"));
                }

                /* Setting up TimeRanges class (no constructor, no finalizer) */
                JS_SETUP_CLASS(html_media_rt->timeRangesClass, "TimeRanges", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec timeRangesClassProps[] = {
                                {"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_time_ranges_get_length, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec timeRangesClassFuncs[] = {
                                SMJS_FUNCTION_SPEC("start",                 html_time_ranges_start, 1),
                                SMJS_FUNCTION_SPEC("end",                   html_time_ranges_end, 1),
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->timeRangesClass, 0, 0, timeRangesClassProps, timeRangesClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TimeRanges class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->audioTrackClass, "AudioTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
                {
                        JSPropertySpec audioTrackClassProps[] = {
                                {"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"kind",             HTML_TRACK_PROP_KIND,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"label",            HTML_TRACK_PROP_LABEL,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"language",         HTML_TRACK_PROP_LANGUAGE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"enabled",          HTML_TRACK_PROP_SELECTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec audioTrackClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->audioTrackClass, 0, 0, audioTrackClassProps, audioTrackClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] AudioTrack class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->videoTrackClass, "VideoTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
                {
                        JSPropertySpec videoTrackClassProps[] = {
                                {"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"kind",             HTML_TRACK_PROP_KIND,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"label",            HTML_TRACK_PROP_LABEL,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"language",         HTML_TRACK_PROP_LANGUAGE,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"selected",         HTML_TRACK_PROP_SELECTED,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec videoTrackClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->videoTrackClass, 0, 0, videoTrackClassProps, videoTrackClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] VideoTrack class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->textTrackClass, "TextTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
                {
                        JSPropertySpec textTrackClassProps[] = {
                                {"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"kind",             HTML_TRACK_PROP_KIND,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"label",            HTML_TRACK_PROP_LABEL,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"language",         HTML_TRACK_PROP_LANGUAGE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"inBandMetadataTrackDispatchType",         HTML_TRACK_PROP_INBANDTYPE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec textTrackClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->textTrackClass, 0, 0, textTrackClassProps, textTrackClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TextTrack class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->audioTrackListClass, "AudioTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec audioTrackListClassProps[] = {
                                {"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
                                {"onchange",             0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onchange, html_track_list_set_onchange},
                                {"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
                                {"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec audioTrackListClassFuncs[] = {
                                SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->audioTrackListClass, 0, 0, audioTrackListClassProps, audioTrackListClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] AudioTrackList class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->videoTrackListClass, "VideoTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec videoTrackListClassProps[] = {
                                {"selectedIndex",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_selected_index, 0},
                                {"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
                                {"onchange",             0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onchange, html_track_list_set_onchange},
                                {"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
                                {"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec videoTrackListClassFuncs[] = {
                                SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->videoTrackListClass, 0, 0, videoTrackListClassProps, videoTrackListClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] VideoTrackList class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->textTrackListClass, "TextTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec textTrackListClassProps[] = {
                                {"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
                                {"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
                                {"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec textTrackListClassFuncs[] = {
                                SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->textTrackListClass, 0, 0, textTrackListClassProps, textTrackListClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TextTrackList class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->mediaControllerClass, "MediaController", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec mediaControllerClassProps[] = {
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec mediaControllerClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->mediaControllerClass, 0, 0, mediaControllerClassProps, mediaControllerClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] MediaController class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->htmlMediaElementClass, "HTMLMediaElement", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
                {
                        JSPropertySpec htmlMediaElementClassProps[] = {
                                {"error",               HTML_MEDIA_PROP_ERROR,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_error, 0},
                                {"src",                 HTML_MEDIA_PROP_SRC,          JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , html_media_get_src, html_media_set_src},
                                {"currentSrc",          HTML_MEDIA_PROP_CURRENTSRC,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_src, 0},
                                {"crossOrigin",         HTML_MEDIA_PROP_CROSSORIGIN,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_cors, html_media_set_cors},
                                {"NETWORK_EMPTY",       HTML_MEDIA_PROP_NETWORK_EMPTY,    JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"NETWORK_IDLE",        HTML_MEDIA_PROP_NETWORK_IDLE,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"NETWORK_LOADING",     HTML_MEDIA_PROP_NETWORK_LOADING,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"NETWORK_NO_SOURCE",   HTML_MEDIA_PROP_NETWORK_NO_SOURCE,JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"networkState",        HTML_MEDIA_PROP_NETWORKSTATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_network_state, 0},
                                {"preload",             HTML_MEDIA_PROP_PRELOAD,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_preload, html_media_set_preload},
                                {"buffered",            HTML_MEDIA_PROP_BUFFERED,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_buffered, 0},
                                {"HAVE_NOTHING",        HTML_MEDIA_PROP_HAVE_NOTHING,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"HAVE_METADATA",       HTML_MEDIA_PROP_HAVE_METADATA,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"HAVE_CURRENT_DATA",   HTML_MEDIA_PROP_HAVE_CURRENT_DATA, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"HAVE_FUTURE_DATA",    HTML_MEDIA_PROP_HAVE_FUTURE_DATA,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"HAVE_ENOUGH_DATA",    HTML_MEDIA_PROP_HAVE_ENOUGH_DATA,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
                                {"readyState",          HTML_MEDIA_PROP_READYSTATE,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_ready_state, 0},
                                {"seeking",             HTML_MEDIA_PROP_SEEKING,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_seeking, 0},
                                {"currentTime",         HTML_MEDIA_PROP_CURRENTTIME,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_current_time, html_media_set_current_time},
                                {"duration",            HTML_MEDIA_PROP_DURATION,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_duration, 0},
                                {"startDate",           HTML_MEDIA_PROP_STARTDATE,    JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_start_date, 0},
                                {"paused",              HTML_MEDIA_PROP_PAUSED,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_paused, 0},
                                {"defaultPlaybackRate", HTML_MEDIA_PROP_DEFAULTPLAYBACKRATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_default_playback_rate, html_media_set_default_playback_rate},
                                {"playbackRate",        HTML_MEDIA_PROP_PLAYBACKRATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_playback_rate, html_media_set_playback_rate},
                                {"played",              HTML_MEDIA_PROP_PLAYED,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_played, 0},
                                {"seekable",            HTML_MEDIA_PROP_SEEKABLE,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_seekable, 0},
                                {"ended",               HTML_MEDIA_PROP_ENDED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_ended, 0},
                                {"autoplay",            HTML_MEDIA_PROP_AUTOPLAY,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_autoplay, html_media_set_autoplay},
                                {"loop",                HTML_MEDIA_PROP_LOOP,         JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_loop, html_media_set_loop},
                                {"mediaGroup",          HTML_MEDIA_PROP_MEDIAGROUP,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_mediagroup, html_media_set_mediagroup},
                                {"controller",          HTML_MEDIA_PROP_CONTROLLER,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_controller, html_media_set_controller},
                                {"controls",            HTML_MEDIA_PROP_CONTROLS,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_controls, html_media_set_controls},
                                {"volume",              HTML_MEDIA_PROP_VOLUME,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_volume, html_media_set_volume},
                                {"muted",               HTML_MEDIA_PROP_MUTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_muted, html_media_set_muted},
                                {"defaultMuted",        HTML_MEDIA_PROP_DEFAULTMUTED, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_default_muted, html_media_set_default_muted},
                                {"audioTracks",         HTML_MEDIA_PROP_AUDIOTRACKS,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_audio_tracks, 0},
                                {"videoTracks",         HTML_MEDIA_PROP_VIDEOTRACKS,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_video_tracks, 0},
                                {"textTracks",          HTML_MEDIA_PROP_TEXTTRACKS,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_text_tracks, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSBool SMJS_FUNCTION(svg_udom_smil_pause);
                        JSBool SMJS_FUNCTION(svg_udom_smil_begin);
                        JSBool SMJS_FUNCTION(svg_udom_smil_end);
                        JSFunctionSpec htmlMediaElementClassFuncs[] = {
                                SMJS_FUNCTION_SPEC("load",                  html_media_load, 0),
                                SMJS_FUNCTION_SPEC("canPlayType",           html_media_canPlayType, 1),
                                SMJS_FUNCTION_SPEC("fastSeek",              html_media_fastSeek, 1),
                                SMJS_FUNCTION_SPEC("play",                  svg_udom_smil_begin, 0),
                                SMJS_FUNCTION_SPEC("pause",                 svg_udom_smil_pause, 0),
                                SMJS_FUNCTION_SPEC("addTextTrack",          html_media_addTextTrack, 1),
                                SMJS_FUNCTION_SPEC("addEventListener",          html_media_event_add_listener, 3),
                                SMJS_FUNCTION_SPEC("removeEventListener",       html_media_event_remove_listener, 3),
                                SMJS_FUNCTION_SPEC("dispatchEvent",                     html_media_event_dispatch, 1),
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        JSObject *elt_proto = dom_js_get_element_proto(scene->svg_js->js_ctx);
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, elt_proto, &html_media_rt->htmlMediaElementClass, 0, 0, htmlMediaElementClassProps, htmlMediaElementClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLMediaElement class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->htmlVideoElementClass, "HTMLVideoElement", JSCLASS_HAS_PRIVATE, html_video_get_property, html_video_set_property, dom_element_finalize);
                {
                        JSPropertySpec htmlVideoElementClassProps[] = {
                                {"width",       HTML_VIDEO_PROP_WIDTH,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
                                {"height",      HTML_VIDEO_PROP_HEIGHT,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
                                {"videoWidth",  HTML_VIDEO_PROP_VIDEOWIDTH,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"videoHeight", HTML_VIDEO_PROP_VIDEOHEIGHT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
                                {"poster",      HTML_VIDEO_PROP_POSTER,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec htmlVideoElementClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, html_media_rt->htmlMediaElementClass._proto, &html_media_rt->htmlVideoElementClass, 0, 0, htmlVideoElementClassProps, htmlVideoElementClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLVideoElement class initialized\n"));
                }

                JS_SETUP_CLASS(html_media_rt->htmlAudioElementClass, "HTMLAudioElement", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, dom_element_finalize);
                {
                        JSPropertySpec htmlAudioElementClassProps[] = {
                                {0, 0, 0, 0, 0}
                        };
                        JSFunctionSpec htmlAudioElementClassFuncs[] = {
                                SMJS_FUNCTION_SPEC(0, 0, 0)
                        };
                        GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, html_media_rt->htmlMediaElementClass._proto, &html_media_rt->htmlAudioElementClass, 0, 0, htmlAudioElementClassProps, htmlAudioElementClassFuncs, 0, 0);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLAudioElement class initialized\n"));
                }
                html_media_source_init_js_api(scene->svg_js->js_ctx, scene->svg_js->global, html_media_rt);
        }
}
#endif  /*GPAC_HAS_SPIDERMONKEY*/

#endif  /*GPAC_DISABLE_SVG*/

/* [<][>][^][v][top][bottom][index][help] */