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) {
				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;