/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / MP4 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"
#include <gpac/network.h>
#include <time.h>

#ifndef GPAC_DISABLE_ISOM


void isor_reset_reader(ISOMChannel *ch)
{
	ch->last_state = GF_OK;
	isor_reader_release_sample(ch);

	ch->sample = NULL;
	ch->sample_num = 0;
	ch->speed = 1.0;
	ch->start = ch->end = 0;
	ch->to_init = 1;
	ch->is_playing = 0;
	memset(&ch->current_slh, 0, sizeof(GF_SLHeader));
}

void isor_check_producer_ref_time(ISOMReader *read)
{
	u32 trackID;
	u64 ntp;
	u64 timestamp;

	if (gf_isom_get_last_producer_time_box(read->mov, &trackID, &ntp, &timestamp, GF_TRUE)) {
#if !defined(_WIN32_WCE) && !defined(GPAC_DISABLE_LOG)

		if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) {
			time_t secs;
			struct tm t;

			s32 diff = gf_net_get_ntp_diff_ms(ntp);

			secs = (ntp>>32) - GF_NTP_SEC_1900_TO_1970;
			t = *gmtime(&secs);

			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] TrackID %d: Timestamp %d matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ - NTP clock diff (local - remote): %d ms\n", trackID, (u32) timestamp, 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, diff));
		}
#endif

		read->last_sender_ntp = ntp;
		read->cts_for_last_sender_ntp = timestamp;
	}
}

/*
	refresh type:
		0: not progressive
		1: progressive
		2: not progressive and don't check current download
*/
void isor_segment_switch_or_refresh(ISOMReader *read, Bool do_refresh)
{
	GF_NetworkCommand param;
	u32 i, count;
	Bool scalable_segment = 0;
	GF_Err e;

	/*access to the segment switching must be protected in case several decoders are threaded on the file using GetSLPacket */
	gf_mx_p(read->segment_mutex);

	if (!read->frag_type || !read->input->query_proxy ) {
		gf_mx_v(read->segment_mutex);
		return;
	}

	memset(&param, 0, sizeof(GF_NetworkCommand));
	param.command_type = GF_NET_SERVICE_QUERY_NEXT;
	//always check current download - this might be ignored by the dash client depending on lowLatency settings
	param.url_query.current_download = 1;

	count = gf_list_count(read->channels);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Refresh seg: do_refresh %d - seg opened %d\n", do_refresh, read->seg_opened));

	if (do_refresh && !read->seg_opened)
		do_refresh = 0;

	if ((read->seg_opened==1) && !do_refresh) {
		gf_mx_v(read->segment_mutex);
		return;
	}

	if (read->drop_next_segment) {
		read->drop_next_segment = 0;
		param.url_query.drop_first_segment = 1;
	}

	//if first time trying to fetch next segment, check if we have to discard it
	if (!do_refresh && (read->seg_opened==2) && !read->pending_scalable_enhancement_segment_index) {
#ifdef DASH_USE_PULL
		for (i=0; i<count; i++) {
			ISOMChannel *ch = gf_list_get(read->channels, i);
			/*check all playing channels are waiting for next segment*/
			if (ch->is_playing && !ch->wait_for_segment_switch) {
				gf_mx_v(read->segment_mutex);
				return;
			}
		}
#endif

		/*close current segment*/
		gf_isom_release_segment(read->mov, 1);
		read->seg_opened = 0;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment - querying new one\n"));

		/*drop this segment*/
		param.url_query.drop_first_segment = 1;
	}

	if (read->pending_scalable_enhancement_segment_index) {
		do_refresh = 0;
		param.url_query.drop_first_segment = 0;
		param.url_query.dependent_representation_index = read->pending_scalable_enhancement_segment_index;
		scalable_segment = 1;
		read->pending_scalable_enhancement_segment_index = 0;
	}


next_segment:
	/*update current fragment if any*/
	e = read->input->query_proxy(read->input, &param);
	if (e == GF_OK) {
		u32 trackID = 0;
		if (param.url_query.next_url) {
			u32 flags;

			//previously loaded file has been aborted, reload segment !
			if (do_refresh && param.url_query.discontinuity_type) {
				gf_isom_release_segment(read->mov, 1);
				gf_isom_reset_fragment_info(read->mov, 1);
				do_refresh = 0;
			}

			if (read->reset_frag_state) {
				read->reset_frag_state = 0;
				gf_isom_reset_fragment_info(read->mov, 0);
			}

			//refresh file
			if (do_refresh) {
				//the url is the current download or we just finished downloaded it, refresh the parsing.
				if ((param.url_query.current_download || (read->seg_opened==1)) && param.url_query.has_new_data) {
					u64 bytesMissing=0;
					e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, read->use_memory ? param.url_query.next_url : NULL);

					GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[IsoMedia] LowLatency mode: Reparsing segment %s boxes at UTC "LLU" - "LLU" bytes still missing\n", param.url_query.next_url, gf_net_get_utc(), bytesMissing ));

#ifndef GPAC_DISABLE_LOG
					if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) {
						for (i=0; i<count; i++) {
							ISOMChannel *ch = gf_list_get(read->channels, i);
							GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] refresh track %d fragment - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));
						}
					}
