in src/media_tools/dash_client.c [3528:4116]
static u32 dash_main_thread_proc(void *par)
{
GF_Err e;
GF_DashClient *dash = (GF_DashClient*) par;
GF_MPD_Representation *rep;
u32 i, group_count, ret = 0;
Bool go_on = GF_TRUE;
u32 min_wait = 0;
Bool first_period_in_mpd = GF_TRUE;
char *new_base_seg_url;
char *key_url=NULL;
bin128 key_iv;
assert(dash);
if (!dash->mpd) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Incorrect state, no dash->mpd for URL=%s, already stopped ?\n", dash->base_url));
return 1;
}
restart_period:
/* Setting the download status in exclusive code */
gf_mx_p(dash->dl_mutex);
dash->dash_state = GF_DASH_STATE_SETUP;
gf_mx_v(dash->dl_mutex);
dash->in_period_setup = 1;
/*setup period*/
e = gf_dash_setup_period(dash);
if (e) {
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
ret = 1;
goto exit;
}
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SELECT_GROUPS, -1, GF_OK);
e = GF_OK;
group_count = gf_list_count(dash->groups);
for (i=0; i<group_count; i++) {
GF_DASH_Group *group = gf_list_get(dash->groups, i);
if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
continue;
//by default all groups are started (init seg download and buffering). They will be (de)selected by the user
if (first_period_in_mpd) {
gf_dash_buffer_on(group);
}
e = gf_dash_download_init_segment(dash, group);
if (e) break;
}
first_period_in_mpd = 0;
/*if error signal to the user*/
if (e != GF_OK) {
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
ret = 1;
goto exit;
}
dash->last_update_time = gf_sys_clock();
gf_mx_p(dash->dl_mutex);
dash->dash_state = GF_DASH_STATE_CONNECTING;
gf_mx_v(dash->dl_mutex);
/*ask the user to connect to desired groups*/
e = dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CREATE_PLAYBACK, -1, GF_OK);
if (e) {
ret = 1;
goto exit;
}
if (dash->mpd_stop_request) {
ret = 1;
goto exit;
}
gf_mx_p(dash->dl_mutex);
dash->in_period_setup = 0;
dash->dash_state = GF_DASH_STATE_RUNNING;
gf_mx_v(dash->dl_mutex);
min_wait = 0;
while (go_on) {
const char *local_file_name = NULL;
const char *resource_name = NULL;
/*wait until next segment is needed*/
while (!dash->mpd_stop_request) {
u32 timer = gf_sys_clock() - dash->last_update_time;
/*refresh MPD*/
if (dash->force_mpd_update || (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period))) {
u32 diff = gf_sys_clock();
dash->force_mpd_update = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At %d Time to update the playlist (%u ms elapsed since last refresh and min reload rate is %u)\n", gf_sys_clock() , timer, dash->mpd->minimum_update_period));
gf_mx_p(dash->dl_mutex);
e = gf_dash_update_manifest(dash);
gf_mx_v(dash->dl_mutex);
group_count = gf_list_count(dash->groups);
diff = gf_sys_clock() - diff;
if (e) {
if (!dash->in_error) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error updating MPD %s\n", gf_error_to_string(e)));
}
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated MPD in %d ms\n", diff));
}
} else {
Bool all_groups_done = GF_TRUE;
Bool cache_full = GF_TRUE;
/*wait if nothing is ready to be downloaded*/
if (min_wait>1) {
u32 sleep_for = MIN(min_wait/2, 1000);
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] No segments available on the server until %d ms - going to sleep for %d ms\n", min_wait, sleep_for));
gf_sleep(sleep_for);
}
/*check if cache is not full*/
gf_mx_p(dash->dl_mutex);
dash->tsb_exceeded = 0;
dash->time_in_tsb = 0;
for (i=0; i<group_count; i++) {
GF_DASH_Group *group = gf_list_get(dash->groups, i);
if ((group->selection != GF_DASH_GROUP_SELECTED) || group->done) continue;
all_groups_done = 0;
if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
gf_dash_group_check_time(group);
}
if (group->nb_cached_segments<group->max_cached_segments) {
cache_full = 0;
break;
}
}
gf_mx_v(dash->dl_mutex);
if (dash->tsb_exceeded) {
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_OVERFLOW, (s32) dash->tsb_exceeded, GF_OK);
dash->tsb_exceeded = 0;
} else if (dash->time_in_tsb != dash->prev_time_in_tsb) {
dash->prev_time_in_tsb = dash->time_in_tsb;
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_UPDATE, 0, GF_OK);
}
if (!cache_full) break;
//seek request
if (dash->request_period_switch==2) all_groups_done = 1;
if (all_groups_done && dash->next_period_checked) {
dash->next_period_checked = 1;
//check if we can continue next period with the same groups
if (gf_dash_is_seamless_period_switch(dash)) {
all_groups_done = 0;
}
}
if (all_groups_done && dash->request_period_switch) {
gf_dash_reset_groups(dash);
if (dash->request_period_switch == 1) {
if (dash->speed<0) {
if (dash->active_period_index) {
dash->active_period_index--;
}
} else {
dash->active_period_index++;
}
}
dash->request_period_switch = 0;
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Switching to period #%d\n", dash->active_period_index+1));
goto restart_period;
}
gf_sleep(30);
}
}
/* stop the thread if requested */
if (dash->mpd_stop_request) {
go_on = 0;
break;
}
min_wait = 0;
/*for each selected groups*/
for (i=0; i<group_count; i++) {
//commented out as we end up doing too many requets
#if 0
Bool in_segment_avail_time = GF_FALSE;
#endif
u64 start_range, end_range;
Bool use_byterange;
u32 representation_index;
u32 clock_time;
Bool empty_file = GF_FALSE;
GF_DASH_Group *group = gf_list_get(dash->groups, i);
if (group->selection != GF_DASH_GROUP_SELECTED) {
if (group->nb_cached_segments) {
gf_dash_group_reset(dash, group);
}
continue;
}
if (group->done) continue;
if (group->nb_cached_segments>=group->max_cached_segments) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At %d Cache is full for this group - skipping\n", gf_sys_clock() ));
continue;
}
/*remember the active rep index, since group->active_rep_index may change because of bandwidth control algorithm*/
representation_index = group->active_rep_index;
rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
/* if the index of the segment to be downloaded is greater or equal to the last segment (as seen in the playlist),
we need to check if a new playlist is ready */
if (group->nb_segments_in_rep && (group->download_segment_index >= (s32) group->nb_segments_in_rep)) {
u32 timer = gf_sys_clock() - dash->last_update_time;
Bool update_playlist = 0;
/* this period is done*/
if ((dash->mpd->type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
}
/* update of the playlist, only if indicated */
else if (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period)) {
update_playlist = 1;
}
/* if media_presentation_duration is 0 and we are in live, force a refresh (not in the spec but safety check*/
else if ((dash->mpd->type==GF_MPD_TYPE_DYNAMIC) && !dash->mpd->media_presentation_duration) {
if (group->segment_duration && (timer > group->segment_duration*1000))
update_playlist = 1;
}
if (update_playlist) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in current playlist downloaded, checking updates after %u ms\n", timer));
e = gf_dash_update_manifest(dash);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error updating MPD %s\n", gf_error_to_string(e)));
}
group_count = gf_list_count(dash->groups);
rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
} else {
gf_sleep(1);
}
/* Now that the playlist is up to date, we can check again */
if (group->download_segment_index >= (s32) group->nb_segments_in_rep) {
/* if there is a specified update period, we redo the whole process */
if (dash->mpd->minimum_update_period || dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
if ((dash->mpd->type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in period (dynamic mode) - group is done\n"));
group->done = 1;
break;
}
else if (! group->maybe_end_of_stream) {
u32 now = gf_sys_clock();
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of segment list reached (%d segments but idx is %d), waiting for next MPD update\n", group->nb_segments_in_rep, group->download_segment_index));
if (group->nb_cached_segments)
continue;
if (!group->time_at_first_reload_required) {
group->time_at_first_reload_required = now;
continue;
}
if (now - group->time_at_first_reload_required < group->cache_duration)
continue;
if (dash->mpd->minimum_update_period && (now - group->time_at_first_reload_required < dash->mpd->minimum_update_period))
continue;
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Segment list has not been updated for more than %d ms - assuming end of period\n", now - group->time_at_first_reload_required));
group->done = 1;
break;
}
} else {
/* if not, we are really at the end of the playlist, we can quit */
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of period reached for group\n"));
group->done = 1;
break;
}
}
}
group->time_at_first_reload_required = 0;
gf_mx_p(dash->dl_mutex);
if (group->force_switch_bandwidth && !dash->auto_switch_count) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing representation switch, retesting group"));
gf_dash_switch_group_representation(dash, group);
/*restart*/
i--;
gf_mx_v(dash->dl_mutex);
continue;
}
/*check availablity start time of segment in Live !!*/
if (!group->broken_timing && (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) && !dash->is_m3u8) {
s32 to_wait = 0;
u32 seg_dur_ms=0;
#ifndef GPAC_DISABLE_LOG
u32 start_number = gf_dash_get_start_number(group, rep);
#endif
s64 segment_ast = (s64) gf_dash_get_segment_availability_start_time(dash->mpd, group, group->download_segment_index, &seg_dur_ms);
s64 now = (s64) gf_net_get_utc();
if (group->retry_after_utc > (u64) now) {
min_wait = (u32) (group->retry_after_utc - (u64) now);
gf_mx_v(dash->dl_mutex);
continue;
}
clock_time = gf_sys_clock();
to_wait = (s32) (segment_ast - now);
/*if segment AST is greater than now, it is not yet available - we would need an estimate on how long the request takes to be sent to the server in order to be more reactive ...*/
if (to_wait > 1) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) is not yet available on server - requesting later in %d ms\n", i+1, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init)/1000.0, to_wait));
if (group->last_segment_time) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
}
gf_mx_v(dash->dl_mutex);
if (!min_wait || ((u32) to_wait < min_wait))
min_wait = to_wait;
continue;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Set #%d At %d Next segment %d (AST "LLD" - sec in period %g) should now be available on server since %d ms - requesting it\n", i+1, gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init)/1000.0, -to_wait));
if (group->last_segment_time) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
}
#if 0
/*check if we are in the segment availability end time*/
if (now < segment_ast + seg_dur_ms + group->time_shift_buffer_depth )
in_segment_avail_time = 1;
#endif
}
}
min_wait = 0;
/* At this stage, there are some segments left to be downloaded */
e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &new_base_seg_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv);
gf_mx_v(dash->dl_mutex);
if (e) {
/*do something!!*/
break;
}
use_byterange = (start_range || end_range) ? 1 : 0;
if (use_byterange) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading new segment: %s (range: "LLD"-"LLD")\n", new_base_seg_url, start_range, end_range));
}
/*local file*/
if (!strstr(new_base_seg_url, "://") || (!strnicmp(new_base_seg_url, "file://", 7) || !strnicmp(new_base_seg_url, "gmem://", 7)) ) {
resource_name = local_file_name = (char *) new_base_seg_url;
e = GF_OK;
/*do not erase local files*/
group->local_files = 1;
gf_dash_buffer_off(group);
if (group->force_switch_bandwidth && !dash->auto_switch_count) {
gf_dash_switch_group_representation(dash, group);
/*restart*/
i--;
continue;
}
} else {
group->max_bitrate = 0;
group->min_bitrate = (u32)-1;
/*use persistent connection for segment downloads*/
gf_mx_p(dash->dl_mutex);
if (use_byterange) {
e = gf_dash_download_resource(dash, &(group->segment_download), new_base_seg_url, start_range, end_range, 1, group);
} else {
e = gf_dash_download_resource(dash, &(group->segment_download), new_base_seg_url, 0, 0, 1, group);
}
gf_mx_v(dash->dl_mutex);
if ((e==GF_IP_CONNECTION_CLOSED) && group->download_abort_type) {
group->download_abort_type = 0;
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Aborted while downloading segment (seek ?)%s \n", new_base_seg_url));
gf_free(new_base_seg_url);
gf_free(key_url);
new_base_seg_url = NULL;
key_url = NULL;
continue;
}
/*TODO decide what is the best, fetch from another representation or ignore ...*/
if (e != GF_OK) {
clock_time = gf_sys_clock();
min_wait = dash->min_timeout_between_404;
group->retry_after_utc = min_wait + gf_net_get_utc();
if (group->maybe_end_of_stream) {
if (group->maybe_end_of_stream==2) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Couldn't get segment %s (error %s) and end of period was guessed during last update - stopping playback\n", new_base_seg_url, gf_error_to_string(e)));
group->maybe_end_of_stream = 0;
group->done = 1;
}
group->maybe_end_of_stream++;
} else if (group->segment_in_valid_range) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - segment was lost at server/proxy side\n", new_base_seg_url, gf_error_to_string(e)));
if (dash->speed >= 0) {
group->download_segment_index++;
} else if (group->download_segment_index) {
group->download_segment_index--;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
group->done = 1;
}
group->segment_in_valid_range=0;
} else if (group->prev_segment_ok && !group->time_at_first_failure) {
if (!group->loop_detected) {
group->time_at_first_failure = clock_time;
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - starting countdown for %d ms\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration));
}
}
//if previous segment download was OK, we are likely asking too early - retry for the complete duration in case one segment was lost - we add 100ms safety
else if (group->prev_segment_ok && (clock_time - group->time_at_first_failure <= group->current_downloaded_segment_duration + dash->segment_lost_after_ms )) {
} else {
if (group->prev_segment_ok) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - waited %d ms but segment still not available, checking next one ...\n", new_base_seg_url, gf_error_to_string(e), clock_time - group->time_at_first_failure));
group->time_at_first_failure = 0;
group->prev_segment_ok = GF_FALSE;
}
#if 1
group->nb_consecutive_fail ++;
//we are lost ....
if (group->nb_consecutive_fail == 20) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Too many consecutive segments not found, sync or signal has been lost - entering end of stream detection mode\n"));
min_wait = 1000;
group->maybe_end_of_stream = 1;
} else
#endif
{
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s\n", new_base_seg_url, gf_error_to_string(e)));
if (dash->speed >= 0) {
group->download_segment_index++;
} else if (group->download_segment_index) {
group->download_segment_index--;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
group->done = 1;
}
}
}
gf_free(new_base_seg_url);
gf_free(key_url);
new_base_seg_url = NULL;
key_url = NULL;
continue;
}
group->prev_segment_ok = GF_TRUE;
if (group->time_at_first_failure) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 - was our download schedule too early ?\n", new_base_seg_url));
group->time_at_first_failure = 0;
group->nb_consecutive_fail = 0;
}
if ((e==GF_OK) && group->force_switch_bandwidth) {
if (!dash->auto_switch_count) {
gf_dash_switch_group_representation(dash, group);
/*restart*/
i--;
gf_free(new_base_seg_url);
new_base_seg_url = NULL;
gf_free(key_url);
key_url = NULL;
continue;
}
if (rep->playback.disabled) {
gf_dash_skip_disabled_representation(group, rep, GF_FALSE);
/*restart*/
i--;
gf_free(new_base_seg_url);
new_base_seg_url = NULL;
gf_free(key_url);
key_url = NULL;
continue;
}
}
if (group->segment_must_be_streamed) local_file_name = dash->dash_io->get_url(dash->dash_io, group->segment_download);
else local_file_name = dash->dash_io->get_cache_name(dash->dash_io, group->segment_download);
if (dash->dash_io->get_total_size(dash->dash_io, group->segment_download)==0) {
empty_file = GF_TRUE;
}
resource_name = dash->dash_io->get_url(dash->dash_io, group->segment_download);
dash_do_rate_adaptation(dash, group, rep);
}
if (local_file_name && (e == GF_OK || group->segment_must_be_streamed )) {
gf_mx_p(dash->dl_mutex);
assert(group->nb_cached_segments<group->max_cached_segments);
assert(local_file_name);
if (!empty_file) {
group->cached[group->nb_cached_segments].cache = gf_strdup(local_file_name);
group->cached[group->nb_cached_segments].url = gf_strdup( resource_name );
group->cached[group->nb_cached_segments].start_range = 0;
group->cached[group->nb_cached_segments].end_range = 0;
group->cached[group->nb_cached_segments].representation_index = representation_index;
group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
group->cached[group->nb_cached_segments].loop_detected = group->loop_detected;
if (key_url) {
group->cached[group->nb_cached_segments].key_url = key_url;
memcpy(group->cached[group->nb_cached_segments].key_IV, key_iv, sizeof(bin128));
key_url = NULL;
}
group->loop_detected = GF_FALSE;
if (group->local_files && use_byterange) {
group->cached[group->nb_cached_segments].start_range = start_range;
group->cached[group->nb_cached_segments].end_range = end_range;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Added file to cache (%u/%u in cache): %s\n", group->nb_cached_segments+1, group->max_cached_segments, group->cached[group->nb_cached_segments].url));
group->nb_cached_segments++;
gf_dash_update_buffering(group, dash);
}
/* download enhancement representation of this segment*/
if ((representation_index != group->force_max_rep_index) && rep->enhancement_rep_index_plus_one) {
group->active_rep_index = rep->enhancement_rep_index_plus_one - 1;
group->has_pending_enhancement = GF_TRUE;
}
/* if we have downloaded all enhancement representations of this segment, restart from base representation and increase dowloaded segment index by 1*/
else {
if (group->base_rep_index_plus_one) group->active_rep_index = group->base_rep_index_plus_one - 1;
if (dash->speed >= 0) {
group->download_segment_index++;
} else if (group->download_segment_index) {
group->download_segment_index--;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
group->done = 1;
}
group->has_pending_enhancement = GF_FALSE;
}
if (dash->auto_switch_count) {
group->nb_segments_done++;
if (group->nb_segments_done==dash->auto_switch_count) {
group->nb_segments_done=0;
gf_dash_skip_disabled_representation(group, rep, GF_TRUE);
}
}
//do not notify segments if there is a pending period switch - since these are decided by the user, we don't
//want to notify old segments
if (!dash->request_period_switch && !group->has_pending_enhancement)
dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, group), GF_OK);
gf_mx_v(dash->dl_mutex);
}
gf_free(new_base_seg_url);
new_base_seg_url = NULL;
if (key_url) {
gf_free(key_url);
key_url = NULL;
}
}
}
exit:
/* Signal that the download thread has ended */
gf_mx_p(dash->dl_mutex);
/*an error occured during playback chain creation and we couldn't release our plyayback chain in time, do it now*/
if (dash->dash_state == GF_DASH_STATE_CONNECTING)
gf_dash_reset_groups(dash);
dash->dash_state = GF_DASH_STATE_STOPPED;
gf_mx_v(dash->dl_mutex);
return ret;
}