blob: 2e82f3101cb6e747f17d17b5419f14a5cbcd5847 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2007-2026
* All rights reserved
*
* This file is part of GPAC / JavaScript libgpac Core bindings
*
* 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.
*
*/
/*
ANY CHANGE TO THE API MUST BE REFLECTED IN THE DOCUMENTATION IN gpac/share/doc/idl/storage.js
(no way to define inline JS doc with doxygen)
*/
#include <gpac/setup.h>
#ifdef GPAC_HAS_QJS
#include <gpac/main.h>
#include <gpac/thread.h>
#include <gpac/bitstream.h>
#include <gpac/network.h>
#include <gpac/base_coding.h>
#include <gpac/filters.h>
#include "../scenegraph/qjs_common.h"
static void qjs_init_runtime_libc(JSRuntime *rt);
static void qjs_uninit_runtime_libc(JSRuntime *rt);
extern void *user_log_cbk;
extern Bool gpac_use_logx;
typedef struct
{
JSRuntime *js_runtime;
u32 nb_inst;
GF_Mutex *mx;
GF_List *allocated_contexts;
//for logging
void *prev_user_log_cbk;
gf_log_cbk prev_log_cbk;
JSValue log_fun;
JSValue log_obj;
u32 js_log_buf_size;
//WARNING - libc mem, not gf_alloc
char *js_log_buf;
JSContext *log_ctx;
char *js_orig_logs;
} GF_JSRuntime;
static GF_JSRuntime *js_rt = NULL;
typedef struct __js_sys_task {
JSValue fun;
JSValue _obj;
u32 type;
JSContext *ctx;
} JS_Sys_Task;
static void js_reset_logs(JSContext *ctx);
JSContext *gf_js_create_context()
{
JSContext *ctx;
if (!js_rt) {
JSRuntime *js_runtime = JS_NewRuntime();
if (!js_runtime) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[ECMAScript] Cannot allocate ECMAScript runtime\n"));
return NULL;
}
GF_SAFEALLOC(js_rt, GF_JSRuntime);
if (!js_rt) {
GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[JS] Failed to create script runtime\n"));
return NULL;
}
js_rt->js_runtime = js_runtime;
js_rt->allocated_contexts = gf_list_new();
js_rt->mx = gf_mx_new("JavaScript");
GF_LOG(GF_LOG_DEBUG, GF_LOG_SCRIPT, ("[ECMAScript] ECMAScript runtime allocated %p\n", js_runtime));
qjs_init_runtime_libc(js_rt->js_runtime);
}
js_rt->nb_inst++;
gf_mx_p(js_rt->mx);
ctx = JS_NewContext(js_rt->js_runtime);
gf_list_add(js_rt->allocated_contexts, ctx);
gf_mx_v(js_rt->mx);
return ctx;
}
void gf_js_delete_context(JSContext *ctx)
{
if (!js_rt) return;
js_reset_logs(ctx);
gf_js_call_gc(ctx);
RMT_WS* rmt = (RMT_WS*) gf_sys_get_rmtws();
if (rmt) {
JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt);
if (task && task->type == RMT_CALLBACK_JS) {
gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);
JS_FreeValue(ctx, task->fun);
JS_FreeValue(ctx, task->_obj);
gf_free(task);
}
}
RMT_WS* urmt = (RMT_WS*) gf_sys_get_userws();
if (urmt) {
JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(urmt);
if (task && task->type == RMT_CALLBACK_JS) {
gf_rmt_set_on_new_client_cbk(urmt, NULL, NULL);
JS_FreeValue(ctx, task->fun);
JS_FreeValue(ctx, task->_obj);
gf_free(task);
}
}
gf_mx_p(js_rt->mx);
gf_list_del_item(js_rt->allocated_contexts, ctx);
JS_FreeContext(ctx);
gf_mx_v(js_rt->mx);
gf_js_call_gc(NULL);
js_rt->nb_inst --;
if (js_rt->nb_inst == 0) {
//we delete the runtime in gf_sys_close (we use static vars for class IDs)
//but perform GC
JS_RunGC(js_rt->js_runtime);
}
}
void gf_js_delete_runtime()
{
//prevent destruction of the runtime if asked (used for android)
if (gf_opts_get_bool("temp", "static-jsrt"))
return;
if (js_rt) {
qjs_uninit_runtime_libc(js_rt->js_runtime);
JS_FreeRuntime(js_rt->js_runtime);
gf_list_del(js_rt->allocated_contexts);
if (js_rt->js_orig_logs) gf_free(js_rt->js_orig_logs);
if (js_rt->js_log_buf) gf_free(js_rt->js_log_buf);
gf_mx_del(js_rt->mx);
gf_free(js_rt);
js_rt = NULL;
}
}
void gf_js_call_gc(JSContext *c)
{
gf_js_lock(c, 1);
JS_RunGC(js_rt->js_runtime);
gf_js_lock(c, 0);
}
Bool gf_js_context_is_valid(JSContext *ctx)
{
if (gf_list_find(js_rt->allocated_contexts, ctx) < 0)
return GF_FALSE;
return GF_TRUE;
}
/*
* locking/try-locking the JS context
*
* */
GF_EXPORT
void gf_js_lock(struct JSContext *cx, Bool LockIt)
{
if (!js_rt) return;
if (LockIt) {
gf_mx_p(js_rt->mx);
//if this is the first time the thread grabs the mutex, update stack top
//do not do it if not first otherwise we would keep extending the current stack size
if (gf_mx_get_num_locks(js_rt->mx)==1) {
JS_UpdateStackTop(js_rt->js_runtime);
}
} else {
gf_mx_v(js_rt->mx);
}
}
GF_EXPORT
Bool gf_js_try_lock(struct JSContext *cx)
{
gf_assert(cx);
if (gf_mx_try_lock(js_rt->mx)) {
return 1;
}
return 0;
}
JSRuntime *gf_js_get_rt()
{
if (!js_rt) return NULL;
return js_rt->js_runtime;
}
Bool is_js_log = GF_FALSE;
static JSValue js_print_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 ltool, u32 error_type)
{
int i=0;
Bool first=GF_TRUE;
s32 logl = GF_LOG_INFO;
Bool no_new_line = GF_FALSE;
JSValue v, global;
const char *c_logname=NULL;
const char *log_name = "JS";
if ((argc>1) && JS_IsNumber(argv[0])) {
JS_ToInt32(ctx, &logl, argv[0]);
i=1;
}
if (error_type)
logl = GF_LOG_ERROR;
global = JS_GetGlobalObject(ctx);
v = JS_GetPropertyStr(ctx, global, "_gpac_log_name");
if (!JS_IsUndefined(v) && !JS_IsNull(v)) {
c_logname = JS_ToCString(ctx, v);
JS_FreeValue(ctx, v);
if (c_logname) {
log_name = c_logname;
if (!strlen(log_name))
log_name = NULL;
}
}
JS_FreeValue(ctx, global);
if (log_name) {
#ifndef GPAC_DISABLE_LOG
if (!is_js_log) {
GF_LOG(logl, ltool, ("[%s] ", log_name));
} else
#endif
fprintf(stderr, "[%s] ", log_name);
}
if (error_type==2) {
#ifndef GPAC_DISABLE_LOG
if (!is_js_log) {
GF_LOG(logl, ltool, ("Throw "));
} else
#endif
fprintf(stderr, "Throw ");
}
for (; i < argc; i++) {
const char *str = JS_ToCString(ctx, argv[i]);
if (!str) return GF_JS_EXCEPTION(ctx);
if (logl==-1) {
gf_sys_format_help(stderr, GF_PRINTARG_HIGHLIGHT_FIRST, "%s\n", str);
} else if (logl==-2) {
gf_sys_format_help(stderr, 0, "%s\n", str);
} else if (logl<0) {
fprintf(stderr, "%s%s", (first) ? "" : " ", str);
} else {
#ifndef GPAC_DISABLE_LOG
if (!is_js_log) {
GF_LOG(logl, ltool, ("%s%s", (first) ? "" : " ", str));
} else
#endif
fprintf(stderr, "%s%s", (first) ? "" : " ", str);
if (JS_IsException(argv[i])) {
js_dump_error_exc(ctx, argv[i]);
}
}
if (i+1==argc) {
u32 len = (u32) strlen(str);
if (len && (str[len-1]=='\r')) no_new_line = GF_TRUE;
}
JS_FreeCString(ctx, str);
first=GF_FALSE;
}
if (!no_new_line) {
#ifndef GPAC_DISABLE_LOG
if (!is_js_log) {
GF_LOG(logl, ltool, ("\n"));
} else
#endif
fprintf(stderr, "\n");
}
if (c_logname) JS_FreeCString(ctx, c_logname);
return JS_UNDEFINED;
}
JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_print_ex(ctx, this_val, argc, argv, GF_LOG_CONSOLE, GF_FALSE);
}
void js_dump_error_exc(JSContext *ctx, const JSValue exception_val)
{
Bool is_error;
u32 err_type = 1;
is_error = JS_IsError(ctx, exception_val);
if (!is_error) err_type = 2;
js_print_ex(ctx, JS_NULL, 1, (JSValueConst *)&exception_val, GF_LOG_SCRIPT, err_type);
if (is_error) {
JSValue val = JS_GetPropertyStr(ctx, exception_val, "stack");
if (!JS_IsUndefined(val)) {
const char *stack = JS_ToCString(ctx, val);
#ifndef GPAC_DISABLE_LOG
GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("%s\n", stack) );
#else
fprintf(stderr, "%s\n", stack);
#endif
JS_FreeCString(ctx, stack);
}
JS_FreeValue(ctx, val);
}
}
void js_dump_error(JSContext *ctx)
{
JSValue exception_val = JS_GetException(ctx);
js_dump_error_exc(ctx, exception_val);
JS_FreeValue(ctx, exception_val);
}
#ifdef GPAC_DISABLE_QJS_LIBC
void js_std_loop(JSContext *ctx)
{
while (1) {
JSContext *ctx1;
int err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) {
if (err < 0) {
js_dump_error(ctx1);
}
break;
}
}
}
#endif
#define JS_CGETSET_MAGIC_DEF_ENUM(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE|JS_PROP_ENUMERABLE, JS_DEF_CGETSET_MAGIC, magic, .u = { .getset = { .get = { .getter_magic = fgetter }, .set = { .setter_magic = fsetter } } } }
static JSClassID bitstream_class_id = 0;
static JSClassID sha1_class_id = 0;
static JSClassID file_class_id = 0;
static JSClassID fileio_class_id = 0;
static JSClassID amix_class_id = 0;
static JSClassID core_class_id = 0;
typedef struct
{
GF_BitStream *bs;
JSValue buf_ref;
} JSBitstream;
#define GET_JSBS \
JSBitstream *jbs = JS_GetOpaque(this_val, bitstream_class_id); \
GF_BitStream *bs = jbs ? jbs->bs : NULL;
static void js_gpac_free(JSRuntime *rt, void *opaque, void *ptr)
{
gf_free(ptr);
}
static void js_bs_finalize(JSRuntime *rt, JSValue this_val)
{
GET_JSBS
if (!bs) return;
gf_bs_del(bs);
JS_FreeValueRT(rt, jbs->buf_ref);
gf_free(jbs);
}
static void js_bs_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
GET_JSBS
if (!bs) return;
JS_MarkValue(rt, jbs->buf_ref, mark_func);
}
JSClassDef bitstreamClass = {
"Bitstream",
.finalizer = js_bs_finalize,
.gc_mark = js_bs_gc_mark
};
static JSValue js_bs_get_u8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u8(bs) );
}
static JSValue js_bs_get_s8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s8 v;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
v = (s8) gf_bs_read_u8(bs);
return JS_NewInt32(ctx, v );
}
static JSValue js_bs_get_u16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u16(bs) );
}
static JSValue js_bs_get_u16_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u16_le(bs) );
}
static JSValue js_bs_get_s16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s16 v;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
v = (s16) gf_bs_read_u16(bs);
return JS_NewInt32(ctx, v );
}
static JSValue js_bs_get_u24(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u24(bs) );
}
static JSValue js_bs_get_s32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s32 v;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
v = (s32) gf_bs_read_u32(bs);
return JS_NewInt32(ctx, v);
}
static JSValue js_bs_get_u32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u32(bs) );
}
static JSValue js_bs_get_u32_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_bs_read_u32_le(bs) );
}
static JSValue js_bs_get_u64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt64(ctx, gf_bs_read_u64(bs) );
}
static JSValue js_bs_get_u64_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewInt64(ctx, gf_bs_read_u64_le(bs) );
}
static JSValue js_bs_get_s64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 v;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
v = (s64) gf_bs_read_u64(bs);
return JS_NewInt64(ctx, v );
}
static JSValue js_bs_get_float(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewFloat64(ctx, gf_bs_read_float(bs) );
}
static JSValue js_bs_get_double(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
return JS_NewFloat64(ctx, gf_bs_read_double(bs) );
}
static JSValue js_bs_get_bits(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s32 nb_bits;
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &nb_bits, argv[0])) return GF_JS_EXCEPTION(ctx);
if (nb_bits<=32)
return JS_NewInt32(ctx, gf_bs_read_int(bs, nb_bits) );
return JS_NewInt64(ctx, gf_bs_read_long_int(bs, nb_bits) );
}
static JSValue js_bs_get_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
char *res;
JSValue ret;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
res = gf_bs_read_utf8(bs);
if (!res) return JS_NULL;
ret = JS_NewString(ctx, res);
gf_free(res);
return ret;
}
static JSValue js_bs_data_io(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 mode)
{
s32 nb_bytes=0;
s64 offset=0;
size_t data_size;
u8 *data=NULL;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
if (argc<1) return GF_JS_EXCEPTION(ctx);
if (JS_IsObject(argv[0])) {
data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
if (argc>1) {
if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
if ((mode==2) && (argc>2)) {
if (JS_ToInt64(ctx, &offset, argv[2])) return GF_JS_EXCEPTION(ctx);
}
}
}
if (!data) return GF_JS_EXCEPTION(ctx);
if (nb_bytes> (s32) data_size) nb_bytes = (s32) data_size;
else if (!nb_bytes) nb_bytes = (s32) data_size;
if (mode==1) {
data_size = gf_bs_write_data(bs, data, nb_bytes);
} else if (mode==2) {
GF_Err e = gf_bs_insert_data(bs, data, nb_bytes, offset);
if (e) js_throw_err(ctx, e);
return JS_UNDEFINED;
} else {
data_size = gf_bs_read_data(bs, data, nb_bytes);
}
return JS_NewInt32(ctx, (s32) data_size);
}
static JSValue js_bs_get_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_data_io(ctx, this_val, argc, argv, 0);
}
static JSValue js_bs_get_4cc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
u32 fcc = gf_bs_read_u32(bs);
return JS_NewString(ctx, gf_4cc_to_str(fcc) );
}
static JSValue js_bs_skip_bytes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s32 nb_bytes;
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &nb_bytes, argv[0])) return GF_JS_EXCEPTION(ctx);
gf_bs_skip_bytes(bs, nb_bytes);
return JS_UNDEFINED;
}
static JSValue js_bs_is_align(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
if (gf_bs_is_align(bs)) return JS_TRUE;
return JS_FALSE;
}
static JSValue js_bs_align(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
gf_bs_align(bs);
return JS_UNDEFINED;
}
static JSValue js_bs_truncate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
gf_bs_truncate(bs);
return JS_UNDEFINED;
}
static JSValue js_bs_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
gf_bs_flush(bs);
return JS_UNDEFINED;
}
static JSValue js_bs_epb_mode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
gf_bs_enable_emulation_byte_removal(bs, (JS_ToBool(ctx, argv[0])) ? GF_TRUE : GF_FALSE);
return JS_UNDEFINED;
}
static JSValue js_bs_peek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u64 byte_offset=0;
s32 nb_bits;
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &nb_bits, argv[0])) return GF_JS_EXCEPTION(ctx);
if (argc>1) {
if (JS_ToInt64(ctx, &byte_offset, argv[1])) return GF_JS_EXCEPTION(ctx);
}
return JS_NewInt32(ctx, gf_bs_peek_bits(bs, nb_bits, byte_offset));
}
static JSValue js_bs_put_val(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 mode)
{
s64 val;
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
switch (mode) {
case 0: gf_bs_write_u8(bs, (u32) val); break;
case 1: gf_bs_write_u8(bs, (u32) val); break;
case 2: gf_bs_write_u16(bs, (u32) val); break;
case 3: gf_bs_write_u16_le(bs, (u32) val); break;
case 4: gf_bs_write_u16(bs, (u32) val); break;
case 5: gf_bs_write_u24(bs, (u32) val); break;
case 6: gf_bs_write_u32(bs, (u32) val); break;
case 7: gf_bs_write_u32_le(bs, (u32) val); break;
case 8: gf_bs_write_u32(bs, (u32) val); break;
case 9: gf_bs_write_u64(bs, (u64) val); break;
case 10: gf_bs_write_u64_le(bs, (u64) val); break;
case 11: gf_bs_write_u64(bs, (u64) val); break;
}
return JS_UNDEFINED;
}
static JSValue js_bs_put_u8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 0);
}
static JSValue js_bs_put_s8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 1);
}
static JSValue js_bs_put_u16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 2);
}
static JSValue js_bs_put_u16_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 3);
}
static JSValue js_bs_put_s16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 4);
}
static JSValue js_bs_put_u24(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 5);
}
static JSValue js_bs_put_u32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 6);
}
static JSValue js_bs_put_u32_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 7);
}
static JSValue js_bs_put_s32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 8);
}
static JSValue js_bs_put_u64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 9);
}
static JSValue js_bs_put_u64_le(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 10);
}
static JSValue js_bs_put_s64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_put_val(ctx, this_val, argc, argv, 11);
}
static JSValue js_bs_put_bits(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 val;
s32 nb_bits;
GET_JSBS
if (!bs || (argc!=2)) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &nb_bits, argv[1])) return GF_JS_EXCEPTION(ctx);
if (nb_bits<=32)
gf_bs_write_int(bs, (u32) val, nb_bits);
else
gf_bs_write_long_int(bs, val, nb_bits);
return JS_UNDEFINED;
}
static JSValue js_bs_put_float(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Double val;
GET_JSBS
if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
if (JS_ToFloat64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
gf_bs_write_float(bs, (Float) val);
return JS_UNDEFINED;
}
static JSValue js_bs_put_double(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Double val;
GET_JSBS
if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
if (JS_ToFloat64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
gf_bs_write_double(bs, val);
return JS_UNDEFINED;
}
static JSValue js_bs_put_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_data_io(ctx, this_val, argc, argv, 1);
}
static JSValue js_bs_insert_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_bs_data_io(ctx, this_val, argc, argv, 2);
}
static JSValue js_bs_put_4cc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 val;
GET_JSBS
if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
const char *str = JS_ToCString(ctx, argv[0]);
if (!str || (strlen(str) != 4)) {
if (str) JS_FreeCString(ctx, str);
return GF_JS_EXCEPTION(ctx);
}
val = GF_4CC(str[0], str[1], str[2], str[3]);
JS_FreeCString(ctx, str);
gf_bs_write_u32(bs, val);
return JS_UNDEFINED;
}
static JSValue js_bs_put_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GET_JSBS
if (!bs || (argc!=1)) return GF_JS_EXCEPTION(ctx);
if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return GF_JS_EXCEPTION(ctx);
gf_bs_write_utf8(bs, str);
JS_FreeCString(ctx, str);
return JS_UNDEFINED;
}
static JSValue js_bs_get_content(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u8 *data;
u32 size;
JSValue res;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
gf_bs_get_content(bs, &data, &size);
if (data) {
res = JS_NewArrayBuffer(ctx, data, size, js_gpac_free, NULL, 0);
} else {
res = JS_NULL;
}
return res;
}
static JSValue js_bs_transfer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Bool keep_src=GF_FALSE;
GF_Err e;
JSBitstream *jssrcbs;
GET_JSBS
if (!bs || !argc) return GF_JS_EXCEPTION(ctx);
jssrcbs = JS_GetOpaque(argv[0], bitstream_class_id);
if (!jssrcbs || !jssrcbs->bs) return GF_JS_EXCEPTION(ctx);
if (argc>1) {
keep_src = JS_ToBool(ctx, argv[1]);
}
e = gf_bs_transfer(bs, jssrcbs->bs, keep_src);
if (e) return js_throw_err(ctx, e);
return JS_UNDEFINED;
}
enum
{
JS_BS_POS=0,
JS_BS_SIZE,
JS_BS_BIT_OFFSET,
JS_BS_BIT_POS,
JS_BS_AVAILABLE,
JS_BS_BITS_AVAILABLE,
JS_BS_OVERFLOW,
JS_BS_REFRESH_SIZE,
};
static JSValue js_bs_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_BS_POS:
return JS_NewInt64(ctx, gf_bs_get_position(bs));
case JS_BS_SIZE:
return JS_NewInt64(ctx, gf_bs_get_size(bs));
case JS_BS_REFRESH_SIZE:
return JS_NewInt64(ctx, gf_bs_get_refreshed_size(bs));
case JS_BS_BIT_OFFSET:
return JS_NewInt64(ctx, gf_bs_get_bit_offset(bs));
case JS_BS_BIT_POS:
return JS_NewInt64(ctx, gf_bs_get_bit_position(bs));
case JS_BS_AVAILABLE:
return JS_NewInt64(ctx, gf_bs_available(bs));
case JS_BS_BITS_AVAILABLE:
return JS_NewInt32(ctx, gf_bs_bits_available(bs));
case JS_BS_OVERFLOW:
return JS_NewBool(ctx, gf_bs_is_overflow(bs) );
}
return JS_UNDEFINED;
}
static JSValue js_bs_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
s64 ival;
GET_JSBS
if (!bs) return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_BS_POS:
if (JS_ToInt64(ctx, &ival, value)) return GF_JS_EXCEPTION(ctx);
gf_bs_seek(bs, ival);
break;
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry bitstream_funcs[] = {
JS_CGETSET_MAGIC_DEF_ENUM("pos", js_bs_prop_get, js_bs_prop_set, JS_BS_POS),
JS_CGETSET_MAGIC_DEF_ENUM("size", js_bs_prop_get, NULL, JS_BS_SIZE),
JS_CGETSET_MAGIC_DEF_ENUM("bit_offset", js_bs_prop_get, NULL, JS_BS_BIT_OFFSET),
JS_CGETSET_MAGIC_DEF_ENUM("bit_pos", js_bs_prop_get, NULL, JS_BS_BIT_POS),
JS_CGETSET_MAGIC_DEF_ENUM("available", js_bs_prop_get, NULL, JS_BS_AVAILABLE),
JS_CGETSET_MAGIC_DEF_ENUM("bits_available", js_bs_prop_get, NULL, JS_BS_BITS_AVAILABLE),
JS_CGETSET_MAGIC_DEF_ENUM("refreshed_size", js_bs_prop_get, NULL, JS_BS_REFRESH_SIZE),
JS_CGETSET_MAGIC_DEF_ENUM("overflow", js_bs_prop_get, NULL, JS_BS_OVERFLOW),
JS_CFUNC_DEF("skip", 0, js_bs_skip_bytes),
JS_CFUNC_DEF("is_align", 0, js_bs_is_align),
JS_CFUNC_DEF("align", 0, js_bs_align),
JS_CFUNC_DEF("truncate", 0, js_bs_truncate),
JS_CFUNC_DEF("peek", 0, js_bs_peek),
JS_CFUNC_DEF("flush", 0, js_bs_flush),
JS_CFUNC_DEF("epb_mode", 0, js_bs_epb_mode),
JS_CFUNC_DEF("get_u8", 0, js_bs_get_u8),
JS_CFUNC_DEF("get_s8", 0, js_bs_get_s8),
JS_CFUNC_DEF("get_u16", 0, js_bs_get_u16),
JS_CFUNC_DEF("get_u16_le", 0, js_bs_get_u16_le),
JS_CFUNC_DEF("get_s16", 0, js_bs_get_s16),
JS_CFUNC_DEF("get_u24", 0, js_bs_get_u24),
JS_CFUNC_DEF("get_u32", 0, js_bs_get_u32),
JS_CFUNC_DEF("get_u32_le", 0, js_bs_get_u32_le),
JS_CFUNC_DEF("get_s32", 0, js_bs_get_s32),
JS_CFUNC_DEF("get_u64", 0, js_bs_get_u64),
JS_CFUNC_DEF("get_u64_le", 0, js_bs_get_u64_le),
JS_CFUNC_DEF("get_s64", 0, js_bs_get_s64),
JS_CFUNC_DEF("get_bits", 0, js_bs_get_bits),
JS_CFUNC_DEF("get_string", 0, js_bs_get_string),
JS_CFUNC_DEF("get_float", 0, js_bs_get_float),
JS_CFUNC_DEF("get_double", 0, js_bs_get_double),
JS_CFUNC_DEF("get_data", 0, js_bs_get_data),
JS_CFUNC_DEF("get_4cc", 0, js_bs_get_4cc),
JS_CFUNC_DEF("put_u8", 0, js_bs_put_u8),
JS_CFUNC_DEF("put_s8", 0, js_bs_put_s8),
JS_CFUNC_DEF("put_u16", 0, js_bs_put_u16),
JS_CFUNC_DEF("put_u16_le", 0, js_bs_put_u16_le),
JS_CFUNC_DEF("put_s16", 0, js_bs_put_s16),
JS_CFUNC_DEF("put_u24", 0, js_bs_put_u24),
JS_CFUNC_DEF("put_u32", 0, js_bs_put_u32),
JS_CFUNC_DEF("put_u32_le", 0, js_bs_put_u32_le),
JS_CFUNC_DEF("put_s32", 0, js_bs_put_s32),
JS_CFUNC_DEF("put_u64", 0, js_bs_put_u64),
JS_CFUNC_DEF("put_u64_le", 0, js_bs_put_u64_le),
JS_CFUNC_DEF("put_s64", 0, js_bs_put_s64),
JS_CFUNC_DEF("put_bits", 0, js_bs_put_bits),
JS_CFUNC_DEF("put_float", 0, js_bs_put_float),
JS_CFUNC_DEF("put_double", 0, js_bs_put_double),
JS_CFUNC_DEF("put_data", 0, js_bs_put_data),
JS_CFUNC_DEF("put_4cc", 0, js_bs_put_4cc),
JS_CFUNC_DEF("put_string", 0, js_bs_put_string),
JS_CFUNC_DEF("insert_data", 0, js_bs_insert_data),
JS_CFUNC_DEF("get_content", 0, js_bs_get_content),
JS_CFUNC_DEF("transfer", 0, js_bs_transfer),
};
static JSValue bitstream_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
JSValue anobj;
JSBitstream *jbs;
GF_BitStream *bs = NULL;
if (!argc) {
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
} else if (JS_IsObject(argv[0])){
FILE *f = JS_GetOpaque(argv[0], file_class_id);
if (f) {
if ((argc>1) && JS_ToBool(ctx, argv[1])) {
bs = gf_bs_from_file(f, GF_BITSTREAM_WRITE);
} else {
bs = gf_bs_from_file(f, GF_BITSTREAM_READ);
}
} else {
u8 *data;
size_t data_size;
data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
if (!data) return GF_JS_EXCEPTION(ctx);
if ((argc>1) && JS_ToBool(ctx, argv[1])) {
bs = gf_bs_new(data, data_size, GF_BITSTREAM_WRITE);
} else {
bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ);
}
}
} else {
return GF_JS_EXCEPTION(ctx);
}
GF_SAFEALLOC(jbs, JSBitstream);
if (!jbs) {
gf_bs_del(bs);
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
anobj = JS_NewObjectClass(ctx, bitstream_class_id);
if (JS_IsException(anobj)) {
gf_bs_del(bs);
gf_free(jbs);
return anobj;
}
jbs->bs = bs;
jbs->buf_ref = JS_UNDEFINED;
if (argc)
jbs->buf_ref = JS_DupValue(ctx, argv[0]);
JS_SetOpaque(anobj, jbs);
return anobj;
}
//// RMTClient js class ////
static JSClassID js_sys_rmt_client_class_id;
static JSValue js_sys_enable_rmtws(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
Bool enable = GF_TRUE;
if (argc > 0 && JS_IsBool(argv[0])) enable = JS_ToBool(ctx, argv[0]);
gf_sys_enable_rmtws(enable);
return JS_UNDEFINED;
}
static JSValue js_sys_enable_userws(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
Bool enable = GF_TRUE;
if (argc > 0 && JS_IsBool(argv[0])) enable = JS_ToBool(ctx, argv[0]);
gf_sys_enable_userws(enable);
return JS_UNDEFINED;
}
static void js_sys_rmt_client_finalizer(JSRuntime *rt, JSValue val) {
RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id);
if (!client) return;
JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client);
GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_client_finalizer client %p task %p\n", __FILE__, __LINE__, client, task));
gf_rmt_client_set_on_data_cbk(client, NULL, NULL);
JS_Sys_Task* deltask = gf_rmt_client_get_on_del_task(client);
gf_rmt_client_set_on_del_cbk(client, NULL, NULL);
JS_SetOpaque(val, NULL);
if (task && task->type == RMT_CALLBACK_JS) {
JS_FreeValue(task->ctx, task->fun);
JS_FreeValue(task->ctx, task->_obj);
gf_free(task);
}
if (deltask && deltask->type == RMT_CALLBACK_JS) {
JS_FreeValue(deltask->ctx, deltask->fun);
JS_FreeValue(deltask->ctx, deltask->_obj);
gf_free(deltask);
}
}
static void js_sys_rmt_client_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
RMT_ClientCtx* client = JS_GetOpaque(val, js_sys_rmt_client_class_id);
if (!client) return;
// JS_Sys_Task* task = gf_rmt_client_get_on_data_task(client);
// if (task && task->type == RMT_CALLBACK_JS) {
// JS_MarkValue(rt, task->fun, mark_func);
// JS_MarkValue(rt, task->_obj, mark_func);
// }
// task = gf_rmt_client_get_on_del_task(client);
// if (task && task->type == RMT_CALLBACK_JS) {
// JS_MarkValue(rt, task->fun, mark_func);
// JS_MarkValue(rt, task->_obj, mark_func);
// }
}
static JSClassDef js_sys_rmt_client_class = {
"RMTClient",
.finalizer = js_sys_rmt_client_finalizer,
.gc_mark = js_sys_rmt_client_gc_mark
};
enum {
JS_SYS_RMT_CLIENT_ON_DATA,
JS_SYS_RMT_CLIENT_ON_CLOSE,
JS_SYS_RMT_CLIENT_PEER_ADDRESS
};
static void js_sys_rmt_run_task(JS_Sys_Task* task, JSValue arg) {
if (!task || task->type != RMT_CALLBACK_JS )
return;
int argc = JS_IsUndefined(arg) ? 0 : 1;
gf_js_lock(task->ctx, GF_TRUE);
JSValue ret = JS_Call(task->ctx, task->fun, task->_obj, argc, &arg);
if (JS_IsException(ret)) {
js_dump_error(task->ctx);
}
JS_FreeValue(task->ctx, ret);
JS_FreeValue(task->ctx, arg);
js_std_loop(task->ctx);
gf_js_lock(task->ctx, GF_FALSE);
}
static void js_sys_rmt_on_del_client(void *udta) {
if (udta) {
JS_Sys_Task *task = udta;
if (task->type == RMT_CALLBACK_JS && task->ctx && !JS_IsUndefined(task->_obj)) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d deleting opaque obj from task %p\n", __FILE__, __LINE__,task));
if (JS_IsFunction(task->ctx, task->fun))
js_sys_rmt_run_task(task, JS_UNDEFINED);
js_sys_rmt_client_finalizer(NULL, task->_obj);
}
}
}
static void js_sys_rmt_client_on_data(void *udta, const u8* payload, u64 size, Bool is_binary) {
JS_Sys_Task *task = udta;
if (!task || task->type != RMT_CALLBACK_JS) return;
JSValue arg;
if (is_binary) {
arg = JS_NewArrayBufferCopy(task->ctx, payload, size);
} else {
arg = JS_NewStringLen(task->ctx, payload, size);
}
js_sys_rmt_run_task(task, arg);
}
static JSValue js_sys_rmt_client_prop_get(JSContext *ctx, JSValueConst this_val, int magic) {
RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
if (!client)
return JS_UNDEFINED;
switch (magic) {
case JS_SYS_RMT_CLIENT_PEER_ADDRESS:;
const char* peer_address = gf_rmt_get_peer_address(client);
if (peer_address) {
return JS_NewString(ctx, peer_address);
}
break;
}
return JS_UNDEFINED;
}
static JSValue js_sys_rmt_client_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) {
RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
if (!client)
return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_SYS_RMT_CLIENT_ON_DATA:;
JS_Sys_Task* oldtask = (JS_Sys_Task*) gf_rmt_client_get_on_data_task(client);
if (oldtask && oldtask->type == RMT_CALLBACK_JS) {
gf_rmt_client_set_on_data_cbk(client, NULL, NULL);
JS_FreeValue(ctx, oldtask->fun);
JS_FreeValue(ctx, oldtask->_obj);
gf_free(oldtask);
}
if (JS_IsFunction(ctx, value)) {
JS_Sys_Task *task;
GF_SAFEALLOC(task, JS_Sys_Task);
if (!task) return GF_JS_EXCEPTION(ctx);
task->type = RMT_CALLBACK_JS;
task->ctx = ctx;
task->fun = JS_DupValue(ctx, value);
task->_obj = JS_DupValue(ctx, this_val);
gf_rmt_client_set_on_data_cbk(client, task, js_sys_rmt_client_on_data);
}
break;
case JS_SYS_RMT_CLIENT_ON_CLOSE:
if (JS_IsUndefined(value) || JS_IsNull(value)) {
JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client);
if (task && task->type == RMT_CALLBACK_JS) {
// reset the js function but keep the ref to the client for on_delete
JS_FreeValue(ctx, task->fun);
task->fun = JS_UNDEFINED;
}
}
if (JS_IsFunction(ctx, value)) {
JS_Sys_Task *task = gf_rmt_client_get_on_del_task(client);
if (task && task->type == RMT_CALLBACK_JS) {
JS_FreeValue(ctx, task->fun);
JS_FreeValue(ctx, task->_obj);
}
else
GF_SAFEALLOC(task, JS_Sys_Task);
if (!task) return GF_JS_EXCEPTION(ctx);
task->type = RMT_CALLBACK_JS;
task->ctx = ctx;
task->fun = JS_DupValue(ctx, value);
task->_obj = JS_DupValue(ctx, this_val);
gf_rmt_client_set_on_del_cbk(client, task, js_sys_rmt_on_del_client);
}
break;
}
return JS_UNDEFINED;
}
static JSValue js_sys_rmt_client_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
RMT_ClientCtx* client = JS_GetOpaque(this_val, js_sys_rmt_client_class_id);
if (!client)
return GF_JS_EXCEPTION(ctx);
if (!argc)
return JS_UNDEFINED;
if (JS_IsString(argv[0])) {
const char *msg = JS_ToCString(ctx, argv[0]);
GF_Err e = gf_rmt_client_send_to_ws(client, msg, strlen(msg), GF_FALSE);
GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("[RMTWS] sent msg <%s> to client <%p> returned <%d>\n", msg, client, e));
JS_FreeCString(ctx, msg);
}
else if (JS_IsArrayBuffer(ctx, argv[0])) {
size_t bufsize=0;
u8* buf = JS_GetArrayBuffer(ctx, &bufsize, argv[0]);
if (buf && bufsize) {
GF_Err e = gf_rmt_client_send_to_ws(client, buf, bufsize, GF_TRUE);
GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("[RMTWS] sent binary msg <"LLU" bytes> to client <%p> returned <%d>\n", (u64) bufsize, client, e));
}
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_sys_rmt_client_funcs[] = {
JS_CGETSET_MAGIC_DEF("on_data", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_DATA),
JS_CGETSET_MAGIC_DEF("on_close", NULL, js_sys_rmt_client_prop_set, JS_SYS_RMT_CLIENT_ON_CLOSE),
JS_CGETSET_MAGIC_DEF("peer_address", js_sys_rmt_client_prop_get, NULL, JS_SYS_RMT_CLIENT_PEER_ADDRESS),
JS_CFUNC_DEF("send", 1, js_sys_rmt_client_send)
};
static void js_sys_rmt_on_new_client(void *udta, void* new_client) {
JS_Sys_Task *task = udta;
if (!task) return;
JSValue obj = JS_NewObjectClass(task->ctx, js_sys_rmt_client_class_id);
if (JS_IsException(obj)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("%s:%d obj JS_IsException\n", __FILE__, __LINE__));
}
JS_SetOpaque(obj, new_client);
JS_Sys_Task *deltask;
GF_SAFEALLOC(deltask, JS_Sys_Task);
deltask->type = RMT_CALLBACK_JS;
deltask->ctx = task->ctx;
deltask->_obj = JS_DupValue(task->ctx, obj);
gf_rmt_client_set_on_del_cbk((RMT_ClientCtx*)new_client, (void*)deltask, js_sys_rmt_on_del_client);
GF_LOG(GF_LOG_DEBUG, GF_LOG_RMTWS, ("%s:%d js_sys_rmt_on_new_client calling task %p with opaque %p\n", __FILE__, __LINE__, task, new_client));
js_sys_rmt_run_task(task, obj);
}
enum
{
JS_SYS_NB_CORES = 1,
JS_SYS_SAMPLE_DUR,
JS_SYS_TOTAL_CPU,
JS_SYS_PROCESS_CPU,
JS_SYS_TOTAL_CPU_DIFF,
JS_SYS_PROCESS_CPU_DIFF,
JS_SYS_CPU_IDLE,
JS_SYS_TOTAL_CPU_USAGE,
JS_SYS_PROCESS_CPU_USAGE,
JS_SYS_PID,
JS_SYS_THREADS,
JS_SYS_PROCESS_MEM,
JS_SYS_TOTAL_MEM,
JS_SYS_TOTAL_MEM_AVAIL,
JS_SYS_GPAC_MEM,
JS_SYS_BATTERY_ON,
JS_SYS_BATTERY_CHARGE,
JS_SYS_BATTERY_PERCENT,
JS_SYS_BATTERY_LIFETIME,
JS_SYS_BATTERY_LIFETIME_FULL,
JS_SYS_HOSTNAME,
JS_SYS_LAST_WORK_DIR,
JS_SYS_TEST_MODE,
JS_SYS_OLD_ARCH,
JS_SYS_LOG_COLOR,
JS_SYS_QUIET,
JS_SYS_USERNAME,
JS_SYS_TIMEZONE,
JS_SYS_FILES_OPEN,
JS_SYS_CACHE_DIR,
JS_SYS_SHARED_DIR,
JS_SYS_VERSION,
JS_SYS_VERSION_FULL,
JS_SYS_COPYRIGHT,
JS_SYS_V_MAJOR,
JS_SYS_V_MINOR,
JS_SYS_V_MICRO,
JS_SYS_LOGX,
JS_SYS_RMT_ON_NEW_CLIENT,
JS_SYS_USERWS_ON_NEW_CLIENT,
JS_SYS_ON_LOG,
};
#define RTI_REFRESH_MS 200
static JSValue js_sys_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
u32 uval;
Bool bval;
const char *res;
GF_SystemRTInfo rti;
if (magic<=JS_SYS_GPAC_MEM) {
gf_sys_get_rti(RTI_REFRESH_MS, &rti, 0);
switch (magic) {
case JS_SYS_NB_CORES:
return JS_NewInt32(ctx, rti.nb_cores);
case JS_SYS_SAMPLE_DUR:
return JS_NewInt32(ctx, rti.sampling_period_duration);
case JS_SYS_TOTAL_CPU:
return JS_NewInt32(ctx, rti.total_cpu_time);
case JS_SYS_PROCESS_CPU:
return JS_NewInt32(ctx, rti.process_cpu_time);
case JS_SYS_TOTAL_CPU_DIFF:
return JS_NewInt32(ctx, rti.total_cpu_time_diff);
case JS_SYS_PROCESS_CPU_DIFF:
return JS_NewInt32(ctx, rti.process_cpu_time_diff);
case JS_SYS_CPU_IDLE:
return JS_NewInt32(ctx, rti.cpu_idle_time);
case JS_SYS_TOTAL_CPU_USAGE:
return JS_NewInt32(ctx, rti.total_cpu_time);
case JS_SYS_PROCESS_CPU_USAGE:
return JS_NewInt32(ctx, rti.process_cpu_usage);
case JS_SYS_PID:
return JS_NewInt32(ctx, rti.pid);
case JS_SYS_THREADS:
return JS_NewInt32(ctx, rti.thread_count);
case JS_SYS_PROCESS_MEM:
return JS_NewInt64(ctx, rti.process_memory);
case JS_SYS_TOTAL_MEM:
return JS_NewInt64(ctx, rti.physical_memory);
case JS_SYS_TOTAL_MEM_AVAIL:
return JS_NewInt64(ctx, rti.physical_memory_avail);
case JS_SYS_GPAC_MEM:
return JS_NewInt64(ctx, rti.gpac_memory);
}
return JS_UNDEFINED;
}
switch (magic) {
case JS_SYS_BATTERY_ON:
bval = GF_FALSE;
gf_sys_get_battery_state(&bval, NULL, NULL, NULL, NULL);
return JS_NewBool(ctx, bval);
case JS_SYS_BATTERY_CHARGE:
uval = 0;
gf_sys_get_battery_state(NULL, &uval, NULL, NULL, NULL);
return JS_NewBool(ctx, uval ? 1 : 0);
case JS_SYS_BATTERY_PERCENT:
uval=0;
gf_sys_get_battery_state(NULL, NULL, &uval, NULL, NULL);
return JS_NewInt32(ctx, uval);
case JS_SYS_BATTERY_LIFETIME:
uval=0;
gf_sys_get_battery_state(NULL, NULL, NULL, &uval, NULL);
return JS_NewInt32(ctx, uval);
case JS_SYS_BATTERY_LIFETIME_FULL:
uval=0;
gf_sys_get_battery_state(NULL, NULL, NULL, NULL, &uval);
return JS_NewInt32(ctx, uval);
case JS_SYS_HOSTNAME:
#ifdef GPAC_DISABLE_NETWORK
return JS_NewString(ctx, "localhost");
#else
{
char hostname[100];
gf_sk_get_host_name((char*)hostname);
return JS_NewString(ctx, hostname);
}
#endif
break;
case JS_SYS_LAST_WORK_DIR:
res = gf_opts_get_key("core", "last-dir");
#ifdef WIN32
if (!res) res = getenv("HOMEPATH");
if (!res) res = "C:\\";
#elif defined(GPAC_CONFIG_DARWIN)
if (!res) res = getenv("HOME");
if (!res) res = "/Users";
#elif defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS)
if (!res) res = (char *) gf_opts_get_key("core", "docs-dir");
#else
if (!res) res = getenv("HOME");
if (!res) res = "/home/";
#endif
return JS_NewString(ctx, res);
case JS_SYS_TEST_MODE:
return JS_NewBool(ctx, gf_sys_is_test_mode() );
case JS_SYS_OLD_ARCH:
return JS_NewBool(ctx, gf_sys_old_arch_compat() );
case JS_SYS_LOG_COLOR:
#ifdef GPAC_DISABLE_LOG
return JS_NewBool(ctx, GF_FALSE );
#else
return JS_NewBool(ctx, gf_log_use_color() );
#endif
case JS_SYS_QUIET:
return JS_NewBool(ctx, gf_sys_is_quiet() );
case JS_SYS_USERNAME:
{
char username[1024];
gf_get_user_name((char*)username);
return JS_NewString(ctx, username);
}
case JS_SYS_TIMEZONE:
return JS_NewInt32(ctx, gf_net_get_timezone());
case JS_SYS_FILES_OPEN:
return JS_NewInt32(ctx, gf_file_handles_count());
case JS_SYS_CACHE_DIR:
res = gf_get_default_cache_directory();
if (res) return JS_NewString(ctx, res);
return JS_NULL;
case JS_SYS_SHARED_DIR:
{
char szDir[GF_MAX_PATH];
if (! gf_opts_default_shared_directory(szDir)) return JS_NULL;
return JS_NewString(ctx, szDir);
}
case JS_SYS_VERSION:
return JS_NewString(ctx, GPAC_VERSION );
case JS_SYS_VERSION_FULL:
return JS_NewString(ctx, gf_gpac_version() );
case JS_SYS_COPYRIGHT:
return JS_NewString(ctx, gf_gpac_copyright() );
case JS_SYS_V_MAJOR:
return JS_NewInt32(ctx, GPAC_VERSION_MAJOR );
case JS_SYS_V_MINOR:
return JS_NewInt32(ctx, GPAC_VERSION_MINOR );
case JS_SYS_V_MICRO:
return JS_NewInt32(ctx, GPAC_VERSION_MICRO );
case JS_SYS_LOGX:
return gpac_use_logx ? JS_TRUE : JS_FALSE;
}
return JS_UNDEFINED;
}
static void js_reset_logs(JSContext *ctx)
{
if (ctx != js_rt->log_ctx) return;
gf_js_lock(ctx, GF_TRUE);
js_rt->js_log_buf_size = 0;
//WARNING - libc mem, not gf_alloc
if (js_rt->js_log_buf) free(js_rt->js_log_buf);
js_rt->js_log_buf = NULL;
if (js_rt->js_orig_logs) {
gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
gf_free(js_rt->js_orig_logs);
js_rt->js_orig_logs = NULL;
}
if (!js_rt->log_ctx) {
gf_js_lock(ctx, GF_FALSE);
return;
}
JS_FreeValue(js_rt->log_ctx, js_rt->log_fun);
js_rt->log_fun = JS_UNDEFINED;
JS_FreeValue(js_rt->log_ctx, js_rt->log_obj);
js_rt->log_obj = JS_UNDEFINED;
gf_log_set_callback(NULL, js_rt->prev_log_cbk);
js_rt->log_ctx = NULL;
gf_js_lock(ctx, GF_FALSE);
}
void *gf_logs_get_thread_tag(u32 *tag_type, u32 *o_th_id);
JSValue gf_fs_get_script_data(JSContext *ctx, void *udta, u32 tag_type);
extern GF_Mutex *logs_mx;
static void js_log_cbk(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist)
{
JSContext *ctx = js_rt->log_ctx;
if (!ctx) return;
JSValue args[5];
u32 nb_args = 3;
//in case mutex@debug is set, unlock logs mutex before locking JS runtime
//not doing so could create a deadlock while unlocking logs_mx or the JS runtime
gf_mx_v(logs_mx);
//if context is locked by another thread and we cannot grab it, drop the log
//this typically happens when audio thread releases a packet (lock tasks_mx) and the main thread
//is querying through JS the filter stats which requires tasks_mx access
//usually only happens if mutex logs are on but we do the check for all tools
if (!gf_js_try_lock(ctx)) {
gf_mx_p(logs_mx);
return;
}
args[0] = JS_NewString(ctx, gf_log_tool_name(log_tool) );
args[1] = JS_NewInt32(ctx, log_level);
va_list vlist_tmp;
va_copy(vlist_tmp, vlist);
u32 len = vsnprintf(NULL, 0, fmt, vlist_tmp);
va_end(vlist_tmp);
if (js_rt->js_log_buf_size < len+2) {
js_rt->js_log_buf_size = len+2;
//WARNING - libc mem, not gf_alloc
js_rt->js_log_buf = realloc(js_rt->js_log_buf, js_rt->js_log_buf_size);
}
vsprintf(js_rt->js_log_buf, fmt, vlist);
args[2] = JS_NewString(ctx, js_rt->js_log_buf);
if (gpac_use_logx) {
u32 tag_type=0, th_id=0;
void *udta = gf_logs_get_thread_tag(&tag_type, &th_id);
args[3] = JS_NewInt32(ctx, th_id);
nb_args = 4;
if (udta) {
args[4] = gf_fs_get_script_data(ctx, udta, tag_type);
if (! JS_IsNull( args[4] )) {
nb_args = 5;
}
}
}
JSValue res = JS_Call(ctx, js_rt->log_fun, js_rt->log_obj, nb_args, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
JS_FreeValue(ctx, args[2]);
if (nb_args>=4) {
JS_FreeValue(ctx, args[3]);
if (nb_args==5)
JS_FreeValue(ctx, args[4]);
}
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
//and relock
gf_mx_p(logs_mx);
}
static JSValue js_sys_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
const char *prop_val;
switch (magic) {
case JS_SYS_LAST_WORK_DIR:
if (!JS_IsString(value)) return GF_JS_EXCEPTION(ctx);
prop_val = JS_ToCString(ctx, value);
gf_opts_set_key("core", "last-dir", prop_val);
JS_FreeCString(ctx, prop_val);
break;
case JS_SYS_LOGX:
gpac_use_logx = JS_ToBool(ctx, value) ? GF_TRUE : GF_FALSE;
break;
case JS_SYS_RMT_ON_NEW_CLIENT:
case JS_SYS_USERWS_ON_NEW_CLIENT:;
RMT_WS* rmt = NULL;
if (magic == JS_SYS_RMT_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_rmtws();
if (magic == JS_SYS_USERWS_ON_NEW_CLIENT) rmt = (RMT_WS*) gf_sys_get_userws();
if (!rmt)
break;
if (JS_IsUndefined(value) || JS_IsNull(value)) {
JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt);
if (task && task->type == RMT_CALLBACK_JS) {
gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);
JS_FreeValue(ctx, task->fun);
JS_FreeValue(ctx, task->_obj);
gf_free(task);
}
return JS_UNDEFINED;
}
if (JS_IsFunction(ctx, value)) {
JS_Sys_Task* task = (JS_Sys_Task*) gf_rmt_get_on_new_client_task(rmt);
if (task && task->type == RMT_CALLBACK_JS) {
//not allowed
if (magic == JS_SYS_RMT_ON_NEW_CLIENT) {
GF_LOG(GF_LOG_WARNING, GF_LOG_RMTWS, ("Attempting to redefine rmt_on_new_client ignored. Set it to null first to reset.\n", __FILE__, __LINE__));
return JS_UNDEFINED;
} else {
gf_rmt_set_on_new_client_cbk(rmt, NULL, NULL);
JS_FreeValue(ctx, task->fun);
JS_FreeValue(ctx, task->_obj);
gf_free(task);
}
}
GF_SAFEALLOC(task, JS_Sys_Task);
if (!task) return GF_JS_EXCEPTION(ctx);
task->type = RMT_CALLBACK_JS;
task->ctx = ctx;
task->fun = JS_DupValue(ctx, value);
task->_obj = JS_DupValue(ctx, this_val);
gf_rmt_set_on_new_client_cbk(rmt, task, js_sys_rmt_on_new_client);
}
break;
case JS_SYS_ON_LOG:
//do not allow override of log call, only assign if non-null
if (!JS_IsUndefined(js_rt->log_fun) && !JS_IsNull(js_rt->log_fun)) {
if (JS_IsUndefined(value) || JS_IsNull(value)) {
return JS_UNDEFINED;
}
}
JS_FreeValue(ctx, js_rt->log_fun);
js_rt->log_fun = JS_UNDEFINED;
JS_FreeValue(ctx, js_rt->log_obj);
js_rt->log_obj = JS_UNDEFINED;
js_rt->log_ctx = NULL;
is_js_log = GF_FALSE;
if (JS_IsUndefined(value) || JS_IsNull(value)) {
if (js_rt->js_orig_logs) {
gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
gf_free(js_rt->js_orig_logs);
js_rt->js_orig_logs = NULL;
}
gf_log_set_callback(NULL, js_rt->prev_log_cbk);
} else if (JS_IsFunction(ctx, value)) {
js_rt->log_fun = JS_DupValue(ctx, value);
js_rt->log_obj = JS_DupValue(ctx, this_val);
js_rt->prev_user_log_cbk = user_log_cbk;
js_rt->prev_log_cbk = gf_log_set_callback(ctx, js_log_cbk);
js_rt->log_ctx = ctx;
is_js_log = GF_TRUE;
}
break;
}
return JS_UNDEFINED;
}
static JSValue js_sys_set_arg_used(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 idx;
Bool used;
if (argc < 2) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &idx, argv[0]))
return GF_JS_EXCEPTION(ctx);
used = JS_ToBool(ctx, argv[1]);
gf_sys_mark_arg_used(idx, used);
return JS_UNDEFINED;
}
typedef struct
{
JSContext *c;
JSValue array;
Bool is_dir;
u32 idx;
} enum_dir_cbk;
static Bool js_enum_dir_fct(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info)
{
u32 i, len;
char *sep;
JSValue s;
JSValue obj;
enum_dir_cbk *cbk = (enum_dir_cbk*)cbck;
if (file_name && (file_name[0]=='.')) return 0;
obj = JS_NewObject(cbk->c);
JS_SetPropertyStr(cbk->c, obj, "name", JS_NewString(cbk->c, file_name) );
sep=NULL;
len = (u32) strlen(file_path);
for (i=0; i<len; i++) {
sep = strchr("/\\", file_path[len-i-1]);
if (sep) {
sep = file_path+len-i-1;
break;
}
}
if (sep) {
sep[0] = '/';
sep[1] = 0;
s = JS_NewString(cbk->c, file_path);
} else {
s = JS_NewString(cbk->c, file_path);
}
JS_SetPropertyStr(cbk->c, obj, "path", s);
JS_SetPropertyStr(cbk->c, obj, "directory", JS_NewBool(cbk->c, cbk->is_dir) );
JS_SetPropertyStr(cbk->c, obj, "drive", JS_NewBool(cbk->c, file_info->drive) );
JS_SetPropertyStr(cbk->c, obj, "hidden", JS_NewBool(cbk->c, file_info->hidden) );
JS_SetPropertyStr(cbk->c, obj, "system", JS_NewBool(cbk->c, file_info->system) );
JS_SetPropertyStr(cbk->c, obj, "size", JS_NewInt64(cbk->c, file_info->size) );
JS_SetPropertyStr(cbk->c, obj, "last_modified", JS_NewInt64(cbk->c, file_info->last_modified) );
JS_SetPropertyUint32(cbk->c, cbk->array, cbk->idx, obj);
cbk->idx++;
return 0;
}
static JSValue js_sys_enum_directory(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_Err err;
enum_dir_cbk cbk;
char *url = NULL;
const char *dir = NULL;
const char *filter = NULL;
Bool dir_only = 0;
Bool browse_root = 0;
if ((argc >= 1) && JS_IsString(argv[0])) {
dir = JS_ToCString(ctx, argv[0]);
if (!strcmp(dir, "/")) browse_root = 1;
}
if ((argc >= 2) && JS_IsString(argv[1])) {
filter = JS_ToCString(ctx, argv[1]);
if (!strcmp(filter, "dir")) {
dir_only = 1;
filter = NULL;
} else if (!strlen(filter)) {
JS_FreeCString(ctx, filter);
filter=NULL;
}
}
if ((argc >= 3) && JS_IsBool(argv[2])) {
if (JS_ToBool(ctx, argv[2])) {
url = gf_url_concatenate(dir, "..");
if (dir && ( !strcmp(url, "..") || (url[0]==0) )) {
if ((dir[1]==':') && ((dir[2]=='/') || (dir[2]=='\\')) ) browse_root = 1;
else if (!strcmp(dir, "/")) browse_root = 1;
}
if (!strcmp(url, "/")) browse_root = 1;
}
}
if ( (!dir || !strlen(dir) ) && (!url || !strlen(url))) browse_root = 1;
if (browse_root) {
cbk.c = ctx;
cbk.idx = 0;
cbk.array = JS_NewArray(ctx);
if (JS_IsException(cbk.array)) return cbk.array;
cbk.is_dir = 1;
gf_enum_directory("/", 1, js_enum_dir_fct, &cbk, NULL);
if (url) gf_free(url);
JS_FreeCString(ctx, dir);
JS_FreeCString(ctx, filter);
return cbk.array;
}
cbk.c = ctx;
cbk.array = JS_NewArray(ctx);
if (JS_IsException(cbk.array)) return cbk.array;
cbk.is_dir = 1;
cbk.idx = 0;
#if 0
compositor = scenejs_get_compositor(ctx, this_val);
/*concatenate with service url*/
char *an_url = gf_url_concatenate(compositor->root_scene->root_od->scene_ns->url, url ? url : dir);
if (an_url) {
gf_free(url);
url = an_url;
}
#endif
err = gf_enum_directory(url ? url : dir, 1, js_enum_dir_fct, &cbk, filter);
if (err) return GF_JS_EXCEPTION(ctx);
if (!dir_only) {
cbk.is_dir = 0;
err = gf_enum_directory(url ? url : dir, 0, js_enum_dir_fct, &cbk, filter);
if (err) return GF_JS_EXCEPTION(ctx);
}
if (url) gf_free(url);
JS_FreeCString(ctx, dir);
JS_FreeCString(ctx, filter);
return cbk.array;
}
static JSValue js_sys_error_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_Err e;
if (argc < 1) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, (int32_t *) &e, argv[0]))
return GF_JS_EXCEPTION(ctx);
return JS_NewString(ctx, gf_error_to_string(e) );
}
static JSValue js_sys_prompt_input(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
char in_char[2];
if (!gf_prompt_has_input())
return JS_NULL;
in_char[0] = gf_prompt_get_char();
in_char[1] = 0;
return JS_NewString(ctx, in_char);
}
static JSValue js_sys_prompt_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
char input[4096], *read;
u32 len;
//#ifdef GPAC_ENABLE_COVERAGE
if (argc) {
return JS_NewString(ctx, "Coverage OK");
}
//#endif // GPAC_ENABLE_COVERAGE
read = fgets(input, 4095, stdin);
if (!read) return JS_NULL;
input[4095]=0;
len = (u32) strlen(input);
if (len && (input[len-1] == '\n')) {
input[len-1] = 0;
len--;
}
if (!len) return JS_NULL;
return JS_NewString(ctx, input);
}
static JSValue js_sys_prompt_echo_off(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Bool echo_off;
if (!argc) return GF_JS_EXCEPTION(ctx);
echo_off = JS_ToBool(ctx, argv[0]);
if (argc<2)
gf_prompt_set_echo_off(echo_off);
return JS_UNDEFINED;
}
static JSValue js_sys_prompt_code(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s32 code;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &code, argv[0])) return GF_JS_EXCEPTION(ctx);
gf_sys_set_console_code(stderr, code);
return JS_UNDEFINED;
}
static JSValue js_sys_prompt_size(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 w, h;
JSValue res;
GF_Err e = gf_prompt_get_size(&w, &h);
if (e) return JS_NULL;
res = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, res, "w", JS_NewInt32(ctx, w));
JS_SetPropertyStr(ctx, res, "h", JS_NewInt32(ctx, h));
return res;
}
const char *gf_dom_get_friendly_name(u32 key_identifier);
static JSValue js_sys_keyname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifndef GPAC_DISABLE_SVG
u32 code;
if (JS_ToInt32(ctx, &code, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewString(ctx, gf_dom_get_friendly_name(code));
#else
return js_throw_err(ctx, GF_NOT_SUPPORTED);
#endif
}
GF_EventType gf_dom_event_type_by_name(const char *name);
static JSValue js_sys_evt_by_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifndef GPAC_DISABLE_SVG
if (!argc) return GF_JS_EXCEPTION(ctx);
const char *name = JS_ToCString(ctx, argv[0]);
if (!name) return GF_JS_EXCEPTION(ctx);
JSValue res = JS_NewInt32(ctx, gf_dom_event_type_by_name(name));
JS_FreeCString(ctx, name);
return res;
#else
return js_throw_err(ctx, GF_NOT_SUPPORTED);
#endif
}
static JSValue js_sys_gc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JS_RunGC(JS_GetRuntime(ctx));
return JS_UNDEFINED;
}
static JSValue js_sys_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return JS_NewInt32(ctx, gf_sys_clock() );
}
static JSValue js_sys_clock_high_res(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return JS_NewInt64(ctx, gf_sys_clock_high_res() );
}
static JSValue js_sys_4cc_to_str(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u64 val;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewString(ctx, gf_4cc_to_str((u32) val) );
}
static JSValue js_sys_rand_init(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Bool reset=GF_FALSE;
if (argc) reset = JS_ToBool(ctx, argv[0]);
gf_rand_init(reset);
return JS_UNDEFINED;
}
static JSValue js_sys_rand(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return JS_NewInt32(ctx, gf_rand());
}
static JSValue js_sys_rand64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u64 lrand = (u64) gf_rand();
lrand<<=32;
lrand |= gf_rand();
return JS_NewInt64(ctx, lrand);
}
static JSValue js_sys_getenv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *str, *val;
if (!argc) return GF_JS_EXCEPTION(ctx);
str = JS_ToCString(ctx, argv[0]);
if (!str) return GF_JS_EXCEPTION(ctx);
val = getenv(str);
JS_FreeCString(ctx, str);
return val ? JS_NewString(ctx, val) : JS_NULL;
}
static JSValue js_sys_get_utc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 y, mo, d, h, m, s;
if (!argc) return JS_NewInt64(ctx, gf_net_get_utc() );
if (argc==1) {
u64 time;
const char *date = JS_ToCString(ctx, argv[0]);
if (!date) return GF_JS_EXCEPTION(ctx);
time = gf_net_parse_date(date);
JS_FreeCString(ctx, date);
return JS_NewInt64(ctx, time);
}
if (argc != 6) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &y, argv[0])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &mo, argv[1])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &d, argv[2])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &h, argv[3])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &m, argv[4])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &s, argv[5])) return GF_JS_EXCEPTION(ctx);
return JS_NewInt64(ctx, gf_net_get_utc_ts(y, mo, d, h, m, s) );
}
static JSValue js_sys_get_ntp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 sec, frac;
gf_net_get_ntp(&sec, &frac);
JSValue ret = JS_NewObject(ctx);
if (JS_IsException(ret)) return ret;
JS_SetPropertyStr(ctx, ret, "n", JS_NewInt64(ctx, sec));
JS_SetPropertyStr(ctx, ret, "d", JS_NewInt64(ctx, frac));
return ret;
}
static JSValue js_sys_ntp_shift(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSValue v;
u64 sec, frac, shift;
s64 frac_usec;
if ((argc<2) || !JS_IsObject(argv[0]))
return GF_JS_EXCEPTION(ctx);
v = JS_GetPropertyStr(ctx, argv[0], "n");
if (JS_IsNull(v)) return GF_JS_EXCEPTION(ctx);
JS_ToInt64(ctx, &sec, v);
JS_FreeValue(ctx, v);
v = JS_GetPropertyStr(ctx, argv[0], "d");
if (JS_IsNull(v)) return GF_JS_EXCEPTION(ctx);
JS_ToInt64(ctx, &frac, v);
JS_FreeValue(ctx, v);
JS_ToInt64(ctx, &shift, argv[1]);
frac_usec = (s64) (frac * 1000000) / 0xFFFFFFFFULL;
frac_usec += shift;
while (frac_usec<0) {
frac_usec += 1000000;
sec -= 1;
}
while (frac_usec>1000000) {
frac_usec -= 1000000;
sec += 1;
}
frac = (frac_usec * 0xFFFFFFFFULL) / 1000000;
v = JS_NewObject(ctx);
if (JS_IsException(v)) return v;
JS_SetPropertyStr(ctx, v, "n", JS_NewInt64(ctx, sec));
JS_SetPropertyStr(ctx, v, "d", JS_NewInt64(ctx, frac));
return v;
}
static JSValue js_sys_sleep(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 sleep_for=0;
if (argc==1) {
if (JS_ToInt32(ctx, &sleep_for, argv[0]))
return GF_JS_EXCEPTION(ctx);
}
gf_sleep(sleep_for);
return JS_UNDEFINED;
}
static JSValue js_sys_exit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 rval=0;
if (argc==2) {
return JS_UNDEFINED;
}
if (argc==1) {
if (JS_ToInt32(ctx, &rval, argv[0]))
return GF_JS_EXCEPTION(ctx);
}
exit(rval);
return JS_UNDEFINED;
}
static JSValue js_sys_crc32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const u8 *data;
size_t data_size;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_IsString(argv[0])) {
u32 crc=0;
const char *str = JS_ToCString(ctx, argv[0]);
if (str) {
crc = gf_crc_32(str, (u32) strlen(str) );
JS_FreeCString(ctx, str);
}
return JS_NewInt32(ctx, crc );
} else {
data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
if (!data) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_crc_32(data, (u32) data_size) );
}
return GF_JS_EXCEPTION(ctx);
}
static JSValue js_sys_sha1(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u8 csum[GF_SHA1_DIGEST_SIZE];
const u8 *data;
size_t data_size;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_IsString(argv[0])) {
const char *filename = JS_ToCString(ctx, argv[0]);
if (!filename) return GF_JS_EXCEPTION(ctx);
gf_sha1_file(filename, csum);
JS_FreeCString(ctx, filename);
return JS_NewArrayBufferCopy(ctx, csum, GF_SHA1_DIGEST_SIZE);
} else if (JS_IsObject(argv[0])) {
data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
if (!data) return GF_JS_EXCEPTION(ctx);
gf_sha1_csum((u8 *) data, (u32) data_size, csum);
return JS_NewArrayBufferCopy(ctx, csum, GF_SHA1_DIGEST_SIZE);
}
return GF_JS_EXCEPTION(ctx);
}
static JSValue js_sys_file_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *filename;
u8 *data;
u32 data_size;
Bool as_utf8 = GF_FALSE;
GF_Err e;
JSValue res;
if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
filename = JS_ToCString(ctx, argv[0]);
if (!filename) return GF_JS_EXCEPTION(ctx);
if ((argc>1) && JS_ToBool(ctx, argv[1]))
as_utf8 = GF_TRUE;
e = gf_file_load_data(filename, &data, &data_size);
if (e) {
res = js_throw_err_msg(ctx, e, "Failed to load file %s", filename);
} else if (as_utf8) {
if (!data || gf_utf8_is_legal(data, data_size)) {
res = JS_NewString(ctx, data ? (const char *) data : "");
} else {
res = js_throw_err_msg(ctx, GF_NON_COMPLIANT_BITSTREAM, "Invalid UTF8 data in file %s", filename);
}
if (data)
gf_free(data);
} else {
res = JS_NewArrayBuffer(ctx, data, data_size, js_gpac_free, NULL, 0);
}
JS_FreeCString(ctx, filename);
return res;
}
/* load and evaluate a file */
static JSValue js_sys_load_script(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *filename;
u8 *data=NULL;
u32 data_size;
JSValue res;
GF_Err e;
char *full_url = NULL;
if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
filename = JS_ToCString(ctx, argv[0]);
if (!filename) return GF_JS_EXCEPTION(ctx);
if ((argc>1) && JS_ToBool(ctx, argv[1]) ) {
const char *par_url = jsf_get_script_filename(ctx);
full_url = gf_url_concatenate(par_url, filename);
JS_FreeCString(ctx, filename);
filename = full_url;
}
e = gf_file_load_data(filename, &data, &data_size);
if (e) {
res = js_throw_err_msg(ctx, e, "Failed to load file %s", filename);
} else if (data) {
if (!gf_utf8_is_legal(data, data_size)) {
res = js_throw_err_msg(ctx, e, "Script file %s is not UTF-8", filename);
} else {
res = JS_Eval(ctx, (char *)data, data_size, filename, JS_EVAL_TYPE_GLOBAL);
}
} else {
res = JS_UNDEFINED;
}
if (data) gf_free(data);
if (full_url)
gf_free(full_url);
else
JS_FreeCString(ctx, filename);
return res;
}
static JSValue js_sys_compress_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_decomp)
{
const u8 *data;
size_t data_size;
Bool use_gz=GF_FALSE;
u32 out_size=0;
u8 *out_ptr = NULL;
JSValue res;
GF_Err e;
if (!argc || !JS_IsObject(argv[0])) return GF_JS_EXCEPTION(ctx);
data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
if (!data) return GF_JS_EXCEPTION(ctx);
if (argc>1) {
use_gz = JS_ToBool(ctx, argv[1]);
}
if (is_decomp) {
e = gf_gz_decompress_payload_ex((u8*) data, (u32) data_size, &out_ptr, &out_size, use_gz);
} else {
e = gf_gz_compress_payload_ex((u8 **)&data, (u32) data_size, &out_size, 0, GF_FALSE, &out_ptr, use_gz);
}
if (e) return js_throw_err(ctx, e);
res = JS_NewArrayBuffer(ctx, out_ptr, out_size, js_gpac_free, NULL, 0);
return res;
}
static JSValue js_sys_compress(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_compress_ex(ctx, this_val, argc, argv, GF_FALSE);
}
static JSValue js_sys_decompress(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_compress_ex(ctx, this_val, argc, argv, GF_TRUE);
}
static JSValue js_sys_url_cat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *parent;
const char *src;
char *url;
JSValue res;
if (argc<2) return GF_JS_EXCEPTION(ctx);
parent = JS_ToCString(ctx, argv[0]);
src = JS_ToCString(ctx, argv[1]);
url = gf_url_concatenate(parent, src);
if (url) {
res = JS_NewString(ctx, url);
gf_free(url);
} else {
res = JS_NewString(ctx, src);
}
JS_FreeCString(ctx, parent);
JS_FreeCString(ctx, src);
return res;
}
static JSValue js_sys_rect_union_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_inter)
{
GF_Rect rc1, rc2;
Double fval;
JSValue v;
int res;
if (argc<2) return GF_JS_EXCEPTION(ctx);
#define GET_PROP(_obj, _n, _f)\
_f = 0;\
v = JS_GetPropertyStr(ctx, _obj, _n);\
res = JS_ToFloat64(ctx, &fval, v);\
if (!res) _f = FLT2FIX(fval);\
JS_FreeValue(ctx, v);\
GET_PROP(argv[0], "x", rc1.x)
GET_PROP(argv[0], "y", rc1.y)
GET_PROP(argv[0], "w", rc1.width)
GET_PROP(argv[0], "h", rc1.height)
GET_PROP(argv[1], "x", rc2.x)
GET_PROP(argv[1], "y", rc2.y)
GET_PROP(argv[1], "w", rc2.width)
GET_PROP(argv[1], "h", rc2.height)
#undef GET_PROP
if (is_inter) {
gf_rect_intersect(&rc1, &rc2);
} else {
gf_rect_union(&rc1, &rc2);
}
v = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, v, "x", JS_NewFloat64(ctx, FIX2FLT(rc1.x) ));
JS_SetPropertyStr(ctx, v, "y", JS_NewFloat64(ctx, FIX2FLT(rc1.y) ));
JS_SetPropertyStr(ctx, v, "w", JS_NewFloat64(ctx, FIX2FLT(rc1.width) ));
JS_SetPropertyStr(ctx, v, "h", JS_NewFloat64(ctx, FIX2FLT(rc1.height) ));
return v;
}
static JSValue js_sys_rect_union(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_rect_union_ex(ctx, this_val, argc, argv, GF_FALSE);
}
static JSValue js_sys_rect_intersect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_rect_union_ex(ctx, this_val, argc, argv, GF_TRUE);
}
#include <gpac/xml.h>
#include <gpac/mpd.h>
static JSValue js_sys_mpd_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc<1) return GF_JS_EXCEPTION(ctx);
size_t ab_size;
const char *data = JS_GetArrayBuffer(ctx, &ab_size, argv[0]);
if (!data || !gf_utf8_is_legal(data, (u32) ab_size)) {
return JS_NULL;
}
char *str = gf_malloc(sizeof(char)*(ab_size+1));
memcpy(str, data, ab_size);
str[ab_size]=0;
//HLS playlist
if (strstr(str, "#EXTM3U")) {
JSValue mpdo = JS_NewObject(ctx);
Bool is_master = GF_FALSE;
Bool is_live = GF_TRUE;
JSValue reps = JS_NewArray(ctx);
if (strstr(str, "#EXT-X-STREAM-INF")) {
is_master = GF_TRUE;
JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 1));
//unused for HLS
JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, 0) );
//unknown until first variant is parsed ...
JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, 0) );
JS_SetPropertyStr(ctx, mpdo, "live", JS_FALSE);
JSValue periods = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, mpdo, "periods", periods);
JSValue po = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, periods, 0, po);
JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, 0) );
JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, 0) );
JS_SetPropertyStr(ctx, po, "ID", JS_NULL );
JS_SetPropertyStr(ctx, po, "reps", reps);
} else {
JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx,2));
JS_SetPropertyStr(ctx, mpdo, "segments", reps);
}
Double target_dur=0;
Double cur_dur=0;
u32 media_seq = 0;
u32 vidx=0;
u32 ASID = 1;
u32 cur_bandwidth=0;
char *cur = str;
while (cur[0]) {
while (cur[0] && strchr(" \n\t\r", cur[0]))
cur++;
char *sep = strchr(cur, '\n');
if (cur[0] != '#') {
if (sep) sep[0] = 0;
JSValue var = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, reps, vidx, var);
if (is_master) {
JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, cur) );
JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(cur) ) );
JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) );
JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) );
JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) );
JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) );
JS_SetPropertyStr(ctx, var, "template", JS_NULL );
JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) );
JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet
JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, cur_bandwidth) );
JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) );
} else {
JS_SetPropertyStr(ctx, var, "url", JS_NewString(ctx, cur) );
if (!cur_dur) cur_dur = target_dur;
JS_SetPropertyStr(ctx, var, "duration", JS_NewFloat64(ctx, cur_dur) );
cur_dur = 0;
}
vidx++;
if (!sep) break;
cur = sep+1;
continue;
}
if (strstr(cur, "EXT-X-ENDLIST")) is_live = GF_FALSE;
//for variant, extract target duration and segment duration
if (!is_master) {
if (sep) sep[0] = 0;
char *tdur = strstr(cur, "EXT-X-TARGETDURATION:");
if (tdur) target_dur = atof(tdur+21);
char *mseq = strstr(cur, "EXT-X-MEDIA-SEQUENCE:");
if (mseq) media_seq = atoi(mseq+21);
char *extinf = strstr(cur, "EXT-INF:");
if (extinf) {
char *last_c = strchr(extinf, ',');
if (last_c) last_c[0] = 0;
cur_dur = atof(extinf+8);
if (last_c) last_c[0] = ',';
}
if (!sep) break;
if (sep) sep[0] = '\n';
cur = sep+1;
continue;
}
//for master, extract URI ad bandwidth
if (sep) sep[0] = 0;
char *bw = strstr(cur, "BANDWIDTH=");
if (bw) {
bw += 10;
char *end = strchr(bw, ',');
if (end) end[0] = 0;
cur_bandwidth = atoi(bw);
if (end) end[0] = ',';
}
char *uri = strstr(cur, "URI=\"");
if (uri) {
ASID++;
uri += 5;
char *end = strchr(uri, '\"');
if (end) {
end[0] = 0;
JSValue var = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, reps, vidx, var);
vidx++;
JS_SetPropertyStr(ctx, var, "xlink", JS_NewString(ctx, uri) );
JS_SetPropertyStr(ctx, var, "ID", JS_NewString(ctx, gf_file_basename(uri) ) );
JS_SetPropertyStr(ctx, var, "AS_ID", JS_NewInt32(ctx, ASID) );
JS_SetPropertyStr(ctx, var, "as_idx", JS_NewInt32(ctx, ASID) );
JS_SetPropertyStr(ctx, var, "URLs", JS_NewArray(ctx) );
JS_SetPropertyStr(ctx, var, "segments", JS_NewArray(ctx) );
JS_SetPropertyStr(ctx, var, "template", JS_NULL );
JS_SetPropertyStr(ctx, var, "timescale", JS_NewInt32(ctx, 1) );
JS_SetPropertyStr(ctx, var, "duration", JS_NewInt32(ctx, 0) ); //not known yet
JS_SetPropertyStr(ctx, var, "bandwidth", JS_NewInt32(ctx, 0) );
JS_SetPropertyStr(ctx, var, "live_seg_num", JS_NewInt64(ctx, 0) );
end[0] = '\n';
}
}
//do we need to extract codec info ?
if (!sep) break;
sep[0] = '\n';
cur = sep+1;
}
JS_SetPropertyStr(ctx, mpdo, "live", is_live ? JS_TRUE : JS_FALSE);
if (media_seq) {
JS_SetPropertyStr(ctx, mpdo, "seq_start", JS_NewInt32(ctx, media_seq) );
JS_SetPropertyStr(ctx, mpdo, "live_seg_num", JS_NewInt32(ctx, media_seq+vidx) );
}
if (!is_master) {
JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, (s32) (cur_dur * 1000) ) );
} else {
JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, -1 ) );
}
gf_free(str);
return mpdo;
} else if (!strstr(str, "<MPD ")) {
gf_free(str);
return JS_NULL;
}
GF_DOMParser *parser = gf_xml_dom_new();
GF_Err e = gf_xml_dom_parse_string(parser, str);
if (e) {
gf_xml_dom_del(parser);
gf_free(str);
return JS_NULL;
}
GF_MPD *mpd = gf_mpd_new();
e = gf_mpd_init_from_dom(gf_xml_dom_get_root(parser), mpd, NULL);
gf_free(str);
gf_xml_dom_del(parser);
if (e) {
gf_mpd_del(mpd);
return JS_NULL;
}
u32 i, count = gf_list_count(mpd->periods);
JSValue mpdo = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, mpdo, "m3u8", JS_NewInt32(ctx, 0));
JS_SetPropertyStr(ctx, mpdo, "ast", JS_NewInt64(ctx, mpd->availabilityStartTime) );
JS_SetPropertyStr(ctx, mpdo, "tsb", JS_NewInt32(ctx, mpd->time_shift_buffer_depth) );
JS_SetPropertyStr(ctx, mpdo, "live", JS_NewBool(ctx, mpd->type==GF_MPD_TYPE_DYNAMIC) );
JS_SetPropertyStr(ctx, mpdo, "min_update", JS_NewInt64(ctx, mpd->minimum_update_period) );
u64 now = gf_net_get_utc();
JS_SetPropertyStr(ctx, mpdo, "live_utc", JS_NewInt64(ctx, now) );
JSValue periods = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, mpdo, "periods", periods);
u32 period_idx=0;
u64 mpd_time = mpd->availabilityStartTime;
for (i=0; i<count; i++) {
u32 j, nb_sets, cur_rep=0;
GF_MPD_Period *p = gf_list_get(mpd->periods, i);
//closed period, don't add if end less than now
if (mpd->type && p->duration) {
//add 10s margin
if (mpd_time + p->start + p->duration + 10000 < now) continue;
//we could also supress period announcements ?
}
JSValue po = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, periods, period_idx, po);
period_idx++;
JS_SetPropertyStr(ctx, po, "start", JS_NewInt64(ctx, p->start) );
JS_SetPropertyStr(ctx, po, "duration", JS_NewInt64(ctx, p->duration) );
JS_SetPropertyStr(ctx, po, "ID", p->ID ? JS_NewString(ctx, p->ID) : JS_NULL );
JSValue reps = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, po, "reps", reps );
u32 mpd_timescale = 0;
u64 seg_duration = 0;
GF_MPD_SegmentTimeline *mpd_stl = NULL;
if (p->segment_template) {
if (p->segment_template->duration) seg_duration = p->segment_template->duration;
if (p->segment_template->timescale) mpd_timescale = p->segment_template->timescale;
if (p->segment_template->segment_timeline) mpd_stl = p->segment_template->segment_timeline;
}
nb_sets = gf_list_count(p->adaptation_sets);
for (j=0; j<nb_sets; j++) {
u32 k, nb_reps;
GF_MPD_AdaptationSet *set = gf_list_get(p->adaptation_sets, j);
if (set->segment_template) {
if (set->segment_template->duration) seg_duration = set->segment_template->duration;
if (set->segment_template->timescale) mpd_timescale = set->segment_template->timescale;
if (set->segment_template->segment_timeline) mpd_stl = set->segment_template->segment_timeline;
}
nb_reps = gf_list_count(set->representations);
for (k=0; k<nb_reps; k++) {
u64 start_range, end_range, segdur_ms;
u32 bidx;
GF_MPD_Representation *rep = gf_list_get(set->representations, k);
char *seg_url = NULL;
if (rep->segment_template) {
if (rep->segment_template->duration) seg_duration = rep->segment_template->duration;
if (rep->segment_template->timescale) mpd_timescale = rep->segment_template->timescale;
if (rep->segment_template->segment_timeline) mpd_stl = rep->segment_template->segment_timeline;
}
JSValue repo = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, reps, cur_rep, repo);
cur_rep++;
JS_SetPropertyStr(ctx, repo, "ID", rep->id ? JS_NewString(ctx, rep->id) : JS_NULL);
JS_SetPropertyStr(ctx, repo, "AS_ID", JS_NewInt32(ctx, set->id) );
JS_SetPropertyStr(ctx, repo, "as_idx", JS_NewInt32(ctx, j+1) );
JS_SetPropertyStr(ctx, repo, "xlink", JS_NULL);
JSValue urls_o = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, repo, "URLs", urls_o );
u32 el_idx, js_idx=0;
for (el_idx=0;el_idx<4; el_idx++) {
GF_List *urls = rep->base_URLs;
if (el_idx==0) urls = mpd->base_URLs;
else if (el_idx==1) urls = p->base_URLs;
else if (el_idx==2) urls = set->base_URLs;
for (bidx=0; bidx < gf_list_count(urls); bidx++) {
GF_MPD_BaseURL *burl = gf_list_get(urls, bidx);
if (burl->URL) {
JS_SetPropertyUint32(ctx, urls_o, js_idx, JS_NewString(ctx, burl->URL) );
js_idx++;
}
}
}
JS_SetPropertyStr(ctx, repo, "timescale", JS_NewInt32(ctx, mpd_timescale ? mpd_timescale : 1) );
JS_SetPropertyStr(ctx, repo, "duration", JS_NewInt64(ctx, seg_duration) );
JS_SetPropertyStr(ctx, repo, "bandwidth", JS_NewInt32(ctx, rep->bandwidth) );
gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0);
JS_SetPropertyStr(ctx, repo, "template", seg_url ? JS_NewString(ctx, seg_url) : JS_NULL );
if (seg_url) gf_free(seg_url);
u64 seg_idx = 0;
if (seg_duration) {
seg_idx = now - p->start - mpd->availabilityStartTime;
seg_idx /= 1000;
if (mpd_timescale) seg_idx *= mpd_timescale;
seg_idx /= seg_duration;
}
JS_SetPropertyStr(ctx, repo, "live_seg_num", JS_NewInt64(ctx, seg_idx) );
JSValue segs = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, repo, "segments", segs );
if (mpd_stl) {
u32 sidx, re, cur_seg=0;
//u64 start = mpd->availabilityStartTime + p->start;
for (sidx=0; sidx<gf_list_count(mpd_stl->entries); sidx++) {
GF_MPD_SegmentTimelineEntry *e = gf_list_get(mpd_stl->entries, sidx);
//if (e->start_time) start = e->start_time;
for (re=0; re<e->repeat_count+1; re++) {
JSValue sego = JS_NewObject(ctx);
gf_mpd_resolve_url(mpd, rep, set, p, "./", 0, GF_MPD_RESOLVE_URL_MEDIA, cur_seg, 0, &seg_url, &start_range, &end_range, &segdur_ms, NULL, NULL, NULL, NULL, 0);
//move back to timescale
segdur_ms = gf_timestamp_rescale(segdur_ms, 1000, mpd_timescale ? mpd_timescale : 1);
JS_SetPropertyStr(ctx, sego, "url", JS_NewString(ctx, seg_url) );
JS_SetPropertyStr(ctx, sego, "duration", JS_NewInt32(ctx, (u32) segdur_ms) );
JS_SetPropertyUint32(ctx, segs, cur_seg, sego );
cur_seg++;
gf_free(seg_url);
}
}
}
}
}
}
gf_mpd_del(mpd);
return mpdo;
}
enum
{
OPT_RMDIR,
OPT_MKDIR,
OPT_DIREXISTS,
OPT_DIRCLEAN,
OPT_FILEBASENAME,
OPT_FILEEXT,
OPT_FILEDEL,
OPT_FILEMODTIME,
OPT_FILEEXISTS,
OPT_FILEMOVE,
};
static JSValue js_sys_file_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 file_opt)
{
const char *dirname, *newfile;
char *ext;
GF_Err e;
JSValue res;
if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
dirname = JS_ToCString(ctx, argv[0]);
if (!dirname) return GF_JS_EXCEPTION(ctx);
res = JS_UNDEFINED;
switch (file_opt) {
case OPT_RMDIR:
e = gf_rmdir(dirname);
if (e) res = js_throw_err_msg(ctx, e, "Failed to remove dir %s", dirname);
break;
case OPT_MKDIR:
e = gf_mkdir(dirname);
if (e) res = js_throw_err_msg(ctx, e, "Failed to create dir %s", dirname);
break;
case OPT_DIREXISTS:
res = JS_NewBool(ctx, gf_dir_exists(dirname) );
break;
case OPT_DIRCLEAN:
e = gf_dir_cleanup(dirname);
if (e) res = js_throw_err_msg(ctx, e, "Failed to clean dir %s", dirname);
break;
case OPT_FILEBASENAME:
res = JS_NewString(ctx, gf_file_basename(dirname) );
break;
case OPT_FILEEXT:
ext = gf_file_ext_start(dirname);
res = ext ? JS_NewString(ctx, ext) : JS_NULL;
break;
case OPT_FILEDEL:
e = gf_file_delete(dirname);
if (e) res = js_throw_err_msg(ctx, e, "Failed to delete file %s", dirname);
break;
case OPT_FILEMODTIME:
res = JS_NewInt64(ctx, gf_file_modification_time(dirname) );
break;
case OPT_FILEEXISTS:
res = JS_NewBool(ctx, gf_file_exists(dirname) );
break;
case OPT_FILEMOVE:
newfile = (argc<2) ? NULL : JS_ToCString(ctx, argv[1]);
if (!newfile) res = js_throw_err_msg(ctx, GF_BAD_PARAM, "Missing new file name");
else {
e = gf_file_move(dirname, newfile);
if (e) res = js_throw_err_msg(ctx, e, "Failed to move file %s to %s", dirname, newfile);
JS_FreeCString(ctx, newfile);
}
break;
}
JS_FreeCString(ctx, dirname);
return res;
}
static JSValue js_sys_rmdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_RMDIR);
}
static JSValue js_sys_mkdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_MKDIR);
}
static JSValue js_sys_dir_exists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_DIREXISTS);
}
static JSValue js_sys_dir_clean(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_DIRCLEAN);
}
static JSValue js_sys_basename(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEBASENAME);
}
static JSValue js_sys_file_ext(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEEXT);
}
static JSValue js_sys_file_exists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEEXISTS);
}
static JSValue js_sys_file_del(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEDEL);
}
static JSValue js_sys_file_modtime(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEMODTIME);
}
static JSValue js_sys_file_move(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_file_opt(ctx, this_val, argc, argv, OPT_FILEMOVE);
}
static JSValue js_sys_get_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *sec, *key, *val;
JSValue res;
if (argc!=2) return GF_JS_EXCEPTION(ctx);
sec = JS_ToCString(ctx, argv[0]);
if (!sec) return GF_JS_EXCEPTION(ctx);
key = JS_ToCString(ctx, argv[1]);
if (!key) {
JS_FreeCString(ctx, sec);
return GF_JS_EXCEPTION(ctx);
}
val = gf_opts_get_key(sec, key);
res = val ? JS_NewString(ctx, val) : JS_NULL;
JS_FreeCString(ctx, sec);
JS_FreeCString(ctx, key);
return res;
}
static JSValue js_sys_set_opt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *sec, *key, *val;
if (argc<2) return GF_JS_EXCEPTION(ctx);
sec = JS_ToCString(ctx, argv[0]);
if (!sec) return GF_JS_EXCEPTION(ctx);
key = JS_ToCString(ctx, argv[1]);
if (!key) {
JS_FreeCString(ctx, sec);
return GF_JS_EXCEPTION(ctx);
}
val = NULL;
if (argc>2)
val = JS_ToCString(ctx, argv[2]);
gf_opts_set_key(sec, key, val);
JS_FreeCString(ctx, sec);
JS_FreeCString(ctx, key);
if (val)
JS_FreeCString(ctx, val);
return JS_UNDEFINED;
}
static JSValue js_sys_discard_opts(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
gf_opts_discard_changes();
return JS_UNDEFINED;
}
static JSValue js_sys_basecode_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_dec, Bool is_16)
{
u32 out_size=0;
u8 *out_ptr = NULL;
JSValue res;
GF_Err e;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (is_dec) {
u32 len;
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return GF_JS_EXCEPTION(ctx);
len = (u32) strlen(str);
out_ptr = gf_malloc(sizeof(u8) * len);
if (!out_ptr) {
e = GF_OUT_OF_MEM;
} else if (is_16) {
out_size = gf_base16_decode((u8*) str, (u32) len, out_ptr, len);
e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
} else {
out_size = gf_base64_decode((u8*) str, (u32) len, out_ptr, len);
e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
}
JS_FreeCString(ctx, str);
} else {
const u8 *data;
size_t data_size;
data = JS_GetArrayBuffer(ctx, &data_size, argv[0] );
if (!data) return GF_JS_EXCEPTION(ctx);
u32 size64 = (u32) data_size * 2 + 3;
out_ptr = gf_malloc(sizeof(char) * size64);
if (!out_ptr) {
e = GF_OUT_OF_MEM;
} else if (is_16) {
out_size = gf_base16_encode((u8*) data, (u32) data_size, out_ptr, size64);
e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
} else {
out_size = gf_base64_encode((u8*) data, (u32) data_size, out_ptr, size64);
e = out_size ? GF_OK : GF_NON_COMPLIANT_BITSTREAM;
}
}
if (e) return js_throw_err(ctx, e);
if (is_dec) {
res = JS_NewArrayBuffer(ctx, out_ptr, out_size, js_gpac_free, NULL, 0);
} else {
out_ptr[out_size] = 0;
res = JS_NewString(ctx, out_ptr);
gf_free(out_ptr);
}
return res;
}
static JSValue js_sys_base64enc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_FALSE, GF_FALSE);
}
static JSValue js_sys_base64dec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_TRUE, GF_FALSE);
}
static JSValue js_sys_base16enc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_FALSE, GF_TRUE);
}
static JSValue js_sys_base16dec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
return js_sys_basecode_ex(ctx, this_val, argc, argv, GF_TRUE, GF_TRUE);
}
static JSValue js_sys_ntohl(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 val;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_ntohl((u32) val));
}
static JSValue js_sys_ntohs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 val;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_ntohs((u16) val));
}
static JSValue js_sys_htonl(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 val;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_htonl((u32) val));
}
static JSValue js_sys_htons(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 val;
if (!argc) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_htons((u16) val));
}
GF_Err jsf_ToProp_ex(GF_Filter *filter, JSContext *ctx, JSValue value, u32 p4cc, GF_PropertyValue *prop, u32 prop_type);
static JSValue js_pixfmt_size(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 w, h, osize;
GF_PropertyValue prop;
if (argc<3) return GF_JS_EXCEPTION(ctx);
GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
if (e) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &w, argv[1])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &h, argv[2])) return GF_JS_EXCEPTION(ctx);
if (!gf_pixel_get_size_info(prop.value.uint, w, h, &osize, NULL, NULL, NULL, NULL))
return js_throw_err_msg(ctx, GF_BAD_PARAM, "Unknown pixel format %d\n", prop.value.uint);
return JS_NewInt32(ctx, osize);
}
static JSValue js_pixfmt_depth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_PropertyValue prop;
if (argc<1) return GF_JS_EXCEPTION(ctx);
GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
if (e) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_pixel_is_wide_depth(prop.value.uint));
}
static JSValue js_pixfmt_transparent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_PropertyValue prop;
if (!argc) return GF_JS_EXCEPTION(ctx);
GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
if (e) return GF_JS_EXCEPTION(ctx);
return JS_NewBool(ctx, gf_pixel_fmt_is_transparent(prop.value.uint) );
}
static JSValue js_pixfmt_yuv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_PropertyValue prop;
if (!argc) return GF_JS_EXCEPTION(ctx);
GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PIXFMT);
if (e) return GF_JS_EXCEPTION(ctx);
return JS_NewBool(ctx, gf_pixel_fmt_is_yuv(prop.value.uint) );
}
static JSValue js_pcmfmt_depth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
GF_PropertyValue prop;
if (argc!=1) return GF_JS_EXCEPTION(ctx);
GF_Err e = jsf_ToProp_ex(NULL, ctx, argv[0], 0, &prop, GF_PROP_PCMFMT);
if (e) return GF_JS_EXCEPTION(ctx);
return JS_NewInt32(ctx, gf_audio_fmt_bit_depth(prop.value.uint)/8 );
}
static JSValue js_sys_set_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *logs;
Bool reset = GF_FALSE;
if (!argc || !JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
if (argc>1)
reset = JS_ToBool(ctx, argv[1]);
if (!js_rt->js_orig_logs) {
js_rt->js_orig_logs = gf_log_get_tools_levels();
}
logs = JS_ToCString(ctx, argv[0]);
if (!logs) {
if (js_rt->js_orig_logs) {
gf_log_set_tools_levels(js_rt->js_orig_logs, GF_TRUE);
gf_free(js_rt->js_orig_logs);
js_rt->js_orig_logs = NULL;
} else {
gf_log_set_tools_levels("all@warning", GF_TRUE);
}
} else {
gf_log_set_tools_levels(logs, reset);
JS_FreeCString(ctx, logs);
}
return JS_UNDEFINED;
}
static JSValue js_sys_get_logs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc && JS_ToBool(ctx, argv[0]) && js_rt->js_orig_logs) {
return JS_NewString(ctx, js_rt->js_orig_logs);
}
char *logs = gf_log_get_tools_levels();
JSValue res = JS_NewString(ctx, logs);
gf_free(logs);
return res;
}
#include <gpac/color.h>
static JSValue js_color_lerp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *str;
u32 col1, col2;
char szCol[12];
Double interp, minterp, a1, r1, g1, b1, a2, r2, g2, b2;
u8 r, g, b, a;
if (argc!=3)
return GF_JS_EXCEPTION(ctx);
if (JS_ToFloat64(ctx, &interp, argv[0]))
return GF_JS_EXCEPTION(ctx);
if (interp<0) interp=0;
else if (interp>1) interp=1;
minterp = 1-interp;
str = JS_ToCString(ctx, argv[1]);
if (!str) return GF_JS_EXCEPTION(ctx);
col1 = gf_color_parse(str);
JS_FreeCString(ctx, str);
str = JS_ToCString(ctx, argv[2]);
if (!str) return GF_JS_EXCEPTION(ctx);
col2 = gf_color_parse(str);
JS_FreeCString(ctx, str);
a1 = GF_COL_A(col1) / 255.0;
a2 = GF_COL_A(col2) / 255.0;
r1 = GF_COL_R(col1) / 255.0;
r2 = GF_COL_R(col2) / 255.0;
g1 = GF_COL_G(col1) / 255.0;
g2 = GF_COL_G(col2) / 255.0;
b1 = GF_COL_B(col1) / 255.0;
b2 = GF_COL_B(col2) / 255.0;
a = (u8) ((a1 * minterp + a2 * interp) * 255);
r = (u8) ((r1 * minterp + r2 * interp) * 255);
g = (u8) ((g1 * minterp + g2 * interp) * 255);
b = (u8) ((b1 * minterp + b2 * interp) * 255);
if (!a && !r && !g && !b)
return JS_NewString(ctx, "none");
sprintf(szCol, "0x%02X%02X%02X%02X", a, r, g, b);
return JS_NewString(ctx, szCol);
}
static JSValue js_color_get_component(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *str;
u32 comp_idx, color;
Double comp;
if (argc!=2)
return GF_JS_EXCEPTION(ctx);
str = JS_ToCString(ctx, argv[0]);
if (!str) return GF_JS_EXCEPTION(ctx);
color = gf_color_parse(str);
JS_FreeCString(ctx, str);
if (JS_ToInt32(ctx, &comp_idx, argv[1]))
return GF_JS_EXCEPTION(ctx);
switch (comp_idx) {
case 0: comp = GF_COL_A(color); break;
case 1: comp = GF_COL_R(color); break;
case 2: comp = GF_COL_G(color); break;
case 3: comp = GF_COL_B(color); break;
default: return GF_JS_EXCEPTION(ctx);
}
comp /= 255.0;
return JS_NewFloat64(ctx, comp);
}
typedef struct
{
u64 last_sample_time, frame_ts;
u32 samples_used, consumed, nb_samples, channels;
u32 fade;
JSValue jspid;
Double volume;
u8 *data;
size_t data_size;
} PidMix;
typedef struct
{
u32 channels, samples_gap, fade_len;
Double *chan_buf;
u32 max_pid_channels;
Double *in_chan_buf;
u32 nb_inputs;
PidMix *inputs;
u32 sample_size;
Double (*get_sample)(u8 *data);
void (*set_sample)(u8 *data, Double val);
} AMixCtx;
static JSValue js_audio_mix(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u32 nb_samples, i, j, k, nb_src, fade_length=0;
u64 audio_time;
JSValue v;
s32 res;
size_t ab_size;
u8 *mix_ab;
u32 max_chan = 0;
PidMix *pids;
AMixCtx *mix = JS_GetOpaque(this_val, amix_class_id);
if (!mix) return GF_JS_EXCEPTION(ctx);
if (argc != 3) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &audio_time, argv[0])) return GF_JS_EXCEPTION(ctx);
mix_ab = JS_GetArrayBuffer(ctx, &ab_size, argv[1]);
if (!mix_ab) return GF_JS_EXCEPTION(ctx);
nb_samples = (u32) (ab_size / mix->channels / mix->sample_size);
v = JS_GetPropertyStr(ctx, argv[2], "length");
if (JS_IsException(v)) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &nb_src, v)) {
JS_FreeValue(ctx, v);
return GF_JS_EXCEPTION(ctx);
}
JS_FreeValue(ctx, v);
if (mix->nb_inputs < nb_src) {
mix->inputs = gf_realloc(mix->inputs, sizeof(PidMix) * nb_src);
if (!mix->inputs) return js_throw_err(ctx, GF_OUT_OF_MEM);
mix->nb_inputs = nb_src;
}
pids = mix->inputs;
//update current input states
for (i=0; i<nb_src; i++) {
PidMix *pid = &pids[i];
pid->jspid = JS_GetPropertyUint32(ctx, argv[2], i);
v = JS_GetPropertyStr(ctx, pid->jspid, "last_sample_time");
res = JS_ToInt64(ctx, &pid->last_sample_time, v);
JS_FreeValue(ctx, v);
//assume first sample if not set
if (res) pid->last_sample_time = 0;
v = JS_GetPropertyStr(ctx, pid->jspid, "frame_ts");
res = JS_ToInt64(ctx, &pid->frame_ts, v);
JS_FreeValue(ctx, v);
//we must have a frame timestamp in audio timescale
if (res) return GF_JS_EXCEPTION(ctx);
v = JS_GetPropertyStr(ctx, pid->jspid, "samples_used");
res = JS_ToInt32(ctx, &pid->samples_used, v);
JS_FreeValue(ctx, v);
//if not set, assume first sample
if (res) pid->samples_used = 0;
v = JS_GetPropertyStr(ctx, pid->jspid, "channels");
res = JS_ToInt32(ctx, &pid->channels, v);
JS_FreeValue(ctx, v);
//we must have the number of channels for this source
if (res || !pid->channels) return GF_JS_EXCEPTION(ctx);
//we must have the audio data
v = JS_GetPropertyStr(ctx, pid->jspid, "data");
pid->data = JS_GetArrayBuffer(ctx, &pid->data_size, v);
JS_FreeValue(ctx, v);
v = JS_GetPropertyStr(ctx, pid->jspid, "nb_samples");
res = JS_ToInt32(ctx, &pid->nb_samples, v);
JS_FreeValue(ctx, v);
//if not set, derive from data size
if (res) {
pid->nb_samples = (u32) (pid->data_size / pid->channels / mix->sample_size);
}
v = JS_GetPropertyStr(ctx, pid->jspid, "fade");
res = JS_ToInt32(ctx, &pid->fade, v);
JS_FreeValue(ctx, v);
if (res) pid->fade = 0;
if (pid->fade) fade_length++;
v = JS_GetPropertyStr(ctx, pid->jspid, "volume");
res = JS_ToFloat64(ctx, &pid->volume, v);
JS_FreeValue(ctx, v);
if (res) pid->volume = 1.0;
if (pid->channels>max_chan)
max_chan = pid->channels;
pid->consumed = 0;
}
if (max_chan > mix->max_pid_channels) {
mix->in_chan_buf = gf_realloc(mix->in_chan_buf, sizeof(Double) * max_chan);
if (!mix->in_chan_buf) return js_throw_err(ctx, GF_OUT_OF_MEM);
mix->max_pid_channels = max_chan;
}
if (fade_length) {
fade_length = MIN(mix->fade_len, nb_samples);
}
for (i=0; i<nb_samples; i++) {
u32 active=0;
memset(mix->chan_buf, 0, sizeof(Double) * mix->channels);
for (j=0; j<nb_src; j++) {
PidMix *pid = &pids[j];
//for the time being, as soon as we start writing audio we no longer check sync
if (!pid->last_sample_time) {
if (pid->frame_ts + pid->samples_used > audio_time + i) {
continue;
}
pid->last_sample_time = 1; //pid.frame_ts + pid.sample_used;
JS_SetPropertyStr(ctx, pid->jspid, "last_sample_time", JS_NewInt64(ctx, pid->last_sample_time) );
} else {
//audio discontinuity
if (pid->frame_ts + pid->samples_used > audio_time + i + mix->samples_gap) {
continue;
}
}
u32 s_pos = pid->samples_used + pid->consumed;
if (s_pos >= pid->nb_samples) {
gf_assert(0);
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[AVMix] error mixing\n"));
}
u32 pos = s_pos * pid->channels;
u8 *buf = pid->data + mix->sample_size * pos;
for (k=0; k<pid->channels; k++) {
mix->in_chan_buf[k] = mix->get_sample(buf);
buf += mix->sample_size;
}
active += 1;
Double fade_scale = 1;
if (pid->fade) {
//fade from 0 to 1 starting at pos=0 up to fade_length
if (pid->fade==1) {
if (s_pos<fade_length) {
fade_scale = s_pos/fade_length;
} else {
pid->fade = 0;
JS_SetPropertyStr(ctx, pid->jspid, "fade", JS_NewInt32(ctx, pid->fade) );
}
}
//fade from 1 to 0 starting at pos=nb_samples-fade_length up to nb_samples
else if (pid->fade==2) {
if (s_pos>nb_samples-fade_length ) {
fade_scale = (nb_samples - s_pos)/fade_length;
}
}
}
fade_scale *= pid->volume;
//todo , proper down/up mix ...
for (k=0; k<mix->channels; k++) {
Double s_val;
if (pid->channels <= k)
s_val = mix->in_chan_buf[0];
else
s_val = mix->in_chan_buf[k];
mix->chan_buf[k] += s_val * fade_scale;
}
pid->consumed ++;
}
u8 *dst = mix_ab + mix->sample_size * (i * mix->channels);
for (j=0; j< mix->channels; j++) {
Double sval = active ? (mix->chan_buf[j] / active) : 0;
mix->set_sample(dst, sval);
dst += mix->sample_size;
}
}
//update pid values
for (i=0; i<nb_src; i++) {
PidMix *pid = &pids[i];
pid->samples_used += pid->consumed;
JS_SetPropertyStr(ctx, pid->jspid, "samples_used", JS_NewInt32(ctx, pid->samples_used));
JS_FreeValue(ctx, pid->jspid);
}
return JS_UNDEFINED;
}
static Double amix_get_s16(u8 *data)
{
u16 val = data[1];
val <<= 8;
val |= data[0];
return ((Double) (s16) val) / 65535;
}
static void amix_set_s16(u8 *data, Double val)
{
val *= 65535;
u16 res = (u16) GF_FLOAT_TO_U32(val);
data[0] = res & 0xFF;
data[1] = (res>>8) & 0xFF;
}
static Double amix_get_s32(u8 *data)
{
u32 val = data[3];
val <<= 8;
val |= data[2];
val <<= 8;
val |= data[1];
val <<= 8;
val |= data[0];
return ((Double) (s32) val) / 0xFFFFFFFF;
}
static void amix_set_s32(u8 *data, Double val)
{
val *= 0xFFFFFFFF;
u32 res = GF_FLOAT_TO_U32(val);
data[0] = res & 0xFF;
data[1] = (res>>8) & 0xFF;
data[2] = (res>>16) & 0xFF;
data[3] = (res>>24) & 0xFF;
}
static Double amix_get_flt(u8 *data)
{
return (Double) *(Float *)data;
}
static void amix_set_flt(u8 *data, Double val)
{
*(Float *)data = (Float) val;
}
static Double amix_get_dbl(u8 *data)
{
return *(Double *)data;
}
static void amix_set_dbl(u8 *data, Double val)
{
*(Double *)data = val;
}
static void js_amix_finalize(JSRuntime *rt, JSValue obj)
{
AMixCtx *mix = JS_GetOpaque(obj, amix_class_id);
if (!mix) return;
if (mix->chan_buf) gf_free(mix->chan_buf);
if (mix->inputs) gf_free(mix->inputs);
if (mix->in_chan_buf) gf_free(mix->in_chan_buf);
gf_free(mix);
}
JSClassDef amixClass = {
"FILE",
.finalizer = js_amix_finalize,
};
static const JSCFunctionListEntry amix_funcs[] = {
JS_CFUNC_DEF("mix", 0, js_audio_mix),
};
static JSValue amix_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
u32 a_fmt=0;
u32 channels, samples_gap, fade_len;
AMixCtx *mix;
JSValue anobj;
if (argc != 4) return js_throw_err(ctx, GF_BAD_PARAM);
const char *fmt = JS_ToCString(ctx, argv[0]);
if (!fmt) return GF_JS_EXCEPTION(ctx);
//we only support these formats for the time being, all in packed mode
if (!strcmp(fmt, "s16")) a_fmt = 1;
else if (!strcmp(fmt, "s32")) a_fmt = 2;
else if (!strcmp(fmt, "flt")) a_fmt = 3;
else if (!strcmp(fmt, "dbl")) a_fmt = 4;
JS_FreeCString(ctx, fmt);
if (!a_fmt) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &channels, argv[1])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &samples_gap, argv[2])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &fade_len, argv[3])) return GF_JS_EXCEPTION(ctx);
if (!channels) return js_throw_err(ctx, GF_BAD_PARAM);
GF_SAFEALLOC(mix, AMixCtx);
mix->channels = channels;
mix->samples_gap = samples_gap;
mix->fade_len = fade_len;
mix->chan_buf = gf_malloc(sizeof(Double) * channels);
if (!mix->chan_buf) {
gf_free(mix);
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
switch (a_fmt) {
case 1:
mix->sample_size = 2;
mix->get_sample = amix_get_s16;
mix->set_sample = amix_set_s16;
break;
case 2:
mix->sample_size = 4;
mix->get_sample = amix_get_s32;
mix->set_sample = amix_set_s32;
break;
case 3:
mix->sample_size = 4;
mix->get_sample = amix_get_flt;
mix->set_sample = amix_set_flt;
break;
case 4:
mix->sample_size = 8;
mix->get_sample = amix_get_dbl;
mix->set_sample = amix_set_dbl;
break;
}
anobj = JS_NewObjectClass(ctx, amix_class_id);
if (JS_IsException(anobj)) {
gf_free(mix);
return anobj;
}
JS_SetOpaque(anobj, mix);
return anobj;
}
static const JSCFunctionListEntry sys_funcs[] = {
JS_CGETSET_MAGIC_DEF_ENUM("nb_cores", js_sys_prop_get, NULL, JS_SYS_NB_CORES),
JS_CGETSET_MAGIC_DEF_ENUM("sampling_period_duration", js_sys_prop_get, NULL, JS_SYS_SAMPLE_DUR),
JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU),
JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU),
JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_DIFF),
JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_time_diff", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_DIFF),
JS_CGETSET_MAGIC_DEF_ENUM("cpu_idle_time", js_sys_prop_get, NULL, JS_SYS_CPU_IDLE),
JS_CGETSET_MAGIC_DEF_ENUM("total_cpu_usage", js_sys_prop_get, NULL, JS_SYS_TOTAL_CPU_USAGE),
JS_CGETSET_MAGIC_DEF_ENUM("process_cpu_usage", js_sys_prop_get, NULL, JS_SYS_PROCESS_CPU_USAGE),
JS_CGETSET_MAGIC_DEF_ENUM("pid", js_sys_prop_get, NULL, JS_SYS_PID),
JS_CGETSET_MAGIC_DEF_ENUM("thread_count", js_sys_prop_get, NULL, JS_SYS_THREADS),
JS_CGETSET_MAGIC_DEF_ENUM("process_memory", js_sys_prop_get, NULL, JS_SYS_PROCESS_MEM),
JS_CGETSET_MAGIC_DEF_ENUM("physical_memory", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM),
JS_CGETSET_MAGIC_DEF_ENUM("physical_memory_avail", js_sys_prop_get, NULL, JS_SYS_TOTAL_MEM_AVAIL),
JS_CGETSET_MAGIC_DEF_ENUM("gpac_memory", js_sys_prop_get, NULL, JS_SYS_GPAC_MEM),
JS_CGETSET_MAGIC_DEF_ENUM("last_wdir", js_sys_prop_get, js_sys_prop_set, JS_SYS_LAST_WORK_DIR),
JS_CGETSET_MAGIC_DEF_ENUM("batteryOn", js_sys_prop_get, NULL, JS_SYS_BATTERY_ON),
JS_CGETSET_MAGIC_DEF_ENUM("batteryCharging", js_sys_prop_get, NULL, JS_SYS_BATTERY_CHARGE),
JS_CGETSET_MAGIC_DEF_ENUM("batteryPercent", js_sys_prop_get, NULL, JS_SYS_BATTERY_PERCENT),
JS_CGETSET_MAGIC_DEF_ENUM("batteryLifeTime", js_sys_prop_get, NULL, JS_SYS_BATTERY_LIFETIME),
JS_CGETSET_MAGIC_DEF_ENUM("batteryFullLifeTime", js_sys_prop_get, NULL, JS_SYS_BATTERY_LIFETIME_FULL),
JS_CGETSET_MAGIC_DEF_ENUM("hostname", js_sys_prop_get, NULL, JS_SYS_HOSTNAME),
JS_CGETSET_MAGIC_DEF_ENUM("username", js_sys_prop_get, NULL, JS_SYS_USERNAME),
JS_CGETSET_MAGIC_DEF_ENUM("test_mode", js_sys_prop_get, NULL, JS_SYS_TEST_MODE),
JS_CGETSET_MAGIC_DEF_ENUM("old_arch", js_sys_prop_get, NULL, JS_SYS_OLD_ARCH),
JS_CGETSET_MAGIC_DEF_ENUM("log_color", js_sys_prop_get, NULL, JS_SYS_LOG_COLOR),
JS_CGETSET_MAGIC_DEF_ENUM("quiet", js_sys_prop_get, NULL, JS_SYS_QUIET),
JS_CGETSET_MAGIC_DEF_ENUM("timezone", js_sys_prop_get, NULL, JS_SYS_TIMEZONE),
JS_CGETSET_MAGIC_DEF_ENUM("nb_files_open", js_sys_prop_get, NULL, JS_SYS_FILES_OPEN),
JS_CGETSET_MAGIC_DEF_ENUM("cache_dir", js_sys_prop_get, NULL, JS_SYS_CACHE_DIR),
JS_CGETSET_MAGIC_DEF_ENUM("shared_dir", js_sys_prop_get, NULL, JS_SYS_SHARED_DIR),
JS_CGETSET_MAGIC_DEF_ENUM("version", js_sys_prop_get, NULL, JS_SYS_VERSION),
JS_CGETSET_MAGIC_DEF_ENUM("version_full", js_sys_prop_get, NULL, JS_SYS_VERSION_FULL),
JS_CGETSET_MAGIC_DEF_ENUM("copyright", js_sys_prop_get, NULL, JS_SYS_COPYRIGHT),
JS_CGETSET_MAGIC_DEF_ENUM("version_major", js_sys_prop_get, NULL, JS_SYS_V_MAJOR),
JS_CGETSET_MAGIC_DEF_ENUM("version_minor", js_sys_prop_get, NULL, JS_SYS_V_MINOR),
JS_CGETSET_MAGIC_DEF_ENUM("version_micro", js_sys_prop_get, NULL, JS_SYS_V_MICRO),
JS_CGETSET_MAGIC_DEF_ENUM("use_logx", js_sys_prop_get, js_sys_prop_set, JS_SYS_LOGX),
JS_CGETSET_MAGIC_DEF_ENUM("rmt_on_new_client", NULL, js_sys_prop_set, JS_SYS_RMT_ON_NEW_CLIENT),
JS_CGETSET_MAGIC_DEF_ENUM("userws_on_new_client", NULL, js_sys_prop_set, JS_SYS_USERWS_ON_NEW_CLIENT),
JS_CGETSET_MAGIC_DEF_ENUM("on_log", NULL, js_sys_prop_set, JS_SYS_ON_LOG),
JS_CFUNC_DEF("set_arg_used", 0, js_sys_set_arg_used),
JS_CFUNC_DEF("error_string", 0, js_sys_error_string),
JS_CFUNC_DEF("prompt_input", 0, js_sys_prompt_input),
JS_CFUNC_DEF("prompt_string", 0, js_sys_prompt_string),
JS_CFUNC_DEF("prompt_echo_off", 0, js_sys_prompt_echo_off),
JS_CFUNC_DEF("prompt_code", 0, js_sys_prompt_code),
JS_CFUNC_DEF("prompt_size", 0, js_sys_prompt_size),
JS_CFUNC_DEF("keyname", 0, js_sys_keyname),
JS_CFUNC_DEF("get_event_type", 0, js_sys_evt_by_name),
JS_CFUNC_DEF("gc", 0, js_sys_gc),
JS_CFUNC_DEF("enum_directory", 0, js_sys_enum_directory),
JS_CFUNC_DEF("clock_ms", 0, js_sys_clock),
JS_CFUNC_DEF("clock_us", 0, js_sys_clock_high_res),
JS_CFUNC_DEF("sleep", 0, js_sys_sleep),
JS_CFUNC_DEF("exit", 0, js_sys_exit),
JS_CFUNC_DEF("fcc_to_str", 0, js_sys_4cc_to_str),
JS_CFUNC_DEF("rand_init", 0, js_sys_rand_init),
JS_CFUNC_DEF("rand", 0, js_sys_rand),
JS_CFUNC_DEF("rand64", 0, js_sys_rand64),
JS_CFUNC_DEF("getenv", 0, js_sys_getenv),
JS_CFUNC_DEF("get_utc", 0, js_sys_get_utc),
JS_CFUNC_DEF("get_ntp", 0, js_sys_get_ntp),
JS_CFUNC_DEF("ntp_shift", 0, js_sys_ntp_shift),
JS_CFUNC_DEF("crc32", 0, js_sys_crc32),
JS_CFUNC_DEF("sha1", 0, js_sys_sha1),
JS_CFUNC_DEF("load_file", 0, js_sys_file_data),
JS_CFUNC_DEF("load_script", 0, js_sys_load_script),
JS_CFUNC_DEF("compress", 0, js_sys_compress),
JS_CFUNC_DEF("decompress", 0, js_sys_decompress),
JS_CFUNC_DEF("rmdir", 0, js_sys_rmdir),
JS_CFUNC_DEF("mkdir", 0, js_sys_mkdir),
JS_CFUNC_DEF("dir_exists", 0, js_sys_dir_exists),
JS_CFUNC_DEF("dir_clean", 0, js_sys_dir_clean),
JS_CFUNC_DEF("basename", 0, js_sys_basename),
JS_CFUNC_DEF("file_ext", 0, js_sys_file_ext),
JS_CFUNC_DEF("file_exists", 0, js_sys_file_exists),
JS_CFUNC_DEF("del", 0, js_sys_file_del),
JS_CFUNC_DEF("mod_time", 0, js_sys_file_modtime),
JS_CFUNC_DEF("move", 0, js_sys_file_move),
JS_CFUNC_DEF("get_opt", 0, js_sys_get_opt),
JS_CFUNC_DEF("set_opt", 0, js_sys_set_opt),
JS_CFUNC_DEF("discard_opts", 0, js_sys_discard_opts),
JS_CFUNC_DEF("base64enc", 0, js_sys_base64enc),
JS_CFUNC_DEF("base64dec", 0, js_sys_base64dec),
JS_CFUNC_DEF("base16enc", 0, js_sys_base16enc),
JS_CFUNC_DEF("base16dec", 0, js_sys_base16dec),
JS_CFUNC_DEF("ntohl", 0, js_sys_ntohl),
JS_CFUNC_DEF("ntohs", 0, js_sys_ntohs),
JS_CFUNC_DEF("htonl", 0, js_sys_htonl),
JS_CFUNC_DEF("htons", 0, js_sys_htons),
JS_CFUNC_DEF("pixfmt_size", 0, js_pixfmt_size),
JS_CFUNC_DEF("pixfmt_transparent", 0, js_pixfmt_transparent),
JS_CFUNC_DEF("pixfmt_yuv", 0, js_pixfmt_yuv),
JS_CFUNC_DEF("pixfmt_depth", 0, js_pixfmt_depth),
JS_CFUNC_DEF("pcmfmt_depth", 0, js_pcmfmt_depth),
JS_CFUNC_DEF("color_lerp", 0, js_color_lerp),
JS_CFUNC_DEF("color_component", 0, js_color_get_component),
JS_CFUNC_DEF("url_cat", 0, js_sys_url_cat),
JS_CFUNC_DEF("rect_union", 0, js_sys_rect_union),
JS_CFUNC_DEF("rect_intersect", 0, js_sys_rect_intersect),
JS_CFUNC_DEF("mpd_parse", 0, js_sys_mpd_parse),
JS_CFUNC_DEF("_avmix_audio", 0, js_audio_mix),
JS_CFUNC_DEF("enable_rmtws", 0, js_sys_enable_rmtws),
JS_CFUNC_DEF("enable_userws", 0, js_sys_enable_userws),
JS_CFUNC_DEF("set_logs", 0, js_sys_set_logs),
JS_CFUNC_DEF("get_logs", 0, js_sys_get_logs),
};
static void js_sha1_finalize(JSRuntime *rt, JSValue obj)
{
GF_SHA1Context *sha1 = JS_GetOpaque(obj, sha1_class_id);
if (!sha1) return;
gf_free(sha1);
}
JSClassDef sha1Class = {
"SHA1",
.finalizer = js_sha1_finalize,
};
static JSValue js_sha1_push(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const u8 *data;
size_t data_size;
GF_SHA1Context *sha1 = JS_GetOpaque(this_val, sha1_class_id);
if (!sha1) {
sha1 = gf_sha1_starts();
if (!sha1) {
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
JS_SetOpaque(this_val, sha1);
}
if (!argc) return GF_JS_EXCEPTION(ctx);
data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
gf_sha1_update(sha1, (u8 *) data, (u32) data_size);
return JS_UNDEFINED;
}
static JSValue js_sha1_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
u8 output[GF_SHA1_DIGEST_SIZE];
GF_SHA1Context *sha1 = JS_GetOpaque(this_val, sha1_class_id);
if (!sha1) return GF_JS_EXCEPTION(ctx);
gf_sha1_finish(sha1, output);
JS_SetOpaque(this_val, NULL);
return JS_NewArrayBufferCopy(ctx, output, GF_SHA1_DIGEST_SIZE);
}
static const JSCFunctionListEntry sha1_funcs[] = {
JS_CFUNC_DEF("push", 0, js_sha1_push),
JS_CFUNC_DEF("get", 0, js_sha1_get),
};
static JSValue sha1_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
JSValue anobj;
GF_SHA1Context *sha1;
sha1 = gf_sha1_starts();
if (!sha1) {
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
anobj = JS_NewObjectClass(ctx, sha1_class_id);
if (JS_IsException(anobj)) {
gf_free(sha1);
return anobj;
}
JS_SetOpaque(anobj, sha1);
return anobj;
}
static void js_file_finalize(JSRuntime *rt, JSValue obj)
{
FILE *f = JS_GetOpaque(obj, file_class_id);
if (!f) return;
gf_fclose(f);
}
JSClassDef fileClass = {
"FILE",
.finalizer = js_file_finalize,
};
enum
{
JS_FILE_POS = 0,
JS_FILE_EOF,
JS_FILE_ERROR,
JS_FILE_SIZE,
JS_FILE_IS_GFIO,
};
static JSValue js_file_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_FILE_POS:
return JS_NewInt64(ctx, gf_ftell(f));
case JS_FILE_EOF:
return JS_NewBool(ctx, gf_feof(f));
case JS_FILE_ERROR:
return JS_NewInt32(ctx, gf_ferror(f) );
case JS_FILE_SIZE:
return JS_NewInt64(ctx, gf_fsize(f) );
case JS_FILE_IS_GFIO:
return JS_NewBool(ctx, gf_fileio_check(f));
}
return JS_UNDEFINED;
}
static JSValue js_file_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic)
{
s64 lival;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_FILE_POS:
if (JS_ToInt64(ctx, &lival, value)) return GF_JS_EXCEPTION(ctx);
gf_fseek(f, lival, SEEK_SET);
break;
}
return JS_UNDEFINED;
}
static JSValue js_file_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
gf_fflush(f);
return JS_UNDEFINED;
}
static JSValue js_file_close(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
gf_fclose(f);
JS_SetOpaque(this_val, NULL);
return JS_UNDEFINED;
}
static JSValue js_file_read(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const u8 *data;
size_t size;
u32 read;
s32 nb_bytes=0;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
if (!argc) return GF_JS_EXCEPTION(ctx);
data = JS_GetArrayBuffer(ctx, &size, argv[0]);
if (!data) return GF_JS_EXCEPTION(ctx);
if (argc>1) {
if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
}
if (!nb_bytes) nb_bytes = (s32) size;
else if (nb_bytes > (s32) size) nb_bytes = (s32) size;
read = (u32) gf_fread((void *) data, nb_bytes, f);
return JS_NewInt64(ctx, read);
}
static JSValue js_file_seek(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
s64 pos;
s32 whence, res;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
if (argc!=2) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt64(ctx, &pos, argv[0])) return GF_JS_EXCEPTION(ctx);
if (JS_ToInt32(ctx, &whence, argv[1])) return GF_JS_EXCEPTION(ctx);
res = gf_fseek(f, pos, whence);
return JS_NewInt32(ctx, res);
}
static JSValue js_file_gets(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
char *data=NULL;
char temp[1025];
JSValue res;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
temp[1024] = 0;
while (1) {
u32 rlen;
temp[0] = 0;
const char *read = gf_fgets(temp, 1024, f);
if (!read) break;
gf_dynstrcat(&data, temp, NULL);
if (!data) return js_throw_err(ctx, GF_OUT_OF_MEM);
rlen = (u32) strlen(temp);
if (rlen <= 1024)
break;
if (temp[rlen-1] == '\n')
break;
}
res = JS_NewString(ctx, data);
gf_free(data);
return res;
}
static JSValue js_file_getc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
char res[2];
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
res[0] = gf_fgetc(f);
res[1] = 0;
return JS_NewString(ctx, res);
}
static JSValue js_file_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const u8 *data;
size_t size;
u32 written;
s32 nb_bytes=0;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f) return GF_JS_EXCEPTION(ctx);
if (!argc) return GF_JS_EXCEPTION(ctx);
data = JS_GetArrayBuffer(ctx, &size, argv[0]);
if (!data) return GF_JS_EXCEPTION(ctx);
if (argc>1) {
if (JS_ToInt32(ctx, &nb_bytes, argv[1])) return GF_JS_EXCEPTION(ctx);
}
if (!nb_bytes) nb_bytes = (u32) size;
else if (nb_bytes > (s32) size) nb_bytes = (u32) size;
written = (u32) gf_fwrite((void *) data, nb_bytes, f);
return JS_NewInt64(ctx, written);
}
static JSValue js_file_puts(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *string;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f || !argc) return GF_JS_EXCEPTION(ctx);
string = JS_ToCString(ctx, argv[0]);
if (!string) return GF_JS_EXCEPTION(ctx);
gf_fputs(string, f);
JS_FreeCString(ctx, string);
return JS_UNDEFINED;
}
static JSValue js_file_putc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
int val=0;
FILE *f = JS_GetOpaque(this_val, file_class_id);
if (!f || !argc) return GF_JS_EXCEPTION(ctx);
if (JS_IsString(argv[0])) {
const char *string = JS_ToCString(ctx, argv[0]);
if (!string) return GF_JS_EXCEPTION(ctx);
val = string[0];
JS_FreeCString(ctx, string);
} else {
if (JS_ToInt32(ctx, &val, argv[0])) return GF_JS_EXCEPTION(ctx);
}
gf_fputc(val, f);
return JS_UNDEFINED;
}
static const JSCFunctionListEntry file_funcs[] = {
JS_CGETSET_MAGIC_DEF_ENUM("pos", js_file_prop_get, js_file_prop_set, JS_FILE_POS),
JS_CGETSET_MAGIC_DEF_ENUM("eof", js_file_prop_get, NULL, JS_FILE_EOF),
JS_CGETSET_MAGIC_DEF_ENUM("error", js_file_prop_get, NULL, JS_FILE_ERROR),
JS_CGETSET_MAGIC_DEF_ENUM("size", js_file_prop_get, NULL, JS_FILE_SIZE),
JS_CGETSET_MAGIC_DEF_ENUM("gfio", js_file_prop_get, NULL, JS_FILE_IS_GFIO),
JS_CFUNC_DEF("flush", 0, js_file_flush),
JS_CFUNC_DEF("close", 0, js_file_close),
JS_CFUNC_DEF("read", 0, js_file_read),
JS_CFUNC_DEF("seek", 0, js_file_seek),
JS_CFUNC_DEF("gets", 0, js_file_gets),
JS_CFUNC_DEF("getc", 0, js_file_getc),
JS_CFUNC_DEF("write", 0, js_file_write),
JS_CFUNC_DEF("puts", 0, js_file_puts),
JS_CFUNC_DEF("putc", 0, js_file_putc),
};
static JSValue file_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
JSValue anobj;
FILE *f=NULL;
if (!argc) {
f = gf_file_temp(NULL);
if (!f) {
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
} else {
GF_Err e = GF_OK;
const char *name=NULL, *mode=NULL, *parent_io=NULL;
name = JS_ToCString(ctx, argv[0] );
if (argc>1) {
mode = JS_ToCString(ctx, argv[1] );
if (argc>2) {
parent_io = JS_ToCString(ctx, argv[2] );
}
}
if (!name || !mode) {
e = GF_BAD_PARAM;
} else {
f = gf_fopen_ex(name, parent_io, mode, GF_FALSE);
if (!f) e = GF_URL_ERROR;
}
if (name) JS_FreeCString(ctx, name);
if (mode) JS_FreeCString(ctx, mode);
if (parent_io) JS_FreeCString(ctx, parent_io);
if (e) {
return js_throw_err(ctx, e);
}
}
anobj = JS_NewObjectClass(ctx, file_class_id);
if (JS_IsException(anobj)) {
gf_fclose(f);
return anobj;
}
JS_SetOpaque(anobj, f);
return anobj;
}
typedef struct
{
JSValue open, close, read, write, tell, seek, eof, exists, factory_obj;
JSContext *ctx;
u32 all_refs;
GF_List *url_pending;
Bool lock;
struct __jsf_fileio *root;
} JSFileIOFactoryCtx;
typedef struct __jsf_fileio
{
JSFileIOFactoryCtx *factory;
JSValue js_obj;
GF_FileIO *gfio;
u32 nb_refs;
} JSFileIOCtx;
static void js_fileio_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
if (!ioctx) return;
if (ioctx->factory->root == ioctx) {
JSFileIOFactoryCtx *iofac = ioctx->factory;
JS_MarkValue(rt, iofac->open, mark_func);
JS_MarkValue(rt, iofac->close, mark_func);
JS_MarkValue(rt, iofac->write, mark_func);
JS_MarkValue(rt, iofac->read, mark_func);
JS_MarkValue(rt, iofac->tell, mark_func);
JS_MarkValue(rt, iofac->seek, mark_func);
JS_MarkValue(rt, iofac->eof, mark_func);
JS_MarkValue(rt, iofac->exists, mark_func);
JS_MarkValue(rt, iofac->factory_obj, mark_func);
}
JS_MarkValue(rt, ioctx->js_obj, mark_func);
}
static void js_fileio_finalize(JSRuntime *rt, JSValue obj)
{
FILE *f = JS_GetOpaque(obj, file_class_id);
if (!f) return;
gf_fclose(f);
}
JSClassDef fileioClass = {
"FILEIO",
.finalizer = js_fileio_finalize,
.gc_mark = js_fileio_gc_mark
};
enum
{
JS_FILEIO_URL = 0,
JS_FILEIO_RES_URL,
JS_FILEIO_PARENT,
};
static JSValue js_fileio_prop_get(JSContext *ctx, JSValueConst this_val, int magic)
{
JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
if (!ioctx) return GF_JS_EXCEPTION(ctx);
switch (magic) {
case JS_FILEIO_URL:
return JS_NewString(ctx, gf_fileio_url(ioctx->gfio) );
case JS_FILEIO_RES_URL:
return JS_NewString(ctx, gf_fileio_resource_url(ioctx->gfio) );
case JS_FILEIO_PARENT:
return JS_DupValue(ctx, ioctx->factory->factory_obj);
}
return JS_UNDEFINED;
}
static JSValue js_fileio_protect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
if (!ioctx) return JS_UNDEFINED;
if (!ioctx->factory->lock) {
ioctx->factory->lock = GF_TRUE;
ioctx->factory->all_refs++;
}
return JS_UNDEFINED;
}
static void js_fileio_factory_delete(JSContext *ctx, JSFileIOFactoryCtx *iofac, JSFileIOCtx *ioctx_deleted)
{
while (gf_list_count(iofac->url_pending)) {
JSFileIOCtx *ioctx = gf_list_pop_back(iofac->url_pending);
gf_fileio_del(ioctx->gfio);
gf_free(ioctx);
}
gf_list_del(iofac->url_pending);
JS_SetOpaque(iofac->factory_obj, NULL);
JS_FreeValue(ctx, iofac->factory_obj);
JS_FreeValue(ctx, iofac->open);
JS_FreeValue(ctx, iofac->close);
JS_FreeValue(ctx, iofac->read);
JS_FreeValue(ctx, iofac->write);
JS_FreeValue(ctx, iofac->seek);
JS_FreeValue(ctx, iofac->tell);
JS_FreeValue(ctx, iofac->eof);
JS_FreeValue(ctx, iofac->exists);
//detach root (might have been closed before)
if (!ioctx_deleted || (ioctx_deleted->factory->root != ioctx_deleted) ) {
JS_SetOpaque(iofac->root->js_obj, NULL);
JS_FreeValue(ctx, iofac->root->js_obj);
gf_fileio_del(iofac->root->gfio);
gf_free(iofac->root);
}
gf_free(iofac);
}
static JSValue js_fileio_destroy(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSFileIOCtx *ioctx = JS_GetOpaque(this_val, fileio_class_id);
if (!ioctx) return JS_UNDEFINED;
if (!ioctx->factory->lock) return JS_UNDEFINED;
ioctx->factory->lock = GF_FALSE;
ioctx->factory->all_refs--;
if (!ioctx->factory->all_refs) {
js_fileio_factory_delete(ctx, ioctx->factory, NULL);
}
return JS_UNDEFINED;
}
static const JSCFunctionListEntry fileio_funcs[] = {
JS_CGETSET_MAGIC_DEF_ENUM("url", js_fileio_prop_get, NULL, JS_FILEIO_URL),
JS_CGETSET_MAGIC_DEF_ENUM("resource_url", js_fileio_prop_get, NULL, JS_FILEIO_RES_URL),
JS_CGETSET_MAGIC_DEF_ENUM("parent", js_fileio_prop_get, NULL, JS_FILEIO_PARENT),
JS_CFUNC_DEF("protect", 0, js_fileio_protect),
JS_CFUNC_DEF("destroy", 0, js_fileio_destroy),
};
static GF_Err jsfio_seek(GF_FileIO *fileio, u64 offset, s32 whence)
{
JSValue argv[2], res;
s32 ret;
JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
JSContext *ctx = ioctx->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
argv[0] = JS_NewInt64(ctx, offset);
argv[1] = JS_NewInt64(ctx, whence);
res = JS_Call(ctx, ioctx->factory->seek, ioctx->js_obj, 2, argv);
JS_ToInt32(ctx, &ret, res);
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return ret;
}
static u32 jsfio_read(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
JSValue argv[1], res;
u32 ret;
JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
JSContext *ctx = ioctx->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
argv[0] = JS_NewArrayBuffer(ctx, buffer, bytes, NULL, 0, 0);
res = JS_Call(ctx, ioctx->factory->read, ioctx->js_obj, 1, argv);
JS_FreeValue(ctx, argv[0]);
JS_ToInt32(ctx, &ret, res);
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return ret;
}
static u32 jsfio_write(GF_FileIO *fileio, u8 *buffer, u32 bytes)
{
JSValue argv[1], res;
u32 ret;
JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
JSContext *ctx = ioctx->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
argv[0] = JS_NewArrayBuffer(ctx, buffer, bytes, NULL, 0, 0);
res = JS_Call(ctx, ioctx->factory->write, ioctx->js_obj, 1, argv);
JS_FreeValue(ctx, argv[0]);
JS_ToInt32(ctx, &ret, res);
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return ret;
}
static s64 jsfio_tell(GF_FileIO *fileio)
{
JSValue res;
s64 ret;
JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
if (!ioctx || !ioctx->gfio) return GF_BAD_PARAM;
JSContext *ctx = ioctx->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
res = JS_Call(ctx, ioctx->factory->tell, ioctx->js_obj, 0, NULL);
JS_ToInt64(ctx, &ret, res);
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return ret;
}
static Bool jsfio_eof(GF_FileIO *fileio)
{
JSValue res;
s32 ret;
JSFileIOCtx *ioctx = gf_fileio_get_udta(fileio);
if (!ioctx || !ioctx->gfio) return GF_FALSE;
JSContext *ctx = ioctx->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
res = JS_Call(ctx, ioctx->factory->eof, ioctx->js_obj, 0, NULL);
JS_ToInt32(ctx, &ret, res);
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return ret ? GF_TRUE : GF_FALSE;
}
static GF_FileIO *jsfio_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_err)
{
JSValue args[2], res;
Bool do_delete = GF_FALSE;
JSFileIOCtx *ioctx_ref = gf_fileio_get_udta(fileio_ref);
if (!ioctx_ref || !ioctx_ref->gfio) return NULL;
JSContext *ctx = ioctx_ref->factory->ctx;
gf_js_lock(ctx, GF_TRUE);
*out_err = GF_OK;
if (!strcmp(mode, "url")) {
JSFileIOCtx *ioctx;
if (!url) return NULL;
GF_SAFEALLOC(ioctx, JSFileIOCtx);
if (!ioctx) return NULL;
char *path = gf_url_concatenate(gf_fileio_resource_url(ioctx_ref->gfio), url);
if (JS_IsUndefined(ioctx_ref->factory->write)) {
ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
} else if (JS_IsUndefined(ioctx_ref->factory->read)) {
ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
} else {
ioctx->gfio = gf_fileio_new((char *) path, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
}
gf_free(path);
if (!ioctx->gfio) {
gf_free(ioctx);
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
ioctx->factory = ioctx_ref->factory;
ioctx->js_obj = JS_UNDEFINED;
gf_list_add(ioctx->factory->url_pending, ioctx);
ioctx_ref->factory->all_refs++;
gf_js_lock(ctx, GF_FALSE);
return ioctx->gfio;
}
if (!strcmp(mode, "probe")) {
args[0] = JS_NewString(ctx, url);
res = JS_Call(ctx, ioctx_ref->factory->exists, ioctx_ref->factory->factory_obj, 1, args);
JS_FreeValue(ctx, args[0]);
if (!JS_ToBool(ctx, res)) *out_err = GF_URL_ERROR;
JS_FreeValue(ctx, res);
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
if (!strcmp(mode, "ref")) {
ioctx_ref->nb_refs++;
ioctx_ref->factory->all_refs++;
gf_js_lock(ctx, GF_FALSE);
return fileio_ref;
}
if (!strcmp(mode, "unref")) {
if (!ioctx_ref->nb_refs) return NULL;
ioctx_ref->nb_refs--;
ioctx_ref->factory->all_refs--;
if (ioctx_ref->nb_refs) {
gf_js_lock(ctx, GF_FALSE);
return fileio_ref;
}
//close
url = NULL;
mode = NULL;
do_delete = GF_TRUE;
}
if (mode && !strcmp(mode, "close")) {
if (!JS_IsUndefined(ioctx_ref->js_obj)) {
ioctx_ref->factory->all_refs--;
res = JS_Call(ctx, ioctx_ref->factory->close, ioctx_ref->js_obj, 0, NULL);
JS_FreeValue(ctx, res);
}
if (ioctx_ref->nb_refs) {
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
do_delete = GF_TRUE;
}
if (do_delete) {
JS_SetOpaque(ioctx_ref->js_obj, NULL);
JS_FreeValue(ctx, ioctx_ref->js_obj);
ioctx_ref->js_obj = JS_UNDEFINED;
//closing the root object
if (!ioctx_ref->factory->all_refs) {
js_fileio_factory_delete(ctx, ioctx_ref->factory, ioctx_ref);
gf_fileio_del(ioctx_ref->gfio);
gf_free(ioctx_ref);
} else if (ioctx_ref->factory->root != ioctx_ref) {
gf_fileio_del(ioctx_ref->gfio);
gf_free(ioctx_ref);
}
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
JSFileIOCtx *ioctx = NULL;
Bool created = GF_FALSE;
//open, create a new obj if underlying file object is not undefined (ie, it is open)
if (JS_IsUndefined(ioctx_ref->js_obj) ) {
ioctx = ioctx_ref;
if (gf_list_del_item(ioctx_ref->factory->url_pending, ioctx)>=0) {
ioctx_ref->factory->all_refs--;
}
}
if (!ioctx) {
u32 i, count = gf_list_count(ioctx_ref->factory->url_pending);
for (i=0; i<count; i++) {
ioctx = gf_list_get(ioctx_ref->factory->url_pending, i);
const char *a_url = gf_fileio_resource_url(ioctx->gfio);
if (!strcmp(url, a_url)) {
ioctx_ref->factory->all_refs--;
gf_list_rem(ioctx_ref->factory->url_pending, i);
break;
}
ioctx = NULL;
}
}
if (!ioctx) {
GF_SAFEALLOC(ioctx, JSFileIOCtx);
if (!ioctx) {
*out_err = GF_OUT_OF_MEM;
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
ioctx->factory = ioctx_ref->factory;
created = GF_TRUE;
}
ioctx->js_obj = JS_NewObjectClass(ctx, fileio_class_id);
if (JS_IsException(ioctx->js_obj)) {
JS_FreeValue(ctx, ioctx->js_obj);
ioctx->js_obj = JS_UNDEFINED;
if (created) gf_free(ioctx);
*out_err = GF_OUT_OF_MEM;
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
JS_SetOpaque(ioctx->js_obj, ioctx);
char *path = NULL;
if (!strnicmp(url, "gfio://", 7)) {
url = gf_fileio_translate_url(url);
} else if (created) {
path = gf_url_concatenate( gf_fileio_resource_url(ioctx_ref->gfio), url);
url = path;
}
if (created) {
if (JS_IsUndefined(ioctx->factory->write)) {
ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
} else if (JS_IsUndefined(ioctx->factory->read)) {
ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
} else {
ioctx->gfio = gf_fileio_new((char *) url, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
}
}
args[0] = JS_NewString(ctx, url);
args[1] = JS_NewString(ctx, mode);
res = JS_Call(ctx, ioctx_ref->factory->open, ioctx->js_obj, 2, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
if (path) gf_free(path);
if (JS_IsBool(res) && JS_ToBool(ctx, res) ) {
ioctx_ref->factory->all_refs++;
gf_js_lock(ctx, GF_FALSE);
return ioctx->gfio;
}
JS_ToInt32(ctx, (int32_t *) out_err, res);
JS_SetOpaque(ioctx->js_obj, NULL);
JS_FreeValue(ctx, ioctx->js_obj);
ioctx->js_obj = JS_UNDEFINED;
if (created) {
gf_fileio_del(ioctx->gfio);
gf_free(ioctx);
}
gf_js_lock(ctx, GF_FALSE);
return NULL;
}
static JSValue fileio_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
JSValue anobj;
JSFileIOCtx *ioctx;
char *full_url;
const char *parent;
if (argc != 9) return GF_JS_EXCEPTION(ctx);
if (!JS_IsString(argv[0])) return GF_JS_EXCEPTION(ctx);
//open
if (!JS_IsFunction(ctx, argv[1]) || JS_IsNull(argv[1])) return GF_JS_EXCEPTION(ctx);
//close
if (!JS_IsFunction(ctx, argv[2]) || JS_IsNull(argv[2])) return GF_JS_EXCEPTION(ctx);
//write, can be null
if (!JS_IsFunction(ctx, argv[3]) || JS_IsNull(argv[3])) {
if (!JS_IsFunction(ctx, argv[4]) || JS_IsNull(argv[4])) return GF_JS_EXCEPTION(ctx);
}
//read, can be null
if (!JS_IsFunction(ctx, argv[4]) || JS_IsNull(argv[4])) {
if (!JS_IsFunction(ctx, argv[3]) || JS_IsNull(argv[3])) return GF_JS_EXCEPTION(ctx);
}
//seek
if (!JS_IsFunction(ctx, argv[5]) || JS_IsNull(argv[5])) return GF_JS_EXCEPTION(ctx);
//tell
if (!JS_IsFunction(ctx, argv[6]) || JS_IsNull(argv[6])) return GF_JS_EXCEPTION(ctx);
//eos
if (!JS_IsFunction(ctx, argv[7]) || JS_IsNull(argv[7])) return GF_JS_EXCEPTION(ctx);
//exists
if (!JS_IsFunction(ctx, argv[8]) || JS_IsNull(argv[8])) return GF_JS_EXCEPTION(ctx);
const char *url = JS_ToCString(ctx, argv[0]);
if (!url) return GF_JS_EXCEPTION(ctx);
GF_SAFEALLOC(ioctx, JSFileIOCtx);
if (!ioctx) return js_throw_err(ctx, GF_OUT_OF_MEM);
GF_SAFEALLOC(ioctx->factory, JSFileIOFactoryCtx);
if (!ioctx->factory) {
gf_free(ioctx);
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
ioctx->factory->url_pending = gf_list_new();
if (!ioctx->factory->url_pending) {
gf_free(ioctx->factory);
gf_free(ioctx);
return js_throw_err(ctx, GF_OUT_OF_MEM);
}
anobj = JS_NewObjectClass(ctx, fileio_class_id);
if (JS_IsException(anobj)) {
gf_free(ioctx);
return anobj;
}
JS_SetOpaque(anobj, ioctx);
ioctx->factory->open = JS_DupValue(ctx, argv[1]);
ioctx->factory->close = JS_DupValue(ctx, argv[2]);
ioctx->factory->write = JS_DupValue(ctx, argv[3]);
ioctx->factory->read = JS_DupValue(ctx, argv[4]);
ioctx->factory->seek = JS_DupValue(ctx, argv[5]);
ioctx->factory->tell = JS_DupValue(ctx, argv[6]);
ioctx->factory->eof = JS_DupValue(ctx, argv[7]);
ioctx->factory->exists = JS_DupValue(ctx, argv[8]);
ioctx->factory->factory_obj = JS_DupValue(ctx, anobj);
ioctx->factory->ctx = ctx;
ioctx->js_obj = JS_UNDEFINED;
ioctx->factory->root = ioctx;
parent = jsf_get_script_filename(ctx);
full_url = gf_url_concatenate(parent, url);
if (JS_IsUndefined(ioctx->factory->write)) {
ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, jsfio_read, NULL, jsfio_tell, jsfio_eof, NULL);
} else if (JS_IsUndefined(ioctx->factory->read)) {
ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, NULL, jsfio_write, jsfio_tell, jsfio_eof, NULL);
} else {
ioctx->gfio = gf_fileio_new((char *) full_url, ioctx, jsfio_open, jsfio_seek, jsfio_read, jsfio_write, jsfio_tell, jsfio_eof, NULL);
}
JS_FreeCString(ctx, url);
gf_free(full_url);
return anobj;
}
static void js_core_gc_mark(JSRuntime *rt, JSValueConst this_val, JS_MarkFunc *mark_func)
{
if (js_rt->log_ctx) {
// JS_MarkValue(rt, js_rt->log_fun, mark_func);
// JS_MarkValue(rt, js_rt->log_obj, mark_func);
}
}
JSClassDef coreClass = {
"Core",
.gc_mark = js_core_gc_mark
};
static int js_gpaccore_init(JSContext *ctx, JSModuleDef *m)
{
JSValue proto, ctor;
if (!bitstream_class_id) {
JS_NewClassID(&bitstream_class_id);
JS_NewClass(JS_GetRuntime(ctx), bitstream_class_id, &bitstreamClass);
JS_NewClassID(&sha1_class_id);
JS_NewClass(JS_GetRuntime(ctx), sha1_class_id, &sha1Class);
JS_NewClassID(&file_class_id);
JS_NewClass(JS_GetRuntime(ctx), file_class_id, &fileClass);
JS_NewClassID(&amix_class_id);
JS_NewClass(JS_GetRuntime(ctx), amix_class_id, &amixClass);
JS_NewClassID(&fileio_class_id);
JS_NewClass(JS_GetRuntime(ctx), fileio_class_id, &fileioClass);
JS_NewClassID(&core_class_id);
JS_NewClass(JS_GetRuntime(ctx), core_class_id, &coreClass);
}
//JSValue core_o = JS_NewObject(ctx);
JSValue core_o = JS_NewObjectClass(ctx, core_class_id);
JS_SetPropertyFunctionList(ctx, core_o, sys_funcs, countof(sys_funcs));
JS_SetModuleExport(ctx, m, "Sys", core_o);
JSValue args = JS_NewArray(ctx);
u32 i, nb_args = gf_sys_get_argc();
for (i=0; i<nb_args; i++) {
JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, gf_sys_get_arg(i)));
}
JS_SetPropertyStr(ctx, core_o, "args", args);
#define DEF_CONST( _val ) \
JS_SetPropertyStr(ctx, core_o, #_val, JS_NewInt32(ctx, _val));
DEF_CONST(GF_CONSOLE_RESET)
DEF_CONST(GF_CONSOLE_RED)
DEF_CONST(GF_CONSOLE_GREEN)
DEF_CONST(GF_CONSOLE_BLUE)
DEF_CONST(GF_CONSOLE_YELLOW)
DEF_CONST(GF_CONSOLE_CYAN)
DEF_CONST(GF_CONSOLE_WHITE)
DEF_CONST(GF_CONSOLE_MAGENTA)
DEF_CONST(GF_CONSOLE_CLEAR)
DEF_CONST(GF_CONSOLE_SAVE)
DEF_CONST(GF_CONSOLE_RESTORE)
DEF_CONST(GF_CONSOLE_BOLD)
DEF_CONST(GF_CONSOLE_ITALIC)
DEF_CONST(GF_CONSOLE_UNDERLINED)
DEF_CONST(GF_CONSOLE_STRIKE)
DEF_CONST(SEEK_SET)
DEF_CONST(SEEK_CUR)
DEF_CONST(SEEK_END)
#undef DEF_CONST
//bitstream constructor
proto = JS_NewObjectClass(ctx, bitstream_class_id);
JS_SetPropertyFunctionList(ctx, proto, bitstream_funcs, countof(bitstream_funcs));
JS_SetClassProto(ctx, bitstream_class_id, proto);
ctor = JS_NewCFunction2(ctx, bitstream_constructor, "Bitstream", 1, JS_CFUNC_constructor, 0);
JS_SetModuleExport(ctx, m, "Bitstream", ctor);
//sha1 constructor
proto = JS_NewObjectClass(ctx, sha1_class_id);
JS_SetPropertyFunctionList(ctx, proto, sha1_funcs, countof(sha1_funcs));
JS_SetClassProto(ctx, sha1_class_id, proto);
ctor = JS_NewCFunction2(ctx, sha1_constructor, "SHA1", 1, JS_CFUNC_constructor, 0);
JS_SetModuleExport(ctx, m, "SHA1", ctor);
//FILE constructor
proto = JS_NewObjectClass(ctx, file_class_id);
JS_SetPropertyFunctionList(ctx, proto, file_funcs, countof(file_funcs));
JS_SetClassProto(ctx, file_class_id, proto);
ctor = JS_NewCFunction2(ctx, file_constructor, "File", 1, JS_CFUNC_constructor, 0);
JS_SetModuleExport(ctx, m, "File", ctor);
//amix constructor
proto = JS_NewObjectClass(ctx, amix_class_id);
JS_SetPropertyFunctionList(ctx, proto, amix_funcs, countof(amix_funcs));
JS_SetClassProto(ctx, amix_class_id, proto);
ctor = JS_NewCFunction2(ctx, amix_constructor, "AudioMixer", 1, JS_CFUNC_constructor, 0);
JS_SetModuleExport(ctx, m, "AudioMixer", ctor);
//FILEIO constructor
proto = JS_NewObjectClass(ctx, fileio_class_id);
JS_SetPropertyFunctionList(ctx, proto, fileio_funcs, countof(fileio_funcs));
JS_SetClassProto(ctx, fileio_class_id, proto);
ctor = JS_NewCFunction2(ctx, fileio_constructor, "FileIO", 1, JS_CFUNC_constructor, 0);
JS_SetModuleExport(ctx, m, "FileIO", ctor);
//RMTClient constructor
JS_NewClassID(&js_sys_rmt_client_class_id);
JS_NewClass(JS_GetRuntime(ctx), js_sys_rmt_client_class_id, &js_sys_rmt_client_class);
proto = JS_NewObjectClass(ctx, js_sys_rmt_client_class_id);
JS_SetPropertyFunctionList(ctx, proto, js_sys_rmt_client_funcs, countof(js_sys_rmt_client_funcs));
JS_SetClassProto(ctx, js_sys_rmt_client_class_id, proto);
return 0;
}
void qjs_module_init_gpaccore(JSContext *ctx)
{
JSModuleDef *m;
m = JS_NewCModule(ctx, "gpaccore", js_gpaccore_init);
if (!m) return;
JS_AddModuleExport(ctx, m, "Sys");
JS_AddModuleExport(ctx, m, "Bitstream");
JS_AddModuleExport(ctx, m, "SHA1");
JS_AddModuleExport(ctx, m, "File");
JS_AddModuleExport(ctx, m, "FileIO");
JS_AddModuleExport(ctx, m, "AudioMixer");
return;
}
void qjs_module_init_scenejs(JSContext *ctx);
void qjs_module_init_storage(JSContext *ctx);
void qjs_module_init_xhr(JSContext *c);
void qjs_module_init_evg(JSContext *c);
void qjs_module_init_webgl(JSContext *c);
void qjs_module_init_gpaccore(JSContext *c);
#ifndef GPAC_DISABLE_QJS_LIBC
#include "../quickjs/quickjs-libc.h"
void qjs_module_init_qjs_libc(JSContext *ctx)
{
#ifdef CONFIG_BIGNUM
if (bignum_ext) {
JS_AddIntrinsicBigFloat(ctx);
JS_AddIntrinsicBigDecimal(ctx);
JS_AddIntrinsicOperators(ctx);
JS_EnableBignumExt(ctx, TRUE);
}
#endif
/* system modules */
js_init_module_std(ctx, "std");
js_init_module_os(ctx, "os");
}
#endif // GPAC_DISABLE_QJS_LIBC
void qjs_init_all_modules(JSContext *ctx, Bool no_webgl, Bool for_vrml)
{
//init modules
qjs_module_init_gpaccore(ctx);
#ifndef GPAC_DISABLE_QJS_LIBC
qjs_module_init_qjs_libc(ctx);
#endif
//vrml, init scene JS but do not init xhr (defined in DOM JS)
if (for_vrml) {
#if !defined(GPAC_DISABLE_COMPOSITOR)
qjs_module_init_scenejs(ctx);
#endif
} else {
qjs_module_init_xhr(ctx);
}
qjs_module_init_evg(ctx);
qjs_module_init_storage(ctx);
if (!no_webgl && !for_vrml)
qjs_module_init_webgl(ctx);
}
int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, JS_BOOL use_realpath, JS_BOOL is_main);
#ifndef GPAC_STATIC_BIN
#if defined(WIN32) || defined(_WIN32_WCE)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, const char *module_name);
static JSModuleDef *qjs_module_loader_dyn_lib(JSContext *ctx,
const char *module_name)
{
JSModuleDef *m=NULL;
void *hd;
JSInitModuleFunc *init;
char *filename;
if (!strchr(module_name, '/') || !strchr(module_name, '\\')) {
/* must add a '/' so that the DLL is not searched in the system library paths */
filename = gf_strdup("./");
gf_dynstrcat(&filename, module_name, NULL);
if (!filename) return NULL;
} else {
filename = (char *)module_name;
}
/* load dynamic lib */
#ifdef WIN32
hd = LoadLibrary(filename);
#else
hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
#endif
if (filename != module_name)
gf_free(filename);
if (!hd) {
JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", module_name);
return NULL;
}
#ifdef WIN32
init = (JSInitModuleFunc *) GetProcAddress(hd, "js_init_module");
#else
init = (JSInitModuleFunc *) dlsym(hd, "js_init_module");
#endif
if (!init) {
JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", module_name);
} else {
m = init(ctx, module_name);
if (!m) {
JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", module_name);
}
}
#ifdef WIN32
FreeLibrary(hd);
#else
dlclose(hd);
#endif
return m;
}
#endif // GPAC_STATIC_BIN
JSModuleDef *create_json_module(JSContext *ctx, const char *module_name, JSValue val);
int js_module_test_json(JSContext *ctx, JSValueConst attributes);
JSModuleDef *qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes)
{
JSModuleDef *m;
const char *fext = gf_file_ext_start(module_name);
if (fext && (!strcmp(fext, ".so") || !strcmp(fext, ".dll") || !strcmp(fext, ".dylib")) ) {
#ifndef GPAC_STATIC_BIN
m = qjs_module_loader_dyn_lib(ctx, module_name);
#else
JS_ThrowReferenceError(ctx, "could not load module filename '%s', dynamic library loading disabled in build", module_name);
m = NULL;
#endif
} else {
u32 buf_len;
u8 *buf;
JSValue func_val;
char *url;
GF_Err e;
const char *par_url = jsf_get_script_filename(ctx);
url = gf_url_concatenate(par_url, module_name);
//depending on caller context, module_name may already be resolved against parent
if (gf_file_exists(url ? url : module_name)) {
e = gf_file_load_data(url ? url : module_name, &buf, &buf_len);
} else if (url && gf_file_exists(module_name)) {
e = gf_file_load_data(module_name, &buf, &buf_len);
} else {
e = GF_URL_ERROR;
}
if (url) gf_free(url);
if (e != GF_OK) {
JS_ThrowReferenceError(ctx, "could not load module filename '%s': %s", module_name, gf_error_to_string(e) );
return NULL;
}
char *fext = gf_file_ext_start(module_name);
int res = js_module_test_json(ctx, attributes);
if ((fext && !stricmp(fext, ".json")) || res > 0) {
/* compile as JSON or JSON5 depending on "type" */
JSValue val;
int flags;
if (res == 2)
flags = JS_PARSE_JSON_EXT;
else
flags = 0;
val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags);
js_free(ctx, buf);
if (JS_IsException(val))
return NULL;
m = create_json_module(ctx, module_name, val);
if (!m)
return NULL;
} else {
/* compile the module */
func_val = JS_Eval(ctx, buf ? (char *) buf : "", buf_len, module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
gf_free(buf);
if (JS_IsException(func_val))
return NULL;
/* XXX: could propagate the exception */
js_module_set_import_meta(ctx, func_val, GF_TRUE, GF_FALSE);
/* the module is already referenced, so we must free it */
m = JS_VALUE_GET_PTR(func_val);
JS_FreeValue(ctx, func_val);
}
}
return m;
}
#ifndef GPAC_DISABLE_QJS_LIBC
static JSContext *JS_NewWorkerContext(JSRuntime *rt)
{
JSContext *ctx = JS_NewContext(rt);
if (!ctx)
return NULL;
JSValue global_obj = JS_GetGlobalObject(ctx);
js_load_constants(ctx, global_obj);
JS_FreeValue(ctx, global_obj);
//disable WebGL and scene.js modules for workers
qjs_init_all_modules(ctx, GF_TRUE, GF_FALSE);
return ctx;
}
void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque)
{
if (!is_handled) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONSOLE, ("Possibly unhandled promise rejection: "));
js_dump_error_exc(ctx, reason);
}
}
#endif
static void qjs_init_runtime_libc(JSRuntime *rt)
{
if (gf_opts_get_bool("core", "no-js-mods"))
return;
/* module loader */
JS_SetModuleLoaderFunc2(rt, NULL, qjs_module_loader, NULL, NULL);
#ifndef GPAC_DISABLE_QJS_LIBC
js_std_set_worker_new_context_func(JS_NewWorkerContext);
js_std_init_handlers(rt);
if (gf_opts_get_bool("core", "unhandled-rejection")) {
JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, NULL);
}
#ifdef GPAC_ENABLE_COVERAGE
if (gf_sys_is_cov_mode()) {
js_promise_rejection_tracker(NULL, JS_NULL, JS_NULL, 1, NULL);
}
#endif
#endif
}
static void qjs_uninit_runtime_libc(JSRuntime *rt)
{
if (gf_opts_get_bool("core", "no-js-mods"))
return;
#ifndef GPAC_DISABLE_QJS_LIBC
js_std_free_handlers(rt);
#endif
}
#endif