#endif
					isor_check_producer_ref_time(read);
				}
				//we did the last refresh and the segment is downloaded, move to fully parsed mode
				if (! param.url_query.current_download) {
					read->seg_opened = 2;
					read->waiting_for_data = GF_FALSE;
				}
				gf_mx_v(read->segment_mutex);
				return;
			}

			//we have to open the file

			if (param.url_query.discontinuity_type==2) {
				gf_isom_reset_fragment_info(read->mov, 0);
				read->clock_discontinuity = 1;
			}
			e = GF_OK;
			if (param.url_query.next_url_init_or_switch_segment) {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Switching between files - opening new init segment %s\n", param.url_query.next_url_init_or_switch_segment));
				if (read->mov) gf_isom_close(read->mov);
				e = gf_isom_open_progressive(param.url_query.next_url_init_or_switch_segment, param.url_query.switch_start_range, param.url_query.switch_end_range, &read->mov, &read->missing_bytes);
				if (e<0) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening init segment %s at UTC "LLU": %s\n", param.url_query.next_url_init_or_switch_segment, gf_net_get_utc(), gf_error_to_string(e) ));
				}
			}
			if (!e) {
				flags = 0;
				if (read->no_order_check) flags |= GF_ISOM_SEGMENT_NO_ORDER_FLAG;
				if (scalable_segment) flags |= GF_ISOM_SEGMENT_SCALABLE_FLAG;
				e = gf_isom_open_segment(read->mov, param.url_query.next_url, param.url_query.start_range, param.url_query.end_range, flags);

				if (param.url_query.current_download  && (e==GF_ISOM_INCOMPLETE_FILE)) {
					e = GF_OK;
				}

#ifndef GPAC_DISABLE_LOG
				if (e<0) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening new segment %s at UTC "LLU": %s\n", param.url_query.next_url, gf_net_get_utc(), gf_error_to_string(e) ));
				} else if (param.url_query.end_range) {
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Playing new range in %s: "LLU"-"LLU"\n", param.url_query.next_url, param.url_query.start_range, param.url_query.end_range ));
				} else {
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] playing new segment %s (has next dep %d)\n", param.url_query.next_url, param.url_query.has_next));
				}
