<commit_before>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: <NAME>
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / IsoMedia reader module
 *
 *  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 "isom_in.h"

#ifndef GPAC_DISABLE_ISOM

#include <gpac/ismacryp.h>

ISOMChannel *isor_get_channel(ISOMReader *reader, LPNETCHANNEL channel)
{
	u32 i=0;
	ISOMChannel *ch;
	while ((ch = (ISOMChannel *)gf_list_enum(reader->channels, &i))) {
		if (ch->channel == channel) return ch;
	}
	return NULL;
}

static void isor_delete_channel(ISOMReader *reader, ISOMChannel *ch)
{
	u32 i=0;
	ISOMChannel *ch2;
	while ((ch2 = (ISOMChannel *)gf_list_enum(reader->channels, &i))) {
		if (ch2 == ch) {
			isor_reset_reader(ch);
			gf_free(ch);
			gf_list_rem(reader->channels, i-1);
			return;
		}
	}
}

static GFINLINE Bool isor_is_local(const char *url)
{
	if (!strnicmp(url, "file://", 7)) return GF_TRUE;
	if (!strnicmp(url, "gmem://", 7)) return GF_TRUE;
	if (strstr(url, "://")) return GF_FALSE;
	/*the rest is local (mounted on FS)*/
	return GF_TRUE;
}

static const char * ISOR_MIME_TYPES[] = {
	/* First one is specific because we probe */
	"application/x-isomedia", "*", "IsoMedia Files",
	"video/mp4", "mp4 mpg4", "MPEG-4 Movies",
	"audio/mp4", "m4a mp4 mpg4", "MPEG-4 Music",
	"application/mp4", "m4i mp4 mpg4", "MPEG-4 Applications",
	"video/3gpp", "3gp 3gpp", "3GPP/MMS Movies",
	"audio/3gpp", "3gp 3gpp", "3GPP/MMS Music",
	"video/3gpp2", "3g2 3gp2", "3GPP2/MMS Movies",
	"audio/3gpp2", "3g2 3gp2", "3GPP2/MMS Music",
	NULL
};

u32 ISOR_RegisterMimeTypes(const GF_InputService *plug) {
	u32 i;
	for (i = 0 ; ISOR_MIME_TYPES[i]; i+=3)
		gf_service_register_mime(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2]);
	return i/3;
}

Bool ISOR_CanHandleURL(GF_InputService *plug, const char *url)
{
	char *ext;
	if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE;
	ext = (char *)strrchr(url, '.');
	{
		u32 i;
		/* We don't start at 0 since first one is specific */
		for (i = 3 ; ISOR_MIME_TYPES[i]; i+=3)
			if (gf_service_check_mime_register(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2], ext))
				return GF_TRUE;
	}

	if (ext && gf_isom_probe_file(url)) {
		gf_service_check_mime_register(plug, ISOR_MIME_TYPES[0], ext+1, ISOR_MIME_TYPES[2], ext);
		return GF_TRUE;
	}
	return GF_FALSE;
}

void send_proxy_command(ISOMReader *read, Bool is_disconnect, Bool is_add_media, GF_Err e, GF_Descriptor *desc, LPNETCHANNEL channel)
{
	GF_NetworkCommand command;
	command.command_type = GF_NET_SERVICE_STATUS_PROXY;
	command.status.e = e;
	command.status.channel = channel;
	command.status.is_disconnect = is_disconnect;
	command.status.is_add_media = is_add_media;
	command.status.desc = desc;
	read->input->query_proxy(read->input, &command);
}

