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

#ifndef GPAC_DISABLE_MSE

#include <gpac/html5_mse.h>
#include <gpac/internal/isomedia_dev.h>

GF_HTML_MediaSource *gf_mse_media_source_new()
{
	GF_HTML_MediaSource *ms;
	GF_SAFEALLOC(ms, GF_HTML_MediaSource);
	if (!ms) return NULL;
	ms->sourceBuffers.list = gf_list_new();
	ms->sourceBuffers.parent = ms;
	ms->sourceBuffers.evt_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST, &ms->sourceBuffers);
	ms->activeSourceBuffers.list = gf_list_new();
	ms->activeSourceBuffers.parent = ms;
	ms->activeSourceBuffers.evt_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST, &ms->activeSourceBuffers);
	ms->reference_count = 1;
	ms->evt_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_MSE_MEDIASOURCE, ms);
	ms->durationType = DURATION_NAN;
	return ms;
}

GF_EXPORT
void gf_mse_mediasource_del(GF_HTML_MediaSource *ms, Bool del_js)
{
	if (ms) {
		if (del_js) {
			/* finalize the object from the JS perspective */
			ms->c = NULL;
			ms->_this = NULL;
		}
		/* only delete the object if it is not used elsewhere */
		if (ms->reference_count) {
			ms->reference_count--;
		}
		if (!ms->reference_count) {
			u32 i;
			for (i = 0; i < gf_list_count(ms->sourceBuffers.list); i++) {
				GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)gf_list_get(ms->sourceBuffers.list, i);

#ifdef FILTER_FIXME
				if (sb->parser && sb->parser_connected)
					sb->parser->CloseService(sb->parser);
#endif

				gf_mse_source_buffer_del(sb);
			}
			gf_list_del(ms->sourceBuffers.list);
			/* all source buffer should have been deleted in the deletion of sourceBuffers */
			gf_list_del(ms->activeSourceBuffers.list);
			if (ms->blobURI) gf_free(ms->blobURI);
			gf_free(ms->evt_target);
			gf_free(ms);
		}
	}
}

void gf_mse_fire_event(GF_DOMEventTarget *target, GF_EventType event_type)
{
	GF_SceneGraph *sg = NULL;
	GF_DOM_Event  mse_event;
	memset(&mse_event, 0, sizeof(GF_DOM_Event));
	mse_event.type = event_type;
	mse_event.target = target->ptr;
	mse_event.target_type = target->ptr_type;
	switch(target->ptr_type) {
	case GF_DOM_EVENT_TARGET_MSE_MEDIASOURCE:
	{
		GF_HTML_MediaSource *ms = (GF_HTML_MediaSource *)target->ptr;
		sg = ms->sg;
	}
	break;
	case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST:
	{
		GF_HTML_SourceBufferList *list = (GF_HTML_SourceBufferList *)target->ptr;
		sg = list->parent->sg;
	}
	break;
	case GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFER:
	{
		GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)target->ptr;
		sg = sb->mediasource->sg;
	}
	break;
	default:
		break;
	}
	assert(sg);
	gf_sg_fire_dom_event(target, &mse_event, sg, NULL);
}

GF_EXPORT
void gf_mse_mediasource_open(GF_HTML_MediaSource *ms, struct _mediaobj *mo)
{
	if (!ms) return;
	if (!mo) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[MSE] Cannot open media source without a media object\n"));
		return;
	}
	ms->readyState = MEDIA_SOURCE_READYSTATE_OPEN;
	/* getting the Scene Tree node attached to this media source */
	ms->node = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(mo, 0));
	ms->sg = gf_node_get_graph(ms->node);
	gf_mse_fire_event(ms->evt_target, GF_EVENT_HTML_MSE_SOURCE_OPEN);
}

GF_EXPORT
void gf_mse_mediasource_close(GF_HTML_MediaSource *ms)
{
	if (!ms) return;
	ms->readyState = MEDIA_SOURCE_READYSTATE_CLOSED;
	gf_mse_fire_event(ms->evt_target, GF_EVENT_HTML_MSE_SOURCE_CLOSE);
}

GF_EXPORT
void gf_mse_mediasource_end(GF_HTML_MediaSource *ms)
{
	if (!ms) return;
	ms->readyState = MEDIA_SOURCE_READYSTATE_ENDED;
	gf_mse_fire_event(ms->evt_target, GF_EVENT_HTML_MSE_SOURCE_ENDED);
}

GF_HTML_SourceBuffer *gf_mse_source_buffer_new(GF_HTML_MediaSource *mediasource)
{
	char name[256];
	GF_HTML_SourceBuffer *source;
	GF_SAFEALLOC(source, GF_HTML_SourceBuffer);
	if (!source) return NULL;
	sprintf(name, "SourceBuffer_Thread_%p", source);
	source->mediasource = mediasource;
	source->buffered = gf_html_timeranges_new(1);
	source->input_buffer = gf_list_new();
	source->tracks = gf_list_new();
	source->threads = gf_list_new();
	source->parser_thread = gf_th_new(name);
	source->remove_thread = gf_th_new(name);
	source->append_mode = MEDIA_SOURCE_APPEND_MODE_SEGMENTS;
	source->appendWindowStart = 0;
	source->appendWindowEnd = GF_MAX_DOUBLE;
	source->evt_target = gf_dom_event_target_new(GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFER, source);
	source->timescale = 1;
	return source;
}

void gf_mse_mediasource_add_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb)
{
	gf_list_add(ms->sourceBuffers.list, sb);
	gf_mse_fire_event(ms->sourceBuffers.evt_target, GF_EVENT_HTML_MSE_ADD_SOURCE_BUFFER);
}