#endif
			}

			if (e<0) {
				gf_isom_release_segment(read->mov, 1);
				//gf_isom_reset_fragment_info(read->mov, 1);
				read->drop_next_segment = 1;
				//error opening the segment, reset everything ...
				gf_isom_reset_fragment_info(read->mov, 0);
				for (i=0; i<count; i++) {
					ISOMChannel *ch = gf_list_get(read->channels, i);
					if (ch)
						ch->sample_num = 0;
				}

				//cannot open file, don't change our state
				gf_mx_v(read->segment_mutex);
				return;
			}

			//segment is the first in our cache, we may need a refresh
			if (param.url_query.current_download ) {
				read->seg_opened = 1;
				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in progressive mode (download in progress)\n"));
			} else {
				read->seg_opened = 2;
				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in non-progressive mode (completely downloaded)\n"));
			}

			isor_check_producer_ref_time(read);

			trackID = 0;
			for (i=0; i<count; i++) {
				ISOMChannel *ch = gf_list_get(read->channels, i);
				ch->wait_for_segment_switch = 0;

				if (scalable_segment) {
					trackID = gf_isom_get_highest_track_in_scalable_segment(read->mov, ch->base_track);
					if (trackID) {
						ch->track_id = trackID;
						ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id);
					}
				}
				else if (ch->base_track) {
					ch->track = ch->base_track;
					ch->track_id = gf_isom_get_track_id(read->mov, ch->track);
				}

				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track %d - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));
				if (param.url_query.next_url_init_or_switch_segment) {
					ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id);
					if (!ch->track) {
						if (gf_isom_get_track_count(read->mov)==1) {
							GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n"));
							ch->track = 1;
						} else {
							GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n"));
						}
					}

					/*we changed our moov structure, sample_num now starts from 0*/
					ch->sample_num = 0;
				}
				//a loop was detected, our timing is no longer reliable if we use edit lists - just reset the sample time to tfdt ...
				else if (param.url_query.discontinuity_type==2) {
					ch->sample_num = 0;
					if (ch->has_edit_list) {
						ch->sample_time = gf_isom_get_current_tfdt(read->mov, ch->track);
						//next read will query sample for ch->sample_time + 1
						if (ch->sample_time) ch->sample_time--;
					}
				}
				/*rewrite all upcoming SPS/PPS into the samples*/
				gf_isom_set_nalu_extract_mode(read->mov, ch->track, ch->nalu_extract_mode);
				ch->last_state = GF_OK;


				if (ch->is_cenc) {
					isor_send_cenc_config(ch);
				}

			}

			read->use_memory = !strncmp(param.url_query.next_url, "gmem://", 7) ? GF_TRUE : GF_FALSE;
		}
		if (param.url_query.has_next) {
			param.url_query.drop_first_segment = GF_FALSE;
			param.url_query.dependent_representation_index++;
			scalable_segment = 1;
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Enhancement layer available in cache - refreshing it\n"));
			goto next_segment;
		}
		read->waiting_for_data = GF_FALSE;
	} else if (e==GF_EOS) {
		/*consider we are done*/
		read->frag_type = 2;
		read->waiting_for_data = GF_FALSE;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] No more segments - done playing file\n"));
	} else if (e==GF_BUFFER_TOO_SMALL) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Next segment is not yet available\n"));
		read->waiting_for_data = GF_TRUE;
		read->pending_scalable_enhancement_segment_index = param.url_query.dependent_representation_index;
	} else {
		/*consider we are done*/
		read->frag_type = 2;
		read->waiting_for_data = GF_FALSE;
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error fetching next DASH segment: no more segments\n"));
	}
	gf_mx_v(read->segment_mutex);
}