void isor_check_buffer_level(ISOMReader *read)
{
	Double dld_time_remaining, mov_rate;
	GF_NetworkCommand com;
	u32 i, total, done, Bps;
	u64 dur;
	GF_NetIOStatus status;
	Bool do_buffer = GF_FALSE;
	if (!read->dnload) return;
	if (!read->mov) return;

	gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total, &done, &Bps, &status);
	if (!Bps) return;


	gf_mx_p(read->segment_mutex);

	dld_time_remaining = total-done;
	dld_time_remaining /= Bps;

	//we add 30 seconds to smooth out bitrate variations ..;
	dld_time_remaining += 30;

	mov_rate = total;
	dur = gf_isom_get_duration(read->mov);
	if (dur) {
		mov_rate /= dur;
		mov_rate *= gf_isom_get_timescale(read->mov);
	}

	for (i=0; i<gf_list_count(read->channels); i++) {
		ISOMChannel *ch = gf_list_get(read->channels, i);
		Double time_remain_ch = (Double) gf_isom_get_media_duration(read->mov, ch->track);
		u32 buffer_level=0;
		if (total==done) {
			time_remain_ch = 0;
			do_buffer = GF_FALSE;
		} else if (ch->last_state == GF_EOS) {
			time_remain_ch = 0;
			do_buffer = GF_TRUE;
		} else {
			u64 data_offset;
			u32 di, sn = ch->sample_num ? ch->sample_num : 1;
			GF_ISOSample *samp = gf_isom_get_sample_info(read->mov, ch->track, sn, &di, &data_offset);
			if (!samp) continue;

			data_offset += samp->dataLength;

			//we only send buffer on/off based on remainging playback time in channel
#if 0
			//we don't have enough data
			if (((data_offset + ch->buffer_min * mov_rate/1000 > done))) {
				do_buffer = GF_TRUE;
			}
			//we have enough buffer
			else if ((data_offset + ch->buffer_max * mov_rate/1000 <= done)) {
				do_buffer = GF_FALSE;
			}
#endif
			time_remain_ch -= (samp->DTS + samp->CTS_Offset);
			if (time_remain_ch<0) time_remain_ch=0;
			gf_isom_sample_del(&samp);

			time_remain_ch /= ch->time_scale;
			if (time_remain_ch && (time_remain_ch < dld_time_remaining)) {
				do_buffer = GF_TRUE;
				if (!read->remain_at_buffering_start || (read->remain_at_buffering_start < dld_time_remaining)) {
					buffer_level = 0;
					read->remain_at_buffering_start = dld_time_remaining;
				} else {
					buffer_level = (u32) (100 * (read->remain_at_buffering_start - dld_time_remaining) / (read->remain_at_buffering_start - time_remain_ch) );
				}
			} else {
				do_buffer = GF_FALSE;
			}
		}

		if (do_buffer != ch->buffering) {
			GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[IsoMedia] Buffering %s at %d: %g sec still to download and %g sec still to play on track %d (movie rate %g - download rate %g kbps)\n", do_buffer ? "on" : "off", gf_sys_clock(), dld_time_remaining , time_remain_ch, ch->track_id, mov_rate*8/1000, Bps*8.0/1000));

			memset(&com, 0, sizeof(GF_NetworkCommand));
			com.command_type = do_buffer ? GF_NET_CHAN_PAUSE : GF_NET_CHAN_RESUME;
			com.buffer.on_channel = ch->channel;
			com.buffer.min = ch->buffer_min;
			com.buffer.max = ch->buffer_max;
			gf_service_command(read->service, &com, GF_OK);
			ch->buffering = do_buffer;
			read->buffering = do_buffer;
		} else if (ch->buffering) {
			memset(&com, 0, sizeof(GF_NetworkCommand));
			com.command_type = GF_NET_CHAN_BUFFER;
			com.buffer.on_channel = ch->channel;
			com.buffer.min = ch->buffer_min;
			com.buffer.max = ch->buffer_max;
			com.buffer.occupancy = buffer_level;
			gf_service_command(read->service, &com, GF_OK);
		}
	}
	gf_mx_v(read->segment_mutex);
}

