/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			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",
	"video/iso.segment", "iso", "ISOBMF Fragments",
	"audio/iso.segment", "iso", "ISOBMF Fragments",
	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;

	mov_rate = total;
	if (read->frag_type) {
		u64 bytesMissing=0;
		gf_isom_refresh_fragmented(read->mov, &bytesMissing, NULL);
		mov_rate = (Double) (done + bytesMissing);
	}
	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) {
				do_buffer = GF_TRUE;
				continue;
			}

			data_offset += samp->dataLength;

#if 1
			//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;
			} else {
				do_buffer = ch->buffering;
			}
			buffer_level = (u32) ( (done - data_offset) / mov_rate * 1000);
#else
			//we only send buffer on/off based on remainging playback time in channel
			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;
			}
#endif
		}

		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);
		read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0;
		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");
	}

	for (i=0; i<count; i++) {
		e = gf_isom_get_alternate_brand(mov, i+1, &brand);
		if (e) return GF_FALSE;
		if ((brand==GF_ISOM_BRAND_MP41) || (brand==GF_ISOM_BRAND_MP42)) {
			has_mpeg4 = GF_TRUE;
			continue;
		}
		bname = (char*)gf_4cc_to_str(brand);
		br = opt;
		while (br) {
			Bool ignore = GF_FALSE;
			u32 orig_len, len;
			next = strchr(br, ' ');
			if (next) next[0] = 0;
			len = orig_len = (u32) strlen(br);

			while (len) {
				if (br[len-1]=='*') {
					br[len-1]=0;
					len--;
				} else {
					break;
				}
			}
			/*ignor all brands*/
			if (!len) ignore = GF_TRUE;
			else if (!strncmp(bname, br, len)) ignore = GF_TRUE;

			while (len<orig_len) {
				br[len] = '*';
				len++;
			}
			if (next) {
				next[0] = ' ';
				br = next + 1;
			} else {
				br = NULL;
			}
			if (ignore) return GF_FALSE;
		}
	}
	return has_mpeg4;
}

static u32 get_track_id(GF_ISOFile *mov, u32 media_type, u32 idx)
{
	u32 i, count, cur;
	cur=0;
	count = gf_isom_get_track_count(mov);
	for (i=0; i<count; i++) {
		if (gf_isom_get_media_type(mov, i+1) != media_type) continue;
		if (!idx) return gf_isom_get_track_id(mov, i+1);
		cur++;
		if (cur==idx) return gf_isom_get_track_id(mov, i+1);
	}
	return GF_FALSE;
}