static void init_reader(ISOMChannel *ch)
{
	u32 sample_desc_index;
	if (ch->is_pulling && ch->wait_for_segment_switch) {
		isor_segment_switch_or_refresh(ch->owner, 0);
		if (ch->wait_for_segment_switch)
			return;
	}

	ch->current_slh.accessUnitEndFlag = 1;
	ch->current_slh.accessUnitStartFlag = 1;
	ch->current_slh.AU_sequenceNumber = 1;
	ch->current_slh.compositionTimeStampFlag = 1;
	ch->current_slh.decodingTimeStampFlag = 1;
	ch->current_slh.packetSequenceNumber = 1;
	ch->current_slh.randomAccessPointFlag = 0;

	assert(ch->sample==NULL);

	if (ch->streamType==GF_STREAM_OCR) {
		assert(!ch->sample);
		ch->sample = gf_isom_sample_new();
		ch->sample->IsRAP = RAP;
		ch->sample->DTS = ch->start;
		ch->last_state=GF_OK;
	} else {
		//if seek is disabled, get the next closest sample for this time; otherwose, get the previous RAP sample for this time
		u32 mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD;

		/*take care of seeking out of the track range*/
		if (!ch->owner->frag_type && (ch->duration<ch->start)) {
			ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->duration, &sample_desc_index, mode, &ch->sample, &ch->sample_num);
		} else {
			ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->start, &sample_desc_index, mode, &ch->sample, &ch->sample_num);
		}
		ch->last_state = GF_OK;

		if (ch->has_rap && ch->has_edit_list) {
			ch->edit_sync_frame = ch->sample_num;
		}
	}

	/*no sample means we're not in the track range - stop*/
	if (!ch->sample) {
		/*incomplete file - check if we're still downloading or not*/
		if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) {
			GF_NetIOStatus net_status;
			gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status);
			if (net_status == GF_NETIO_DATA_EXCHANGE) {
				ch->last_state = GF_OK;
				return;
			}
			ch->last_state = GF_ISOM_INCOMPLETE_FILE;
		} else if (ch->sample_num) {
			ch->last_state = (ch->owner->frag_type==1) ? GF_OK : GF_EOS;
			ch->to_init = 0;
		}
		return;
	}

	if (ch->has_edit_list) {
		ch->sample_time = ch->sample->DTS;
	} else {
		//store movie time in media timescale in the sample time, eg no edit list is used but we may have a shift (dts_offset) between
		//movie and media timelines

		if ((ch->dts_offset<0) && (ch->sample->DTS  < (u64) -ch->dts_offset))	//should not happen
			ch->sample_time = 0;
		else
			ch->sample_time = ch->sample->DTS + ch->dts_offset;
	}
	ch->to_init = 0;

	ch->current_slh.seekFlag = 0;
	if (ch->disable_seek) {
		ch->current_slh.decodingTimeStamp = ch->sample->DTS;
		ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset;
		ch->start = 0;
	} else {
		ch->current_slh.decodingTimeStamp = ch->start;
		ch->current_slh.compositionTimeStamp = ch->start;
		if (ch->current_slh.compositionTimeStamp != ch->sample->DTS + ch->sample->CTS_Offset) {
			ch->current_slh.seekFlag = 1;
		}
	}
	ch->current_slh.randomAccessPointFlag = ch->sample ? ch->sample->IsRAP : 0;
	if (!sample_desc_index) sample_desc_index = 1;
	ch->last_sample_desc_index = sample_desc_index;
	ch->owner->no_order_check = ch->speed < 0 ? GF_TRUE : GF_FALSE;
}