/* Not yet used
void gf_mse_add_active_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb)
{
    gf_list_add(ms->activeSourceBuffers.list, sb);
	gf_mse_fire_event(ms->activeSourceBuffers.evt_target, GF_EVENT_HTML_MSE_ADD_SOURCE_BUFFER);
} */

void gf_mse_remove_active_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb) {
	s32 activePos;
	activePos = gf_list_find(ms->activeSourceBuffers.list, sb);
	if (activePos >= 0) {
		gf_list_rem(ms->activeSourceBuffers.list, activePos);
		gf_mse_fire_event(ms->activeSourceBuffers.evt_target, GF_EVENT_HTML_MSE_REMOVE_SOURCE_BUFFER);
	}
}

static void gf_mse_reset_input_buffer(GF_List *input_buffer)
{
	while (gf_list_count(input_buffer)) {
		GF_HTML_ArrayBuffer *b = (GF_HTML_ArrayBuffer *)gf_list_get(input_buffer, 0);
		gf_list_rem(input_buffer, 0);
		gf_arraybuffer_del(b, GF_FALSE);
	}
}

/* Deletes all unparsed data buffers from all tracks in the source buffer */
static void gf_mse_source_buffer_reset_parser(GF_HTML_SourceBuffer *sb)
{
	u32 i, track_count;
	track_count = gf_list_count(sb->tracks);

	/* wait until all remaining entire AU are parsed and then flush the remaining bytes in the parser */

	for (i = 0; i < track_count; i++)
	{
		GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
		track->last_dts_set     = GF_FALSE;
		track->highest_pts_set  = GF_FALSE;
		track->needs_rap        = GF_TRUE;
	}
	sb->group_end_timestamp_set = GF_FALSE;
	gf_mse_reset_input_buffer(sb->input_buffer);
	sb->append_state = MEDIA_SOURCE_APPEND_STATE_WAITING_FOR_SEGMENT;
}

GF_Err gf_mse_source_buffer_abort(GF_HTML_SourceBuffer *sb)
{
	if (sb->updating) {
		/* setting to false should stop the parsing thread */
		sb->updating = GF_FALSE;
		gf_mse_fire_event(sb->evt_target, GF_EVENT_HTML_MSE_UPDATE_ABORT);
		gf_mse_fire_event(sb->evt_target, GF_EVENT_HTML_MSE_UPDATE_END);
	}
	gf_mse_source_buffer_reset_parser(sb);
	sb->appendWindowStart = 0;
	sb->appendWindowEnd = GF_MAX_DOUBLE;
	return GF_OK;
}

GF_Err gf_mse_remove_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb) {
	s32 pos;
	pos = gf_list_find(ms->sourceBuffers.list, sb);
	if (pos < 0) {
		return GF_NOT_FOUND;
	} else {
		gf_mse_source_buffer_abort(sb);
		/* TODO: update the audio/video/text tracks */
		gf_mse_remove_active_source_buffer(ms, sb);
		gf_list_rem(ms->sourceBuffers.list, pos);
		gf_mse_fire_event(ms->sourceBuffers.evt_target, GF_EVENT_HTML_MSE_REMOVE_SOURCE_BUFFER);
		gf_mse_source_buffer_del(sb);
	}
	return GF_OK;
}

/* TODO: not yet used
void gf_mse_detach(GF_HTML_MediaSource *ms) {
	u32 count;
	u32 i;
	GF_HTML_SourceBuffer *sb;
	ms->readyState = MEDIA_SOURCE_READYSTATE_CLOSED;
	ms->durationType = DURATION_NAN;
	count = gf_list_count(ms->sourceBuffers.list);
	for (i = 0; i < count; i++) {
		sb = (GF_HTML_SourceBuffer *)gf_list_get(ms->sourceBuffers.list, i);
		gf_mse_remove_source_buffer(ms, sb);
	}
} */

void gf_mse_source_buffer_del(GF_HTML_SourceBuffer *sb)
{
	GF_HTML_TrackList tlist;
	gf_html_timeranges_del(sb->buffered);
	gf_mse_reset_input_buffer(sb->input_buffer);
	gf_list_del(sb->input_buffer);

	if (sb->prev_buffer) gf_arraybuffer_del((GF_HTML_ArrayBuffer *)sb->prev_buffer, GF_FALSE);

	tlist.tracks = sb->tracks;
	gf_html_tracklist_del(&tlist);
	{
		u32 i, count;
		count = gf_list_count(sb->threads);
		for(i = 0; i < count; i++) {
			GF_Thread *t = (GF_Thread *)gf_list_get(sb->threads, i);
			gf_th_del(t);
		}
		gf_list_del(sb->threads);
	}
	gf_th_del(sb->parser_thread);
	gf_th_del(sb->remove_thread);

	if (sb->service_desc) gf_odf_desc_del((GF_Descriptor *)sb->service_desc);
	gf_free(sb);

}

void gf_mse_source_buffer_set_update(GF_HTML_SourceBuffer *sb, Bool update)
{
	if (sb->updating == update) return;
	sb->updating = update;
	if (update) {
		gf_mse_fire_event(sb->evt_target, GF_EVENT_HTML_MSE_UPDATE_START);
	} else {
		gf_mse_fire_event(sb->evt_target, GF_EVENT_HTML_MSE_UPDATE);
		gf_mse_fire_event(sb->evt_target, GF_EVENT_HTML_MSE_UPDATE_END);
	}
}

