ds->first_cts_in_next_seg = ds->first_cts_in_seg + gf_timestamp_rescale(ds->dash_dur.num, ds->dash_dur.den, ds->timescale);
	ds->est_first_cts_in_next_seg = ds->first_cts_in_next_seg;
}



static GF_Err dasher_process(GF_Filter *filter)
{
	u32 i, count, nb_init, has_init, nb_reg_done;
	GF_DasherCtx *ctx = gf_filter_get_udta(filter);
	GF_Err e;
	Bool seg_done = GF_FALSE;
	u32 nb_seg_waiting = 0;
	u32 nb_seg_active = 0;

	if (ctx->in_error) {
		gf_filter_abort(filter);
		return GF_SERVICE_ERROR;
	}

	//session regulation is on and we have a an MPD (setup done) and a next time (first seg processed)
	//check if we have reached the next time
	if (ctx->sreg && !ctx->state && ctx->mpd && ctx->mpd->gpac_next_ntp_ms) {
		s64 diff = (s64) ctx->mpd->gpac_next_ntp_ms;
		diff -= (s64) gf_net_get_ntp_ms();
		if (diff>100) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] Next generation scheduled in %d ms, nothing to do\n", diff));
			gf_filter_ask_rt_reschedule(filter, (u32) (diff*1000));
			return GF_OK;
		}
	}

	//streams in period are not all ready, wait for them
	if (ctx->period_not_ready) {
		Bool is_eos;
		//potpone until no pending connections, otherwise we may add input streams in the wrong period
		if (gf_filter_connections_pending(filter))
			return GF_OK;

		is_eos = gf_filter_end_of_session(filter);
		if (! dasher_check_period_ready(ctx, is_eos)) {
			return is_eos ? GF_SERVICE_ERROR : GF_OK;
		}
		e = dasher_setup_period(filter, ctx, NULL);
		if (e) return e;
	}
	if (ctx->check_connections) {
		if (gf_filter_connections_pending(filter))
			return GF_OK;
		ctx->check_connections = GF_FALSE;
	}

	if (ctx->is_eos)
		return GF_EOS;
	if (ctx->setup_failure) return ctx->setup_failure;

	nb_init = has_init = nb_reg_done = 0;

	count = gf_list_count(ctx->current_period->streams);
	for (i=0; i<count; i++) {
		GF_DashStream *base_ds;
		GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i);
		assert(ds);
		if (ds->done) continue;
		base_ds = ds->muxed_base ? ds->muxed_base : ds;
		//subdur mode abort, don't process
		if (ds->subdur_done) {
			continue;
		}
		if (ds->seg_done) continue;

		if (ctx->dmode == GF_MPD_TYPE_DYNAMIC_LAST) {
			ds->done = 1;
			continue;
		}

		//flush as much as possible
		while (1) {
			u32 sap_type, dur, o_dur, split_dur;
			s32 check_dur;
			u64 cts, orig_cts, dts, split_dur_next, pcont_cts;
			Bool seg_over = GF_FALSE;
			Bool is_packet_split = GF_FALSE;
			Bool is_queue_flush = GF_FALSE;
			GF_FilterPacket *dst;
			GF_FilterPacket *pck = NULL;

			if (!ds->request_period_switch) {
				assert(ds->period == ctx->current_period);
				pck = gf_filter_pid_get_packet(ds->ipid);
				//we may change period after a packet fetch (reconfigure of input pid)
				if ((ds->period != ctx->current_period) || ds->request_period_switch) {
					//in closest mode, flush queue
					if (!ds->sbound || !gf_list_count(ds->packet_queue)) {
						assert(gf_list_find(ctx->current_period->streams, ds)<0);
						count = gf_list_count(ctx->current_period->streams);
						i--;
						break;
					}
					is_queue_flush = GF_TRUE;
				}

				//text streams, insert an empty segment if we are one segment behind last produced segment on other pids
				//we don't generate if behing this last time in case a packet comes in
				//
				//TODO: extend this to send empty segments for other streams (audio, video) in case of signal loss ??
				if (!pck
					&& (ds->stream_type==GF_STREAM_TEXT)
					&& !ds->muxed_base
				) {
					u64 ddur_ms = (1000*ds->dash_dur.num)/ds->dash_dur.den;
					while (ds->last_min_segment_start_time + ddur_ms < ctx->last_min_segment_start_time) {
						dasher_send_empty_segment(ctx, ds);
					}
				}
			} else {
				is_queue_flush = GF_TRUE;
			}
			if (ds->sbound && pck && gf_filter_pck_is_blocking_ref(pck)) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot use `sbound` with blocking input packet references, disabling packet buffering for PID %s\n", gf_filter_pid_get_name(ds->ipid) ));
				ds->sbound = DASHER_BOUNDS_OUT;
			}

			//skipped merged tile base
			if (ds->merged_tile_dep) {
				if (pck) gf_filter_pid_drop_packet(ds->ipid);
				pck = NULL;
			}
			//queue mode
			else if (ds->sbound) {
				if (!is_queue_flush && pck) {
					gf_filter_pck_ref(&pck);
					gf_filter_pid_drop_packet(ds->ipid);
					gf_list_add(ds->packet_queue, pck);
					if (gf_filter_pck_get_sap(pck))
						ds->nb_sap_in_queue ++;
				}
				if (
					//we are flushing due to period switch
					is_queue_flush
					//we are flushing due to end of stream
					|| gf_filter_pid_is_eos(ds->ipid) || ds->clamp_done
				) {
					pck = gf_list_get(ds->packet_queue, 0);
					is_queue_flush = GF_TRUE;
				} else if (
					//if current segment is not started, always get packet from queue
					!ds->segment_started
					//wait until we have more than 2 saps to get packet from queue, to check if next sap will be closer or not
					|| (ds->nb_sap_in_queue>=2)
				) {
					pck = gf_list_get(ds->packet_queue, 0);
				} else {
					pck = NULL;
				}
			}


			if (!pck) {
				if (ds->request_period_switch) {
					e = dasher_stream_period_changed(filter, ctx, ds, (ds->request_period_switch==2) ? GF_TRUE : GF_FALSE);
					if (e < 0) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Period switch request failed.\n"));
						i--;
						break;
					}
					assert(gf_list_find(ctx->current_period->streams, ds)<0);
					count = gf_list_count(ctx->current_period->streams);
					i--;
					break;
				}


				if (gf_filter_pid_is_eos(ds->ipid) || ds->clamp_done) {
					u32 ds_done = 1;

					if (!ds->clamp_done && !ds->muxed_base && (ds->stream_type==GF_STREAM_TEXT)) {
						u32 s_idx;
						u64 ddur_ms;
						Bool over = GF_TRUE;
						for (s_idx=0; s_idx<count; s_idx++) {
							GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, s_idx);
							if (a_ds == ds) continue;
							if (a_ds->stream_type==GF_STREAM_TEXT) continue;
							if (!a_ds->done) {
								over = GF_FALSE;
								break;
							}
						}
						if (!over)
							break;

						//text streams, insert empty segments if we are one segment behind (and including) last produced segment on other pids
						ddur_ms = (1000*ds->dash_dur.num)/ds->dash_dur.den;
						while (ds->last_min_segment_start_time + ddur_ms <= ctx->min_segment_start_time) {
							dasher_send_empty_segment(ctx, ds);
						}
					}


					if (ctx->loop && dasher_check_loop(ctx, ds)) {
						if (ctx->subdur)
							break;
						//loop on the entire source, consider the stream not done for segment flush
						ds_done = 0;
					}

					ds->clamp_done = GF_FALSE;

					ctx->update_report = -1;
					//opid may be NULL for skipped tile rep
					if (!ctx->sigfrag && ds->opid)
						gf_filter_pid_set_eos(ds->opid);

					if (!ds->done) ds->done = ds_done;
					ds->seg_done = GF_TRUE;
					seg_done = GF_TRUE;
					ds->first_cts_in_next_seg = ds->est_first_cts_in_next_seg;
					ds->est_first_cts_in_next_seg = 0;
					assert(base_ds->nb_comp_done < base_ds->nb_comp);
					base_ds->nb_comp_done ++;
					if (base_ds->nb_comp_done == base_ds->nb_comp) {
						dasher_flush_segment(ctx, base_ds, GF_FALSE);
						base_ds->nb_comp_done = 0;
					}
					//loop on the entire source, mark as done for subdur and check if all other streams are done
					if (!ds->done) {
						u32 j;
						ds->done = 2;
						ds->subdur_done = GF_TRUE;
						u32 nb_sub_done=0;
						for (j=0; j<count; j++) {
							GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, j);
							if (a_ds->muxed_base) a_ds = a_ds->muxed_base;
							if (a_ds->subdur_done) {
								nb_sub_done++;
							}
						}
						if (nb_sub_done==count)
							ctx->subdur_done = GF_TRUE;
					} else if (ctx->reschedule && !ctx->loop && (ctx->dmode==GF_MPD_TYPE_DYNAMIC) && !strcmp(ds->period_id, DEFAULT_PERIOD_ID) ) {
						if (gf_list_find(ctx->next_period->streams, ds)<0) {
							gf_list_add(ctx->next_period->streams, ds);
						}
						ctx->post_play_events = GF_TRUE;
						ds->nb_repeat++;
						ds->reschedule = GF_TRUE;
						gf_filter_pid_discard_block(ds->opid);
					}
				}
				break;
			}
			if (ds->seek_to_pck) {
				u32 sn = gf_filter_pck_get_seq_num(pck);
				if (sn) {
					if (sn <= ds->seek_to_pck) {
						dasher_drop_input(ctx, ds, GF_FALSE);
						continue;
					}
					ds->nb_pck = sn-1;
				} else {
					//no sn signaled, this implies we played from the beginning
					if (ds->nb_pck < ds->seek_to_pck) {
						ds->nb_pck ++;
						dasher_drop_input(ctx, ds, GF_FALSE);
						continue;
					}
				}
			}
			sap_type = gf_filter_pck_get_sap(pck);
			ds->loop_state = 0;

			cts = gf_filter_pck_get_cts(pck);
			dts = gf_filter_pck_get_dts(pck);
			if (dts==GF_FILTER_NO_TS) dts = cts;

			pcont_cts = cts;

			if (!ds->rep_init) {
				u32 set_start_with_sap;
				//for video, resync on sap 1 or 2 if not full profile
				if ((ds->stream_type==GF_STREAM_VISUAL) && (ctx->profile != GF_DASH_PROFILE_FULL)) {
					if ((sap_type!=GF_FILTER_SAP_1) && (sap_type!=GF_FILTER_SAP_2))
						sap_type = 0;
				}
				if (!sap_type) {
					//remember our timing
					if (!ds->presentation_time_offset)
						ds->presentation_time_offset = cts + 1;

					GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Representation not initialized, dropping non-SAP1/2 packet CTS "LLU"/%d\n", cts, ds->timescale));
					dasher_drop_input(ctx, ds, GF_FALSE);
					break;
				}

				set_start_with_sap = ctx->sseg ? base_ds->set->subsegment_starts_with_sap : base_ds->set->starts_with_sap;
				if (!ds->muxed_base) {
					u64 check_ts;
					//force sap type to 1 for non-visual streams if strict_sap is set to off
					if ((ds->stream_type!=GF_STREAM_VISUAL) && (ctx->strict_sap==DASHER_SAP_OFF) ) {
						switch (ds->codec_id) {
						//MPEG-H requires saps
						case GF_CODECID_MPHA:
						case GF_CODECID_MHAS:
							break;
						default:
							sap_type = 1;
							break;
						}
					}
					//set AS sap type
					if (!set_start_with_sap) {
						//don't set SAP type if not a base rep - could be further checked
						//if (!gf_list_count(ds->complementary_streams) )
						{
							if (ctx->sseg) {
								ds->set->subsegment_starts_with_sap = sap_type;
							} else {
								ds->set->starts_with_sap = sap_type;
							}
						}
					}
					else if (set_start_with_sap != sap_type) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Segments do not start with the same SAP types: set initialized with %d but first packet got %d - bitstream will not be compliant\n", set_start_with_sap, sap_type));
					}

					check_ts = cts;
					//in case we droped frames
					if (ds->presentation_time_offset)
						check_ts = ds->presentation_time_offset - 1;
					//The code below assumes that the first frame in the stream has a presentation time of 0
					ds->presentation_time_offset = 0;
					if ((s64) check_ts + ds->pts_minus_cts > 0) {
						u64 pto = check_ts + ds->pts_minus_cts;
						u64 pto_adj = pto;
						if (ds->timescale != ds->mpd_timescale) {
							pto_adj = gf_timestamp_rescale(pto_adj, ds->timescale, ds->mpd_timescale);
						}
						if (ds->rep->segment_list)
							ds->rep->segment_list->presentation_time_offset = pto_adj;
						else if (ds->rep->segment_template)
							ds->rep->segment_template->presentation_time_offset = pto_adj;
						else if (ds->set->segment_template)
							ds->set->segment_template->presentation_time_offset = pto_adj;
						else if (ds->set->segment_list)
							ds->set->segment_list->presentation_time_offset = pto_adj;
						else if (ds->rep->segment_base)
							ds->rep->segment_base->presentation_time_offset = pto_adj;

						ds->presentation_time_offset = pto;
					}
					//period continuity, skip priming in new periods
					if (ds->period_continuity_id)
						ds->pts_minus_cts = 0;
				}

				ds->first_cts = cts;
				ds->first_dts = dts;
				ds->rep_init++;
				has_init++;
			}

			nb_init++;

			if (ds->ts_offset) {
				cts += ds->ts_offset;
				dts += ds->ts_offset;
			}

			//ready to write MPD for the first time in dynamic mode with template
			if (has_init && (nb_init==count) && (ctx->dmode==GF_MPD_TYPE_DYNAMIC) && ctx->tpl && ctx->do_mpd && !ctx->dyn_rate) {
				e = dasher_send_manifest(filter, ctx, GF_TRUE);
				if (e) return e;
			}

			cts = dasher_translate_cts(ds, cts);
			dts -= ds->first_dts;

			if (ctx->sreg && ctx->mpd->gpac_mpd_time && gf_timestamp_greater(dts, ds->timescale, ctx->mpd->gpac_mpd_time, 1000)) {
				nb_reg_done++;
				break;
			}

			dur = o_dur = gf_filter_pck_get_duration(pck);
			pcont_cts += dur;
			if (ds->period_continuity_next_cts < pcont_cts)
				ds->period_continuity_next_cts = pcont_cts;

			split_dur = 0;
			split_dur_next = 0;

			//patch to align old arch with new
			check_dur = 0;
			if (ds->stream_type==GF_STREAM_AUDIO)
				check_dur = dur;

			//perform regulation of inputs to avoid dashing one stream faster than the others
			//this is needed when inputs are not realtime and we have text streams for which we must decide
			//if we insert empty segments
			if (!base_ds->segment_started && ctx->min_segment_start_time) {
				orig_cts = cts;
				if (ds->split_dur_next)
					cts += ds->split_dur_next;

				if (gf_timestamp_greater(cts, ds->timescale, ctx->min_segment_start_time, 1000)) {
					nb_seg_waiting++;
					break;
				}
				cts = orig_cts;
			}
			nb_seg_active++;

			//adjust duration and cts
			orig_cts = cts;
			if (ds->split_dur_next) {
				cts += ds->split_dur_next;
				assert(dur > ds->split_dur_next);
				dur -= ds->split_dur_next;
				split_dur_next = ds->split_dur_next;
				ds->split_dur_next = 0;
				is_packet_split = GF_TRUE;
			}

			if (ds->splitable && !ds->split_dur_next && !ds->cues && !ds->inband_cues) {
				Bool do_split = GF_FALSE;
				//adding this sample would exceed the segment duration
				if (gf_sys_old_arch_compat()) {
					if (gf_timestamp_greater_or_equal(cts + dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale))
						do_split = GF_TRUE;
				} else {
					if ( gf_timestamp_greater(cts + dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale))
						do_split = GF_TRUE;
				}
				if (do_split) {
					//this sample starts in the current segment - split it
					if (gf_timestamp_less(cts, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) ) {
						split_dur = (u32) (gf_timestamp_rescale(base_ds->adjusted_next_seg_start, base_ds->timescale, ds->timescale) - ds->last_cts);

						if (gf_sys_old_arch_compat() && (split_dur==dur))
							split_dur=0;

						if (split_dur>=dur)
							split_dur=0;
					}
				}
			}

			//mux rep, wait for a CTS more than our base if base not yet over
			if ((base_ds != ds) && !base_ds->seg_done && gf_timestamp_greater(cts, ds->timescale, base_ds->last_cts, base_ds->timescale) )
				break;

			if (ds->seek_to_pck) {
				ds->seek_to_pck = 0;
			}
			//base rep has been forced to another period, we switch asap
			else if (base_ds->forced_period_switch) {
				ds->seg_done = GF_TRUE;
				dasher_inject_eods(ctx, ds);
				seg_done = GF_TRUE;
				dasher_stream_period_changed(filter, ctx, ds, GF_FALSE);
				i--;
				count--;
				break;
			}
			//force flush mode, segment is done upon eos
			else if (ctx->force_flush) {
			}
			//source-driven fragmentation check for segment start
			else if (ctx->sigfrag) {
				const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FRAG_START);
				if (p && (p->value.uint>=1) && base_ds->segment_started) {
					seg_over = GF_TRUE;
					if (ds == base_ds) {
						base_ds->adjusted_next_seg_start = cts;
					}
				}
			}
			//inband-cue based segmentation
			else if (ds->inband_cues) {
				const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CUE_START);
				if (p && p->value.boolean && base_ds->segment_started) {
					seg_over = GF_TRUE;
					if (ds == base_ds) {
						base_ds->adjusted_next_seg_start = cts;
					}
				}
			}
			//cue-list based segmentation
			else if (ds->cues) {
				u32 cidx;
				GF_DASHCueInfo *cue=NULL;
				Bool is_cue_split = GF_FALSE;
				s32 has_mismatch = -1;

				for (cidx=0;cidx<ds->nb_cues; cidx++) {
					cue = &ds->cues[cidx];
					if (cue->sample_num) {
						if (cue->sample_num == ds->nb_pck + 1) {
							is_cue_split = GF_TRUE;
							break;
						} else if (cue->sample_num < ds->nb_pck) {
							has_mismatch = cidx;
						} else {
							break;
						}
					}
					else if (cue->dts) {
						u64 ts = (cue->dts - ds->cues_ts_offset) * ds->timescale;
						u64 ts2 = dts * ds->cues_timescale;
						if (ts == ts2) {
							is_cue_split = GF_TRUE;
							break;
						} else if (ts < ts2) {
							has_mismatch = cidx;
						} else {
							break;
						}
					}
					else if (cue->cts) {
						s64 ts = (cue->cts - ds->cues_ts_offset) * ds->timescale;
						s64 ts2 = (cts + ds->first_cts) * ds->cues_timescale;

						//cues are given in track timeline (presentation time), subtract the media time to pres time offset
						if (ds->cues_use_edits) {
							ts2 += (s64) (ds->pts_minus_cts) * ds->cues_timescale;
						}
						if (ts == ts2) {
							is_cue_split = GF_TRUE;
							break;
						} else if (ts < ts2) {
							has_mismatch = cidx;
						} else {
							break;
						}
					}
				}
				//start of first segment
				if (is_cue_split && !ds->segment_started) {
					memmove(ds->cues, &ds->cues[cidx+1], (ds->nb_cues-cidx-1) * sizeof(GF_DASHCueInfo));
					ds->nb_cues -= cidx+1;
					is_cue_split = 0;
				}

				if (is_cue_split) {
					if (!sap_type) {
						GF_LOG(ctx->strict_cues ?  GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not RAP !\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck));
						if (ctx->strict_cues) {
							gf_filter_pid_drop_packet(ds->ipid);
							gf_filter_pid_set_discard(ds->ipid, GF_TRUE);
							return GF_BAD_PARAM;
						}
					}
					memmove(ds->cues, &ds->cues[cidx+1], (ds->nb_cues-cidx-1) * sizeof(GF_DASHCueInfo));
					ds->nb_cues -= cidx+1;

					if (sap_type==3)
						ds->nb_sap_3 ++;
					else if (sap_type>3)
						ds->nb_sap_4 ++;

					/*check requested profiles can be generated, or adjust them*/
					if (
						(ds->nb_sap_4 || (ds->nb_sap_3 > 1))
						&& (ctx->profile != GF_DASH_PROFILE_FULL)
						/*TODO: store at DS level whether the usage of sap4 is ok or not (eg roll info for AAC is OK, not for xHEAAC-v2)
						for now we only complain for video*/
						&& ((ds->stream_type==GF_STREAM_VISUAL) || (ctx->strict_sap==DASHER_SAP_ON) )
					) {
						GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] WARNING! Max SAP type %d detected - switching to FULL profile\n", ds->nb_sap_4 ? 4 : 3));
						ctx->profile = GF_DASH_PROFILE_FULL;
						if (ctx->sseg)
							ds->set->subsegment_starts_with_sap = sap_type;
						else
							ds->set->starts_with_sap = sap_type;
					}


					seg_over = GF_TRUE;
					if (ds == base_ds) {
						base_ds->adjusted_next_seg_start = cts;
					}
				}

				if (has_mismatch>=0) {
					cue = &ds->cues[has_mismatch];
					GF_LOG(ctx->strict_cues ?  GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] found cue (sn %d - dts "LLD" - cts "LLD") in stream %s before current packet (sn %d - dts "LLD" - cts "LLD") , buggy source cues ?\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck+1, dts + ds->first_cts, cts + ds->first_cts));
					if (ctx->strict_cues) {
						gf_filter_pid_drop_packet(ds->ipid);
						gf_filter_pid_set_discard(ds->ipid, GF_TRUE);
						return GF_BAD_PARAM;
					}
				}
			}
			//forcing max time
			else if (
				(base_ds->force_rep_end && gf_timestamp_greater_or_equal(cts, ds->timescale, base_ds->force_rep_end, base_ds->timescale) )
				|| (base_ds->clamped_dur.num && (cts + o_dur > ds->ts_offset + base_ds->clamped_dur.num * ds->timescale / base_ds->clamped_dur.den))
			) {
				if (!base_ds->period->period->duration && base_ds->force_rep_end) {
					GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Inputs duration do not match, %s truncated to %g duration\n", ds->src_url, ((Double)base_ds->force_rep_end)/base_ds->timescale ));
				}
				dasher_drop_input(ctx, ds, GF_TRUE);
				ds->clamp_done = GF_TRUE;
				continue;
			}
			//we have a SAP and we work in closest mode: check the next SAP in the queue, and decide if we
			//split the segment at this SAP or wait for the next one
			else if (ds->segment_started && ds->sbound && sap_type) {
				u32 idx, nb_queued, nb_pck = gf_list_count(ds->packet_queue);
				nb_queued = nb_pck;
				if (is_queue_flush) nb_queued += 1;
				
				for (idx=1; idx<nb_queued; idx++) {
					GF_FilterPacket *next;
					if (idx==nb_pck) {
						next = gf_list_last(ds->packet_queue);
					} else {
						next = gf_list_get(ds->packet_queue, idx);
						u32 sap_next = gf_filter_pck_get_sap(next);
						if (!sap_next) continue;
					}
					u32 next_dur = gf_filter_pck_get_duration(next);
					//compute cts next
					u64 cts_next = gf_filter_pck_get_cts(next);
					if (ds->ts_offset) {
						cts_next += ds->ts_offset;
					}
					cts_next = dasher_translate_cts(ds, cts_next);

					if ((idx==nb_pck) && ctx->last_seg_merge) {
						u64 next_seg_dur = (cts_next + next_dur - cts);
						if (next_seg_dur * ds->dash_dur.den < (u64) ds->dash_dur.num * ds->timescale / 2)
							continue;
					}

					//same rule as above
					if (gf_timestamp_greater_or_equal(cts_next + next_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale)) {
						Bool force_seg_flush = GF_FALSE;
						s64 diff_next = gf_timestamp_rescale(cts_next, ds->timescale, base_ds->timescale);
						diff_next -= base_ds->adjusted_next_seg_start;
						//bounds at closest: if this SAP is closer to the target next segment start than the next SAP, split at this packet
						if (ds->sbound==DASHER_BOUNDS_CLOSEST) {
							s64 diff = gf_timestamp_rescale(cts, ds->timescale, base_ds->timescale);
							diff -= base_ds->adjusted_next_seg_start;
							//this one may be negative, but we always want diff_next positive (next SAP in next segment)
							if (diff<0)
								diff = -diff;
							//old arch was only using closest for tracks with sync points
							if (gf_sys_old_arch_compat() && (base_ds->sync_points_type==DASHER_SYNC_NONE) ) {
								if (diff_next > 0) {
									force_seg_flush = GF_TRUE;
								}
							}
							else if (diff<diff_next) {
								force_seg_flush = GF_TRUE;
							}
						}
						//bounds always in: if the next SAP is strictly greater than the target next segment start, split at this packet
						else {
							if (diff_next > 0) {
								force_seg_flush = GF_TRUE;
							}
						}
						if (force_seg_flush) {
							seg_over = GF_TRUE;
							if (ds == base_ds) {
								base_ds->adjusted_next_seg_start = cts;
							}
							break;
						}
					}
				}
			}
			//we exceed segment duration - if segment was started, check if we need to stop segment
			//if segment was not started we insert the packet anyway
			else if (!ds->sbound && ds->segment_started && gf_timestamp_greater_or_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale) ) {
				//no sap, segment is over
				if (! ctx->sap) {
					seg_over = GF_TRUE;
				}
				else if ((ds->stream_type==GF_STREAM_AUDIO)
					&& gf_timestamp_equal(cts + check_dur, ds->timescale, base_ds->adjusted_next_seg_start, base_ds->timescale)
				) {

				}
				// sap, segment is over
				else if (sap_type) {

					if (sap_type==3)
						ds->nb_sap_3 ++;
					else if (sap_type>3)
						ds->nb_sap_4 ++;

					/*check requested profiles can be generated, or adjust them*/
					if ((ctx->profile != GF_DASH_PROFILE_FULL)
						&& (ds->nb_sap_4 || (ds->nb_sap_3 > 1))
						/*TODO: store at DS level whether the usage of sap4 is ok or not (eg roll info for AAC is OK, not for xHEAAC-v2)
						for now we only complain for video*/
						&& ((ds->stream_type==GF_STREAM_VISUAL) || (ctx->strict_sap==DASHER_SAP_ON) )
					) {
						if ((sap_type == GF_FILTER_SAP_3)
							&& (ds->codec_id==GF_CODECID_VVC)
							&& (ds->inband_params==2)
						) {
							if (ds->set->starts_with_sap<3) {
								GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Using VVC with SAP type 3 and inband PPS, profile not yet defined\n", ds->nb_sap_4 ? 4 : 3));
							}
						} else {
							GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] WARNING! Max SAP type %d detected - switching to FULL profile\n", ds->nb_sap_4 ? 4 : 3));
							ctx->profile = GF_DASH_PROFILE_FULL;
						}
						if (ctx->sseg)
							ds->set->subsegment_starts_with_sap = sap_type;
						else
							ds->set->starts_with_sap = sap_type;
					}

					seg_over = GF_TRUE;
					if (ds == base_ds) {
						base_ds->adjusted_next_seg_start = cts;
					}
				}
			}


			if (ds->muxed_base && ds->muxed_base->done) {
				seg_over = GF_FALSE;
			}
			//if flushing now will result in a one sample fragment afterwards
			//because this is the before-last sample, don't flush unless:
			//- we have an asto set (low latency)
			//- this is not an audio stream or all samples are SAPs
			//- we use cues
			else if (seg_over && ds->nb_samples_in_source && !ctx->loop
				&& (ds->nb_pck+1 == ds->nb_samples_in_source)
				&& !ds->inband_cues && !ds->cues
				&& !ctx->asto
				&& ! ((ds->sync_points_type==DASHER_SYNC_NONE) && (ds->stream_type!=GF_STREAM_AUDIO))
			) {
				seg_over = GF_FALSE;
			}
			//if dur=0 (some text streams), don't flush segment
			if (seg_over && dur) {
				assert(!ds->seg_done);

				if (ds->request_period_switch && !gf_list_count(ds->packet_queue)) {
					e = dasher_stream_period_changed(filter, ctx, ds, (ds->request_period_switch==2) ? GF_TRUE : GF_FALSE);
					if (e < 0) {
						GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Period switch request failed.\n"));
						break;
					}
					assert(gf_list_find(ctx->current_period->streams, ds)<0);
					count = gf_list_count(ctx->current_period->streams);
					i--;
					break;
				}

				ds->seg_done = GF_TRUE;

				dasher_inject_eods(ctx, ds);

				ds->first_cts_in_next_seg = cts;
				assert(base_ds->nb_comp_done < base_ds->nb_comp);
				base_ds->nb_comp_done ++;

				if (split_dur_next)
					ds->split_dur_next = (u32) split_dur_next;

				if (base_ds->nb_comp_done == base_ds->nb_comp) {
					dasher_flush_segment(ctx, base_ds, GF_FALSE);
					seg_done = GF_TRUE;
				}
				break;
			}

			if (cts==GF_FILTER_NO_TS) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] WARNING! Source packet has no timestamp !\n"));

				cts = ds->last_cts;
				dts = ds->last_dts;
			} else {
				u64 ncts = cts + (split_dur ? split_dur : dur);
				if (ncts>ds->est_first_cts_in_next_seg)
					ds->est_first_cts_in_next_seg = ncts;

				ncts = gf_timestamp_rescale(ncts, ds->timescale, 1000);
				if (ncts>base_ds->max_period_dur)
					base_ds->max_period_dur = ncts;

				ds->last_cts = cts + (split_dur ? split_dur : dur);
				ds->last_dts = dts;
				ds->est_next_dts = dts + o_dur;
			}
			ds->nb_pck ++;

			if (ctx->sigfrag) {
				if (!ds->segment_started) {
					ds->first_cts_in_seg = cts;
					dasher_mark_segment_start(ctx, ds, NULL, pck);
					ds->segment_started = GF_TRUE;
				}

				ds->cumulated_dur += dur;

				//drop packet if not splitting
				if (!ds->split_dur_next)
					gf_filter_pid_drop_packet(ds->ipid);

				if (ctx->in_error) {
					gf_filter_pid_set_discard(ds->ipid, GF_TRUE);
					gf_filter_pid_set_eos(ctx->opid);
					return GF_BAD_PARAM;
				}
				continue;
			}
			//create new ref to input
			dst = gf_filter_pck_new_ref(ds->opid, 0, 0, pck);
			if (!dst) return GF_OUT_OF_MEM;

			//merge all props
			gf_filter_pck_merge_properties(pck, dst);
			//we have ts offset, use computed cts and dts
			if (ds->ts_offset) {
				gf_filter_pck_set_cts(dst, gf_filter_pck_get_cts(pck) + ds->ts_offset);
				gf_filter_pck_set_dts(dst, gf_filter_pck_get_dts(pck) + ds->ts_offset);
			}

			if (gf_sys_old_arch_compat() && ds->clamped_dur.num && ctx->loop
				&& (cts + 2*o_dur >= ds->ts_offset + base_ds->clamped_dur.num * ds->timescale / base_ds->clamped_dur.den)
			) {
				u32 _dur = dur;
				/* simple round with (int)+.5 to avoid trucating .99999 to 0 */
				dur = (u32) (ds->clamped_dur.num * ds->timescale / ds->clamped_dur.den - (dts - ds->ts_offset) + 0.5);
				//it may happen that the sample duration is 0 if the clamp duration is right after the sample DTS and timescale is not big enough to express it - force to 1
				if (dur==0)
					dur=1;

				gf_filter_pck_set_duration(dst, dur);
				ds->est_next_dts += (s32) dur - (s32) _dur;;
			}

			if (!ds->segment_started) {
				ds->first_cts_in_seg = cts;
				dasher_mark_segment_start(ctx, ds, dst, pck);
				ds->segment_started = GF_TRUE;
			}
			//prev packet was split
			if (is_packet_split) {
				u64 diff=0;
				u8 dep_flags = gf_filter_pck_get_dependency_flags(pck);
				u64 ts = gf_filter_pck_get_cts(pck);
				if (ts != GF_FILTER_NO_TS) {
					cts += ds->first_cts;
					assert(cts >= ts);
					diff = cts - ts;
				} else {
					cts = ds->last_cts;
				}
				gf_filter_pck_set_cts(dst, cts + ds->ts_offset);

				ts = gf_filter_pck_get_dts(pck);
				if (ts != GF_FILTER_NO_TS)
					gf_filter_pck_set_dts(dst, ts + diff + ds->ts_offset);

				//add sample is redundant flag
				dep_flags |= 0x1;
				gf_filter_pck_set_dependency_flags(dst, dep_flags);
				//this one might be incorrect of this split packet is also split, but we update the duration right below
				gf_filter_pck_set_duration(dst, dur);
			}

			//if split, adjust duration - this may happen on a split packet, if it covered 3 or more segments
			if (split_dur) {
				u32 cumulated_split_dur = split_dur;
				gf_filter_pck_set_duration(dst, split_dur);
				//adjust dur
				cumulated_split_dur += (u32) (cts - orig_cts);
				assert( dur > split_dur);
				ds->split_dur_next = cumulated_split_dur;
				dur = split_dur;
			}

			//remove NTP
			if (ctx->ntp != DASHER_NTP_KEEP)
				gf_filter_pck_set_property(dst, GF_PROP_PCK_SENDER_NTP, NULL);

			//change packet times
			if (ds->force_timescale) {
				u64 ats;
				ats = gf_filter_pck_get_dts(dst);
				if (ats!=GF_FILTER_NO_TS) {
					ats = gf_timestamp_rescale(ats, ds->timescale, ds->force_timescale);
					gf_filter_pck_set_dts(dst, ats);
				}
				ats = gf_filter_pck_get_cts(dst);
				if (ats!=GF_FILTER_NO_TS) {
					ats = gf_timestamp_rescale(ats, ds->timescale, ds->force_timescale);
					gf_filter_pck_set_cts(dst, ats);
				}
				ats = (u64) gf_filter_pck_get_duration(dst);
				if (ats) {
					ats = gf_timestamp_rescale(ats, ds->timescale, ds->force_timescale);
					gf_filter_pck_set_duration(dst, (u32) ats);
				}
			}

			ds->cumulated_dur += dur;

			if (ds->current_seg_state && gf_filter_pck_get_crypt_flags(pck))
				ds->current_seg_state->encrypted = GF_TRUE;
			//TODO check drift between MPD start time and min CTS in segment (not just first CTS in segment)

			//send packet
			gf_filter_pck_send(dst);

			if (ctx->update_report>=0)
				ctx->update_report++;

			if (ds->dyn_bitrate) {
				u32 dsize;
				u64 rdts = gf_filter_pck_get_dts(pck);
				gf_filter_pck_get_data(pck, &dsize);
				if (!ds->rate_first_dts_plus_one)
					ds->rate_first_dts_plus_one = 1 + rdts;
				ds->rate_last_dts = rdts;
				ds->rate_media_size += dsize;
			}

			//drop packet if not splitting
			if (!ds->split_dur_next)
				dasher_drop_input(ctx, ds, GF_FALSE);

		}
	}

	if (nb_seg_waiting && !nb_seg_active) {
		ctx->last_min_segment_start_time = ctx->min_segment_start_time;
		ctx->min_segment_start_time = 0;
		return GF_OK;
	}

	nb_init = 0;
	for (i=0; i<count; i++) {
		GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i);
		//if (ds->muxed_base) ds = ds->muxed_base;
		if (ds->done || ds->subdur_done) nb_init++;
		else if (ds->seg_done && ctx->force_period_switch) nb_init++;
		else if (ds->seg_done && ds->muxed_base && ds->muxed_base->done) {
			nb_init++;
			ds->done = 1;
		}
	}

	if (nb_reg_done && (nb_reg_done == count)) {
		ctx->mpd->gpac_mpd_time = 0;
	}

	dasher_format_report(filter, ctx);

	if (seg_done) {
		Bool update_period = GF_FALSE;
		Bool update_manifest = GF_FALSE;
		if (ctx->purge_segments) update_period = GF_TRUE;
		if (ctx->mpd) {
			//segment timeline used, always update manifest
			if (ctx->stl)
				update_manifest = GF_TRUE;
			else if (ctx->dmode==GF_DASH_DYNAMIC) {
				//publish time not set, we never send the manifest, do it
				if (!ctx->mpd->publishTime) {
					update_manifest = GF_TRUE;
				}
				//whenever we have a new seg in HLS, push new manifest
				else if (ctx->do_m3u8) {
					update_manifest = GF_TRUE;
				}
				//we have a minimum ipdate period
				else if (ctx->mpd->minimum_update_period) {
					u64 diff = dasher_get_utc(ctx) - ctx->mpd->publishTime;
					if (diff >= ctx->mpd->minimum_update_period)
						update_manifest = GF_TRUE;
				}
			}
			if (update_period)
				dasher_update_period_duration(ctx, GF_FALSE);

			if (update_manifest)
				dasher_send_manifest(filter, ctx, GF_FALSE);
		}
	} else if (ctx->force_hls_ll_manifest) {
		ctx->force_hls_ll_manifest = GF_FALSE;
		dasher_send_manifest(filter, ctx, GF_FALSE);
	}

	//still some running streams in period - do not ask for reschedule, wait for input packets to be available
	if (count && (nb_init<count)) {
		return GF_OK;
	}

	//in subdur mode once we are done, flush output pids and discard all input packets
	//this is done at the end to be able to resume dashing when loop is requested
	if (ctx->subdur) {
		for (i=0; i<count; i++) {
			GF_FilterPacket *eods_pck;
			GF_DashStream *ds = gf_list_get(ctx->current_period->streams, i);
			if (ds->done) continue;
			eods_pck = gf_filter_pck_new_alloc(ds->opid, 0, NULL);
			if (!eods_pck) return GF_OUT_OF_MEM;
			ds->done = 2;
			ds->subdur_done = GF_TRUE;
			gf_filter_pck_set_property(eods_pck, GF_PROP_PCK_EODS, &PROP_BOOL(GF_TRUE) );
			gf_filter_pck_send(eods_pck);

			dasher_drop_input(ctx, ds, GF_TRUE);
		}
	}

	//we need to wait for full flush of packets before switching periods in order to get the
	//proper segment size for segment_list+byte_range mode
	if (ctx->nb_seg_url_pending) {
		u64 diff;
		if (!ctx->last_evt_check_time) {
			ctx->last_evt_check_time = gf_sys_clock_high_res();
			gf_filter_prevent_blocking(filter, GF_TRUE);
		}

		diff = gf_sys_clock_high_res() - ctx->last_evt_check_time;
		if (diff < 10000000) {
			gf_filter_ask_rt_reschedule(filter, 1000);
			return GF_OK;
		}
		GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] timeout %d segment info still pending but no event from muxer after "LLU" us, aborting\n", ctx->nb_seg_url_pending, diff));
		ctx->nb_seg_url_pending = 0;
		return GF_SERVICE_ERROR;
	}
	if (ctx->sseg && !ctx->on_demand_done && !ctx->sigfrag) {
		return GF_OK;
	}
	gf_filter_prevent_blocking(filter, GF_FALSE);
	ctx->force_period_switch = GF_FALSE;
	//done with this period, do period switch - this will update the MPD if needed
	e = dasher_switch_period(filter, ctx);
	//no more periods
	if (e==GF_EOS) {