void isor_reader_get_sample(ISOMChannel *ch)
{
	GF_Err e;
	u32 sample_desc_index;
	if (ch->sample) return;

	if (ch->next_track) {
		ch->track = ch->next_track;
		ch->next_track = 0;
	}

	if ((ch->owner->seg_opened==1) && ch->is_pulling) {
		isor_segment_switch_or_refresh(ch->owner, 1);
	}

	if (ch->to_init) {
		init_reader(ch);
		sample_desc_index = ch->last_sample_desc_index;
	} else if (ch->speed < 0) {
		if (!ch->sample_time) {
			ch->last_state = GF_EOS;
			return;
		} else {
			e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time - 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->sample, &ch->sample_num);
			if (e) {
				if ((e==GF_EOS) && !ch->owner->frag_type) {
					ch->last_state = GF_EOS;
				}
				return;
			}
		}
		if (ch->sample->DTS + ch->dts_offset == ch->sample_time) {
			if (!ch->owner->frag_type) {
				ch->last_state = GF_EOS;
			} else {
				if (ch->sample)
					gf_isom_sample_del(&ch->sample);
			}
		}
		if (ch->sample) {
			if ((ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset))	//should not happen
				ch->sample_time = 0;
			else
				ch->sample_time = ch->sample->DTS + ch->dts_offset;
		}

	} else if (ch->has_edit_list) {
		u32 prev_sample = ch->sample_num;
		e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->sample, &ch->sample_num);

		if (e == GF_OK) {

			/*we are in forced seek mode: fetch all samples before the one matching the sample time*/
			if (ch->edit_sync_frame) {
				ch->edit_sync_frame++;
				if (ch->edit_sync_frame < ch->sample_num) {
					gf_isom_sample_del(&ch->sample);
					ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->edit_sync_frame, &sample_desc_index);
					ch->sample->DTS = ch->sample_time;
					ch->sample->CTS_Offset = 0;
				} else {
					ch->edit_sync_frame = 0;
					if (ch->sample) ch->sample_time = ch->sample->DTS;
				}
			} else {
				/*if we get the same sample, figure out next interesting time (current sample + DTS gap to next sample should be a good bet)*/
				if (prev_sample == ch->sample_num) {
					if (ch->owner->frag_type && (ch->sample_num==gf_isom_get_sample_count(ch->owner->mov, ch->track))) {
						if (ch->sample)
							gf_isom_sample_del(&ch->sample);
					} else {
						u32 time_diff = 2;
						u32 sample_num = ch->sample_num ? ch->sample_num : 1;
						GF_ISOSample *s1 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num, NULL);
						GF_ISOSample *s2 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num+1, NULL);

						gf_isom_sample_del(&ch->sample);

						if (s2 && s1) {
							assert(s2->DTS >= s1->DTS);
							time_diff = (u32) (s2->DTS - s1->DTS);
							e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + time_diff, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->sample, &ch->sample_num);
						} else if (s1 && !s2) {
							e = GF_EOS;
						}
						gf_isom_sample_del(&s1);
						gf_isom_sample_del(&s2);

					}
				}

				/*we jumped to another segment - if RAP is needed look for closest rap in decoding order and
				force seek mode*/
				if (ch->sample && !ch->sample->IsRAP && ch->has_rap && (ch->sample_num != prev_sample+1)) {
					GF_ISOSample *found = ch->sample;
					u32 samp_num = ch->sample_num;
					ch->sample = NULL;
					e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->sample, &ch->sample_num);
					assert (e == GF_OK);
					/*if no sync point in the past, use the first non-sync for the given time*/
					if (!ch->sample || !ch->sample->data) {
						gf_isom_sample_del(&ch->sample);
						ch->sample = found;
						ch->sample_time = ch->sample->DTS;
						ch->sample_num = samp_num;
					} else {
						gf_isom_sample_del(&found);
						ch->edit_sync_frame = ch->sample_num;
						ch->sample->DTS = ch->sample_time;
						ch->sample->CTS_Offset = 0;
					}
				} else {
					if (ch->sample) ch->sample_time = ch->sample->DTS;
				}
			}
		}
	} else {
		ch->sample_num++;

		ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index);
		/*if sync shadow / carousel RAP skip*/
		if (ch->sample && (ch->sample->IsRAP==RAP_REDUNDANT)) {
			gf_isom_sample_del(&ch->sample);
			ch->sample_num++;
			isor_reader_get_sample(ch);
			return;
		}
	}

	//check scalable track change
	if (ch->sample && ch->sample->IsRAP && ch->next_track) {
		ch->track = ch->next_track;
		ch->next_track = 0;
		gf_isom_sample_del(&ch->sample);
		isor_reader_get_sample(ch);
		return;
	}
	if (ch->sample && ch->dts_offset) {
		if ( (ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) {
			ch->sample->DTS = 0;
		} else {
			ch->sample->DTS += ch->dts_offset;
		}
	}

	if (!ch->sample) {
		/*incomplete file - check if we're still downloading or not*/
		if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) {
			ch->last_state = GF_ISOM_INCOMPLETE_FILE;
			if (ch->owner->dnload) {
				GF_NetIOStatus net_status;
				gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status);
				if (net_status == GF_NETIO_DATA_EXCHANGE) {
					ch->last_state = GF_OK;
					if (!ch->has_edit_list)
						ch->sample_num--;
				}
			}
			else if (ch->owner->input->query_proxy) {
				ch->last_state = GF_OK;
				if (!ch->has_edit_list && ch->sample_num)
					ch->sample_num--;
			}
		}
		else if (!ch->sample_num
		         || ((ch->speed >= 0) && (ch->sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track)))
		         || ((ch->speed < 0) && (ch->sample_time == gf_isom_get_current_tfdt(ch->owner->mov, ch->track) + ch->dts_offset))
		        ) {

			if (ch->owner->frag_type==1) {
				//if segment is fully opened and no more data, this track is done, wait for next segment
				if (!ch->wait_for_segment_switch && ch->owner->input->query_proxy && (ch->owner->seg_opened==2) ) {
					ch->wait_for_segment_switch = 1;
					GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of segment reached - waiting for sample %d - current count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) ));
				}
				/*if sample cannot be found and file is fragmented, rewind sample*/
				if (ch->sample_num) ch->sample_num--;
				ch->last_state = GF_OK;
			} else {
				GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of stream reached\n", ch->track));
				ch->last_state = GF_EOS;
			}
		} else {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d fail to fetch sample %d / %d: %s\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track), gf_error_to_string(gf_isom_last_error(ch->owner->mov)) ));
		}
		if (ch->wait_for_segment_switch && ch->is_pulling) {
			isor_segment_switch_or_refresh(ch->owner, 0);
			if (ch->owner->seg_opened) {
				isor_reader_get_sample(ch);
				return;
			}
		}
		return;
	}

	if (sample_desc_index != ch->last_sample_desc_index) {
		u32 mtype = gf_isom_get_media_type(ch->owner->mov, ch->track);
		switch (mtype) {
		case GF_ISOM_MEDIA_VISUAL:
			//code is here as a reminder, but by default we use inband param set extraction so no need for it
#if 0
			if ( ! (ch->nalu_extract_mode & GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG) ) {
				u32 extract_mode = ch->nalu_extract_mode | GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG;

				gf_isom_sample_del(&ch->sample);
				ch->sample = NULL;
				gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, extract_mode);
				ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &ch->last_sample_desc_index);

				gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode);
			}