void isor_net_io(void *cbk, GF_NETIO_Parameter *param)
{
	GF_Err e;
	u32 size = 0;
	char *local_name;
	ISOMReader *read = (ISOMReader *) cbk;

	/*handle service message*/
	if (!read->buffering)
		gf_service_download_update_stats(read->dnload);

	if (param->msg_type==GF_NETIO_DATA_TRANSFERED) {
		e = GF_EOS;
	} else if (param->msg_type==GF_NETIO_DATA_EXCHANGE) {
		e = GF_OK;
		size = param->size;
	} else {
		e = param->error;
	}

	if (e<GF_OK) {
		/*error opening service*/
		if (!read->mov) {
			/* if there is an intermediate between this module and the terminal, report to it */
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, e);
			}
		}
		return;
	}

	/*open file if not done yet (bad interleaving)*/
	if (e==GF_EOS) {
		const char *local_name;
		if (read->mov) return;
		local_name = gf_dm_sess_get_cache_name(read->dnload);
		if (!local_name) {
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR);
			}
			return;
		}
		e = GF_OK;
		read->mov = gf_isom_open(local_name, GF_ISOM_OPEN_READ, NULL);
		if (!read->mov) e = gf_isom_last_error(NULL);
		else read->time_scale = gf_isom_get_timescale(read->mov);
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_OK);
		}
		if (read->no_service_desc) isor_declare_objects(read);
	}

	if (!size) return;

	/*service is opened, nothing to do*/
	if (read->mov) {
		isor_check_buffer_level(read);

		/*end of chunk*/
		if (read->frag_type && (param->reply==1) ) {
			u64 bytesMissing = 0;
			gf_mx_p(read->segment_mutex);
			e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, NULL);
			gf_mx_v(read->segment_mutex);
		}
		return;
	}

	/*try to open the service*/
	local_name = (char *)gf_dm_sess_get_cache_name(read->dnload);
	if (!local_name) {
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR);
		}
		return;
	}

	/*not enogh data yet*/
	if (read->missing_bytes && (read->missing_bytes > size) ) {
		read->missing_bytes -= size;
		return;
	}

	e = gf_isom_open_progressive(local_name, 0, 0, &read->mov, &read->missing_bytes);
	switch (e) {
	case GF_ISOM_INCOMPLETE_FILE:
		return;
	case GF_OK:
		break;
	default:
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, e);
		}
		return;
	}
	read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;

	/*ok let's go, we can setup the decoders */
	read->time_scale = gf_isom_get_timescale(read->mov);
	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
	} else {
		gf_service_connect_ack(read->service, NULL, GF_OK);
	}

	if (read->no_service_desc) isor_declare_objects(read);
}


void isor_setup_download(GF_InputService *plug, const char *url)
{
	ISOMReader *read = (ISOMReader *) plug->priv;
	read->dnload = gf_service_download_new(read->service, url, 0, isor_net_io, read);
	if (!read->dnload) {
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_NOT_SUPPORTED, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED);
		}
	} else {
		/*start our download (threaded)*/
		gf_dm_sess_process(read->dnload);
	}
	/*service confirm is done once IOD can be fetched*/
}

GF_Err ISOR_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url)
{
	char szURL[2048];
	char *tmp;
	ISOMReader *read;
	if (!plug || !plug->priv || !serv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;

	read->input = plug;
	read->service = serv;

	if (read->dnload) gf_service_download_del(read->dnload);
	read->dnload = NULL;

	if (!url) return GF_URL_ERROR;

	strcpy(szURL, url);
	tmp = strrchr(szURL, '.');
	if (tmp) {
		tmp = strchr(tmp, '#');
		if (tmp) {
			if (!strnicmp(tmp, "#trackID=", 9)) {
				read->play_only_track_id = atoi(tmp+9);
			} else {
				read->play_only_track_id = atoi(tmp+1);
			}
			tmp[0] = 0;
		}
	}

	if (isor_is_local(szURL)) {
		GF_Err e;
		u64 start_range, end_range;
		start_range = end_range = 0;
		if (plug->query_proxy) {
			GF_NetworkCommand param;
			param.command_type = GF_NET_SERVICE_QUERY_INIT_RANGE;
			if (read->input->query_proxy(read->input, &param)==GF_OK) {
				start_range = param.url_query.start_range;
				end_range = param.url_query.end_range;
			}
		}
		e = gf_isom_open_progressive(szURL, start_range, end_range, &read->mov, &read->missing_bytes);
		if (e != GF_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[IsoMedia] : error while opening %s, error=%s\n", szURL, gf_error_to_string(e)));
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, 0, 0, e, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, e);
			}
			return GF_OK;
		}
		read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;
		read->seg_opened = 2;

		read->time_scale = gf_isom_get_timescale(read->mov);
		/*reply to user*/
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_OK);
		}

		if (read->no_service_desc) isor_declare_objects(read);

	} else {
		/*setup downloader*/
		isor_setup_download(plug, szURL);
	}
	return GF_OK;
}