/*fixme, this doesn't work properly with respect to @expect_type*/
static GF_Descriptor *ISOR_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url)
{
	u32 count, nb_st, i, trackID;
	GF_ESD *esd;
	ISOMReader *read;
	GF_InitialObjectDescriptor *iod;
	if (!plug || !plug->priv) return NULL;
	read = (ISOMReader *) plug->priv;
	if (!read->mov) return NULL;

	/*no matter what always read text as TTUs*/
	gf_isom_text_set_streaming_mode(read->mov, 1);

	trackID = 0;
	if (!sub_url) {
		trackID = read->play_only_track_id;
		read->play_only_track_id = 0;
	} else {
		char *ext = (char *)strrchr(sub_url, '#');
		if (!ext) {
			trackID = 0;
		} else {
			if (!strnicmp(ext, "#trackID=", 9)) trackID = atoi(ext+9);
			else if (!stricmp(ext, "#video")) trackID = get_track_id(read->mov, GF_ISOM_MEDIA_VISUAL, 0);
			else if (!strnicmp(ext, "#video", 6)) {
				trackID = atoi(ext+6);
				trackID = get_track_id(read->mov, GF_ISOM_MEDIA_VISUAL, trackID);
			}
			else if (!stricmp(ext, "#audio")) trackID = get_track_id(read->mov, GF_ISOM_MEDIA_AUDIO, 0);
			else if (!strnicmp(ext, "#audio", 6)) {
				trackID = atoi(ext+6);
				trackID = get_track_id(read->mov, GF_ISOM_MEDIA_AUDIO, trackID);
			}
			else trackID = atoi(ext+1);

			/*if trackID is 0, assume this is a fragment identifier*/
		}
	}

	if (!trackID && (expect_type!=GF_MEDIA_OBJECT_SCENE) && (expect_type!=GF_MEDIA_OBJECT_UNDEF)) {
		for (i=0; i<gf_isom_get_track_count(read->mov); i++) {
			u32 type = gf_isom_get_media_type(read->mov, i+1);
			if (
			    ((type==GF_ISOM_MEDIA_VISUAL) && (expect_type==GF_MEDIA_OBJECT_VIDEO))
			    || ((type==GF_ISOM_MEDIA_AUDIO) && (expect_type==GF_MEDIA_OBJECT_AUDIO)) ) {
				trackID = gf_isom_get_track_id(read->mov, i+1);
				break;
			}

		}
	}
	if (trackID && (expect_type!=GF_MEDIA_OBJECT_SCENE) ) {
		u32 track = gf_isom_get_track_by_id(read->mov, trackID);
		if (!track) return NULL;
		esd = gf_media_map_esd(read->mov, track);
		esd->OCRESID = 0;
		iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(read->mov);
		if (!iod) {
			iod = (GF_InitialObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG);
			iod->OD_profileAndLevel = iod->audio_profileAndLevel = iod->graphics_profileAndLevel = iod->scene_profileAndLevel = iod->visual_profileAndLevel = 0xFE;
		} else {
			while (gf_list_count(iod->ESDescriptors)) {
				GF_ESD *old = (GF_ESD *)gf_list_get(iod->ESDescriptors, 0);
				gf_odf_desc_del((GF_Descriptor *) old);
				gf_list_rem(iod->ESDescriptors, 0);
			}
		}
		gf_list_add(iod->ESDescriptors, esd);
		isor_emulate_chapters(read->mov, iod);
		return (GF_Descriptor *) iod;
	}

	iod = NULL;
	if (check_mpeg4_systems(plug, read->mov)) {
		iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(read->mov);
		if (!iod) {
#ifndef GPAC_DISABLE_LOG
			GF_Err e = gf_isom_last_error(read->mov);
			if (e) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[IsoMedia] Cannot fetch MPEG-4 IOD (error %s) - generating one\n", gf_error_to_string(e) ));
			} else {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[IsoMedia] No MPEG-4 IOD found in file - generating one\n"));
			}
#endif
		}
	}
	if (!iod) return isor_emulate_iod(read);

	count = gf_list_count(iod->ESDescriptors);
	if (!count) {
		gf_odf_desc_del((GF_Descriptor*) iod);
		return isor_emulate_iod(read);
	}
	if (count==1) {
		esd = (GF_ESD *)gf_list_get(iod->ESDescriptors, 0);
		switch (esd->decoderConfig->streamType) {
		case GF_STREAM_SCENE:
		case GF_STREAM_PRIVATE_SCENE:
			break;
		case GF_STREAM_VISUAL:
			if (expect_type!=GF_MEDIA_OBJECT_VIDEO) {
				gf_odf_desc_del((GF_Descriptor*) iod);
				return isor_emulate_iod(read);
			}
			break;
		case GF_STREAM_AUDIO:
			/*we need a fake scene graph*/
			if (expect_type!=GF_MEDIA_OBJECT_AUDIO) {
				gf_odf_desc_del((GF_Descriptor*) iod);
				return isor_emulate_iod(read);
			}
			break;
		default:
			gf_odf_desc_del((GF_Descriptor*) iod);
			return NULL;
		}
	}
	/*check IOD is not badly formed (eg mixing audio, video or text streams)*/
	nb_st = 0;
	for (i=0; i<count; i++) {
		esd = (GF_ESD *)gf_list_get(iod->ESDescriptors, i);
		switch (esd->decoderConfig->streamType) {
		case GF_STREAM_VISUAL:
			nb_st |= 1;
			break;
		case GF_STREAM_AUDIO:
			nb_st |= 2;
			break;
		case GF_STREAM_TEXT:
			nb_st |= 4;
			break;
		}
	}
	if ( (nb_st & 1) + (nb_st & 2) + (nb_st & 4) > 1) {
		gf_odf_desc_del((GF_Descriptor*) iod);
		return isor_emulate_iod(read);
	}

	isor_emulate_chapters(read->mov, iod);
	return (GF_Descriptor *) iod;
}