#endif
			break;
		case GF_ISOM_MEDIA_TEXT:
		case GF_ISOM_MEDIA_SUBPIC:
		case GF_ISOM_MEDIA_MPEG_SUBT:
			break;
		default:
			//TODO: do we want to support codec changes ?
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Change of sample description (%d->%d) for media type %s not supported\n", ch->last_sample_desc_index, sample_desc_index, gf_4cc_to_str(mtype) ));
			gf_isom_sample_del(&ch->sample);
			ch->sample = NULL;
			ch->last_state = GF_NOT_SUPPORTED;
			return;
		}
	}

	ch->last_state = GF_OK;
	ch->current_slh.accessUnitEndFlag = ch->current_slh.accessUnitStartFlag = 1;
	ch->current_slh.accessUnitLength = ch->sample->dataLength;
	ch->current_slh.au_duration = gf_isom_get_sample_duration(ch->owner->mov, ch->track, ch->sample_num);

	/*still seeking or not ?
	 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do
	 2- otherwise set DTS+CTS to start value
	 */
	if ((ch->speed < 0) || (ch->start <= ch->sample->DTS + ch->sample->CTS_Offset)) {
		ch->current_slh.decodingTimeStamp = ch->sample->DTS;
		ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset;
		ch->current_slh.seekFlag = 0;
	} else {
		ch->current_slh.compositionTimeStamp = ch->start;
		ch->current_slh.seekFlag = 1;
		if (ch->streamType==GF_STREAM_SCENE)
			ch->current_slh.decodingTimeStamp = ch->sample->DTS;
		else
			ch->current_slh.decodingTimeStamp = ch->start;
	}
	ch->current_slh.randomAccessPointFlag = ch->sample->IsRAP;
	ch->current_slh.OCRflag = ch->owner->clock_discontinuity ? 2 : 0;
	ch->owner->clock_discontinuity = 0;

	if (ch->end && (ch->end < ch->sample->DTS + ch->sample->CTS_Offset)) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] End of Channel "LLD" (CTS "LLD")\n", ch->end, ch->sample->DTS + ch->sample->CTS_Offset));
		ch->last_state = GF_EOS;
	}
	if (ch->owner->last_sender_ntp && ch->current_slh.compositionTimeStamp==ch->owner->cts_for_last_sender_ntp) {
		ch->current_slh.sender_ntp = ch->owner->last_sender_ntp;
	} else {
		ch->current_slh.sender_ntp = 0;
	}


	if (ch->is_encrypted) {
		GF_ISMASample *ismasamp = gf_isom_get_ismacryp_sample(ch->owner->mov, ch->track, ch->sample, 1);
		if (ismasamp) {
			gf_free(ch->sample->data);
			ch->sample->data = ismasamp->data;
			ch->sample->dataLength = ismasamp->dataLength;
			ismasamp->data = NULL;
			ismasamp->dataLength = 0;
			ch->current_slh.isma_encrypted = (ismasamp->flags & GF_ISOM_ISMA_IS_ENCRYPTED) ? 1 : 0;
			ch->current_slh.isma_BSO = ismasamp->IV;
			gf_isom_ismacryp_delete_sample(ismasamp);
		} else {
			ch->current_slh.isma_encrypted = 0;
			/*in case of CENC: we write sample auxiliary information to slh->sai; its size is in saiz*/
			if (gf_isom_is_cenc_media(ch->owner->mov, ch->track, 1)) {
				GF_CENCSampleAuxInfo *sai;
				u32 Is_Encrypted;
				u8 IV_size;
				bin128 KID;

				gf_isom_get_sample_cenc_info(ch->owner->mov, ch->track, ch->sample_num, &Is_Encrypted, &IV_size, &KID);
				ch->current_slh.IV_size = IV_size;
				if (Is_Encrypted) {
					ch->current_slh.cenc_encrypted = 1;
					sai = NULL;
					gf_isom_cenc_get_sample_aux_info(ch->owner->mov, ch->track, ch->sample_num, &sai, NULL);
					if (sai) {
						u32 i;
						GF_BitStream *bs;
						bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
						/*write sample auxiliary information*/
						gf_bs_write_data(bs, (char *)KID, 16);
						gf_bs_write_data(bs, (char *)sai->IV, IV_size);
						gf_bs_write_u16(bs, sai->subsample_count);
						for (i = 0; i < sai->subsample_count; i++) {
							gf_bs_write_u16(bs, sai->subsamples[i].bytes_clear_data);
							gf_bs_write_u32(bs, sai->subsamples[i].bytes_encrypted_data);
						}
						gf_bs_get_content(bs, &ch->current_slh.sai, &ch->current_slh.saiz);
						gf_bs_del(bs);
						gf_isom_cenc_samp_aux_info_del(sai);
						sai = NULL;
					}
				}
				else
					ch->current_slh.cenc_encrypted = 0;
			}
		}
	}
}