/*locates and loads an input service (demuxer, parser) for this source buffer based on mime type or segment name*/
GF_Err gf_mse_source_buffer_load_parser(GF_HTML_SourceBuffer *sourcebuffer, const char *mime)
{
#ifdef FILTER_FIXME
	GF_InputService *parser = NULL;

	if (mime) {
		const char *sPlug;
		/* strip the 'codecs' and 'profile' parameters from the MIME type */
		char *param = (char *)strchr(mime, ';');
		if (param) {
			*param = 0;
		}

		/* Check MIME type to start the right InputService */
		sPlug = gf_opts_get_key("MimeTypes", mime);
		if (sPlug) sPlug = strrchr(sPlug, '"');
		if (sPlug) {
			sPlug += 2;
			parser = (GF_InputService *) gf_modules_load_by_name(sPlug, GF_NET_CLIENT_INTERFACE);
		}

		if (param) {
			*param = ';';
		}
	}
	if (parser) {
		sourcebuffer->parser = parser;
		parser->query_proxy = gf_mse_proxy;
		parser->proxy_udta = sourcebuffer;
		parser->proxy_type = GF_TRUE;
		return GF_OK;
	} else {
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MSE] Error locating plugin for source - mime type %s\n", mime));
		return GF_BAD_PARAM;
	}
#else
	return GF_NOT_SUPPORTED;
#endif
}

#ifdef FILTER_FIXME

/* create a track based on the ESD and adds it to the source buffer */
static GF_HTML_Track *gf_mse_source_buffer_add_track(GF_HTML_SourceBuffer *sb, GF_ESD *esd)
{
	GF_HTML_Track *track;
	GF_HTML_TrackType type;

	track = NULL;
	type = HTML_MEDIA_TRACK_TYPE_UNKNOWN;
	if (esd->decoderConfig->streamType==GF_STREAM_VISUAL) {
		type = HTML_MEDIA_TRACK_TYPE_VIDEO;
	} else if (esd->decoderConfig->streamType==GF_STREAM_AUDIO) {
		type = HTML_MEDIA_TRACK_TYPE_AUDIO;
	} else {
		/* TODO: Text tracks */
	}

	track = gf_html_media_track_new(type, "", GF_TRUE, "", "", "", "");
	if (track) {
		char mx_name[256];
		track->bin_id = esd->ESID;
		track->buffer = gf_list_new();
		sprintf(mx_name, "track_mutex_%d", track->bin_id);
		track->buffer_mutex = gf_mx_new(mx_name);
		track->timescale = esd->slConfig->timestampResolution;
		gf_list_add(sb->tracks, track);
	}
	return track;
}

/* Creates the different tracks based on the demuxer parser information
   Needs the parser to be setup and the initialization segment passed */
static GF_Err gf_mse_source_buffer_setup_tracks(GF_HTML_SourceBuffer *sb)
{
	if (!sb || !sb->parser || !sb->parser_connected) return GF_BAD_PARAM;
	sb->service_desc = (GF_ObjectDescriptor *)sb->parser->GetServiceDescriptor(sb->parser, GF_MEDIA_OBJECT_UNDEF, NULL);
	if (sb->service_desc) {
		u32 count;
		count = gf_list_count(sb->service_desc->ESDescriptors);
		if (count) {
			u32 i;
			for (i = 0; i < count; i++) {
				GF_ESD *esd = (GF_ESD *)gf_list_get(sb->service_desc->ESDescriptors, i);
				gf_mse_source_buffer_add_track(sb, esd);
			}
		}
		return GF_OK;
	} else {
		return GF_BAD_PARAM;
	}
	return GF_BAD_PARAM;
}

/* Adds the ObjectDescriptor to the associated track (creating a new track if needed)
   Called in response to addmedia events received from the parser */
static GF_Err gf_mse_source_buffer_store_track_desc(GF_HTML_SourceBuffer *sb, GF_ObjectDescriptor *od)
{
	u32 i;
	u32 count;
	GF_ESD *esd;
	Bool found = GF_FALSE;
	GF_HTML_Track *track;

	esd = (GF_ESD *)gf_list_get(od->ESDescriptors, 0);
	count = gf_list_count(sb->tracks);
	for (i = 0; i < count; i++) {
		track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
		if (track->bin_id == esd->ESID) {
			track->od = od;
			found = GF_TRUE;
			break;
		}
	}
	if (!found) {
		track = gf_mse_source_buffer_add_track(sb, esd);
		if (track) {
			track->od = od;
		} else {
			/* TODO: error */
			return GF_BAD_PARAM;
		}
	}
	return GF_OK;
}

#endif

#define SECONDS_TO_TIMESCALE(s) ((s)*track->timescale)
#define TIMESCALE_TO_SECONDS(u) ((u)*1.0/track->timescale)


GF_HTML_MediaTimeRanges *gf_mse_timeranges_from_track_packets(GF_HTML_Track *track) {
	u32 i, count;
	GF_HTML_MediaTimeRanges *ranges;
	u64 start;
	u64 end=0;
	Bool end_set = GF_FALSE;
	GF_MSE_Packet *packet;

	ranges = gf_html_timeranges_new(track->timescale);
	count = gf_list_count(track->buffer);
	for (i = 0; i < count; i++) {
		packet = (GF_MSE_Packet *)gf_list_get(track->buffer, i);
		if (end_set == GF_FALSE|| packet->sl_header.compositionTimeStamp > end) {
			if (end_set == GF_TRUE) {
				gf_html_timeranges_add_end(ranges, end);
			}
			start = packet->sl_header.compositionTimeStamp;
			gf_html_timeranges_add_start(ranges, start);
			end = packet->sl_header.compositionTimeStamp + packet->sl_header.au_duration;
			end_set = GF_TRUE;
		} else if (packet->sl_header.compositionTimeStamp == end) {
			end = packet->sl_header.compositionTimeStamp + packet->sl_header.au_duration;
		}
	}
	if (end_set == GF_TRUE) {
		gf_html_timeranges_add_end(ranges, end);
	}
	return ranges;
}