void isor_send_cenc_config(ISOMChannel *ch)
{
	GF_NetworkCommand com;
	u32 i;

	memset(&com, 0, sizeof(GF_NetworkCommand));
	com.base.on_channel = ch->channel;
	com.command_type = GF_NET_CHAN_DRM_CFG;
	ch->is_encrypted = GF_TRUE;

	gf_isom_get_cenc_info(ch->owner->mov, ch->track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, NULL);

	com.drm_cfg.PSSH_count = gf_isom_get_pssh_count(ch->owner->mov);
	com.drm_cfg.PSSHs = gf_malloc(sizeof(GF_NetComDRMConfigPSSH)*(com.drm_cfg.PSSH_count) );

	/*fill PSSH in the structure. We will free it in CENC_Setup*/
	for (i=0; i<com.drm_cfg.PSSH_count; i++) {
		GF_NetComDRMConfigPSSH *pssh = &com.drm_cfg.PSSHs[i];
		gf_isom_get_pssh_info(ch->owner->mov, i+1, pssh->SystemID, &pssh->KID_count, (const bin128 **) & pssh->KIDs, (const u8 **) &pssh->private_data, &pssh->private_data_size);
	}
	//fixme - check MSE and EME
#if 0
	if (read->input->query_proxy && read->input->proxy_udta) {
		read->input->query_proxy(read->input, &com);
	} else
#endif
		gf_service_command(ch->owner->service, &com, GF_OK);
	//free our PSSH
	if (com.drm_cfg.PSSHs) gf_free(com.drm_cfg.PSSHs);
}



GF_Err ISOR_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream)
{
	u32 ESID;
	ISOMChannel *ch;
	GF_NetworkCommand com;
	u32 track;
	Bool is_esd_url;
	GF_Err e;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;

	track = 0;
	ch = NULL;
	is_esd_url = GF_FALSE;
	e = GF_OK;
	if (upstream) {
		e = GF_ISOM_INVALID_FILE;
		goto exit;
	}
	if (!read->mov) return GF_SERVICE_ERROR;

	if (strstr(url, "ES_ID")) {
		sscanf(url, "ES_ID=%ud", &ESID);
	} else {
		/*handle url like mypath/myfile.mp4#trackID*/
		char *track_id = (char *)strrchr(url, '.');
		if (track_id) {
			track_id = (char *)strchr(url, '#');
			if (track_id) track_id ++;
		}
		is_esd_url = GF_TRUE;

		ESID = 0;
		/*if only one track ok*/
		if (gf_isom_get_track_count(read->mov)==1) ESID = gf_isom_get_track_id(read->mov, 1);
		else if (track_id) {
			ESID = atoi(track_id);
			track = gf_isom_get_track_by_id(read->mov, (u32) ESID);
			if (!track) ESID = 0;
		}

	}
	if (!ESID) {
		e = GF_NOT_SUPPORTED;
		goto exit;
	}

	/*a channel cannot be open twice, it has to be closed before - NOTE a track is NOT a channel and the user can open
	several times the same track as long as a dedicated channel is used*/
	ch = isor_get_channel(read, channel);
	if (ch) {
		e = GF_SERVICE_ERROR;
		goto exit;
	}
	track = gf_isom_get_track_by_id(read->mov, (u32) ESID);
	if (!track) {
		e = GF_STREAM_NOT_FOUND;
		goto exit;
	}

	GF_SAFEALLOC(ch, ISOMChannel);
	ch->owner = read;
	ch->channel = channel;
	gf_list_add(read->channels, ch);
	ch->track = track;
	ch->track_id = gf_isom_get_track_id(read->mov, ch->track);
	switch (gf_isom_get_media_type(ch->owner->mov, ch->track)) {
	case GF_ISOM_MEDIA_OCR:
		ch->streamType = GF_STREAM_OCR;
		break;
	case GF_ISOM_MEDIA_SCENE:
		ch->streamType = GF_STREAM_SCENE;
		break;
	case GF_ISOM_MEDIA_VISUAL:
		gf_isom_get_reference(ch->owner->mov, ch->track, GF_ISOM_REF_BASE, 1, &ch->base_track);
		ch->next_track = 0;
		/*in scalable mode add SPS/PPS in-band*/
		ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG /*| GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG*/;
		gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
		break;
	}

	ch->has_edit_list = gf_isom_get_edit_list_type(ch->owner->mov, ch->track, &ch->dts_offset) ? GF_TRUE : GF_FALSE;
	ch->has_rap = (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1) ? GF_TRUE : GF_FALSE;
	ch->time_scale = gf_isom_get_media_timescale(ch->owner->mov, ch->track);

exit:
	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, channel);
	} else {
		gf_service_connect_ack(read->service, channel, e);
	}
	/*if esd url reconfig SL layer*/
	if (!e && is_esd_url) {
		GF_ESD *esd;
		memset(&com, 0, sizeof(GF_NetworkCommand));
		com.base.on_channel = channel;
		com.command_type = GF_NET_CHAN_RECONFIG;
		esd = gf_isom_get_esd(read->mov, ch->track, 1);
		if (esd) {
			memcpy(&com.cfg.sl_config, esd->slConfig, sizeof(GF_SLConfig));
			gf_odf_desc_del((GF_Descriptor *)esd);
		} else {
			com.cfg.sl_config.tag = GF_ODF_SLC_TAG;
			com.cfg.sl_config.timestampLength = 32;
			com.cfg.sl_config.timestampResolution = ch->time_scale;
			com.cfg.sl_config.useRandomAccessPointFlag = 1;
		}
		if (read->input->query_proxy && read->input->proxy_udta) {
			read->input->query_proxy(read->input, &com);
		} else {
			gf_service_command(read->service, &com, GF_OK);
		}
	}
	if (!e && track && gf_isom_is_track_encrypted(read->mov, track)) {
		memset(&com, 0, sizeof(GF_NetworkCommand));
		com.base.on_channel = channel;
		com.command_type = GF_NET_CHAN_DRM_CFG;
		ch->is_encrypted = GF_TRUE;
		if (gf_isom_is_ismacryp_media(read->mov, track, 1)) {
			gf_isom_get_ismacryp_info(read->mov, track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, &com.drm_cfg.scheme_uri, &com.drm_cfg.kms_uri, NULL, NULL, NULL);
			if (read->input->query_proxy && read->input->proxy_udta) {
				read->input->query_proxy(read->input, &com);
			} else {
				gf_service_command(read->service, &com, GF_OK);
			}
		} else if (gf_isom_is_omadrm_media(read->mov, track, 1)) {
			gf_isom_get_omadrm_info(read->mov, track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, &com.drm_cfg.contentID, &com.drm_cfg.kms_uri, &com.drm_cfg.oma_drm_textual_headers, &com.drm_cfg.oma_drm_textual_headers_len, NULL, &com.drm_cfg.oma_drm_crypt_type, NULL, NULL, NULL);

			gf_media_get_file_hash(gf_isom_get_filename(read->mov), com.drm_cfg.hash);
			if (read->input->query_proxy && read->input->proxy_udta) {
				read->input->query_proxy(read->input, &com);
			} else {
				gf_service_command(read->service, &com, GF_OK);
			}
		} else if (gf_isom_is_cenc_media(read->mov, track, 1)) {
			ch->is_cenc = GF_TRUE;
			isor_send_cenc_config(ch);
		}
	}
	return e;
}