void isor_reader_release_sample(ISOMChannel *ch)
{
	if (ch->current_slh.sai) {
		gf_free(ch->current_slh.sai);
		ch->current_slh.sai = NULL;
	}
	if (ch->sample) gf_isom_sample_del(&ch->sample);
	ch->sample = NULL;
	ch->current_slh.AU_sequenceNumber++;
	ch->current_slh.packetSequenceNumber++;
}


void isor_flush_data(ISOMReader *read, Bool check_buffer_level, Bool is_chunk_flush)
{
	u32 i, count;
	GF_Err e = GF_OK;
	Bool do_refresh;
	GF_NetworkCommand com;
	ISOMChannel *ch;

	if (read->in_data_flush) {
		if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++;
		return;
	}

	//if another thread grabs the mutex at the same time, just return
	if (!gf_mx_try_lock(read->segment_mutex)) {
		if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++;
		return;
	}
	read->in_data_flush = 1;
	count = gf_list_count(read->channels);

	do_refresh = (read->seg_opened==1) ? 1 : 0;

	if (do_refresh) {
		//query from terminal - do nothing if we are not done downloading the segment - otherwise we could access media data while it is reallocated by the downloader
		if (!check_buffer_level) {
			read->in_data_flush = 0;
			gf_mx_v(read->segment_mutex);
			return;
		}
		//otherwise this is new chunk or end of chunk, process
	}
	//this is a new file, check buffer level
	else if (/*!is_chunk_flush && */ check_buffer_level) {
		Bool buffer_full = 1;
		for (i=0; i<count; i++) {
			ch = (ISOMChannel *)gf_list_get(read->channels, i);
			/*query buffer level on each channel, don't sleep if too low*/
			memset(&com, 0, sizeof(GF_NetworkCommand));
			com.command_type = GF_NET_BUFFER_QUERY;
			com.base.on_channel = ch->channel;
			gf_service_command(read->service, &com, GF_OK);
			if ((com.buffer.occupancy < MAX(com.buffer.max, 1000) )) {
				buffer_full = 0;
				break;
			}
		}
		if (buffer_full) {
			read->has_pending_segments++;
			read->in_data_flush = 0;
			gf_mx_v(read->segment_mutex);
			if (count) {
				GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[IsoMedia] Buffer level %d ms higher than max allowed %d ms - skipping dispatch\n", com.buffer.occupancy,  com.buffer.max));
			}
			return;
		}
	}
	//flush request from terminal: only process if nothing is opened and we have pending segments
	//we have to keep the polling event when no segments are pending, in order to detect period switch - we therefore tolerate a couble of requests even though no segments are pending
	if (!check_buffer_level && read->seg_opened && (!read->has_pending_segments || (read->nb_force_flush > 2)) && !read->drop_next_segment) {
		read->in_data_flush = 0;
		gf_mx_v(read->segment_mutex);
		return;
	}

	//if this is a request from terminal to flush pending segments, try to refresh
	if (!check_buffer_level && !do_refresh)
		do_refresh = 1;

	//update data
	isor_segment_switch_or_refresh(read, do_refresh);

	//segment not ready
	if ( read->pending_scalable_enhancement_segment_index) {
		read->in_data_flush = 0;
		gf_mx_v(read->segment_mutex);
		return;
	}


	//for all channels, fetch and send ...
	count = gf_list_count(read->channels);
	for (i=0; i<count; i++) {
		ch = (ISOMChannel *)gf_list_get(read->channels, i);

		while (!ch->sample) {
			isor_reader_get_sample(ch);
			if (!ch->sample) break;
			if (ch->is_playing)
				gf_service_send_packet(read->service, ch->channel, ch->sample->data, ch->sample->dataLength, &ch->current_slh, GF_OK);
			isor_reader_release_sample(ch);
		}
		if (!ch->sample && (ch->last_state==GF_EOS)) {
			gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS);
		}
	}

	//done playing the file after notification from DASH client:
	if (read->seg_opened==2) {
		GF_NetworkCommand param;
		//drop this segment
		memset(&param, 0, sizeof(GF_NetworkCommand));
		param.command_type = GF_NET_SERVICE_QUERY_NEXT;
		param.url_query.dependent_representation_index = 0;
		param.url_query.drop_first_segment = 1;
		e = read->input->query_proxy(read->input, &param);

		read->nb_force_flush = 0;
		if (read->has_pending_segments) {
			read->has_pending_segments--;
		}

		if (e==GF_EOS) {
			for (i=0; i<count; i++) {
				ch = (ISOMChannel *)gf_list_get(read->channels, i);
				gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS);
			}
		}
		//not enough data
		else if (param.url_query.in_end_of_period) {
			//act as if we have a pending segment to process at next call until we get to the next period
			if (!read->has_pending_segments)
				read->has_pending_segments++;
		}

		/*close current segment*/
		gf_isom_release_segment(read->mov, 1);
		read->seg_opened = 0;
		GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment - closing it\n"));
	}
	read->in_data_flush = 0;

	if (!read->has_pending_segments) {
		read->nb_force_flush ++;
	}
	gf_mx_v(read->segment_mutex);
}

#endif /*GPAC_DISABLE_ISOM*/