GF_Err ISOR_CloseService(GF_InputService *plug)
{
	GF_Err reply;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;
	reply = GF_OK;

	read->disconnected = GF_TRUE;
	if (read->mov) gf_isom_close(read->mov);
	read->mov = NULL;

	while (gf_list_count(read->channels)) {
		ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0);
		gf_list_rem(read->channels, 0);
		isor_delete_channel(read, ch);
	}

	if (read->dnload) gf_service_download_del(read->dnload);
	read->dnload = NULL;

	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, GF_TRUE, GF_FALSE, reply, NULL, NULL);
	} else {
		gf_service_disconnect_ack(read->service, NULL, reply);
	}
	return GF_OK;
}

static Bool check_mpeg4_systems(GF_InputService *plug, GF_ISOFile *mov)
{
	char *opt, *bname, *br, *next;
	u32 i, count, brand;
	Bool has_mpeg4;
	GF_Err e;
	e = gf_isom_get_brand_info(mov, &brand, &i, &count);
	/*no brand == MP4 v1*/
	if (e || !brand) return GF_TRUE;

	has_mpeg4 = GF_FALSE;
	if ((brand==GF_ISOM_BRAND_MP41) || (brand==GF_ISOM_BRAND_MP42)) has_mpeg4 = GF_TRUE;

	opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands");
	if (!opt) {
		gf_modules_set_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands", "nd*");
		opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands");
	}
<commit_msg>Fixed crash when closing ISOBMFF playback from HTTP<commit_after>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: <NAME>
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / IsoMedia reader module
 *
 *  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 "isom_in.h"

#ifndef GPAC_DISABLE_ISOM

#include <gpac/ismacryp.h>

ISOMChannel *isor_get_channel(ISOMReader *reader, LPNETCHANNEL channel)
{
	u32 i=0;
	ISOMChannel *ch;
	while ((ch = (ISOMChannel *)gf_list_enum(reader->channels, &i))) {
		if (ch->channel == channel) return ch;
	}
	return NULL;
}

static void isor_delete_channel(ISOMReader *reader, ISOMChannel *ch)
{
	u32 i=0;
	ISOMChannel *ch2;
	while ((ch2 = (ISOMChannel *)gf_list_enum(reader->channels, &i))) {
		if (ch2 == ch) {
			isor_reset_reader(ch);
			gf_free(ch);
			gf_list_rem(reader->channels, i-1);
			return;
		}
	}
}

static GFINLINE Bool isor_is_local(const char *url)
{
	if (!strnicmp(url, "file://", 7)) return GF_TRUE;
	if (!strnicmp(url, "gmem://", 7)) return GF_TRUE;
	if (strstr(url, "://")) return GF_FALSE;
	/*the rest is local (mounted on FS)*/
	return GF_TRUE;
}

static const char * ISOR_MIME_TYPES[] = {
	/* First one is specific because we probe */
	"application/x-isomedia", "*", "IsoMedia Files",
	"video/mp4", "mp4 mpg4", "MPEG-4 Movies",
	"audio/mp4", "m4a mp4 mpg4", "MPEG-4 Music",
	"application/mp4", "m4i mp4 mpg4", "MPEG-4 Applications",
	"video/3gpp", "3gp 3gpp", "3GPP/MMS Movies",
	"audio/3gpp", "3gp 3gpp", "3GPP/MMS Music",
	"video/3gpp2", "3g2 3gp2", "3GPP2/MMS Movies",
	"audio/3gpp2", "3g2 3gp2", "3GPP2/MMS Music",
	NULL
};

u32 ISOR_RegisterMimeTypes(const GF_InputService *plug) {
	u32 i;
	for (i = 0 ; ISOR_MIME_TYPES[i]; i+=3)
		gf_service_register_mime(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2]);
	return i/3;
}