/* Traverses the list of Access Units already demuxed & parsed to update the buffered status */
void gf_mse_source_buffer_update_buffered(GF_HTML_SourceBuffer *sb) {
	u32 i;
	u32 track_count;

	track_count = gf_list_count(sb->tracks);
	gf_html_timeranges_reset(sb->buffered);
	for (i = 0; i < track_count; i++) {
		GF_HTML_MediaTimeRanges *track_ranges;
		GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
		gf_mx_p(track->buffer_mutex);
		track_ranges = gf_mse_timeranges_from_track_packets(track);
		if (i != 0) {
			GF_HTML_MediaTimeRanges *tmp;
			tmp = gf_html_timeranges_intersection(sb->buffered, track_ranges);
			gf_html_timeranges_del(track_ranges);
			gf_list_del(sb->buffered->times);
			sb->buffered->times = tmp->times;
			sb->buffered->timescale = tmp->timescale;
			gf_free(tmp);
		} else {
			gf_list_del(sb->buffered->times);
			sb->buffered->times = track_ranges->times;
			sb->buffered->timescale = track_ranges->timescale;
			gf_free(track_ranges);
		}
		gf_mx_v(track->buffer_mutex);
	}
}

void gf_mse_source_buffer_set_timestampOffset(GF_HTML_SourceBuffer *sb, double d) {
	u32 i;
	sb->timestampOffset = (s64)(d*sb->timescale);
	if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEQUENCE) {
		sb->group_start_timestamp_flag = GF_TRUE;
		sb->group_start_timestamp = sb->timestampOffset;
	}
	for (i = 0; i < gf_list_count(sb->tracks); i++) {
		GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
		track->timestampOffset = sb->timestampOffset*track->timescale;
	}
}

void gf_mse_source_buffer_set_timescale(GF_HTML_SourceBuffer *sb, u32 new_timescale) {
	u32 old_timescale = sb->timescale;
	if (old_timescale == new_timescale) return;
	sb->timescale = new_timescale;
	sb->timestampOffset = (s64)((sb->timestampOffset * new_timescale * 1.0)/old_timescale);
	if (sb->group_start_timestamp_flag) {
		sb->group_start_timestamp = (u64)((sb->group_start_timestamp * new_timescale * 1.0)/old_timescale);
	}
	if (sb->group_end_timestamp_set) {
		sb->group_end_timestamp = (u64)((sb->group_end_timestamp * new_timescale * 1.0)/old_timescale);
	}
	sb->remove_start = (u64)((sb->remove_start * new_timescale * 1.0)/old_timescale);
	sb->remove_end = (u64)((sb->remove_end * new_timescale * 1.0)/old_timescale);

}

void gf_mse_packet_del(GF_MSE_Packet *packet) {
	gf_free(packet->data);
	gf_free(packet);
}

#ifdef FILTER_FIXME
static GF_MSE_Packet *gf_mse_find_overlapped_packet(GF_HTML_Track           *track,
        GF_MSE_Packet           *packet)
{
	u32     i;
	u32     count;
	Bool    found_previous;

	found_previous = GF_FALSE;
	gf_mx_p(track->buffer_mutex);
	count = gf_list_count(track->buffer);
	for (i = 0; i < count; i++)
	{
		GF_MSE_Packet *p = (GF_MSE_Packet *)gf_list_get(track->buffer, i);
		if (p->sl_header.compositionTimeStamp < packet->sl_header.compositionTimeStamp) {
			found_previous = GF_TRUE;
		}
		if (found_previous == GF_TRUE && p->sl_header.compositionTimeStamp > packet->sl_header.compositionTimeStamp) {
			gf_mx_v(track->buffer_mutex);
			return p;
		}
	}
	gf_mx_v(track->buffer_mutex);
	return NULL;
}
#endif

static void gf_mse_remove_frames_from_to(GF_HTML_Track *track,
        u64           from,
        u64           to)
{
	u32     i;

	gf_mx_p(track->buffer_mutex);
	i = 0;
	while (i < gf_list_count(track->buffer)) {
		GF_MSE_Packet *frame = (GF_MSE_Packet *)gf_list_get(track->buffer, i);
		if (frame->sl_header.compositionTimeStamp >= to) {
			break;
		} else if (frame->sl_header.compositionTimeStamp >= from && frame->sl_header.compositionTimeStamp < to) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Removing frame with PTS %g s (%d frames remaining)\n", TIMESCALE_TO_SECONDS(frame->sl_header.compositionTimeStamp), gf_list_count(track->buffer)));
			gf_list_rem(track->buffer, i);
		} else {
			i++;
		}
	}
	gf_mx_v(track->buffer_mutex);
}