GF_Err ISOR_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel)
{
	ISOMChannel *ch;
	GF_Err e;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;
	if (!read->mov) return GF_SERVICE_ERROR;

	gf_mx_p(read->segment_mutex);
	e = GF_OK;
	ch = isor_get_channel(read, channel);
	assert(ch);
	if (!ch) {
		e = GF_STREAM_NOT_FOUND;
		goto exit;
	}
	/*signal the service is broken but still process the delete*/
	isor_delete_channel(read, ch);
	assert(!isor_get_channel(read, channel));

exit:
	if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) {
		send_proxy_command(read, 1, 0, e, NULL, channel);
	} else {
		gf_service_disconnect_ack(read->service, channel, e);
	}
	gf_mx_v(read->segment_mutex);
	return e;
}

GF_Err ISOR_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, 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)
{
	ISOMChannel *ch;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	/*cannot read native SL-PDUs*/
	if (!out_sl_hdr) return GF_NOT_SUPPORTED;
	read = (ISOMReader *) plug->priv;
	if (!read->mov) return GF_SERVICE_ERROR;

	*out_data_ptr = NULL;
	*out_data_size = 0;
	*sl_compressed = GF_FALSE;
	*out_reception_status = GF_OK;
	*is_new_data = GF_FALSE;
	ch = isor_get_channel(read, channel);
	if (!ch) return GF_STREAM_NOT_FOUND;
	if (!ch->is_playing) return GF_OK;

	*is_new_data = GF_FALSE;
	if (!ch->sample) {
		/*get sample*/
		gf_mx_p(read->segment_mutex);
		isor_reader_get_sample(ch);
		gf_mx_v(read->segment_mutex);
		*is_new_data = ch->sample ? GF_TRUE : GF_FALSE;
	}

	if (ch->sample) {
		*out_data_ptr = ch->sample->data;
		*out_data_size = ch->sample->dataLength;
		*out_sl_hdr = ch->current_slh;
	}
	*out_reception_status = ch->last_state;
	if (read->waiting_for_data)
		*out_reception_status = GF_BUFFER_TOO_SMALL;

	return GF_OK;
}