Bool ISOR_CanHandleURL(GF_InputService *plug, const char *url)
{
	char *ext;
	if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE;
	ext = (char *)strrchr(url, '.');
	{
		u32 i;
		/* We don't start at 0 since first one is specific */
		for (i = 3 ; ISOR_MIME_TYPES[i]; i+=3)
			if (gf_service_check_mime_register(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2], ext))
				return GF_TRUE;
	}

	if (ext && gf_isom_probe_file(url)) {
		gf_service_check_mime_register(plug, ISOR_MIME_TYPES[0], ext+1, ISOR_MIME_TYPES[2], ext);
		return GF_TRUE;
	}
	return GF_FALSE;
}

void send_proxy_command(ISOMReader *read, Bool is_disconnect, Bool is_add_media, GF_Err e, GF_Descriptor *desc, LPNETCHANNEL channel)
{
	GF_NetworkCommand command;
	command.command_type = GF_NET_SERVICE_STATUS_PROXY;
	command.status.e = e;
	command.status.channel = channel;
	command.status.is_disconnect = is_disconnect;
	command.status.is_add_media = is_add_media;
	command.status.desc = desc;
	read->input->query_proxy(read->input, &command);
}

void isor_check_buffer_level(ISOMReader *read)
{
	Double dld_time_remaining, mov_rate;
	GF_NetworkCommand com;
	u32 i, total, done, Bps;
	u64 dur;
	GF_NetIOStatus status;
	Bool do_buffer = GF_FALSE;
	if (!read->dnload) return;
	if (!read->mov) return;

	gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total, &done, &Bps, &status);
	if (!Bps) return;


	gf_mx_p(read->segment_mutex);

	dld_time_remaining = total-done;
	dld_time_remaining /= Bps;

	//we add 30 seconds to smooth out bitrate variations ..;
	dld_time_remaining += 30;

	mov_rate = total;
	dur = gf_isom_get_duration(read->mov);
	if (dur) {
		mov_rate /= dur;
		mov_rate *= gf_isom_get_timescale(read->mov);
	}

	for (i=0; i<gf_list_count(read->channels); i++) {
		ISOMChannel *ch = gf_list_get(read->channels, i);
		Double time_remain_ch = (Double) gf_isom_get_media_duration(read->mov, ch->track);
		u32 buffer_level=0;
		if (total==done) {
			time_remain_ch = 0;
			do_buffer = GF_FALSE;
		} else if (ch->last_state == GF_EOS) {
			time_remain_ch = 0;
			do_buffer = GF_TRUE;
		} else {
			u64 data_offset;
			u32 di, sn = ch->sample_num ? ch->sample_num : 1;
			GF_ISOSample *samp = gf_isom_get_sample_info(read->mov, ch->track, sn, &di, &data_offset);
			if (!samp) continue;

			data_offset += samp->dataLength;

			//we only send buffer on/off based on remainging playback time in channel
#if 0
			//we don't have enough data
			if (((data_offset + ch->buffer_min * mov_rate/1000 > done))) {
				do_buffer = GF_TRUE;
			}
			//we have enough buffer
			else if ((data_offset + ch->buffer_max * mov_rate/1000 <= done)) {
				do_buffer = GF_FALSE;
			}
#endif
			time_remain_ch -= (samp->DTS + samp->CTS_Offset);
			if (time_remain_ch<0) time_remain_ch=0;
			gf_isom_sample_del(&samp);

			time_remain_ch /= ch->time_scale;
			if (time_remain_ch && (time_remain_ch < dld_time_remaining)) {
				do_buffer = GF_TRUE;
				if (!read->remain_at_buffering_start || (read->remain_at_buffering_start < dld_time_remaining)) {
					buffer_level = 0;
					read->remain_at_buffering_start = dld_time_remaining;
				} else {
					buffer_level = (u32) (100 * (read->remain_at_buffering_start - dld_time_remaining) / (read->remain_at_buffering_start - time_remain_ch) );
				}
			} else {
				do_buffer = GF_FALSE;
			}
		}

		if (do_buffer != ch->buffering) {
			GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[IsoMedia] Buffering %s at %d: %g sec still to download and %g sec still to play on track %d (movie rate %g - download rate %g kbps)\n", do_buffer ? "on" : "off", gf_sys_clock(), dld_time_remaining , time_remain_ch, ch->track_id, mov_rate*8/1000, Bps*8.0/1000));

			memset(&com, 0, sizeof(GF_NetworkCommand));
			com.command_type = do_buffer ? GF_NET_CHAN_PAUSE : GF_NET_CHAN_RESUME;
			com.buffer.on_channel = ch->channel;
			com.buffer.min = ch->buffer_min;
			com.buffer.max = ch->buffer_max;
			gf_service_command(read->service, &com, GF_OK);
			ch->buffering = do_buffer;
			read->buffering = do_buffer;
		} else if (ch->buffering) {
			memset(&com, 0, sizeof(GF_NetworkCommand));
			com.command_type = GF_NET_CHAN_BUFFER;
			com.buffer.on_channel = ch->channel;
			com.buffer.min = ch->buffer_min;
			com.buffer.max = ch->buffer_max;
			com.buffer.occupancy = buffer_level;
			gf_service_command(read->service, &com, GF_OK);
		}
	}
	gf_mx_v(read->segment_mutex);
}