#ifdef FILTER_FIXME
static void gf_mse_track_buffer_add_packet(GF_HTML_Track *track, GF_MSE_Packet *frame)
{
	u32 i, count;
	Bool inserted = GF_FALSE;

	gf_mx_p(track->buffer_mutex);
	/* TODO: improve insertion*/
	count = gf_list_count(track->buffer);
	for (i = 0; i < count; i++) {
		GF_MSE_Packet *next_frame =  (GF_MSE_Packet *)gf_list_get(track->buffer, i);
		if (frame->sl_header.decodingTimeStamp < next_frame->sl_header.decodingTimeStamp) {
			gf_list_insert(track->buffer, frame, i);
			/* if the frame had no duration, we can now tell its duration because of the next frame */
			if (!frame->sl_header.au_duration) {
				frame->sl_header.au_duration = (u32)(next_frame->sl_header.decodingTimeStamp - frame->sl_header.decodingTimeStamp);
				/* we need also to check the duration of the previous frame */
				if (i > 0) {
					GF_MSE_Packet *prev_frame =  (GF_MSE_Packet *)gf_list_get(track->buffer, i-1);
					/* we update the frame duration if the newly inserted frame modifies it */
					if (!prev_frame->sl_header.au_duration ||
					        prev_frame->sl_header.au_duration > frame->sl_header.decodingTimeStamp - prev_frame->sl_header.decodingTimeStamp) {
						prev_frame->sl_header.au_duration = (u32)(frame->sl_header.decodingTimeStamp - prev_frame->sl_header.decodingTimeStamp);
					}
				}
			}
			inserted = GF_TRUE;
			break;
		}
	}
	if (!inserted) {
		gf_list_add(track->buffer, frame);
		/* if the frame is inserted last, we cannot know its duration until a new frame is appended or unless the transport format carried it */
		count = gf_list_count(track->buffer);
		if (count > 1) {
			GF_MSE_Packet *prev_frame =  (GF_MSE_Packet *)gf_list_get(track->buffer, count-2);
			/* we update the frame duration if the newly inserted frame modifies it */
			if (!prev_frame->sl_header.au_duration ||
			        prev_frame->sl_header.au_duration > frame->sl_header.decodingTimeStamp - prev_frame->sl_header.decodingTimeStamp) {
				prev_frame->sl_header.au_duration = (u32)(frame->sl_header.decodingTimeStamp - prev_frame->sl_header.decodingTimeStamp);
			}
		}
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Adding frame with PTS %g s and duration %g s (%d frames in buffer)\n", TIMESCALE_TO_SECONDS(frame->sl_header.compositionTimeStamp), TIMESCALE_TO_SECONDS(frame->sl_header.au_duration), gf_list_count(track->buffer)));
	gf_mx_v(track->buffer_mutex);
}

static GF_Err gf_mse_process_coded_frame(GF_HTML_SourceBuffer    *sb,
        GF_HTML_Track           *track,
        GF_MSE_Packet           *frame,
        Bool *stored)
{
	s64 PTS_with_offset = frame->sl_header.compositionTimeStamp + frame->sl_header.timeStampOffset;
	s64 DTS_with_offset = frame->sl_header.decodingTimeStamp + frame->sl_header.timeStampOffset;
	*stored = GF_FALSE;

	if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEQUENCE && sb->group_start_timestamp_flag) {
		u32 i, count;
		/* compute the new offset without taking care of the previous one, since this is a new coded frame group */
		/* first adjust existing times to the new timescale */
		gf_mse_source_buffer_set_timescale(sb, track->timescale);
		sb->timestampOffset = (sb->group_start_timestamp - frame->sl_header.compositionTimeStamp);
		track->timestampOffset = (sb->group_start_timestamp - frame->sl_header.compositionTimeStamp);
		sb->group_end_timestamp = sb->group_start_timestamp;
		count = gf_list_count(sb->tracks);
		for (i = 0; i < count; i++) {
			GF_HTML_Track *t = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
			t->needs_rap = GF_TRUE;
		}
		sb->group_start_timestamp_flag = GF_FALSE;
	}

	if (track->timestampOffset != 0) {
		frame->sl_header.timeStampOffset = track->timestampOffset;
		PTS_with_offset = frame->sl_header.compositionTimeStamp + frame->sl_header.timeStampOffset;
		DTS_with_offset = frame->sl_header.decodingTimeStamp + frame->sl_header.timeStampOffset;
	}

	if (track->last_dts_set) {
		if (DTS_with_offset < (s64) track->last_dts ||
		        DTS_with_offset - track->last_dts > 2*track->last_dur) {
			/* A discontinuity in the timestamps is detected, this triggers the start of a new coded frame group */
			if (sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEGMENTS) {
				/* the current group ends at the start of this frame */
				/* check if sb.timescale has to be adjusted first with gf_mse_source_buffer_set_timescale(sb, track->timescale);*/
				sb->group_end_timestamp = PTS_with_offset;
				sb->group_end_timestamp_set = GF_TRUE;
			} else { /* sb->append_mode == MEDIA_SOURCE_APPEND_MODE_SEQUENCE */
				/* check if sb.timescale has to be adjusted first with gf_mse_source_buffer_set_timescale(sb, track->timescale);*/
				sb->group_start_timestamp = sb->group_end_timestamp;
				sb->group_start_timestamp_flag = GF_TRUE;
			}
			{
				u32 i, count;
				count = gf_list_count(sb->tracks);
				for (i = 0; i < count; i++) {
					GF_HTML_Track *t = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
					t->last_dts_set = GF_FALSE;
					t->last_dts = 0;
					t->last_dur = 0;
					t->highest_pts_set = GF_FALSE;
					t->highest_pts = 0;
					t->needs_rap = GF_TRUE;
				}
			}
			return gf_mse_process_coded_frame(sb, track, frame, stored);
		}
	}

	/* we only update the timestamps in the frame when we are sure the offset is the right one */
	frame->sl_header.compositionTimeStamp += frame->sl_header.timeStampOffset;
	frame->sl_header.decodingTimeStamp += frame->sl_header.timeStampOffset;
	frame->sl_header.timeStampOffset = 0;

	if (frame->sl_header.compositionTimeStamp < SECONDS_TO_TIMESCALE(sb->appendWindowStart)) {
		track->needs_rap = GF_TRUE;
		return GF_OK;
	}

	if (frame->sl_header.compositionTimeStamp + frame->sl_header.au_duration > SECONDS_TO_TIMESCALE(sb->appendWindowEnd)) {
		track->needs_rap = GF_TRUE;
		return GF_OK;
	}

	if (track->needs_rap) {
		if (!frame->sl_header.randomAccessPointFlag) {
			return GF_OK;
		}
		track->needs_rap = GF_FALSE;
	}

	if (!track->last_dts_set) {
		/* find a frame in the track buffer whose PTS is less than the current packet and whose PTS + dur is greater than the current packet */
		GF_MSE_Packet           *overlapped_packet;
		overlapped_packet = gf_mse_find_overlapped_packet(track, frame);
		if (overlapped_packet) {
			gf_mse_remove_frames_from_to(track, overlapped_packet->sl_header.compositionTimeStamp,
			                             overlapped_packet->sl_header.compositionTimeStamp + (u64)SECONDS_TO_TIMESCALE(0.000001));
		}
	}

	if (!track->highest_pts_set) {
		/* this is the first time a frame is processed in the append sequence */
		gf_mse_remove_frames_from_to(track, frame->sl_header.compositionTimeStamp, frame->sl_header.compositionTimeStamp + frame->sl_header.au_duration);
	} else if (track->highest_pts <= frame->sl_header.compositionTimeStamp) {
		/* the highest pts has already been set in this append sequence, so we just need to remove frames from that point on, it's safe */
		gf_mse_remove_frames_from_to(track, track->highest_pts, track->highest_pts + track->last_dur);
	}

	/* remove dependencies !! */

	/* TODO: spliced frames */

	*stored = GF_TRUE;
	/* adds the packet and update the previous frame duration */
	gf_mse_track_buffer_add_packet(track, frame);

	track->last_dts = frame->sl_header.decodingTimeStamp;
	track->last_dts_set = GF_TRUE;
	if (frame->sl_header.au_duration) {
		track->last_dur = frame->sl_header.au_duration;
	} else {
		/* assuming CFR - FIXME */
		frame->sl_header.au_duration = track->last_dur;
	}

	if (!track->highest_pts_set ||
	        (frame->sl_header.compositionTimeStamp + track->last_dur) > track->highest_pts) {
		track->highest_pts_set = GF_TRUE;
		track->highest_pts = frame->sl_header.compositionTimeStamp + frame->sl_header.au_duration;
	}

	if (!sb->group_end_timestamp_set || (frame->sl_header.compositionTimeStamp + frame->sl_header.au_duration > sb->group_end_timestamp)) {
		/* check if sb.timescale has to be adjusted first with gf_mse_source_buffer_set_timescale(sb, track->timescale);*/
		sb->group_end_timestamp = frame->sl_header.compositionTimeStamp + frame->sl_header.au_duration;
		sb->group_end_timestamp_set = GF_TRUE;
	}

	return GF_OK;
}
#endif