GF_Err ISOR_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel)
{
	ISOMChannel *ch;
	ISOMReader *read;
	if (!plug || !plug->priv) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;
	if (!read->mov) return GF_SERVICE_ERROR;
	ch = isor_get_channel(read, channel);
	if (!ch) return GF_STREAM_NOT_FOUND;
	if (!ch->is_playing) return GF_SERVICE_ERROR;

	if (ch->sample) {
		isor_reader_release_sample(ch);
		/*release sample*/
	}
	return GF_OK;
}

/*switch channel quality. Return next channel or current channel if error*/
static
u32 gf_channel_switch_quality(ISOMChannel *ch, GF_ISOFile *the_file, Bool switch_up)
{
	u32 i, count, next_track, trackID, cur_track;
	s32 ref_count;

	cur_track = ch->next_track ? ch->next_track : ch->track;
	count = gf_isom_get_track_count(the_file);
	trackID = gf_isom_get_track_id(the_file, cur_track);
	next_track = 0;

	if (switch_up)
	{
		for (i = 0; i < count; i++)
		{
			ref_count = gf_isom_get_reference_count(the_file, i+1, GF_ISOM_REF_SCAL);
			if (ref_count < 0)
				return cur_track; //error
			else if (ref_count == 0)
				continue;
			/*next track is the one that has the last reference of type GF_ISOM_REF_SCAL refers to this current track*/
			else if ((u32)ref_count == gf_isom_has_track_reference(the_file, i+1, GF_ISOM_REF_SCAL, trackID))
			{
				next_track = i+1;
				break;
			}
		}
		/*this is the highest quality*/
		if (!next_track)
			return cur_track;
	}
	else
	{
		if (cur_track == ch->base_track)
			return cur_track;
		ref_count = gf_isom_get_reference_count(the_file, cur_track, GF_ISOM_REF_SCAL);
		if (ref_count < 0)
			return cur_track;
		gf_isom_get_reference(the_file, cur_track, GF_ISOM_REF_SCAL, ref_count, &next_track);
		if (!next_track)
			return cur_track;
	}

	/*in scalable mode add SPS/PPS in-band*/
	gf_isom_set_nalu_extract_mode(the_file, next_track, ch->nalu_extract_mode);

	return next_track;
}