void isor_net_io(void *cbk, GF_NETIO_Parameter *param)
{
	GF_Err e;
	u32 size = 0;
	char *local_name;
	ISOMReader *read = (ISOMReader *) cbk;

	/*handle service message*/
	if (!read->buffering)
		gf_service_download_update_stats(read->dnload);

	if (param->msg_type==GF_NETIO_DATA_TRANSFERED) {
		e = GF_EOS;
	} else if (param->msg_type==GF_NETIO_DATA_EXCHANGE) {
		e = GF_OK;
		size = param->size;
	} else {
		e = param->error;
	}

	if (e<GF_OK) {
		/*error opening service*/
		if (!read->mov) {
			/* if there is an intermediate between this module and the terminal, report to it */
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, e);
			}
		}
		return;
	}

	/*open file if not done yet (bad interleaving)*/
	if (e==GF_EOS) {
		const char *local_name;
		if (read->mov) return;
		local_name = gf_dm_sess_get_cache_name(read->dnload);
		if (!local_name) {
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR);
			}
			return;
		}
		e = GF_OK;
		read->mov = gf_isom_open(local_name, GF_ISOM_OPEN_READ, NULL);
		if (!read->mov) e = gf_isom_last_error(NULL);
		else read->time_scale = gf_isom_get_timescale(read->mov);
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_OK);
		}
		if (read->no_service_desc) isor_declare_objects(read);
	}

	if (!size) return;

	/*service is opened, nothing to do*/
	if (read->mov) {
		isor_check_buffer_level(read);

		/*end of chunk*/
		if (read->frag_type && (param->reply==1) ) {
			u64 bytesMissing = 0;
			gf_mx_p(read->segment_mutex);
			e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, NULL);
			gf_mx_v(read->segment_mutex);
		}
		return;
	}

	/*try to open the service*/
	local_name = (char *)gf_dm_sess_get_cache_name(read->dnload);
	if (!local_name) {
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR);
		}
		return;
	}

	/*not enogh data yet*/
	if (read->missing_bytes && (read->missing_bytes > size) ) {
		read->missing_bytes -= size;
		return;
	}

	e = gf_isom_open_progressive(local_name, 0, 0, &read->mov, &read->missing_bytes);
	switch (e) {
	case GF_ISOM_INCOMPLETE_FILE:
		return;
	case GF_OK:
		break;
	default:
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, e);
		}
		return;
	}
	read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;

	/*ok let's go, we can setup the decoders */
	read->time_scale = gf_isom_get_timescale(read->mov);
	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
	} else {
		gf_service_connect_ack(read->service, NULL, GF_OK);
	}

	if (read->no_service_desc) isor_declare_objects(read);
}