/* Thread run function: called as a result of an append buffer
 * Parses/Demultiplexes media segments and places the parsed AU in the track buffers
 *
 * \param par the GF_HTML_SourceBuffer object used for the append
 */
u32 gf_mse_parse_segment(void *par)
{
#ifdef FILTER_FIXME
	GF_MSE_Packet           *packet;
	GF_HTML_Track           *track;
	GF_HTML_SourceBuffer    *sb = (GF_HTML_SourceBuffer *)par;
	u32                     i;
	u32                     track_count;

	if (!sb->parser_connected) {
		GF_HTML_ArrayBuffer *buffer = (GF_HTML_ArrayBuffer *)gf_list_get(sb->input_buffer, 0);
		gf_list_rem(sb->input_buffer, 0);
		assert(buffer);
		/* we expect an initialization segment to connect the service */
		if (!buffer->is_init) {
			/* TODO: set the media element decode error and run the end of stream algo */
			goto exit;
		}
		sb->parser->ConnectService(sb->parser, sb->mediasource->service, buffer->url);
		sb->prev_buffer = buffer;
	} else {
		/* we expect a media segment */
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Starting to fetch AUs from parser\n"));

		/* For each track, ask the parser for all the Access Unit, until it is empty
		 * AU are placed as GF_MSE_Packets in the track buffer
		 */
		track_count = gf_list_count(sb->tracks);
		while (sb->updating) {
			u32 track_with_data = 0;
			for (i = 0; i < track_count; i++) {
				Bool stored = GF_FALSE;
				GF_Err e;
				track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
				GF_SAFEALLOC(packet, GF_MSE_Packet);
				if (!packet) continue;
				
				assert(track->channel);
				e = sb->parser->ChannelGetSLP(sb->parser, track->channel,
				                              &packet->data, &packet->size, &packet->sl_header,
				                              &packet->is_compressed, &packet->status, &packet->is_new_data);
				if (e!=GF_OK) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MSE] Failed to fetch AU from channel\n"));
				}
				else if (packet->status == GF_OK && packet->is_new_data && packet->size) {
					char *data;
					assert(packet->is_new_data && packet->size);
					data = (char *)gf_malloc(packet->size);
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] New AU parsed with PTS %g s\n", TIMESCALE_TO_SECONDS(packet->sl_header.compositionTimeStamp)));
					memcpy(data, packet->data, packet->size);
					packet->data = data;
					gf_mse_process_coded_frame(sb, track, packet, &stored);
					track_with_data++;
					sb->parser->ChannelReleaseSLP(sb->parser, track->channel);
				}

				if (!stored) gf_free(packet);
			}
			if (!track_with_data) {
				/* try to delete the previous buffer */
				gf_arraybuffer_del((GF_HTML_ArrayBuffer *)sb->prev_buffer, GF_FALSE);
				sb->prev_buffer = NULL;
				/* get ready to receive a new segment and start a new thread */
				break;
			}
		}
		/* reaching here, the parser does not have enough data to produce new AU */
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Done fetching AUs from parser\n"));
	}