GF_Err ISOR_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com)
{
	Double track_dur, media_dur;
	ISOMChannel *ch;
	ISOMReader *read;
	u32 count, i;

	if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR;
	read = (ISOMReader *) plug->priv;
	if (read->disconnected) return GF_OK;

	if (com->command_type==GF_NET_SERVICE_INFO) {
		u32 tag_len;
		const char *tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_NAME, &tag, &tag_len)==GF_OK) com->info.name = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_ARTIST, &tag, &tag_len)==GF_OK) com->info.artist = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_ALBUM, &tag, &tag_len)==GF_OK) com->info.album = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COMMENT, &tag, &tag_len)==GF_OK) com->info.comment = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_TRACK, &tag, &tag_len)==GF_OK) {
			com->info.track_info = (((tag[2]<<8)|tag[3]) << 16) | ((tag[4]<<8)|tag[5]);
		}
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COMPOSER, &tag, &tag_len)==GF_OK) com->info.composer = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_WRITER, &tag, &tag_len)==GF_OK) com->info.writer = tag;
		if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_GENRE, &tag, &tag_len)==GF_OK) {
			if (tag[0]) {
				com->info.genre = 0;
			} else {
				com->info.genre = (tag[0]<<8) | tag[1];
			}
		}
		return GF_OK;
	}
	if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) {
		u32 i, count;
		count = gf_isom_get_track_count(read->mov);
		for (i=0; i<count; i++) {
			if (gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_AUDIO) return GF_OK;
		}
		return GF_NOT_SUPPORTED;
	}

	if (com->command_type == GF_NET_SERVICE_QUALITY_SWITCH)
	{
		count = gf_list_count(read->channels);
		for (i = 0; i < count; i++)
		{
			ch = (ISOMChannel *)gf_list_get(read->channels, i);
			if (gf_isom_needs_layer_reconstruction(read->mov)) {
				ch->next_track = gf_channel_switch_quality(ch, read->mov, com->switch_quality.up);
			}
		}
		return GF_OK;
	}
	if (com->command_type == GF_NET_SERVICE_PROXY_DATA_RECEIVE) {
		isor_flush_data(read, 1, com->proxy_data.is_chunk);
		return GF_OK;
	}
	if (com->command_type == GF_NET_SERVICE_FLUSH_DATA) {
		if (read->nb_playing && plug->query_proxy)
			isor_flush_data(read, 0, 0);
		return GF_OK;
	}
	if (com->command_type == GF_NET_SERVICE_CAN_REVERSE_PLAYBACK)
		return GF_OK;

	if (!com->base.on_channel) return GF_NOT_SUPPORTED;

	ch = isor_get_channel(read, com->base.on_channel);
	if (!ch) return GF_STREAM_NOT_FOUND;

	switch (com->command_type) {
	case GF_NET_CHAN_SET_PADDING:
		if (!ch->track) return GF_OK;
		gf_isom_set_sample_padding(read->mov, ch->track, com->pad.padding_bytes);
		return GF_OK;
	case GF_NET_CHAN_SET_PULL:
		//we don't pull in DASH base services, we flush as soon as we have a complete segment
#ifndef DASH_USE_PULL
		if (read->input->proxy_udta && !read->input->proxy_type)
			return GF_NOT_SUPPORTED;
#endif

		ch->is_pulling = 1;
		return GF_OK;
	case GF_NET_CHAN_INTERACTIVE:
		return GF_OK;
	case GF_NET_CHAN_BUFFER:
		//dash or HTTP, do rebuffer if not disabled
		if (plug->query_proxy) {
		} else if (read->dnload) {
			ch->buffer_min = com->buffer.min;
			ch->buffer_max = com->buffer.max;
		} else {
			com->buffer.max = com->buffer.min = 0;
		}
		return GF_OK;
	case GF_NET_CHAN_DURATION:
		if (!ch->track) {
			com->duration.duration = 0;
			return GF_OK;
		}
		ch->duration = gf_isom_get_track_duration(read->mov, ch->track);
		track_dur = (Double) (s64) ch->duration;
		track_dur /= read->time_scale;
		if (gf_isom_get_edit_segment_count(read->mov, ch->track)) {
			com->duration.duration = (Double) track_dur;
			ch->duration = (u32) (track_dur * ch->time_scale);
		} else {
			/*some file indicate a wrong TrackDuration, get the longest*/
			ch->duration = gf_isom_get_media_duration(read->mov, ch->track);
			media_dur = (Double) (s64) ch->duration;
			media_dur /= ch->time_scale;
			com->duration.duration = MAX(track_dur, media_dur);
		}
		return GF_OK;
	case GF_NET_CHAN_PLAY:

		gf_mx_p(read->segment_mutex);
		isor_reset_reader(ch);
		ch->speed = com->play.speed;
		read->reset_frag_state = 1;
		if (read->frag_type)
			read->frag_type = 1;
		gf_mx_v(read->segment_mutex);

		ch->start = ch->end = 0;
		if (com->play.speed>0) {
			Double t;
			if (com->play.start_range>=0) {
				t = com->play.start_range;
				t *= ch->time_scale;
				ch->start = (u64) t;
			}
			if (com->play.end_range >= com->play.start_range) {
				t = com->play.end_range;
				t *= ch->time_scale;
				ch->end = (u64) t;
			}
		} else if (com->play.speed<0) {
			Double end = com->play.end_range;
			if (end==-1) end = 0;
			ch->start = (u64) (s64) (com->play.start_range * ch->time_scale);
			if (end <= com->play.start_range)
				ch->end = (u64) (s64) (end  * ch->time_scale);
		}
		ch->is_playing = 1;
		if (com->play.dash_segment_switch) ch->wait_for_segment_switch = 1;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[IsoMedia] Starting channel playback "LLD" to "LLD" (%g to %g)\n", ch->start, ch->end, com->play.start_range, com->play.end_range));

		//and check buffer level on play request
		isor_check_buffer_level(read);
		read->nb_playing++;
		return GF_OK;
	case GF_NET_CHAN_STOP:
		if (read->nb_playing) read->nb_playing--;
		isor_reset_reader(ch);
		return GF_OK;

	case GF_NET_CHAN_SET_SPEED:
		gf_mx_p(read->segment_mutex);
		ch->speed = com->play.speed;
		gf_mx_v(read->segment_mutex);
		return GF_OK;
	/*nothing to do on MP4 for channel config*/
	case GF_NET_CHAN_CONFIG:
		return GF_OK;
	case GF_NET_CHAN_GET_PIXEL_AR:
		return gf_isom_get_pixel_aspect_ratio(read->mov, ch->track, 1, &com->par.hSpacing, &com->par.vSpacing);
	case GF_NET_CHAN_GET_DSI:
	{
		/*it may happen that there are conflicting config when using ESD URLs...*/
		GF_DecoderConfig *dcd = gf_isom_get_decoder_config(read->mov, ch->track, 1);
		com->get_dsi.dsi = NULL;
		com->get_dsi.dsi_len = 0;
		if (dcd) {
			if (dcd->decoderSpecificInfo) {
				com->get_dsi.dsi = dcd->decoderSpecificInfo->data;
				com->get_dsi.dsi_len = dcd->decoderSpecificInfo->dataLength;
				dcd->decoderSpecificInfo->data = NULL;
			}
			gf_odf_desc_del((GF_Descriptor *) dcd);
		}
		return GF_OK;
	}
	case GF_NET_CHAN_NALU_MODE:
		ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG;
		ch->disable_seek = 1;
		//when this is set, we work in real scalable (eg N streams reassembled by the player) so only extract the layer. This wll need refinements if we plan to support
		//several scalable layers ...
		if (com->nalu_mode.extract_mode==1) {
			ch->nalu_extract_mode |= GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG | GF_ISOM_NALU_EXTRACT_VDRD_FLAG | GF_ISOM_NALU_EXTRACT_LAYER_ONLY;
		}
		gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
		break;
	default:
		break;
	}
	return GF_NOT_SUPPORTED;
}

