1 /*
2  *          GPAC - Multimedia Framework C SDK
3  *
4  *          Authors: Cyril Concolato
5  *          Copyright (c) Telecom ParisTech 2007-2012
6  *          All rights reserved
7  *
8  *  This file is part of GPAC / Scene Graph sub-project
9  *
10  *  GPAC is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU Lesser General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  GPAC is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 
26 
27 #include <gpac/internal/scenegraph_dev.h>
28 
29 /*base SVG type*/
30 #include <gpac/nodes_svg.h>
31 /*dom events*/
32 #include <gpac/events.h>
33 /*dom text event*/
34 #include <gpac/utf.h>
35 
36 #include <gpac/download.h>
37 #include <gpac/network.h>
38 #include <gpac/xml.h>
39 
40 #ifndef GPAC_DISABLE_SVG
41 
42 
43 #ifdef GPAC_CONFIG_ANDROID
44 #ifndef XP_UNIX
45 #define XP_UNIX
46 #endif /* XP_UNIX */
47 #endif
48 
49 #ifdef GPAC_HAS_SPIDERMONKEY
50 
51 #include <gpac/internal/smjs_api.h>
52 #include <gpac/html5_media.h>
53 
54 /***********************************************************
55  *
56  * DOM generic functions being used
57  *
58  ***********************************************************/
59 #ifdef USE_FFDEV_15
60 void dom_element_finalize(JSFreeOp *fop, JSObject *obj);
61 void dom_document_finalize(JSFreeOp *fop, JSObject *obj);
62 #else
63 void dom_element_finalize(JSContext *c, JSObject *obj);
64 void dom_document_finalize(JSContext *c, JSObject *obj);
65 #endif
66 
67 void gf_svg_set_attributeNS(GF_Node *n, u32 ns_code, char *name, char *val);
68 
69 #define JSVAL_CHECK_STRING(_v) (JSVAL_IS_STRING(_v) || JSVAL_IS_NULL(_v))
70 
71 /* including terminal dev for MediaObject */
72 
73 /* including Compositor dev for TextureHandler and AudioInput */
74 #include <gpac/internal/compositor_dev.h>
75 
76 static GF_HTML_MediaRuntime *html_media_rt = NULL;
77 
78 /* Enumeration of the property codes for use in JS Bindings for the the HTML5 MediaError interface */
79 typedef enum
80 {
81 	MEDIA_ERROR_PROP_ABORTED            = -1,
82 	MEDIA_ERROR_PROP_NETWORK            = -2,
83 	MEDIA_ERROR_PROP_DECODE             = -3,
84 	MEDIA_ERROR_PROP_SRC_NOT_SUPPORTED  = -4,
85 	MEDIA_ERROR_PROP_CODE               = -5
86 } GF_HTML_MediaErrorPropEnum;
87 
88 /* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLMediaElement interface */
89 typedef enum
90 {
91 	HTML_MEDIA_PROP_SRC                         = -1,
92 	HTML_MEDIA_PROP_CURRENTSRC                  = -2,
93 	HTML_MEDIA_PROP_CROSSORIGIN                 = -3,
94 	HTML_MEDIA_PROP_NETWORKSTATE                = -4,
95 	HTML_MEDIA_PROP_PRELOAD                     = -5,
96 	HTML_MEDIA_PROP_BUFFERED                    = -6,
97 	HTML_MEDIA_PROP_READYSTATE                  = -7,
98 	HTML_MEDIA_PROP_SEEKING                     = -8,
99 	HTML_MEDIA_PROP_CURRENTTIME                 = -9,
100 	HTML_MEDIA_PROP_DURATION                    = -10,
101 	HTML_MEDIA_PROP_STARTDATE                   = -11,
102 	HTML_MEDIA_PROP_PAUSED                      = -12,
103 	HTML_MEDIA_PROP_DEFAULTPLAYBACKRATE         = -13,
104 	HTML_MEDIA_PROP_PLAYBACKRATE                = -14,
105 	HTML_MEDIA_PROP_PLAYED                      = -15,
106 	HTML_MEDIA_PROP_SEEKABLE                    = -16,
107 	HTML_MEDIA_PROP_ENDED                       = -17,
108 	HTML_MEDIA_PROP_AUTOPLAY                    = -18,
109 	HTML_MEDIA_PROP_LOOP                        = -19,
110 	HTML_MEDIA_PROP_MEDIAGROUP                  = -20,
111 	HTML_MEDIA_PROP_CONTROLLER                  = -21,
112 	HTML_MEDIA_PROP_CONTROLS                    = -22,
113 	HTML_MEDIA_PROP_VOLUME                      = -23,
114 	HTML_MEDIA_PROP_MUTED                       = -24,
115 	HTML_MEDIA_PROP_DEFAULTMUTED                = -25,
116 	HTML_MEDIA_PROP_AUDIOTRACKS                 = -26,
117 	HTML_MEDIA_PROP_VIDEOTRACKS                 = -27,
118 	HTML_MEDIA_PROP_TEXTTRACKS                  = -28,
119 	HTML_MEDIA_PROP_NETWORK_EMPTY               = -29,
120 	HTML_MEDIA_PROP_NETWORK_IDLE                = -30,
121 	HTML_MEDIA_PROP_NETWORK_LOADING             = -31,
122 	HTML_MEDIA_PROP_NETWORK_NO_SOURCE           = -32,
123 	HTML_MEDIA_PROP_HAVE_NOTHING                = -33,
124 	HTML_MEDIA_PROP_HAVE_METADATA               = -34,
125 	HTML_MEDIA_PROP_HAVE_CURRENT_DATA           = -35,
126 	HTML_MEDIA_PROP_HAVE_FUTURE_DATA            = -36,
127 	HTML_MEDIA_PROP_HAVE_ENOUGH_DATA            = -37,
128 	HTML_MEDIA_PROP_ERROR                       = -38
129 } GF_HTML_MediaEltPropEnum;
130 
131 /* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLVideoElement interface */
132 typedef enum {
133 	HTML_VIDEO_PROP_WIDTH                       = -1,
134 	HTML_VIDEO_PROP_HEIGHT                      = -2,
135 	HTML_VIDEO_PROP_VIDEOWIDTH                  = -3,
136 	HTML_VIDEO_PROP_VIDEOHEIGHT                 = -4,
137 	HTML_VIDEO_PROP_POSTER                      = -5
138 } GF_HTML_VideoEltPropEnum;
139 
140 /* Enumeration of the property codes for use in JS Bindings for the the HTML5 HTMLTrackElement interface */
141 typedef enum {
142 	HTML_TRACK_PROP_ID                          = -1,
143 	HTML_TRACK_PROP_KIND                        = -2,
144 	HTML_TRACK_PROP_LABEL                       = -3,
145 	HTML_TRACK_PROP_LANGUAGE                    = -4,
146 	HTML_TRACK_PROP_SELECTED                    = -5,
147 	HTML_TRACK_PROP_ENABLED                     = -6,
148 	HTML_TRACK_PROP_INBANDTYPE                  = -7
149 } GF_HTML_TrackPropEnum;
150 
151 #define HTML_MEDIA_JS_CHECK     GF_HTML_MediaElement *me; \
152                                 GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj); \
153                                 if (!n || (n->sgprivate->tag != TAG_SVG_video && n->sgprivate->tag != TAG_SVG_audio)) \
154                                 { \
155 									return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
156                                 } \
157                                 me = (GF_HTML_MediaElement *)html_media_element_get_from_node(c, n); \
158                                 if (!me) { \
159 									return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR); \
160                                 }
161 
162 #define HTML_MEDIA_JS_START     HTML_MEDIA_JS_CHECK
163 
164 /* Structure copied over from svg_media.c
165    TODO : fix this - remove the copy */
166 typedef struct
167 {
168 	GF_TextureHandler txh;
169 	struct _drawable *graph;
170 	MFURL txurl;
171 	Bool first_frame_fetched;
172 	GF_Node *audio;
173 	Bool audio_dirty;
174 	Bool stop_requested;
175 } SVG_video_stack;
176 
177 /* Structure copied over from svg_media.c
178    TODO : fix this - remove the copy */
179 typedef struct
180 {
181 	GF_AudioInput input;
182 	Bool is_active, is_error;
183 	MFURL aurl;
184 } SVG_audio_stack;
185 
186 /* Gets the GF_MediaObject associated with this node
187  *
188  *  \param n the node either an SVG/HTML5 video or audio element
189  *  \return the GF_MediaObject attached to this node or null if none
190  */
gf_html_media_object(GF_Node * n)191 static GF_MediaObject *gf_html_media_object(GF_Node *n)
192 {
193 	GF_MediaObject *mo = NULL;
194 	if (n->sgprivate->tag == TAG_SVG_video)
195 	{
196 		SVG_video_stack *video_stack;
197 		video_stack = (SVG_video_stack *)n->sgprivate->UserPrivate;
198 		mo = video_stack->txh.stream;
199 	}
200 	else if (n->sgprivate->tag == TAG_SVG_audio)
201 	{
202 		SVG_audio_stack *audio_stack;
203 		audio_stack = (SVG_audio_stack *)n->sgprivate->UserPrivate;
204 		mo = audio_stack->input.stream;
205 	}
206 	return mo;
207 }
208 
gf_html_track_init_js(GF_HTML_Track * track,JSContext * c,GF_HTML_TrackList * tracklist)209 static void gf_html_track_init_js(GF_HTML_Track *track, JSContext *c, GF_HTML_TrackList *tracklist)
210 {
211 	JSObject *track_obj;
212 	track->c     = c;
213 	if (track->type == HTML_MEDIA_TRACK_TYPE_AUDIO)
214 	{
215 		track_obj = JS_NewObject(c, &html_media_rt->audioTrackClass._class, NULL, tracklist->_this);
216 	}
217 	else if (track->type == HTML_MEDIA_TRACK_TYPE_VIDEO)
218 	{
219 		track_obj = JS_NewObject(c, &html_media_rt->videoTrackClass._class, NULL, tracklist->_this);
220 	}
221 	else if (track->type == HTML_MEDIA_TRACK_TYPE_TEXT)
222 	{
223 		track_obj = JS_NewObject(c, &html_media_rt->textTrackClass._class, NULL, tracklist->_this);
224 	}
225 	else
226 	{
227 		return;
228 	}
229 	tracklist->onchange = JSVAL_NULL;
230 	tracklist->onaddtrack = JSVAL_NULL;
231 	tracklist->onremovetrack = JSVAL_NULL;
232 	track->_this = track_obj;
233 	SMJS_SET_PRIVATE(c, track->_this, track);
234 }
235 
236 /* Not yet used, removed for GCC warning
237 static void gf_html_media_controller_init_js(GF_HTML_MediaController *mc, JSContext *c)
238 {
239     mc->c            = c;
240     mc->_this        = JS_NewObject(c, &html_media_rt->mediaControllerClass._class, NULL, NULL);
241     SMJS_SET_PRIVATE(c, mc->_this, mc);
242     mc->buffered->c              = c;
243     mc->buffered->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
244     SMJS_SET_PRIVATE(c, mc->buffered->_this, mc->buffered);
245     mc->played->c                = c;
246     mc->played->_this            = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
247     SMJS_SET_PRIVATE(c, mc->played->_this, mc->played);
248     mc->seekable->c              = c;
249     mc->seekable->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, mc->_this);
250     SMJS_SET_PRIVATE(c, mc->seekable->_this, mc->seekable);
251 }
252 */
253 
gf_html_media_element_init_js(GF_HTML_MediaElement * me,JSContext * c,JSObject * node_obj)254 static void gf_html_media_element_init_js(GF_HTML_MediaElement *me, JSContext *c, JSObject *node_obj)
255 {
256 	me->c                       = c;
257 	me->_this                   = JS_NewObject(c, &html_media_rt->htmlMediaElementClass._class, NULL, node_obj);
258 	SMJS_SET_PRIVATE(c, me->_this, me);
259 
260 	me->error.c                 = c;
261 	me->error._this             = JS_NewObject(c, &html_media_rt->mediaErrorClass._class, NULL, me->_this);
262 	SMJS_SET_PRIVATE(c, me->error._this, &me->error);
263 
264 	me->audioTracks.c           = c;
265 	me->audioTracks._this       = JS_NewObject(c, &html_media_rt->audioTrackListClass._class, NULL, me->_this);
266 	SMJS_SET_PRIVATE(c, me->audioTracks._this, &me->audioTracks);
267 
268 	me->videoTracks.c           = c;
269 	me->videoTracks._this       = JS_NewObject(c, &html_media_rt->videoTrackListClass._class, NULL, me->_this);
270 	SMJS_SET_PRIVATE(c, me->videoTracks._this, &me->videoTracks);
271 
272 	me->textTracks.c            = c;
273 	me->textTracks._this        = JS_NewObject(c, &html_media_rt->textTrackListClass._class, NULL, me->_this);
274 	SMJS_SET_PRIVATE(c, me->textTracks._this, &me->textTracks);
275 
276 	me->buffered->c              = c;
277 	me->buffered->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
278 	SMJS_SET_PRIVATE(c, me->buffered->_this, me->buffered);
279 
280 	me->played->c                = c;
281 	me->played->_this            = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
282 	SMJS_SET_PRIVATE(c, me->played->_this, me->played);
283 
284 	me->seekable->c              = c;
285 	me->seekable->_this          = JS_NewObject(c, &html_media_rt->timeRangesClass._class, NULL, me->_this);
286 	SMJS_SET_PRIVATE(c, me->seekable->_this, me->seekable);
287 }
288 
289 /* Function to browse the tracks in the MediaObject associated with the Media Element and to create appropriate HTML Track objects
290  *
291  * \param c The JavaScript Context to create the new JS object
292  * \param me the HTMLMediaElement to update
293  */
html_media_element_populate_tracks(JSContext * c,GF_HTML_MediaElement * me)294 static void html_media_element_populate_tracks(JSContext *c, GF_HTML_MediaElement *me)
295 {
296 	GF_HTML_Track *track;
297 	GF_MediaObject *mo;
298 	u32 source_id=0;
299 	GF_Scene *scene;
300 	u32 i, count;
301 
302 	if (!me || !me->node) {
303 		return;
304 	}
305 	mo = gf_html_media_object(me->node);
306 	if (!mo || !mo->odm || !mo->odm->parentscene) {
307 		return;
308 	}
309 	source_id = mo->odm->source_id;
310 	scene = mo->odm->parentscene;
311 
312 	count = gf_list_count(scene->resources);
313 	for (i=0; i<count; i++) {
314 		char id[256];
315 		char *lang = "";
316 		GF_ObjectManager *odm = (GF_ObjectManager *)gf_list_get(scene->resources, i);
317 		if (odm->source_id != source_id) continue;
318 		if (!odm->mo) continue;
319 
320 		sprintf(id, "track%d", odm->ID);
321 #ifdef FILTER_FIXME
322 #error "set track ID and lang"
323 #endif
324 		switch(odm->mo->type) {
325 		case GF_MEDIA_OBJECT_VIDEO:
326 			if (!html_media_tracklist_has_track(&me->videoTracks, id)) {
327 				track = html_media_add_new_track_to_list(&me->videoTracks, HTML_MEDIA_TRACK_TYPE_VIDEO,
328 					        "video/mp4", GF_TRUE, id, "myKind", "myLabel", lang);
329 				gf_html_track_init_js(track, c, &me->videoTracks);
330 			}
331 			break;
332 		case GF_MEDIA_OBJECT_AUDIO:
333 			if (!html_media_tracklist_has_track(&me->audioTracks, id)) {
334 				track = html_media_add_new_track_to_list(&me->audioTracks, HTML_MEDIA_TRACK_TYPE_AUDIO,
335 					        "audio/mp4", GF_TRUE, id, "myKind", "myLabel", lang);
336 				gf_html_track_init_js(track, c, &me->audioTracks);
337 			}
338 			break;
339 		case GF_MEDIA_OBJECT_TEXT:
340 			if (!html_media_tracklist_has_track(&me->textTracks, id)) {
341 				track = html_media_add_new_track_to_list(&me->textTracks, HTML_MEDIA_TRACK_TYPE_TEXT,
342 					        "text/plain", GF_TRUE, id, "myKind", "myLabel", lang);
343 				gf_html_track_init_js(track, c, &me->textTracks);
344 			}
345 			break;
346 		}
347 	}
348 }
349 
350 /* Used to retrieve the structure implementing the GF_HTML_MediaElement interface associated with this node
351  * Usually this is done with the private stack of the node (see gf_node_get_private), but in this case,
352  * the stack already contains the rendering stack SVG_video_stack.
353  * So, we store the structure implementing the GF_HTML_MediaElement interface in the JavaScript context of this node,
354  * as a non enumeratable property named 'gpac_me_impl'
355  *
356  *  \param c the global JavaScript context
357  *  \param n the audio or video node
358  *  \return the GF_HTML_MediaElement associated with this node in the given context
359  */
html_media_element_get_from_node(JSContext * c,GF_Node * n)360 static GF_HTML_MediaElement *html_media_element_get_from_node(JSContext *c, GF_Node *n)
361 {
362 	jsval vp;
363 	JSObject *me_obj;
364 	JSObject *node_obj;
365 	GF_HTML_MediaElement *me = NULL;
366 
367 	if ((n->sgprivate->tag == TAG_SVG_video || n->sgprivate->tag == TAG_SVG_audio) && n->sgprivate->interact && n->sgprivate->interact->js_binding) {
368 		node_obj = (JSObject *)n->sgprivate->interact->js_binding->node;
369 		if (node_obj) {
370 			JS_GetProperty(c, node_obj, "gpac_me_impl", &vp);
371 			me_obj = JSVAL_TO_OBJECT(vp);
372 			me = (GF_HTML_MediaElement *)SMJS_GET_PRIVATE(c, me_obj);
373 		}
374 	}
375 	return me;
376 }
377 
SMJS_FUNCTION(html_media_load)378 static JSBool SMJS_FUNCTION(html_media_load)
379 {
380 	SMJS_OBJ
381 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
382 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
383 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
384 	{
385 		MFURL mfurl;
386 		GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
387 		GF_HTML_MediaElement *me = html_media_element_get_from_node(c, n);
388 		mfurl.count = 1;
389 		mfurl.vals = (SFURL *)gf_malloc(sizeof(SFURL));
390 		mfurl.vals[0].url = me->currentSrc;
391 		mfurl.vals[0].OD_ID = GF_MEDIA_EXTERNAL_ID;
392 		gf_mo_register(n, &mfurl, GF_FALSE, GF_FALSE);
393 		gf_free(mfurl.vals);
394 		return JS_TRUE;
395 	} else {
396 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
397 	}
398 }
399 
SMJS_FUNCTION(html_media_canPlayType)400 static JSBool SMJS_FUNCTION(html_media_canPlayType)
401 {
402 	SMJS_OBJ
403 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
404 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
405 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
406 	{
407 		return JS_TRUE;
408 	} else {
409 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
410 	}
411 }
412 
SMJS_FUNCTION(html_media_fastSeek)413 static JSBool SMJS_FUNCTION(html_media_fastSeek)
414 {
415 	SMJS_OBJ
416 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
417 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
418 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
419 	{
420 		return JS_TRUE;
421 	} else {
422 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
423 	}
424 }
425 
SMJS_FUNCTION(html_media_addTextTrack)426 static JSBool SMJS_FUNCTION(html_media_addTextTrack)
427 {
428 	SMJS_OBJ
429 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ||
430 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
431 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL))
432 	{
433 		return JS_TRUE;
434 	} else {
435 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
436 	}
437 }
438 
html_get_element_class(GF_Node * n)439 void *html_get_element_class(GF_Node *n)
440 {
441 	if (!n || !html_media_rt)
442 	{
443 		return NULL;
444 	} else if (n->sgprivate->tag == TAG_SVG_video) {
445 		return &html_media_rt->htmlVideoElementClass;
446 	} else if (n->sgprivate->tag == TAG_SVG_audio) {
447 		return &html_media_rt->htmlAudioElementClass;
448 	} else {
449 		return NULL;
450 	}
451 }
452 
453 /* Creates the GF_HTML_MediaElement structure for this node
454  * Store it in the JavaScript context of this node, as a non enumeratable property named 'gpac_me_impl'
455  * see \ref html_media_element_get_from_node for retrieving it
456  *
457  *  \param c the global JavaScript context
458  *  \param n the audio or video node
459  *  \param node_obj the JavaScript object representing the audio or video node
460  */
html_media_element_js_init(JSContext * c,JSObject * node_obj,GF_Node * n)461 void html_media_element_js_init(JSContext *c, JSObject *node_obj, GF_Node *n)
462 {
463 	if (n->sgprivate->tag == TAG_SVG_video || n->sgprivate->tag == TAG_SVG_audio) {
464 		GF_HTML_MediaElement *me;
465 		me = gf_html_media_element_new(n, NULL);
466 		gf_html_media_element_init_js(me, c, node_obj);
467 		/* To be able to retrieve the Media Element interface from the Node element */
468 		JS_DefineProperty(c, node_obj, "gpac_me_impl", OBJECT_TO_JSVAL(me->_this), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT);
469 	}
470 }
471 
472 /* Deletes the GF_HTML_MediaElement structure for this node
473  *
474  *  \param c the global JavaScript context
475  *  \param n the audio or video node
476  */
html_media_element_js_finalize(JSContext * c,GF_Node * n)477 void html_media_element_js_finalize(JSContext *c, GF_Node *n)
478 {
479 	GF_HTML_MediaElement *me = html_media_element_get_from_node(c, n);
480 	/* TODO: finalize child objects */
481 	gf_html_media_element_del(me);
482 }
483 
484 /* Gets The EventTarget and SceneGraph from the GF_HTML_MediaElement structure for a audio/video JS node
485  *
486  *  \param c the global JavaScript context
487  *  \param obj the JS object representing the HTMLAudioElement or HTMLVideoElement
488  *  \param target the GF_DOMEventTarget object
489  *  \param sg the scene graph
490  */
gf_html_media_get_event_target(JSContext * c,JSObject * obj,GF_DOMEventTarget ** target,GF_SceneGraph ** sg)491 void gf_html_media_get_event_target(JSContext *c, JSObject *obj, GF_DOMEventTarget **target, GF_SceneGraph **sg) {
492 	if (!sg || !target || !html_media_rt) return;
493 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
494 	        GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL) ) {
495 		GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
496 		*target = gf_dom_event_get_target_from_node(n);
497 		*sg = n->sgprivate->scenegraph;
498 	} else {
499 		*target = NULL;
500 		*sg = NULL;
501 	}
502 }
503 
504 static SMJS_FUNC_PROP_GET(html_media_error_get_code)
505 if (GF_JS_InstanceOf(c, obj, &html_media_rt->mediaErrorClass, NULL))
506 {
507 	GF_HTML_MediaError *error = (GF_HTML_MediaError*)SMJS_GET_PRIVATE(c, obj);
508 	if (!SMJS_ID_IS_INT(id)) {
509 		return JS_TRUE;
510 	}
511 	switch (SMJS_ID_TO_INT(id))
512 	{
513 	case MEDIA_ERROR_PROP_ABORTED:
514 		*vp = INT_TO_JSVAL(MEDIA_ERR_ABORTED);
515 		return JS_TRUE;
516 	case MEDIA_ERROR_PROP_NETWORK:
517 		*vp = INT_TO_JSVAL(MEDIA_ERR_NETWORK);
518 		return JS_TRUE;
519 	case MEDIA_ERROR_PROP_DECODE:
520 		*vp = INT_TO_JSVAL(MEDIA_ERR_DECODE);
521 		return JS_TRUE;
522 	case MEDIA_ERROR_PROP_SRC_NOT_SUPPORTED:
523 		*vp = INT_TO_JSVAL(MEDIA_ERR_SRC_NOT_SUPPORTED);
524 		return JS_TRUE;
525 	case MEDIA_ERROR_PROP_CODE:
526 		*vp = INT_TO_JSVAL(error->code);
527 		return JS_TRUE;
528 	}
529 	return JS_TRUE;
530 } else {
531 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
532 }
533 }
534 
535 static SMJS_FUNC_PROP_GET(html_media_get_error)
536 HTML_MEDIA_JS_START
537 *vp = OBJECT_TO_JSVAL(me->error._this);
538 return JS_TRUE;
539 }
540 
541 static SMJS_FUNC_PROP_GET(html_media_get_src)
542 GF_FieldInfo info;
543 HTML_MEDIA_JS_START
544 if (gf_node_get_attribute_by_name(n, "href", GF_XMLNS_XLINK, GF_TRUE, GF_TRUE, &info)==GF_OK) {
545 	char *szAtt = gf_svg_dump_attribute(n, &info);
546 	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
547 	if (szAtt) gf_free(szAtt);
548 }
549 return JS_TRUE;
550 }
551 
552 static SMJS_FUNC_PROP_SET(html_media_set_src)
553 HTML_MEDIA_JS_START
554 if (JSVAL_CHECK_STRING(*vp)) {
555 	char *str = SMJS_CHARS(c, *vp);
556 	gf_svg_set_attributeNS(n, GF_XMLNS_XLINK, "href", str);
557 	return JS_TRUE;
558 } else {
559 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
560 }
561 }
562 
563 static SMJS_FUNC_PROP_GET(html_media_get_cors)
564 GF_FieldInfo info;
565 HTML_MEDIA_JS_START
566 if (gf_node_get_attribute_by_name(n, "crossorigin", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
567 	char *szAtt = gf_svg_dump_attribute(n, &info);
568 	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
569 	if (szAtt) gf_free(szAtt);
570 }
571 return JS_TRUE;
572 }
573 
574 static SMJS_FUNC_PROP_SET(html_media_set_cors)
575 HTML_MEDIA_JS_START
576 if (JSVAL_CHECK_STRING(*vp)) {
577 	char *str = SMJS_CHARS(c, *vp);
578 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "crossorigin", str);
579 	return JS_TRUE;
580 } else {
581 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
582 }
583 }
584 
585 void gf_odm_collect_buffer_info(void *net, GF_ObjectManager *odm, GF_DOMMediaEvent *media_event, u32 *min_time, u32 *min_buffer);
586 
587 static void html_media_get_states(GF_MediaObject *mo, GF_HTML_MediaReadyState *readyState, GF_HTML_NetworkState *networkState)
588 {
589 	GF_DOMMediaEvent media_event;
590 #ifdef FILTER_FIXME
591 	u32 min_time=0;
592 #endif
593 	u32 min_buffer=0;
594 	if (!mo) return;
595 
596 	if (!mo->odm) {
597 		*readyState = HAVE_NOTHING;
598 		*networkState = NETWORK_EMPTY;
599 		return;
600 	}
601 
602 	memset(&media_event, 0, sizeof(GF_DOMMediaEvent));
603 #ifdef FILTER_FIXME
604 	gf_odm_collect_buffer_info(mo->odm->net_service, mo->odm, &media_event, &min_time, &min_buffer);
605 #endif
606 	//GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] min_time: %d, min_buffer: %d loaded: "LLD"/"LLD"\n",
607 	//                                                    min_time, min_buffer, media_event.loaded_size, media_event.total_size));
608 	if (min_buffer == 0)
609 	{
610 		*readyState = HAVE_NOTHING;
611 		*networkState = NETWORK_EMPTY;
612 	}
613 	else
614 	{
615 		if (media_event.loaded_size != 0)
616 		{
617 			*readyState = HAVE_METADATA;
618 		}
619 		else if (media_event.loaded_size == media_event.total_size)
620 		{
621 			*readyState = HAVE_ENOUGH_DATA;
622 		}
623 	}
624 }
625 
626 static SMJS_FUNC_PROP_GET(html_media_get_ready_state)
627 GF_HTML_MediaReadyState readyState = HAVE_NOTHING;
628 GF_HTML_NetworkState networkState = NETWORK_EMPTY;
629 GF_MediaObject *mo = NULL;
630 HTML_MEDIA_JS_START
631 html_media_element_populate_tracks(c, me);
632 mo = gf_html_media_object(n);
633 html_media_get_states(mo, &readyState, &networkState);
634 *vp = INT_TO_JSVAL( readyState );
635 return JS_TRUE;
636 }
637 
638 static SMJS_FUNC_PROP_GET(html_media_get_network_state)
639 GF_HTML_MediaReadyState readyState = HAVE_NOTHING;
640 GF_HTML_NetworkState networkState = NETWORK_EMPTY;
641 GF_MediaObject *mo = NULL;
642 HTML_MEDIA_JS_START
643 mo = gf_html_media_object(n);
644 html_media_get_states(mo, &readyState, &networkState);
645 *vp = INT_TO_JSVAL( networkState );
646 return JS_TRUE;
647 }
648 
649 static SMJS_FUNC_PROP_GET(html_media_get_const)
650 u32 v = 0;
651 if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlMediaElementClass, NULL) ||
652         GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL) ||
653         GF_JS_InstanceOf(c, obj, &html_media_rt->htmlAudioElementClass, NULL))
654 {
655 	if (!SMJS_ID_IS_INT(id)) {
656 		return JS_TRUE;
657 	}
658 	switch (SMJS_ID_TO_INT(id)) {
659 	case HTML_MEDIA_PROP_NETWORK_EMPTY:
660 		v = NETWORK_EMPTY;
661 		break;
662 	case HTML_MEDIA_PROP_NETWORK_IDLE:
663 		v = NETWORK_IDLE;
664 		break;
665 	case HTML_MEDIA_PROP_NETWORK_LOADING:
666 		v = NETWORK_LOADING;
667 		break;
668 	case HTML_MEDIA_PROP_NETWORK_NO_SOURCE:
669 		v = NETWORK_NO_SOURCE;
670 		break;
671 	case HTML_MEDIA_PROP_HAVE_NOTHING:
672 		v = HAVE_NOTHING;
673 		break;
674 	case HTML_MEDIA_PROP_HAVE_METADATA:
675 		v = HAVE_METADATA;
676 		break;
677 	case HTML_MEDIA_PROP_HAVE_CURRENT_DATA:
678 		v = HAVE_CURRENT_DATA;
679 		break;
680 	case HTML_MEDIA_PROP_HAVE_FUTURE_DATA:
681 		v = HAVE_FUTURE_DATA;
682 		break;
683 	case HTML_MEDIA_PROP_HAVE_ENOUGH_DATA:
684 		v = HAVE_ENOUGH_DATA;
685 		break;
686 	default:
687 		return JS_TRUE;
688 	}
689 	*vp = INT_TO_JSVAL( v );
690 	return JS_TRUE;
691 } else {
692 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
693 }
694 }
695 
696 static SMJS_FUNC_PROP_GET(html_media_get_preload)
697 GF_FieldInfo info;
698 HTML_MEDIA_JS_START
699 if (gf_node_get_attribute_by_name(n, "preload", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
700 	char *szAtt = gf_svg_dump_attribute(n, &info);
701 	*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
702 	if (szAtt) gf_free(szAtt);
703 }
704 return JS_TRUE;
705 }
706 
707 static SMJS_FUNC_PROP_SET(html_media_set_preload)
708 HTML_MEDIA_JS_START
709 if (JSVAL_CHECK_STRING(*vp)) {
710 	char *str = SMJS_CHARS(c, *vp);
711 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "preload", str);
712 	return JS_TRUE;
713 } else {
714 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
715 }
716 }
717 
718 static SMJS_FUNC_PROP_GET(html_media_get_buffered)
719 HTML_MEDIA_JS_START
720 *vp = OBJECT_TO_JSVAL( me->buffered->_this );
721 return JS_TRUE;
722 }
723 
724 static SMJS_FUNC_PROP_GET(html_media_get_seeking)
725 HTML_MEDIA_JS_START
726 /* TODO: implement it for real */
727 *vp = BOOLEAN_TO_JSVAL( (me->seeking ? JS_TRUE : JS_FALSE) );
728 return JS_TRUE;
729 }
730 
731 static SMJS_FUNC_PROP_GET(html_media_get_current_time)
732 u32 time_ms = 0;
733 Double time_s;
734 GF_MediaObject *mo = NULL;
735 HTML_MEDIA_JS_START
736 mo = gf_html_media_object(n);
737 gf_mo_get_object_time(mo, &time_ms);
738 time_s = time_ms / 1000.0;
739 *vp = JS_MAKE_DOUBLE(c, time_s );
740 return JS_TRUE;
741 }
742 
743 static GFINLINE Bool ScriptAction(GF_SceneGraph *scene, u32 type, GF_Node *node, GF_JSAPIParam *param)
744 {
745 	if (scene->script_action)
746 	{
747 		return scene->script_action(scene->script_action_cbck, type, node, param);
748 	}
749 	return GF_FALSE;
750 }
751 
752 static SMJS_FUNC_PROP_SET(html_media_set_current_time)
753 double d;
754 GF_JSAPIParam par;
755 HTML_MEDIA_JS_START
756 if (!JSVAL_IS_NUMBER(*vp)) {
757 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
758 }
759 JS_ValueToNumber(c, *vp, &d);
760 par.time = d;
761 ScriptAction(n->sgprivate->scenegraph, GF_JSAPI_OP_SET_TIME, (GF_Node *)n, &par);
762 return JS_TRUE;
763 }
764 
765 static SMJS_FUNC_PROP_GET(html_media_get_duration)
766 Double dur;
767 GF_MediaObject *mo = NULL;
768 HTML_MEDIA_JS_START
769 mo = gf_html_media_object(n);
770 dur = gf_mo_get_duration(mo);
771 if (dur <= 0) {
772 	*vp = JS_GetNaNValue(c);
773 } else {
774 	*vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, dur));
775 }
776 return JS_TRUE;
777 }
778 
779 static SMJS_FUNC_PROP_GET(html_media_get_start_date)
780 HTML_MEDIA_JS_START
781 // SpiderMonkey 1.8.5 JSObject * JS_NewDateObject(JSContext *cx, int year, int mon, int mday, int hour, int min, int sec);
782 *vp = JSVAL_NULL;
783 /* TODO */
784 return JS_TRUE;
785 }
786 
787 static SMJS_FUNC_PROP_GET(html_media_get_paused)
788 HTML_MEDIA_JS_START
789 *vp =( BOOLEAN_TO_JSVAL( (me->paused ? JS_TRUE : JS_FALSE) ) );
790 return JS_TRUE;
791 }
792 
793 static SMJS_FUNC_PROP_GET(html_media_get_default_playback_rate)
794 HTML_MEDIA_JS_START
795 *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, me->defaultPlaybackRate));
796 return JS_TRUE;
797 }
798 
799 static SMJS_FUNC_PROP_SET(html_media_set_default_playback_rate)
800 jsdouble d;
801 HTML_MEDIA_JS_START
802 if (!JSVAL_IS_NUMBER(*vp)) {
803 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
804 }
805 JS_ValueToNumber(c, *vp, &d);
806 me->defaultPlaybackRate = d;
807 return JS_TRUE;
808 }
809 
810 static SMJS_FUNC_PROP_GET(html_media_get_playback_rate)
811 Double speed;
812 GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
813 GF_MediaObject *mo = gf_html_media_object(n);
814 speed = gf_mo_get_current_speed(mo);
815 *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, speed));
816 return JS_TRUE;
817 }
818 
819 static SMJS_FUNC_PROP_SET(html_media_set_playback_rate)
820 jsdouble d;
821 Fixed speed;
822 GF_MediaObject *mo;
823 HTML_MEDIA_JS_START
824 mo = gf_html_media_object(n);
825 if (!JSVAL_IS_NUMBER(*vp)) {
826 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
827 }
828 JS_ValueToNumber(c, *vp, &d);
829 speed = FLT2FIX(d);
830 gf_mo_set_speed(mo, speed);
831 return JS_TRUE;
832 }
833 
834 static SMJS_FUNC_PROP_GET(html_media_get_played)
835 HTML_MEDIA_JS_START
836 *vp =( OBJECT_TO_JSVAL( me->played->_this ) );
837 return JS_TRUE;
838 }
839 
840 static SMJS_FUNC_PROP_GET(html_media_get_seekable)
841 HTML_MEDIA_JS_START
842 *vp =( OBJECT_TO_JSVAL( me->seekable->_this ) );
843 return JS_TRUE;
844 }
845 
846 static SMJS_FUNC_PROP_GET(html_media_get_ended)
847 GF_MediaObject *mo;
848 HTML_MEDIA_JS_START
849 mo = gf_html_media_object(n);
850 *vp = BOOLEAN_TO_JSVAL( gf_mo_is_done(mo) ? JS_TRUE : JS_FALSE);
851 return JS_TRUE;
852 }
853 
854 static SMJS_FUNC_PROP_GET(html_media_get_autoplay)
855 GF_FieldInfo info;
856 HTML_MEDIA_JS_START
857 if (gf_node_get_attribute_by_name(n, "autoplay", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
858 	char *szAtt = gf_svg_dump_attribute(n, &info);
859 	if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
860 		*vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
861 	} else {
862 		*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
863 	}
864 	if (szAtt) gf_free(szAtt);
865 }
866 return JS_TRUE;
867 }
868 
869 static SMJS_FUNC_PROP_SET(html_media_set_autoplay)
870 HTML_MEDIA_JS_START
871 if (JSVAL_CHECK_STRING(*vp)) {
872 	char *str = SMJS_CHARS(c, *vp);
873 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "autoplay", str);
874 	return JS_TRUE;
875 } else {
876 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
877 }
878 }
879 
880 static SMJS_FUNC_PROP_GET(html_media_get_loop)
881 GF_FieldInfo info;
882 HTML_MEDIA_JS_START
883 /* TODO: gf_mo_get_loop */
884 if (gf_node_get_attribute_by_name(n, "loop", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
885 	char *szAtt = gf_svg_dump_attribute(n, &info);
886 	if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
887 		*vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
888 	} else {
889 		*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
890 	}
891 	if (szAtt) gf_free(szAtt);
892 }
893 return JS_TRUE;
894 }
895 
896 static SMJS_FUNC_PROP_SET(html_media_set_loop)
897 HTML_MEDIA_JS_START
898 if (JSVAL_CHECK_STRING(*vp)) {
899 	char *str = SMJS_CHARS(c, *vp);
900 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "loop", str);
901 	//TODO: use gf_mo_get_loop
902 	return JS_TRUE;
903 } else {
904 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
905 }
906 }
907 
908 static SMJS_FUNC_PROP_GET(html_media_get_mediagroup)
909 GF_FieldInfo info;
910 HTML_MEDIA_JS_START
911 if (gf_node_get_attribute_by_name(n, "mediagroup", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
912 	char *szAtt = gf_svg_dump_attribute(n, &info);
913 	*vp =( STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) ) );
914 	if (szAtt) gf_free(szAtt);
915 }
916 return JS_TRUE;
917 }
918 
919 static SMJS_FUNC_PROP_SET(html_media_set_mediagroup)
920 HTML_MEDIA_JS_START
921 if (JSVAL_CHECK_STRING(*vp)) {
922 	char *str = SMJS_CHARS(c, *vp);
923 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "mediagroup", str);
924 	return JS_TRUE;
925 } else {
926 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
927 }
928 }
929 
930 static SMJS_FUNC_PROP_GET(html_media_get_controller)
931 HTML_MEDIA_JS_START
932 if (me->controller) {
933 	*vp = OBJECT_TO_JSVAL( me->controller->_this );
934 } else {
935 	*vp = JSVAL_NULL;
936 }
937 return JS_TRUE;
938 }
939 
940 static SMJS_FUNC_PROP_SET(html_media_set_controller)
941 HTML_MEDIA_JS_START
942 if (JSVAL_IS_OBJECT(*vp)) {
943 	me->controller = (GF_HTML_MediaController *)SMJS_GET_PRIVATE(c, JSVAL_TO_OBJECT(*vp));
944 	return JS_TRUE;
945 } else {
946 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
947 }
948 }
949 
950 static SMJS_FUNC_PROP_GET(html_media_get_controls)
951 GF_FieldInfo info;
952 HTML_MEDIA_JS_START
953 if (gf_node_get_attribute_by_name(n, "controls", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
954 	char *szAtt = gf_svg_dump_attribute(n, &info);
955 	if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
956 		*vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
957 	} else {
958 		*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
959 	}
960 	if (szAtt) gf_free(szAtt);
961 }
962 return JS_TRUE;
963 }
964 
965 static SMJS_FUNC_PROP_SET(html_media_set_controls)
966 HTML_MEDIA_JS_START
967 if (JSVAL_CHECK_STRING(*vp)) {
968 	char *str = SMJS_CHARS(c, *vp);
969 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "controls", str);
970 	return JS_TRUE;
971 } else {
972 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
973 }
974 }
975 
976 static SVG_audio_stack *html_media_get_audio_stack(GF_Node *n) {
977 	SVG_video_stack *video_stack = NULL;
978 	SVG_audio_stack *audio_stack = NULL;
979 	if (n->sgprivate->tag == TAG_SVG_video) {
980 		video_stack = (SVG_video_stack *)n->sgprivate->UserPrivate;
981 		if (video_stack->audio) {
982 			audio_stack = (SVG_audio_stack *)video_stack->audio->sgprivate->UserPrivate;
983 		}
984 	} else if (n->sgprivate->tag == TAG_SVG_audio) {
985 		audio_stack = (SVG_audio_stack *)n->sgprivate->UserPrivate;
986 	}
987 	return audio_stack;
988 }
989 
990 static SMJS_FUNC_PROP_GET(html_media_get_volume)
991 Fixed volume = FIX_ONE;
992 SVG_audio_stack *audio_stack = NULL;
993 HTML_MEDIA_JS_CHECK
994 audio_stack = html_media_get_audio_stack(n);
995 if (audio_stack) {
996 	audio_stack->input.input_ifce.GetChannelVolume(&audio_stack->input.input_ifce.callback, &volume);
997 }
998 *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, FIX2FLT(volume)));
999 return JS_TRUE;
1000 }
1001 
1002 static SMJS_FUNC_PROP_SET(html_media_set_volume)
1003 jsdouble volume;
1004 SVG_audio_stack *audio_stack = NULL;
1005 HTML_MEDIA_JS_CHECK
1006 audio_stack = html_media_get_audio_stack(n);
1007 if (audio_stack) {
1008 	JS_ValueToNumber(c, *vp, &volume);
1009 	audio_stack->input.intensity = FLT2FIX(volume);
1010 	return JS_TRUE;
1011 } else {
1012 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1013 }
1014 }
1015 
1016 static SMJS_FUNC_PROP_GET(html_media_get_muted)
1017 SVG_audio_stack *audio_stack = NULL;
1018 HTML_MEDIA_JS_CHECK
1019 audio_stack = html_media_get_audio_stack(n);
1020 if (audio_stack) {
1021 	*vp = BOOLEAN_TO_JSVAL(audio_stack->input.is_muted ? JS_TRUE : JS_FALSE);
1022 } else {
1023 	*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
1024 }
1025 return JS_TRUE;
1026 }
1027 
1028 static SMJS_FUNC_PROP_SET(html_media_set_muted)
1029 SVG_audio_stack *audio_stack = NULL;
1030 HTML_MEDIA_JS_CHECK
1031 audio_stack = html_media_get_audio_stack(n);
1032 if (audio_stack) {
1033 	audio_stack->input.is_muted = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
1034 	return JS_TRUE;
1035 } else {
1036 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1037 }
1038 }
1039 
1040 static SMJS_FUNC_PROP_GET(html_media_get_default_muted)
1041 GF_FieldInfo info;
1042 HTML_MEDIA_JS_START
1043 if (gf_node_get_attribute_by_name(n, "muted", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
1044 	char *szAtt = gf_svg_dump_attribute(n, &info);
1045 	if (!strcmp(szAtt, "true") || !strcmp(szAtt, "TRUE")) {
1046 		*vp =( BOOLEAN_TO_JSVAL( JS_TRUE ) );
1047 	} else {
1048 		*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
1049 	}
1050 	if (szAtt) gf_free(szAtt);
1051 } else {
1052 	*vp =( BOOLEAN_TO_JSVAL( JS_FALSE ) );
1053 }
1054 return JS_TRUE;
1055 }
1056 
1057 static SMJS_FUNC_PROP_SET(html_media_set_default_muted)
1058 HTML_MEDIA_JS_START
1059 if (JSVAL_CHECK_STRING(*vp)) {
1060 	char *str = SMJS_CHARS(c, *vp);
1061 	gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "muted", str);
1062 	return JS_TRUE;
1063 } else {
1064 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1065 }
1066 }
1067 
1068 static SMJS_FUNC_PROP_GET(html_media_get_audio_tracks)
1069 HTML_MEDIA_JS_START
1070 *vp =( OBJECT_TO_JSVAL( me->audioTracks._this ) );
1071 return JS_TRUE;
1072 }
1073 
1074 static SMJS_FUNC_PROP_GET(html_media_get_video_tracks)
1075 HTML_MEDIA_JS_START
1076 *vp =( OBJECT_TO_JSVAL( me->videoTracks._this ) );
1077 return JS_TRUE;
1078 }
1079 
1080 static SMJS_FUNC_PROP_GET(html_media_get_text_tracks)
1081 HTML_MEDIA_JS_START
1082 *vp =( OBJECT_TO_JSVAL( me->textTracks._this ) );
1083 return JS_TRUE;
1084 }
1085 
1086 static SMJS_FUNC_PROP_GET(html_time_ranges_get_length)
1087 GF_HTML_MediaTimeRanges *timeranges;
1088 if (!GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
1089 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1090 }
1091 timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
1092 *vp = INT_TO_JSVAL( gf_list_count(timeranges->times)/2);
1093 return JS_TRUE;
1094 }
1095 
1096 static JSBool SMJS_FUNCTION(html_time_ranges_start)
1097 {
1098 	GF_HTML_MediaTimeRanges *timeranges;
1099 	SMJS_OBJ
1100 	SMJS_ARGS
1101 	if ((argc!=1) || !GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
1102 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1103 	}
1104 	timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
1105 	if (!timeranges) {
1106 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1107 	}
1108 	if (JSVAL_IS_INT(argv[0])) {
1109 		u32 i = JSVAL_TO_INT(argv[0]);
1110 		u64 *start_value = (u64 *)gf_list_get(timeranges->times, 2*i);
1111 		if (!start_value) {
1112 			return dom_throw_exception(c, GF_DOM_EXC_INDEX_SIZE_ERR);
1113 		} else {
1114 			SMJS_SET_RVAL(DOUBLE_TO_JSVAL(JS_NewDouble(c, (*start_value)*1.0/timeranges->timescale)));
1115 		}
1116 	} else {
1117 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1118 	}
1119 	return JS_TRUE;
1120 }
1121 
1122 static JSBool SMJS_FUNCTION(html_time_ranges_end)
1123 {
1124 	GF_HTML_MediaTimeRanges *timeranges;
1125 	SMJS_OBJ
1126 	SMJS_ARGS
1127 	if ((argc!=1) || !GF_JS_InstanceOf(c, obj, &html_media_rt->timeRangesClass, NULL)) {
1128 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1129 	}
1130 	timeranges = (GF_HTML_MediaTimeRanges *)SMJS_GET_PRIVATE(c, obj);
1131 	if (!timeranges) {
1132 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1133 	}
1134 	if (JSVAL_IS_INT(argv[0])) {
1135 		u32 i = JSVAL_TO_INT(argv[0]);
1136 		u64 *end_value = (u64 *)gf_list_get(timeranges->times, 2*i+1);
1137 		if (!end_value) {
1138 			return dom_throw_exception(c, GF_DOM_EXC_INDEX_SIZE_ERR);
1139 		} else {
1140 			SMJS_SET_RVAL(DOUBLE_TO_JSVAL(JS_NewDouble(c, (*end_value)*1.0/timeranges->timescale)));
1141 		}
1142 	} else {
1143 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1144 	}
1145 	return JS_TRUE;
1146 }
1147 
1148 static Bool html_is_track_list(JSContext *c, JSObject *obj) {
1149 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackListClass, NULL)) return GF_TRUE;
1150 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackListClass, NULL)) return GF_TRUE;
1151 	if (GF_JS_InstanceOf(c, obj, &html_media_rt->textTrackListClass, NULL)) return GF_TRUE;
1152 	return GF_FALSE;
1153 }
1154 
1155 static SMJS_FUNC_PROP_GET(html_track_list_get_length)
1156 GF_HTML_TrackList *tracklist;
1157 if (html_is_track_list(c, obj)) {
1158 	tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1159 	if (tracklist) {
1160 		*vp = INT_TO_JSVAL( gf_list_count(tracklist->tracks) );
1161 		return JS_TRUE;
1162 	} else {
1163 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1164 	}
1165 } else {
1166 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1167 }
1168 }
1169 
1170 static JSBool SMJS_FUNCTION(html_track_list_get_track_by_id)
1171 {
1172 	SMJS_OBJ
1173 	SMJS_ARGS
1174 	if (html_is_track_list(c, obj)) {
1175 		GF_HTML_TrackList *tracklist;
1176 		GF_HTML_Track *track;
1177 		char *str;
1178 		tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1179 		str = SMJS_CHARS_FROM_STRING(c, JS_ValueToString(c, argv[0]) );
1180 		track = html_media_tracklist_get_track(tracklist, str);
1181 		SMJS_FREE(c, str);
1182 		if (track) {
1183 			SMJS_SET_RVAL(OBJECT_TO_JSVAL(track->_this));
1184 			return JS_TRUE;
1185 		} else {
1186 			return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1187 		}
1188 	} else {
1189 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1190 	}
1191 }
1192 
1193 static SMJS_FUNC_PROP_GET(html_track_list_get_property)
1194 if (html_is_track_list(c, obj)) {
1195 	GF_HTML_TrackList *tracklist;
1196 	s32 index;
1197 
1198 	tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1199 	index = SMJS_ID_TO_INT(id);
1200 	if (index >= 0 && (u32)index < gf_list_count(tracklist->tracks)) {
1201 		GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(tracklist->tracks, (u32)index);
1202 		*vp = OBJECT_TO_JSVAL(track->_this);
1203 	}
1204 	return JS_TRUE;
1205 } else {
1206 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1207 }
1208 }
1209 
1210 static SMJS_FUNC_PROP_GET(html_track_list_get_selected_index)
1211 if (html_is_track_list(c, obj)) {
1212 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1213 	*vp = INT_TO_JSVAL(tracklist->selected_index);
1214 	return JS_TRUE;
1215 } else {
1216 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1217 }
1218 }
1219 
1220 static SMJS_FUNC_PROP_GET(html_track_list_get_onchange)
1221 if (html_is_track_list(c, obj)) {
1222 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1223 	if (! JSVAL_IS_NULL(tracklist->onchange)) {
1224 		*vp = tracklist->onchange;
1225 	} else {
1226 		*vp = JSVAL_NULL;
1227 	}
1228 	return JS_TRUE;
1229 } else {
1230 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1231 }
1232 }
1233 
1234 JSBool gf_set_js_eventhandler(JSContext *c, jsval vp, jsval *callbackfuncval);
1235 
1236 static SMJS_FUNC_PROP_SET(html_track_list_set_onchange)
1237 if (html_is_track_list(c, obj)) {
1238 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1239 	gf_set_js_eventhandler(c, *vp, &tracklist->onchange);
1240 	return JS_TRUE;
1241 } else {
1242 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1243 }
1244 }
1245 
1246 static SMJS_FUNC_PROP_GET(html_track_list_get_onaddtrack)
1247 if (html_is_track_list(c, obj)) {
1248 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1249 	if (! JSVAL_IS_NULL(tracklist->onaddtrack)) {
1250 		*vp = tracklist->onaddtrack;
1251 	} else {
1252 		*vp = JSVAL_NULL;
1253 	}
1254 	return JS_TRUE;
1255 } else {
1256 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1257 }
1258 }
1259 
1260 static SMJS_FUNC_PROP_SET(html_track_list_set_onaddtrack)
1261 if (html_is_track_list(c, obj)) {
1262 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1263 	gf_set_js_eventhandler(c, *vp, &tracklist->onaddtrack);
1264 	return JS_TRUE;
1265 } else {
1266 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1267 }
1268 }
1269 
1270 static SMJS_FUNC_PROP_GET(html_track_list_get_onremovetrack)
1271 if (html_is_track_list(c, obj)) {
1272 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1273 	if (! JSVAL_IS_NULL(tracklist->onremovetrack) ) {
1274 		*vp = tracklist->onremovetrack;
1275 	} else {
1276 		*vp = JSVAL_NULL;
1277 	}
1278 	return JS_TRUE;
1279 } else {
1280 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1281 }
1282 }
1283 
1284 static SMJS_FUNC_PROP_SET(html_track_list_set_onremovetrack)
1285 if (html_is_track_list(c, obj)) {
1286 	GF_HTML_TrackList *tracklist = (GF_HTML_TrackList *)SMJS_GET_PRIVATE(c, obj);
1287 	gf_set_js_eventhandler(c, *vp, &tracklist->onremovetrack);
1288 	return JS_TRUE;
1289 } else {
1290 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1291 }
1292 }
1293 
1294 static SMJS_FUNC_PROP_GET(html_track_get_property)
1295 if (html_is_track_list(c, obj)) {
1296 	GF_HTML_Track *track = (GF_HTML_Track *)SMJS_GET_PRIVATE(c, obj);
1297 	if (!SMJS_ID_IS_INT(id)) {
1298 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1299 	}
1300 	switch (SMJS_ID_TO_INT(id)) {
1301 	case HTML_TRACK_PROP_ID:
1302 		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->id ? track->id : "") );
1303 		return JS_TRUE;
1304 	case HTML_TRACK_PROP_KIND:
1305 		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->kind ? track->kind : "") );
1306 		return JS_TRUE;
1307 	case HTML_TRACK_PROP_LABEL:
1308 		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->label ? track->label : "") );
1309 		return JS_TRUE;
1310 	case HTML_TRACK_PROP_LANGUAGE:
1311 		*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->language ? track->language  : "") );
1312 		return JS_TRUE;
1313 	case HTML_TRACK_PROP_INBANDTYPE:
1314 		if (GF_JS_InstanceOf(c, obj, &html_media_rt->textTrackClass, NULL)) {
1315 			*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, track->mime ? track->mime  : "") );
1316 			return JS_TRUE;
1317 		}
1318 	case HTML_TRACK_PROP_SELECTED:
1319 		if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackClass, NULL)) {
1320 			*vp = BOOLEAN_TO_JSVAL(track->enabled_or_selected ? JS_TRUE : JS_FALSE);
1321 		}
1322 		return JS_TRUE;
1323 	case HTML_TRACK_PROP_ENABLED:
1324 		if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackClass, NULL)) {
1325 			*vp = BOOLEAN_TO_JSVAL(track->enabled_or_selected ? JS_TRUE : JS_FALSE);
1326 		}
1327 		return JS_TRUE;
1328 	}
1329 	return JS_TRUE;
1330 } else {
1331 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1332 }
1333 }
1334 
1335 static SMJS_FUNC_PROP_SET(html_track_set_property)
1336 if (html_is_track_list(c, obj)) {
1337 	GF_HTML_Track *track = (GF_HTML_Track *)SMJS_GET_PRIVATE(c, obj);
1338 	if (!SMJS_ID_IS_INT(id)) {
1339 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1340 	}
1341 	switch (SMJS_ID_TO_INT(id)) {
1342 	case HTML_TRACK_PROP_SELECTED:
1343 		if (GF_JS_InstanceOf(c, obj, &html_media_rt->videoTrackClass, NULL))
1344 		{
1345 			track->enabled_or_selected = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
1346 		}
1347 		return JS_TRUE;
1348 	case HTML_TRACK_PROP_ENABLED:
1349 		if (GF_JS_InstanceOf(c, obj, &html_media_rt->audioTrackClass, NULL))
1350 		{
1351 			track->enabled_or_selected = (JSVAL_TO_BOOLEAN(*vp) == JS_TRUE ? GF_TRUE : GF_FALSE);
1352 		}
1353 		return JS_TRUE;
1354 	}
1355 	return JS_TRUE;
1356 } else {
1357 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1358 }
1359 }
1360 
1361 static SMJS_FUNC_PROP_GET(html_video_get_property)
1362 if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL))
1363 {
1364 	GF_FieldInfo    info;
1365 	SVG_video_stack *video;
1366 	GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
1367 	if (!n) return JS_TRUE;
1368 
1369 	video = (SVG_video_stack *)n->sgprivate->UserPrivate;
1370 	if (!SMJS_ID_IS_INT(id)) {
1371 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1372 	}
1373 	switch (SMJS_ID_TO_INT(id)) {
1374 	case HTML_VIDEO_PROP_WIDTH:
1375 		if (gf_node_get_attribute_by_tag(n, TAG_SVG_ATT_width, GF_TRUE, GF_TRUE, &info)==GF_OK) {
1376 			SVG_Length *length;
1377 			length = (SVG_Length *)info.far_ptr;
1378 			*vp = INT_TO_JSVAL( FIX2FLT(length->value) );
1379 		}
1380 		return JS_TRUE;
1381 	case HTML_VIDEO_PROP_HEIGHT:
1382 		if (gf_node_get_attribute_by_tag(n, TAG_SVG_ATT_height, GF_TRUE, GF_TRUE, &info)==GF_OK) {
1383 			SVG_Length *length;
1384 			length = (SVG_Length *)info.far_ptr;
1385 			*vp = INT_TO_JSVAL( FIX2FLT(length->value) );
1386 		}
1387 		return JS_TRUE;
1388 	case HTML_VIDEO_PROP_VIDEOWIDTH:
1389 		*vp = INT_TO_JSVAL( video->txh.width );
1390 		return JS_TRUE;
1391 	case HTML_VIDEO_PROP_VIDEOHEIGHT:
1392 		*vp = INT_TO_JSVAL( video->txh.height );
1393 		return JS_TRUE;
1394 	case HTML_VIDEO_PROP_POSTER:
1395 		if (gf_node_get_attribute_by_name(n, "poster", GF_XMLNS_SVG, GF_TRUE, GF_TRUE, &info)==GF_OK) {
1396 			char *szAtt = gf_svg_dump_attribute(n, &info);
1397 			*vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szAtt) );
1398 			if (szAtt) gf_free(szAtt);
1399 		}
1400 		return JS_TRUE;
1401 	}
1402 	return JS_TRUE;
1403 } else {
1404 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1405 }
1406 }
1407 
1408 static SMJS_FUNC_PROP_SET(html_video_set_property)
1409 if (GF_JS_InstanceOf(c, obj, &html_media_rt->htmlVideoElementClass, NULL))
1410 {
1411 	GF_Node *n = (GF_Node *)SMJS_GET_PRIVATE(c, obj);
1412 	if (!SMJS_ID_IS_INT(id)) {
1413 		return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1414 	}
1415 	switch (SMJS_ID_TO_INT(id)) {
1416 	case HTML_VIDEO_PROP_WIDTH:
1417 		/* TODO: change value and dirty node */
1418 		return JS_TRUE;
1419 	case HTML_VIDEO_PROP_HEIGHT:
1420 		/* TODO: change value and dirty node */
1421 		return JS_TRUE;
1422 	case HTML_VIDEO_PROP_POSTER:
1423 	{
1424 		char *str = SMJS_CHARS_FROM_STRING(c, JS_ValueToString(c, *vp) );
1425 		gf_svg_set_attributeNS(n, GF_XMLNS_SVG, "poster", str);
1426 		SMJS_FREE(c, str);
1427 		return JS_TRUE;
1428 	}
1429 	}
1430 	return JS_TRUE;
1431 } else {
1432 	return dom_throw_exception(c, GF_DOM_EXC_INVALID_ACCESS_ERR);
1433 }
1434 }
1435 
1436 static JSBool SMJS_FUNCTION(html_media_event_add_listener)
1437 {
1438 	JSBool SMJS_FUNCTION_EXT(gf_sg_js_event_add_listener, GF_Node *vrml_node);
1439 	return gf_sg_js_event_add_listener(SMJS_CALL_ARGS, NULL);
1440 }
1441 
1442 static JSBool SMJS_FUNCTION(html_media_event_remove_listener)
1443 {
1444 	JSBool SMJS_FUNCTION_EXT(gf_sg_js_event_remove_listener, GF_Node *vrml_node);
1445 	return gf_sg_js_event_remove_listener(SMJS_CALL_ARGS, NULL);
1446 }
1447 
1448 static JSBool SMJS_FUNCTION(html_media_event_dispatch)
1449 {
1450 	return JS_TRUE;
1451 }
1452 
1453 void html_media_source_init_js_api(JSContext *js_ctx, JSObject *global, GF_HTML_MediaRuntime *html_media_rt);
1454 
1455 void html_media_js_api_del() {
1456 	if (html_media_rt) {
1457 		gf_free(html_media_rt);
1458 		html_media_rt = NULL;
1459 	}
1460 }
1461 
1462 void html_media_init_js_api(GF_SceneGraph *scene) {
1463 	/* HTML Media Element */
1464 	if (!html_media_rt) {
1465 		GF_SAFEALLOC(html_media_rt, GF_HTML_MediaRuntime);
1466 
1467 		/* Setting up MediaError class (no constructor, no finalizer) */
1468 		JS_SETUP_CLASS(html_media_rt->mediaErrorClass, "MediaError", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
1469 		{
1470 			JSPropertySpec mediaErrorClassProps[] = {
1471 				{"MEDIA_ERR_ABORTED",           MEDIA_ERROR_PROP_ABORTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
1472 				{"MEDIA_ERR_NETWORK",           MEDIA_ERROR_PROP_NETWORK,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
1473 				{"MEDIA_ERR_DECODE",            MEDIA_ERROR_PROP_DECODE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
1474 				{"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},
1475 				{"code",                        MEDIA_ERROR_PROP_CODE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_error_get_code, 0},
1476 				{0, 0, 0, 0, 0}
1477 			};
1478 			JSFunctionSpec mediaErrorClassFuncs[] = {
1479 				SMJS_FUNCTION_SPEC(0, 0, 0)
1480 			};
1481 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->mediaErrorClass, 0, 0, mediaErrorClassProps, mediaErrorClassFuncs, 0, 0);
1482 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] MediaError class initialized\n"));
1483 		}
1484 
1485 		/* Setting up TimeRanges class (no constructor, no finalizer) */
1486 		JS_SETUP_CLASS(html_media_rt->timeRangesClass, "TimeRanges", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
1487 		{
1488 			JSPropertySpec timeRangesClassProps[] = {
1489 				{"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_time_ranges_get_length, 0},
1490 				{0, 0, 0, 0, 0}
1491 			};
1492 			JSFunctionSpec timeRangesClassFuncs[] = {
1493 				SMJS_FUNCTION_SPEC("start",                 html_time_ranges_start, 1),
1494 				SMJS_FUNCTION_SPEC("end",                   html_time_ranges_end, 1),
1495 				SMJS_FUNCTION_SPEC(0, 0, 0)
1496 			};
1497 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->timeRangesClass, 0, 0, timeRangesClassProps, timeRangesClassFuncs, 0, 0);
1498 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TimeRanges class initialized\n"));
1499 		}
1500 
1501 		JS_SETUP_CLASS(html_media_rt->audioTrackClass, "AudioTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
1502 		{
1503 			JSPropertySpec audioTrackClassProps[] = {
1504 				{"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1505 				{"kind",             HTML_TRACK_PROP_KIND,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1506 				{"label",            HTML_TRACK_PROP_LABEL,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1507 				{"language",         HTML_TRACK_PROP_LANGUAGE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1508 				{"enabled",          HTML_TRACK_PROP_SELECTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0},
1509 				{0, 0, 0, 0, 0}
1510 			};
1511 			JSFunctionSpec audioTrackClassFuncs[] = {
1512 				SMJS_FUNCTION_SPEC(0, 0, 0)
1513 			};
1514 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->audioTrackClass, 0, 0, audioTrackClassProps, audioTrackClassFuncs, 0, 0);
1515 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] AudioTrack class initialized\n"));
1516 		}
1517 
1518 		JS_SETUP_CLASS(html_media_rt->videoTrackClass, "VideoTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
1519 		{
1520 			JSPropertySpec videoTrackClassProps[] = {
1521 				{"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1522 				{"kind",             HTML_TRACK_PROP_KIND,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1523 				{"label",            HTML_TRACK_PROP_LABEL,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1524 				{"language",         HTML_TRACK_PROP_LANGUAGE,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1525 				{"selected",         HTML_TRACK_PROP_SELECTED,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0},
1526 				{0, 0, 0, 0, 0}
1527 			};
1528 			JSFunctionSpec videoTrackClassFuncs[] = {
1529 				SMJS_FUNCTION_SPEC(0, 0, 0)
1530 			};
1531 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->videoTrackClass, 0, 0, videoTrackClassProps, videoTrackClassFuncs, 0, 0);
1532 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] VideoTrack class initialized\n"));
1533 		}
1534 
1535 		JS_SETUP_CLASS(html_media_rt->textTrackClass, "TextTrack", JSCLASS_HAS_PRIVATE, html_track_get_property, html_track_set_property, JS_FinalizeStub);
1536 		{
1537 			JSPropertySpec textTrackClassProps[] = {
1538 				{"id",               HTML_TRACK_PROP_ID,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1539 				{"kind",             HTML_TRACK_PROP_KIND,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1540 				{"label",            HTML_TRACK_PROP_LABEL,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1541 				{"language",         HTML_TRACK_PROP_LANGUAGE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1542 				{"inBandMetadataTrackDispatchType",         HTML_TRACK_PROP_INBANDTYPE,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1543 				{0, 0, 0, 0, 0}
1544 			};
1545 			JSFunctionSpec textTrackClassFuncs[] = {
1546 				SMJS_FUNCTION_SPEC(0, 0, 0)
1547 			};
1548 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->textTrackClass, 0, 0, textTrackClassProps, textTrackClassFuncs, 0, 0);
1549 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TextTrack class initialized\n"));
1550 		}
1551 
1552 		JS_SETUP_CLASS(html_media_rt->audioTrackListClass, "AudioTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
1553 		{
1554 			JSPropertySpec audioTrackListClassProps[] = {
1555 				{"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
1556 				{"onchange",             0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onchange, html_track_list_set_onchange},
1557 				{"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
1558 				{"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
1559 				{0, 0, 0, 0, 0}
1560 			};
1561 			JSFunctionSpec audioTrackListClassFuncs[] = {
1562 				SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
1563 				SMJS_FUNCTION_SPEC(0, 0, 0)
1564 			};
1565 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->audioTrackListClass, 0, 0, audioTrackListClassProps, audioTrackListClassFuncs, 0, 0);
1566 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] AudioTrackList class initialized\n"));
1567 		}
1568 
1569 		JS_SETUP_CLASS(html_media_rt->videoTrackListClass, "VideoTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
1570 		{
1571 			JSPropertySpec videoTrackListClassProps[] = {
1572 				{"selectedIndex",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_selected_index, 0},
1573 				{"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
1574 				{"onchange",             0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onchange, html_track_list_set_onchange},
1575 				{"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
1576 				{"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
1577 				{0, 0, 0, 0, 0}
1578 			};
1579 			JSFunctionSpec videoTrackListClassFuncs[] = {
1580 				SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
1581 				SMJS_FUNCTION_SPEC(0, 0, 0)
1582 			};
1583 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->videoTrackListClass, 0, 0, videoTrackListClassProps, videoTrackListClassFuncs, 0, 0);
1584 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] VideoTrackList class initialized\n"));
1585 		}
1586 
1587 		JS_SETUP_CLASS(html_media_rt->textTrackListClass, "TextTrackList", JSCLASS_HAS_PRIVATE, html_track_list_get_property, JS_PropertyStub_forSetter, JS_FinalizeStub);
1588 		{
1589 			JSPropertySpec textTrackListClassProps[] = {
1590 				{"length",               0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_track_list_get_length, 0},
1591 				{"onaddtrack",           0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onaddtrack, html_track_list_set_onaddtrack},
1592 				{"onremovetrack",        0,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_track_list_get_onremovetrack, html_track_list_set_onremovetrack},
1593 				{0, 0, 0, 0, 0}
1594 			};
1595 			JSFunctionSpec textTrackListClassFuncs[] = {
1596 				SMJS_FUNCTION_SPEC("getTrackById",          html_track_list_get_track_by_id, 1),
1597 				SMJS_FUNCTION_SPEC(0, 0, 0)
1598 			};
1599 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->textTrackListClass, 0, 0, textTrackListClassProps, textTrackListClassFuncs, 0, 0);
1600 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] TextTrackList class initialized\n"));
1601 		}
1602 
1603 		JS_SETUP_CLASS(html_media_rt->mediaControllerClass, "MediaController", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
1604 		{
1605 			JSPropertySpec mediaControllerClassProps[] = {
1606 				{0, 0, 0, 0, 0}
1607 			};
1608 			JSFunctionSpec mediaControllerClassFuncs[] = {
1609 				SMJS_FUNCTION_SPEC(0, 0, 0)
1610 			};
1611 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, 0, &html_media_rt->mediaControllerClass, 0, 0, mediaControllerClassProps, mediaControllerClassFuncs, 0, 0);
1612 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] MediaController class initialized\n"));
1613 		}
1614 
1615 		JS_SETUP_CLASS(html_media_rt->htmlMediaElementClass, "HTMLMediaElement", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub);
1616 		{
1617 			JSPropertySpec htmlMediaElementClassProps[] = {
1618 				{"error",               HTML_MEDIA_PROP_ERROR,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_error, 0},
1619 				{"src",                 HTML_MEDIA_PROP_SRC,          JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , html_media_get_src, html_media_set_src},
1620 				{"currentSrc",          HTML_MEDIA_PROP_CURRENTSRC,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_src, 0},
1621 				{"crossOrigin",         HTML_MEDIA_PROP_CROSSORIGIN,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_cors, html_media_set_cors},
1622 				{"NETWORK_EMPTY",       HTML_MEDIA_PROP_NETWORK_EMPTY,    JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1623 				{"NETWORK_IDLE",        HTML_MEDIA_PROP_NETWORK_IDLE,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1624 				{"NETWORK_LOADING",     HTML_MEDIA_PROP_NETWORK_LOADING,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1625 				{"NETWORK_NO_SOURCE",   HTML_MEDIA_PROP_NETWORK_NO_SOURCE,JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1626 				{"networkState",        HTML_MEDIA_PROP_NETWORKSTATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_network_state, 0},
1627 				{"preload",             HTML_MEDIA_PROP_PRELOAD,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_preload, html_media_set_preload},
1628 				{"buffered",            HTML_MEDIA_PROP_BUFFERED,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_buffered, 0},
1629 				{"HAVE_NOTHING",        HTML_MEDIA_PROP_HAVE_NOTHING,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1630 				{"HAVE_METADATA",       HTML_MEDIA_PROP_HAVE_METADATA,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1631 				{"HAVE_CURRENT_DATA",   HTML_MEDIA_PROP_HAVE_CURRENT_DATA, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1632 				{"HAVE_FUTURE_DATA",    HTML_MEDIA_PROP_HAVE_FUTURE_DATA,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1633 				{"HAVE_ENOUGH_DATA",    HTML_MEDIA_PROP_HAVE_ENOUGH_DATA,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_const, 0},
1634 				{"readyState",          HTML_MEDIA_PROP_READYSTATE,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_ready_state, 0},
1635 				{"seeking",             HTML_MEDIA_PROP_SEEKING,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_seeking, 0},
1636 				{"currentTime",         HTML_MEDIA_PROP_CURRENTTIME,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_current_time, html_media_set_current_time},
1637 				{"duration",            HTML_MEDIA_PROP_DURATION,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_duration, 0},
1638 				{"startDate",           HTML_MEDIA_PROP_STARTDATE,    JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_start_date, 0},
1639 				{"paused",              HTML_MEDIA_PROP_PAUSED,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_paused, 0},
1640 				{"defaultPlaybackRate", HTML_MEDIA_PROP_DEFAULTPLAYBACKRATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_default_playback_rate, html_media_set_default_playback_rate},
1641 				{"playbackRate",        HTML_MEDIA_PROP_PLAYBACKRATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_playback_rate, html_media_set_playback_rate},
1642 				{"played",              HTML_MEDIA_PROP_PLAYED,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_played, 0},
1643 				{"seekable",            HTML_MEDIA_PROP_SEEKABLE,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_seekable, 0},
1644 				{"ended",               HTML_MEDIA_PROP_ENDED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_ended, 0},
1645 				{"autoplay",            HTML_MEDIA_PROP_AUTOPLAY,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_autoplay, html_media_set_autoplay},
1646 				{"loop",                HTML_MEDIA_PROP_LOOP,         JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_loop, html_media_set_loop},
1647 				{"mediaGroup",          HTML_MEDIA_PROP_MEDIAGROUP,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_mediagroup, html_media_set_mediagroup},
1648 				{"controller",          HTML_MEDIA_PROP_CONTROLLER,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_controller, html_media_set_controller},
1649 				{"controls",            HTML_MEDIA_PROP_CONTROLS,     JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_controls, html_media_set_controls},
1650 				{"volume",              HTML_MEDIA_PROP_VOLUME,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_volume, html_media_set_volume},
1651 				{"muted",               HTML_MEDIA_PROP_MUTED,        JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_muted, html_media_set_muted},
1652 				{"defaultMuted",        HTML_MEDIA_PROP_DEFAULTMUTED, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, html_media_get_default_muted, html_media_set_default_muted},
1653 				{"audioTracks",         HTML_MEDIA_PROP_AUDIOTRACKS,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_audio_tracks, 0},
1654 				{"videoTracks",         HTML_MEDIA_PROP_VIDEOTRACKS,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_video_tracks, 0},
1655 				{"textTracks",          HTML_MEDIA_PROP_TEXTTRACKS,   JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, html_media_get_text_tracks, 0},
1656 				{0, 0, 0, 0, 0}
1657 			};
1658 			JSBool SMJS_FUNCTION(svg_udom_smil_pause);
1659 			JSBool SMJS_FUNCTION(svg_udom_smil_begin);
1660 			JSBool SMJS_FUNCTION(svg_udom_smil_end);
1661 			JSFunctionSpec htmlMediaElementClassFuncs[] = {
1662 				SMJS_FUNCTION_SPEC("load",                  html_media_load, 0),
1663 				SMJS_FUNCTION_SPEC("canPlayType",           html_media_canPlayType, 1),
1664 				SMJS_FUNCTION_SPEC("fastSeek",              html_media_fastSeek, 1),
1665 				SMJS_FUNCTION_SPEC("play",                  svg_udom_smil_begin, 0),
1666 				SMJS_FUNCTION_SPEC("pause",                 svg_udom_smil_pause, 0),
1667 				SMJS_FUNCTION_SPEC("addTextTrack",          html_media_addTextTrack, 1),
1668 				SMJS_FUNCTION_SPEC("addEventListener",		html_media_event_add_listener, 3),
1669 				SMJS_FUNCTION_SPEC("removeEventListener",	html_media_event_remove_listener, 3),
1670 				SMJS_FUNCTION_SPEC("dispatchEvent",			html_media_event_dispatch, 1),
1671 				SMJS_FUNCTION_SPEC(0, 0, 0)
1672 			};
1673 			JSObject *elt_proto = dom_js_get_element_proto(scene->svg_js->js_ctx);
1674 			GF_JS_InitClass(scene->svg_js->js_ctx, scene->svg_js->global, elt_proto, &html_media_rt->htmlMediaElementClass, 0, 0, htmlMediaElementClassProps, htmlMediaElementClassFuncs, 0, 0);
1675 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLMediaElement class initialized\n"));
1676 		}
1677 
1678 		JS_SETUP_CLASS(html_media_rt->htmlVideoElementClass, "HTMLVideoElement", JSCLASS_HAS_PRIVATE, html_video_get_property, html_video_set_property, dom_element_finalize);
1679 		{
1680 			JSPropertySpec htmlVideoElementClassProps[] = {
1681 				{"width",       HTML_VIDEO_PROP_WIDTH,       JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
1682 				{"height",      HTML_VIDEO_PROP_HEIGHT,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
1683 				{"videoWidth",  HTML_VIDEO_PROP_VIDEOWIDTH,  JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1684 				{"videoHeight", HTML_VIDEO_PROP_VIDEOHEIGHT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0},
1685 				{"poster",      HTML_VIDEO_PROP_POSTER,      JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0},
1686 				{0, 0, 0, 0, 0}
1687 			};
1688 			JSFunctionSpec htmlVideoElementClassFuncs[] = {
1689 				SMJS_FUNCTION_SPEC(0, 0, 0)
1690 			};
1691 			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);
1692 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLVideoElement class initialized\n"));
1693 		}
1694 
1695 		JS_SETUP_CLASS(html_media_rt->htmlAudioElementClass, "HTMLAudioElement", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, dom_element_finalize);
1696 		{
1697 			JSPropertySpec htmlAudioElementClassProps[] = {
1698 				{0, 0, 0, 0, 0}
1699 			};
1700 			JSFunctionSpec htmlAudioElementClassFuncs[] = {
1701 				SMJS_FUNCTION_SPEC(0, 0, 0)
1702 			};
1703 			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);
1704 			GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[HTMLMediaAPI] HTMLAudioElement class initialized\n"));
1705 		}
1706 		html_media_source_init_js_api(scene->svg_js->js_ctx, scene->svg_js->global, html_media_rt);
1707 	}
1708 }
1709 #endif  /*GPAC_HAS_SPIDERMONKEY*/
1710 
1711 #endif  /*GPAC_DISABLE_SVG*/
1712