exit:
	gf_mse_source_buffer_set_update(sb, GF_FALSE);
	return 0;
#else
	return 0;
#endif
}


void gf_mse_source_buffer_append_arraybuffer(GF_HTML_SourceBuffer *sb, GF_HTML_ArrayBuffer *buffer)
{
	assert(sb->parser);
	gf_mse_source_buffer_set_update(sb, GF_TRUE);

	buffer->url = (char *)gf_malloc(256);
	buffer->blob.data = buffer->data;
	buffer->blob.size = buffer->length;
	sprintf(buffer->url, "gmem://%p", &buffer->blob);
	buffer->reference_count++;
#ifndef GPAC_DISABLE_ISOM
	buffer->is_init = (gf_isom_probe_file(buffer->url) == 2 ? GF_TRUE : GF_FALSE);
#endif
	GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Appending segment %s to SourceBuffer %p\n", buffer->url, sb));

	gf_list_add(sb->input_buffer, buffer);
	/* Call the parser (asynchronously) and return */
	/* the updating attribute will be positioned back to 0 when the parser is done */
	{
		GF_Thread *t = gf_th_new(NULL);
		gf_list_add(sb->threads, t);
		gf_th_run(t, gf_mse_parse_segment, sb);
	}
}

/* Threaded function called upon request from JS
   - Removes data in each track buffer until the next RAP is found */
static u32 gf_mse_source_buffer_remove(void *par)
{
	GF_HTML_SourceBuffer    *sb = (GF_HTML_SourceBuffer *)par;
	u32                     i;
	u32                     j;
	u32                     track_count;
	u32                     frame_count;
	u64                     end = 0;

	gf_mse_source_buffer_set_update(sb, GF_TRUE);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Removing media until next RAP\n"));

	track_count = gf_list_count(sb->tracks);
	for (i = 0; i < track_count; i++) {
		GF_HTML_Track *track = (GF_HTML_Track *)gf_list_get(sb->tracks, i);
		gf_mse_source_buffer_set_timescale(sb, track->timescale);
		/* find the next random access point */
		gf_mx_p(track->buffer_mutex);
		frame_count = gf_list_count(track->buffer);
		for (j = 0; j < frame_count; j++) {
			GF_MSE_Packet *frame = (GF_MSE_Packet *)gf_list_get(track->buffer, j);
			if (frame->sl_header.randomAccessPointFlag &&
			        frame->sl_header.compositionTimeStamp >= sb->remove_end) {
				end = frame->sl_header.compositionTimeStamp;
				break;
			}
		}
		gf_mx_v(track->buffer_mutex);
		if (!end) end = (u64)SECONDS_TO_TIMESCALE(sb->remove_end);

		/* remove up to the next RAP found */
		gf_mse_remove_frames_from_to(track, sb->remove_start, end);
	}
	gf_mse_source_buffer_set_update(sb, GF_FALSE);
	return 0;
}

void gf_mse_remove(GF_HTML_SourceBuffer *sb, double start, double end)
{
	sb->remove_start = (u64)(start*sb->timescale);
	sb->remove_end = (u64)(end*sb->timescale);
	{
		GF_Thread *t = gf_th_new(NULL);
		gf_list_add(sb->threads, t);
		gf_th_run(t, gf_mse_source_buffer_remove, sb);
	}
}