void isor_setup_download(GF_InputService *plug, const char *url)
{
	ISOMReader *read = (ISOMReader *) plug->priv;
	read->dnload = gf_service_download_new(read->service, url, 0, isor_net_io, read);
	if (!read->dnload) {
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_NOT_SUPPORTED, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED);
		}
	} else {
		/*start our download (threaded)*/
		gf_dm_sess_process(read->dnload);
	}
	/*service confirm is done once IOD can be fetched*/
}

GF_Err ISOR_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url)
{
	char szURL[2048];
	char *tmp;
	ISOMReader *read;
	if (!plug || !plug->priv || !serv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;

	read->input = plug;
	read->service = serv;

	if (read->dnload) gf_service_download_del(read->dnload);
	read->dnload = NULL;

	if (!url) return GF_URL_ERROR;

	strcpy(szURL, url);
	tmp = strrchr(szURL, '.');
	if (tmp) {
		tmp = strchr(tmp, '#');
		if (tmp) {
			if (!strnicmp(tmp, "#trackID=", 9)) {
				read->play_only_track_id = atoi(tmp+9);
			} else {
				read->play_only_track_id = atoi(tmp+1);
			}
			tmp[0] = 0;
		}
	}

	if (isor_is_local(szURL)) {
		GF_Err e;
		u64 start_range, end_range;
		start_range = end_range = 0;
		if (plug->query_proxy) {
			GF_NetworkCommand param;
			param.command_type = GF_NET_SERVICE_QUERY_INIT_RANGE;
			if (read->input->query_proxy(read->input, &param)==GF_OK) {
				start_range = param.url_query.start_range;
				end_range = param.url_query.end_range;
			}
		}
		e = gf_isom_open_progressive(szURL, start_range, end_range, &read->mov, &read->missing_bytes);
		if (e != GF_OK) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[IsoMedia] : error while opening %s, error=%s\n", szURL, gf_error_to_string(e)));
			if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
				send_proxy_command(read, 0, 0, e, NULL, NULL);
			} else {
				gf_service_connect_ack(read->service, NULL, e);
			}
			return GF_OK;
		}
		read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;
		read->seg_opened = 2;

		read->time_scale = gf_isom_get_timescale(read->mov);
		/*reply to user*/
		if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
			send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL);
		} else {
			gf_service_connect_ack(read->service, NULL, GF_OK);
		}

		if (read->no_service_desc) isor_declare_objects(read);

	} else {
		/*setup downloader*/
		isor_setup_download(plug, szURL);
	}
	return GF_OK;
}

GF_Err ISOR_CloseService(GF_InputService *plug)
{
	GF_Err reply;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;
	reply = GF_OK;

	read->disconnected = GF_TRUE;

	while (gf_list_count(read->channels)) {
		ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0);
		gf_list_rem(read->channels, 0);
		isor_delete_channel(read, ch);
	}

	if (read->dnload) gf_service_download_del(read->dnload);
	read->dnload = NULL;

	if (read->mov) gf_isom_close(read->mov);
	read->mov = NULL;

	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, GF_TRUE, GF_FALSE, reply, NULL, NULL);
	} else {
		gf_service_disconnect_ack(read->service, NULL, reply);
	}
	return GF_OK;
}

static Bool check_mpeg4_systems(GF_InputService *plug, GF_ISOFile *mov)
{
	char *opt, *bname, *br, *next;
	u32 i, count, brand;
	Bool has_mpeg4;
	GF_Err e;
	e = gf_isom_get_brand_info(mov, &brand, &i, &count);
	/*no brand == MP4 v1*/
	if (e || !brand) return GF_TRUE;

	has_mpeg4 = GF_FALSE;
	if ((brand==GF_ISOM_BRAND_MP41) || (brand==GF_ISOM_BRAND_MP42)) has_mpeg4 = GF_TRUE;

	opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands");
	if (!opt) {
		gf_modules_set_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands", "nd*");
		opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands");
	}

{'fraction_non_alphanumeric': 0.14444942889678733, 'fraction_numerical': 0.012560819904068463, 'mean_word_length': 4.423919146546884, 'pattern_counts': {'":': 2, '<': 19, '<?xml version=': 0, '>': 383, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7622323', 'n_tokens_mistral': 12431, 'n_tokens_neox': 11439, 'n_words': 2425}