static Bool ISOR_CanHandleURLInService(GF_InputService *plug, const char *url)
{
	char szURL[2048], *sep;
	ISOMReader *read = (ISOMReader *)plug->priv;
	const char *this_url = gf_service_get_url(read->service);
	if (!this_url || !url) return GF_FALSE;

	if (!strcmp(this_url, url)) return GF_TRUE;

	strcpy(szURL, this_url);
	sep = strrchr(szURL, '#');
	if (sep) sep[0] = 0;

	/*direct addressing in service*/
	if (url[0] == '#') return GF_TRUE;
	if (strnicmp(szURL, url, sizeof(char)*strlen(szURL))) return GF_FALSE;
	return GF_TRUE;
}

GF_InputService *isor_client_load()
{
	ISOMReader *reader;
	GF_InputService *plug;
	GF_SAFEALLOC(plug, GF_InputService);
	GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC IsoMedia Reader", "gpac distribution")
	plug->RegisterMimeTypes = ISOR_RegisterMimeTypes;
	plug->CanHandleURL = ISOR_CanHandleURL;
	plug->ConnectService = ISOR_ConnectService;
	plug->CloseService = ISOR_CloseService;
	plug->GetServiceDescriptor = ISOR_GetServiceDesc;
	plug->ConnectChannel = ISOR_ConnectChannel;
	plug->DisconnectChannel = ISOR_DisconnectChannel;
	plug->ServiceCommand = ISOR_ServiceCommand;
	plug->CanHandleURLInService = ISOR_CanHandleURLInService;
	/*we do support pull mode*/
	plug->ChannelGetSLP = ISOR_ChannelGetSLP;
	plug->ChannelReleaseSLP = ISOR_ChannelReleaseSLP;

	GF_SAFEALLOC(reader, ISOMReader);
	reader->channels = gf_list_new();
	reader->segment_mutex = gf_mx_new("ISO Segment");

	plug->priv = reader;
	return plug;
}

void isor_client_del(GF_BaseInterface *bi)
{
	GF_InputService *plug = (GF_InputService *) bi;
	ISOMReader *read = (ISOMReader *)plug->priv;

	if (read->segment_mutex) gf_mx_del(read->segment_mutex);
	gf_list_del(read->channels);
	gf_free(read);
	gf_free(bi);
}

#endif /*GPAC_DISABLE_ISOM*/