/* Callback functions used by a media parser when parsing events happens */
GF_Err gf_mse_proxy(void *parser, void *command)
{
#if FILTER_FIXME
	if (!parser || !command || !parser->proxy_udta) {
		return GF_BAD_PARAM;
	} else {
		GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)parser->proxy_udta;
		switch (command->command_type) {
		case GF_NET_SERVICE_QUERY_INIT_RANGE:
			break;
		case GF_NET_SERVICE_QUERY_NEXT:
			/* The parser is asking for the next media segment in the buffer,
			   check for the media time and give the right one */
		{
			GF_HTML_ArrayBuffer *buffer;
			/* The input buffer should not be modified by append operations at the same time, no need to protect access */
			buffer = (GF_HTML_ArrayBuffer *)gf_list_get(sb->input_buffer, 0);
			if (buffer) {
				command->url_query.discontinuity_type = 0;
				command->url_query.current_download = GF_FALSE;
				command->url_query.start_range = 0;
				command->url_query.end_range = 0;
				command->url_query.switch_start_range = 0;
				command->url_query.switch_end_range = 0;
				command->url_query.next_url_init_or_switch_segment = NULL;
				if (buffer->is_init) {
					GF_HTML_ArrayBuffer *next = (GF_HTML_ArrayBuffer *)gf_list_get(sb->input_buffer, 1);
					command->url_query.discontinuity_type = 1;
					if (next) {
						GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Next segment to parse %s with init \n", next->url, buffer->url));
						command->url_query.next_url = next->url;
						command->url_query.next_url_init_or_switch_segment = buffer->url;
						gf_list_rem(sb->input_buffer, 0);
						gf_list_rem(sb->input_buffer, 0);
					} else {
						GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Only one init segment to parse %s, need to wait\n", buffer->url));
						command->url_query.next_url = NULL;
					}
				} else {
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE] Next segment to parse %s\n", buffer->url));
					command->url_query.next_url = buffer->url;
					gf_list_rem(sb->input_buffer, 0);
				}
				sb->prev_buffer = buffer;
			} else {
				command->url_query.next_url = NULL;
				command->url_query.discontinuity_type = 0;
			}
		}
		break;
		case GF_NET_SERVICE_STATUS_PROXY:
			/* The parser is informing the proxy about its status changes:
			    - new track found
				- all tracks parsed
				- connect/disconnect
				- */
			if (command->status.is_add_media) {
				if (command->status.desc) {
					gf_mse_source_buffer_store_track_desc(sb, (GF_ObjectDescriptor *)command->status.desc);
				} else {
					/* this is the last add media, we can switch updating to false */
					/* the first init segment was correctly processed */
					gf_mse_source_buffer_set_update(sb, GF_FALSE);
					/* TODO: set active tracks and send addsourcebuffer event */
					/* TODO: send media loadedmetadata event */
				}
				gf_service_declare_media(sb->mediasource->service, command->status.desc, (command->status.desc ? GF_TRUE : GF_FALSE));
			}
			/* general connection/disconnection messages from the media parser (not track related) */
			else if (!command->status.channel) {
				/* connection message */
				if (!command->status.is_disconnect) {
					if (command->status.e == GF_OK) {
						/* nothing needs to be done. Setup is done with final add media */
						sb->parser_connected = GF_TRUE;
						sb->mediasource->durationType = DURATION_INFINITY;
						gf_mse_source_buffer_setup_tracks(sb);
					} else {
						/* wrong first init segment */
						/* TODO: fire an error event */
					}
					gf_service_connect_ack(sb->mediasource->service, command->status.channel, command->status.e);
				} else {
					gf_service_disconnect_ack(sb->mediasource->service, command->status.channel, command->status.e);
				}
			}
			/* channel (track related) specific connection/disconnection messages from the media parser */
			else {
				if (!command->status.is_disconnect) {
					gf_service_connect_ack(sb->mediasource->service, command->status.channel, command->status.e);
				} else {
					gf_service_disconnect_ack(sb->mediasource->service, command->status.channel, command->status.e);
				}
			}
			break;
		default:
			gf_service_command(sb->mediasource->service, command, GF_OK);
			break;
		}
		return GF_OK;
	}
#else
	return GF_NOT_SUPPORTED;
#endif
}

/* Track Buffer Managment:
 * - Access Units are parsed/demultiplexed from the SourceBuffer input buffer and placed into individual track buffers
 * - The parsing/demux is done in a separate thread (so access to the the track buffer is protected by a mutex)
 *
 * Track Buffer Length: number of Access Units */
#define MSE_TRACK_BUFFER_LENGTH 1000

/* When an Access Unit can be released, we check if it needs to be kept or not in the track buffer */
GF_EXPORT
GF_Err gf_mse_track_buffer_release_packet(GF_HTML_Track *track) {
	GF_MSE_Packet *packet;
	gf_mx_p(track->buffer_mutex);
	packet = (GF_MSE_Packet *)gf_list_get(track->buffer, track->packet_index);
	if (packet) {
		track->packet_index++;
		if (gf_list_count(track->buffer) > MSE_TRACK_BUFFER_LENGTH) {
			packet = (GF_MSE_Packet *)gf_list_get(track->buffer, 0);
			track->packet_index--;
			gf_list_rem(track->buffer, 0);
			gf_free(packet->data);
			gf_free(packet);
		}
	}
	gf_mx_v(track->buffer_mutex);
	return GF_OK;
}

/* Requests from the decoders to get the next Access Unit: we get it from the track buffer */
GF_EXPORT
GF_Err gf_mse_track_buffer_get_next_packet(GF_HTML_Track *track,
        char **out_data_ptr, u32 *out_data_size,
        GF_SLHeader *out_sl_hdr, Bool *sl_compressed,
        GF_Err *out_reception_status, Bool *is_new_data)
{
	GF_MSE_Packet *packet;
	u32 count;
	gf_mx_p(track->buffer_mutex);
	count = gf_list_count(track->buffer);
	packet = (GF_MSE_Packet *)gf_list_get(track->buffer, track->packet_index);
	if (packet) {
		*out_data_ptr = packet->data;
		*out_data_size = packet->size;
		*out_sl_hdr = packet->sl_header;
		*sl_compressed = packet->is_compressed;
		*out_reception_status = packet->status;
		*is_new_data = packet->is_new_data;
		packet->is_new_data = GF_FALSE;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE_IN] Sending AU #%d/%d to decoder with PTS %g s\n", track->packet_index, count, TIMESCALE_TO_SECONDS(packet->sl_header.compositionTimeStamp)));
	} else {
		*out_data_ptr = NULL;
		*out_data_size = 0;
		*sl_compressed = GF_FALSE;
		*out_reception_status = GF_OK;
		*is_new_data = GF_FALSE;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MSE_In] No AU for decoder\n"));
	}
	gf_mx_v(track->buffer_mutex);
	return GF_OK;
}


#endif //GPAC_DISABLE_MSE
