LCOV - code coverage report
Current view: top level - media_tools - dash_client.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 3243 4609 70.4 %
Date: 2021-04-29 23:48:07 Functions: 167 175 95.4 %

          Line data    Source code
       1             : /**
       2             :  *                      GPAC - Multimedia Framework C SDK
       3             :  *
       4             :  *                      Authors: Jean Le Feuvre, Cyril Concolato
       5             :  *                      Copyright (c) Telecom ParisTech 2010-2021
       6             :  *                                      All rights reserved
       7             :  *
       8             :  *  This file is part of GPAC / Adaptive HTTP Streaming
       9             :  *
      10             :  *  GPAC is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU Lesser General Public License as published by
      12             :  *  the Free Software Foundation; either version 2, or (at your option)
      13             :  *  any later version.
      14             :  *
      15             :  *  GPAC is distributed in the hope that it will be useful,
      16             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :  *  GNU Lesser General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU Lesser General Public
      21             :  *  License along with this library; see the file COPYING.  If not, write to
      22             :  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
      23             :  *
      24             :  */
      25             : 
      26             : #include <gpac/network.h>
      27             : #include <gpac/dash.h>
      28             : #include <gpac/mpd.h>
      29             : #include <gpac/internal/m3u8.h>
      30             : #include <gpac/internal/isomedia_dev.h>
      31             : #include <gpac/base_coding.h>
      32             : #include <string.h>
      33             : #include <sys/stat.h>
      34             : 
      35             : #include <math.h>
      36             : 
      37             : 
      38             : #ifndef GPAC_DISABLE_DASH_CLIENT
      39             : 
      40             : /*ISO 639 languages*/
      41             : #include <gpac/iso639.h>
      42             : 
      43             : /*set to 1 if you want MPD to use SegmentTemplate if possible instead of SegmentList*/
      44             : #define M3U8_TO_MPD_USE_TEMPLATE        0
      45             : /*set to 1 if you want MPD to use SegmentTimeline*/
      46             : #define M3U8_TO_MPD_USE_SEGTIMELINE     0
      47             : 
      48             : typedef enum {
      49             :         GF_DASH_STATE_STOPPED = 0,
      50             :         /*period setup and playback chain creation*/
      51             :         GF_DASH_STATE_SETUP,
      52             :         /*request to start playback chain*/
      53             :         GF_DASH_STATE_CONNECTING,
      54             :         GF_DASH_STATE_RUNNING,
      55             : } GF_DASH_STATE;
      56             : 
      57             : 
      58             : //shifts AST in the past(>0) or future (<0)  so that client starts request in the future or in the past
      59             : //#define FORCE_DESYNC  4000
      60             : 
      61             : /* WARNING: GF_DASH_Group does not represent a Group in DASH
      62             :    It corresponds to an AdaptationSet with additional live information not present in the MPD
      63             :    (e.g. current active representation)
      64             : */
      65             : typedef struct __dash_group GF_DASH_Group;
      66             : 
      67             : struct __dash_client
      68             : {
      69             :         GF_DASHFileIO *dash_io;
      70             : 
      71             :         /*interface to mpd parser - get rid of this and use the DASHIO instead ?*/
      72             :         GF_FileDownload getter;
      73             : 
      74             :         char *base_url;
      75             : 
      76             :         u32 max_cache_duration, max_width, max_height;
      77             :         u8 max_bit_per_pixel;
      78             :         u32 auto_switch_count;
      79             :         Bool keep_files, disable_switching, allow_local_mpd_update, estimate_utc_drift, ntp_forced;
      80             :         Bool is_m3u8, is_smooth;
      81             :         Bool split_adaptation_set;
      82             :         GF_DASHLowLatencyMode low_latency_mode;
      83             :         //set when MPD downloading fails. Will resetup DASH live once MPD is sync again
      84             :         Bool in_error;
      85             : 
      86             :         u64 mpd_fetch_time;
      87             :         GF_DASHInitialSelectionMode first_select_mode;
      88             : 
      89             :         /* MPD downloader*/
      90             :         GF_DASHFileIOSession mpd_dnload;
      91             :         /* MPD */
      92             :         GF_MPD *mpd;
      93             :         /* number of time the MPD has been reloaded and last update time*/
      94             :         u32 reload_count, last_update_time;
      95             :         /*signature of last MPD*/
      96             :         u8 lastMPDSignature[GF_SHA1_DIGEST_SIZE];
      97             :         /*mime type of media segments (m3u8)*/
      98             :         char *mimeTypeForM3U8Segments;
      99             : 
     100             :         /* active period in MPD */
     101             :         u32 active_period_index;
     102             :         u32 reinit_period_index;
     103             :         u32 request_period_switch;
     104             : 
     105             :         Bool next_period_checked;
     106             : 
     107             :         u64 start_time_in_active_period;
     108             : 
     109             :         Bool ignore_mpd_duration;
     110             :         u32 initial_time_shift_value;
     111             : 
     112             :         const char *query_string;
     113             : 
     114             :         /*list of groups in the active period*/
     115             :         GF_List *groups;
     116             : 
     117             :         /* one of the above state*/
     118             :         GF_DASH_STATE dash_state;
     119             : 
     120             :         Bool in_period_setup;
     121             :         Bool all_groups_done_notified;
     122             : 
     123             :         s64 utc_drift_estimate;
     124             :         s32 utc_shift;
     125             : 
     126             :         Double start_range_period;
     127             : 
     128             :         Double speed;
     129             :         Bool is_rt_speed;
     130             :         u32 probe_times_before_switch;
     131             :         Bool agressive_switching;
     132             :         u32 min_wait_ms_before_next_request;
     133             :         u32 min_wait_sys_clock;
     134             : 
     135             :         Bool force_mpd_update;
     136             :         u32 force_period_reload;
     137             : 
     138             :         u32 user_buffer_ms;
     139             : 
     140             :         u32 min_timeout_between_404, segment_lost_after_ms;
     141             : 
     142             :         Bool ignore_xlink;
     143             : 
     144             :         //0: not ROUTE - 1: ROUTE but clock not init - 2: ROUTE clock init
     145             :         u32 route_clock_state;
     146             :         //ROUTE AST shift in ms
     147             :         u32 route_ast_shift;
     148             :         u32 route_skip_segments_ms;
     149             :     Bool route_low_latency;
     150             : 
     151             :         Bool initial_period_tunein;
     152             : 
     153             :         Bool llhls_single_range;
     154             :         Bool m3u8_reload_master;
     155             :         u32 hls_reload_time;
     156             : 
     157             : 
     158             :         //in ms
     159             :         u32 time_in_tsb, prev_time_in_tsb;
     160             :         u32 tsb_exceeded;
     161             :         const u32 *dbg_grps_index;
     162             :         u32 nb_dbg_grps;
     163             :         Bool disable_speed_adaptation;
     164             : 
     165             :         Bool period_groups_setup;
     166             :         u32 tile_rate_decrease;
     167             :         GF_DASHTileAdaptationMode tile_adapt_mode;
     168             :         Bool disable_low_quality_tiles;
     169             : 
     170             :         GF_List *SRDs;
     171             : 
     172             :         GF_DASHAdaptationAlgorithm adaptation_algorithm;
     173             : 
     174             :         s32 (*rate_adaptation_algo)(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
     175             :                                                                                                   u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
     176             :                                                                                                   GF_MPD_Representation *rep, Bool go_up_bitrate);
     177             : 
     178             :         s32 (*rate_adaptation_download_monitor)(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur);
     179             : 
     180             :         //for custom algo, total rate of all active groups being downloaded
     181             :         u32 total_rate;
     182             : 
     183             :         gf_dash_rate_adaptation rate_adaptation_algo_custom;
     184             :         gf_dash_download_monitor rate_adaptation_download_monitor_custom;
     185             :         void *udta_custom_algo;
     186             : };
     187             : 
     188             : static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic);
     189             : 
     190             : 
     191             : enum
     192             : {
     193             :         SEG_FLAG_LOOP_DETECTED = 1,
     194             :         SEG_FLAG_DEP_FOLLOWING = 1<<1,
     195             :         SEG_FLAG_DISABLED = 1<<2,
     196             : };
     197             : 
     198             : typedef struct
     199             : {
     200             :         char *url;
     201             :         u64 start_range, end_range;
     202             :         /*representation index in adaptation_set->representations*/
     203             :         u32 representation_index;
     204             :         u32 duration;
     205             :         char *key_url;
     206             :         bin128 key_IV;
     207             :         u32 seg_number;
     208             :         const char *seg_name_start;
     209             :         GF_Fraction64 time;
     210             : 
     211             :         u32 flags;
     212             : } segment_cache_entry;
     213             : 
     214             : typedef enum
     215             : {
     216             :         /*set if group cannot be selected (wrong MPD)*/
     217             :         GF_DASH_GROUP_NOT_SELECTABLE = 0,
     218             :         GF_DASH_GROUP_NOT_SELECTED,
     219             :         GF_DASH_GROUP_SELECTED,
     220             : } GF_DASHGroupSelection;
     221             : 
     222             : /*this structure Group is the implementation of the adaptationSet element of the MPD.*/
     223             : struct __dash_group
     224             : {
     225             :         GF_DashClient *dash;
     226             : 
     227             :         /*pointer to adaptation set*/
     228             :         GF_MPD_AdaptationSet *adaptation_set;
     229             :         /*pointer to active period*/
     230             :         GF_MPD_Period *period;
     231             : 
     232             :         /*active representation index in adaptation_set->representations*/
     233             :         u32 active_rep_index;
     234             : 
     235             :         u32 prev_active_rep_index;
     236             : 
     237             :         Bool timeline_setup;
     238             :         Bool force_timeline_reeval;
     239             :         Bool first_hls_chunk;
     240             : 
     241             :         GF_DASHGroupSelection selection;
     242             : 
     243             :         /*may be mpd@time_shift_buffer_depth or rep@time_shift_buffer_depth*/
     244             :         u32 time_shift_buffer_depth;
     245             : 
     246             :         Bool bitstream_switching;
     247             :         GF_DASH_Group *depend_on_group;
     248             :         Bool done;
     249             :         //if set, will redownload the last segment partially downloaded
     250             :         Bool force_switch_bandwidth;
     251             :         Bool min_bandwidth_selected;
     252             : 
     253             :         u32 active_bitrate, max_bitrate, min_bitrate;
     254             :         u32 min_representation_bitrate;
     255             : 
     256             :         u32 nb_segments_in_rep;
     257             : 
     258             :         /* Segment duration as advertised in the MPD
     259             :            for the real duration of the segment being downloaded see current_downloaded_segment_duration */
     260             :         Double segment_duration;
     261             : 
     262             :         Double start_playback_range;
     263             : 
     264             :         Bool group_setup;
     265             : 
     266             :         Bool was_segment_base;
     267             :         /*local file playback, do not delete them*/
     268             :         Bool local_files;
     269             :         /*next segment to download for this group - negative number to take into account SN wrapping*/
     270             :         s32 download_segment_index;
     271             :         /*number of segments pruged since the start of the period*/
     272             :         u32 nb_segments_purged;
     273             : 
     274             :         u32 nb_retry_on_last_segment;
     275             :         s32 start_number_at_last_ast;
     276             :         u64 ast_at_init;
     277             :         u32 ast_offset;
     278             : 
     279             :         u32 max_cached_segments, nb_cached_segments;
     280             :         segment_cache_entry *cached;
     281             : 
     282             :         /*usually 0-0 (no range) but can be non-zero when playing local MPD/DASH sessions*/
     283             :         u64 bs_switching_init_segment_url_start_range, bs_switching_init_segment_url_end_range;
     284             :         char *bs_switching_init_segment_url;
     285             :         const char *bs_switching_init_segment_url_name_start;
     286             : 
     287             :         u32 nb_segments_done;
     288             :         u32 last_segment_time;
     289             :         u32 nb_segments_since_switch;
     290             : 
     291             :         //stats of last downloaded segment
     292             :         u32 total_size, bytes_per_sec, bytes_done, backup_Bps;
     293             : 
     294             : 
     295             :         Bool segment_must_be_streamed;
     296             :         Bool broken_timing;
     297             : 
     298             :         u32 maybe_end_of_stream;
     299             :         u32 cache_duration;
     300             :         u32 time_at_first_reload_required;
     301             :         u32 force_representation_idx_plus_one;
     302             : 
     303             :         Bool force_segment_switch;
     304             :         Bool loop_detected;
     305             : 
     306             :         u32 time_at_first_failure, time_at_last_request;
     307             :         Bool prev_segment_ok, segment_in_valid_range;
     308             :         //this is the number of 404
     309             :         u32 nb_consecutive_segments_lost;
     310             :         u64 retry_after_utc;
     311             :         /*set when switching segment, indicates the current downloaded segment duration*/
     312             :         u64 current_downloaded_segment_duration;
     313             : 
     314             :         char *service_mime;
     315             : 
     316             :         /* base representation index of this group plus one, or 0 if all representations in this group are independent*/
     317             :         u32 base_rep_index_plus_one;
     318             : 
     319             :         /* maximum representation index we want to download*/
     320             :         u32 max_complementary_rep_index;
     321             :         //start time and timescales of currently downloaded segment
     322             :         u64 current_start_time;
     323             :         u32 current_timescale;
     324             : 
     325             :         void *udta;
     326             : 
     327             :         Bool has_pending_enhancement;
     328             : 
     329             :         /*Codec statistics*/
     330             :         u32 avg_dec_time, max_dec_time, irap_avg_dec_time, irap_max_dec_time;
     331             :         Bool codec_reset;
     332             :         Bool decode_only_rap;
     333             :         /*display statistics*/
     334             :         u32 display_width, display_height;
     335             :         /*sets by user, indicates when the client will decide to play/resume after a buffering period (this is a static value for the entire session)*/
     336             :         u32 max_buffer_playout_ms;
     337             :         /*buffer status*/
     338             :         u32 buffer_min_ms, buffer_max_ms, buffer_occupancy_ms;
     339             :         u32 buffer_occupancy_at_last_seg;
     340             : 
     341             :         u32 m3u8_start_media_seq;
     342             :         u32 hls_next_seq_num;
     343             : 
     344             :         GF_List *groups_depending_on;
     345             :         u32 current_dep_idx;
     346             : 
     347             :         u32 target_new_rep;
     348             : 
     349             :         u32 srd_x, srd_y, srd_w, srd_h, srd_row_idx, srd_col_idx;
     350             :         struct _dash_srd_desc *srd_desc;
     351             : 
     352             :         /*current index of the base URL used*/
     353             :         u32 current_base_url_idx;
     354             : 
     355             :         u32 quality_degradation_hint;
     356             : 
     357             :         Bool rate_adaptation_postponed;
     358             :         Bool update_tile_qualities;
     359             : 
     360             :         //for dash custom, allows temporary disabling a group
     361             :         Bool disabled;
     362             : 
     363             :         /* current segment index in BBA and BOLA algorithm */
     364             :         u32 current_index;
     365             : 
     366             :         //in non-threaded mode, indicates that the demux for this group has nothing to do...
     367             :         Bool force_early_fetch;
     368             :         Bool is_low_latency;
     369             : 
     370             :         u32 hint_visible_width, hint_visible_height;
     371             : 
     372             :         //last chunk scheduled for download
     373             :         GF_MPD_SegmentURL *llhls_edge_chunk;
     374             :         Bool llhls_last_was_merged;
     375             :         s32 llhls_switch_request;
     376             :         u32 last_mpd_change_time;
     377             : };
     378             : 
     379             : //wait time before requesting again a M3U8 child playlist update when something goes wrong during the update: either same file or the expected next segment is not there
     380             : #define HLS_MIN_RELOAD_TIME(_dash) _dash->hls_reload_time = 50 + gf_sys_clock();
     381             : 
     382             : 
     383             : static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx);
     384             : 
     385             : struct _dash_srd_desc
     386             : {
     387             :         u32 srd_nb_rows, srd_nb_cols;
     388             :         u32 id, width, height, srd_fw, srd_fh;
     389             : };
     390             : 
     391             : void drm_decrypt(unsigned char * data, unsigned long dataSize, const char * decryptMethod, const char * keyfileURL, const unsigned char * keyIV);
     392             : 
     393             : 
     394             : 
     395             : static const char *gf_dash_get_mime_type(GF_MPD_SubRepresentation *subrep, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set)
     396             : {
     397             :         if (subrep && subrep->mime_type) return subrep->mime_type;
     398         459 :         if (rep && rep->mime_type) return rep->mime_type;
     399           4 :         if (set && set->mime_type) return set->mime_type;
     400             :         return NULL;
     401             : }
     402             : 
     403             : 
     404         218 : static u64 dash_get_fetch_time(GF_DashClient *dash)
     405             : {
     406             :         u64 utc = 0;
     407             : 
     408         218 :         if (dash->mpd_dnload && dash->dash_io->get_utc_start_time)
     409         133 :                 utc = dash->dash_io->get_utc_start_time(dash->dash_io, dash->mpd_dnload);
     410         133 :         if (!utc)
     411          92 :                 utc = gf_net_get_utc();
     412         218 :         return utc;
     413             : }
     414             : 
     415             : 
     416         792 : static u32 gf_dash_group_count_rep_needed(GF_DASH_Group *group)
     417             : {
     418             :         u32 count, nb_rep_need, next_rep_index_plus_one;
     419             :         GF_MPD_Representation *rep;
     420         792 :         count  = gf_list_count(group->adaptation_set->representations);
     421             :         nb_rep_need = 1;
     422         792 :         if (!group->base_rep_index_plus_one || (group->base_rep_index_plus_one == group->max_complementary_rep_index+1))
     423             :                 return nb_rep_need; // we need to download only one representation
     424           0 :         rep = gf_list_get(group->adaptation_set->representations, group->base_rep_index_plus_one-1);
     425           0 :         next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
     426           0 :         while ((nb_rep_need < count) && rep->playback.enhancement_rep_index_plus_one) {
     427           0 :                 nb_rep_need++;
     428           0 :                 if (next_rep_index_plus_one == group->max_complementary_rep_index+1)
     429             :                         break;
     430           0 :                 rep = gf_list_get(group->adaptation_set->representations, next_rep_index_plus_one-1);
     431           0 :                 next_rep_index_plus_one = rep->playback.enhancement_rep_index_plus_one;
     432             :         }
     433             : 
     434             :         assert(nb_rep_need <= count);
     435             : 
     436             :         return nb_rep_need;
     437             : }
     438             : 
     439             : static
     440         113 : u32 gf_dash_check_mpd_root_type(const char *local_url)
     441             : {
     442         113 :         if (local_url) {
     443         113 :                 char *rtype = gf_xml_get_root_type(local_url, NULL);
     444         113 :                 if (rtype) {
     445             :                         u32 handled = 0;
     446         113 :                         if (!strcmp(rtype, "MPD")) {
     447             :                                 handled = 1;
     448             :                         }
     449           2 :                         else if (!strcmp(rtype, "SmoothStreamingMedia")) {
     450             :                                 handled = 2;
     451             :                         }
     452         113 :                         gf_free(rtype);
     453         113 :                         return handled;
     454             :                 }
     455             :         }
     456             :         return GF_FALSE;
     457             : }
     458             : 
     459           0 : static Bool gf_dash_get_date(GF_DashClient *dash, char *scheme_id, char *url, u64 *utc)
     460             : {
     461             :         GF_DASHFileIOSession session;
     462             :         GF_Err e;
     463             :         u8 *data;
     464             :         u32 len;
     465             :         Bool res = GF_TRUE;
     466             :         const char *cache_name;
     467           0 :         *utc = 0;
     468             : 
     469             :         //unsupported schemes
     470           0 :         if (!strcmp(scheme_id, "urn:mpeg:dash:utc:ntp:2014")) return GF_FALSE;
     471           0 :         if (!strcmp(scheme_id, "urn:mpeg:dash:utc:sntp:2014")) return GF_FALSE;
     472             : 
     473           0 :         if (!dash->dash_io) return GF_FALSE;
     474             : 
     475           0 :         session = dash->dash_io->create(dash->dash_io, GF_FALSE, url, -2);
     476           0 :         if (!session) return GF_FALSE;
     477           0 :         e = dash->dash_io->run(dash->dash_io, session);
     478           0 :         if (e) {
     479           0 :                 dash->dash_io->del(dash->dash_io, session);
     480             :                 return GF_FALSE;
     481             :         }
     482           0 :         cache_name = dash->dash_io->get_cache_name(dash->dash_io, session);
     483           0 :         gf_blob_get(cache_name, &data, &len, NULL);
     484             : 
     485           0 :         if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-head:2014")) {
     486           0 :                 const char *hdr = dash->dash_io->get_header_value(dash->dash_io, session, "Date");
     487           0 :                 if (hdr)
     488           0 :                         *utc = gf_net_parse_date(hdr);
     489             :                 else
     490             :                         res = GF_FALSE;
     491             :         }
     492           0 :         else if (!data) {
     493             :                 res = GF_FALSE;
     494             :         } else {
     495           0 :                 if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-xsdate:2014")) {
     496           0 :                         *utc = gf_mpd_parse_date(data);
     497             :                 }
     498           0 :                 else if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-iso:2014")) {
     499           0 :                         *utc = gf_net_parse_date(data);
     500             :                 }
     501           0 :                 else if (!strcmp(scheme_id, "urn:mpeg:dash:utc:http-ntp:2014")) {
     502             :                         u64 ntp_ts;
     503           0 :                         if (sscanf((char *) data, LLU, &ntp_ts) == 1) {
     504             :                                 //ntp value not counted since 1900, assume format is seconds till 1 jan 1970
     505           0 :                                 if (ntp_ts<=GF_NTP_SEC_1900_TO_1970) {
     506           0 :                                         *utc = ntp_ts*1000;
     507             :                                 } else {
     508           0 :                                         *utc = gf_net_ntp_to_utc(ntp_ts);
     509             :                                 }
     510             :                         } else {
     511             :                                 res = GF_FALSE;
     512             :                         }
     513             :                 }
     514             :         }
     515           0 :     gf_blob_release(cache_name);
     516             :     
     517           0 :         dash->dash_io->del(dash->dash_io, session);
     518             :         return res;
     519             : }
     520             : 
     521             : GF_Err gf_dash_download_resource(GF_DashClient *dash, GF_DASHFileIOSession *sess, const char *url, u64 start_range, u64 end_range, u32 persistent_mode, GF_DASH_Group *group);
     522             : 
     523         546 : static void gf_dash_group_timeline_setup(GF_MPD *mpd, GF_DASH_Group *group, u64 fetch_time)
     524             : {
     525             :         GF_MPD_SegmentTimeline *timeline = NULL;
     526             :         GF_MPD_Representation *rep = NULL;
     527             :         GF_MPD_Descriptor *utc_timing = NULL;
     528             :         const char *val;
     529             :         u32 shift, timescale;
     530             :         u64 current_time, current_time_no_timeshift, availabilityStartTime;
     531             :         u32 ast_diff, start_number;
     532             :         Double ast_offset = 0;
     533             : 
     534         546 :         if (mpd->type==GF_MPD_TYPE_STATIC) {
     535         443 :                 if (group->dash->route_clock_state)
     536             :                         goto setup_route;
     537             :                 return;
     538             :         }
     539             : 
     540             :         //always init clock even if active period is a remote one
     541             : #if 0
     542             :         if (group->period->origin_base_url && (group->period->type != GF_MPD_TYPE_DYNAMIC))
     543             :                 return;
     544             : #endif
     545             : 
     546             :         /*M3U8 does not use NTP sync, we solve edge while loading subplaylist */
     547         103 :         if (group->dash->is_m3u8) {
     548             :                 return;
     549             :         }
     550             : 
     551          81 :         if (group->dash->is_smooth) {
     552             :                 u32 seg_idx = 0;
     553             :                 u64 timeshift = 0;
     554           0 :                 if (group->dash->initial_time_shift_value && ((s32) mpd->time_shift_buffer_depth>=0)) {
     555           0 :                         if (group->dash->initial_time_shift_value<=100) {
     556           0 :                                 timeshift = mpd->time_shift_buffer_depth;
     557           0 :                                 timeshift *= group->dash->initial_time_shift_value;
     558           0 :                                 timeshift /= 100;
     559             :                         } else {
     560           0 :                                 timeshift = (u32) group->dash->initial_time_shift_value;
     561           0 :                                 if (timeshift > mpd->time_shift_buffer_depth) timeshift = mpd->time_shift_buffer_depth;
     562             :                         }
     563           0 :                         timeshift = mpd->time_shift_buffer_depth - timeshift;
     564             :                 }
     565             : 
     566           0 :                 if (!timeshift && group->adaptation_set->smooth_max_chunks) {
     567             :                         seg_idx = group->adaptation_set->smooth_max_chunks;
     568             :                 } else {
     569             :                         u32 i, count;
     570             :                         u64 start = 0;
     571             :                         u64 cumulated_dur = 0;
     572             :                         GF_MPD_SegmentTimeline *stl;
     573           0 :                         if (!group->adaptation_set->segment_template || !group->adaptation_set->segment_template->segment_timeline)
     574             :                                 return;
     575             : 
     576           0 :                         timeshift *= group->adaptation_set->segment_template->timescale;
     577           0 :                         timeshift /= 1000;
     578             :                         stl = group->adaptation_set->segment_template->segment_timeline;
     579           0 :                         count = gf_list_count(stl->entries);
     580           0 :                         for (i=0; i<count; i++) {
     581             :                                 u64 dur;
     582           0 :                                 GF_MPD_SegmentTimelineEntry *e = gf_list_get(stl->entries, i);
     583           0 :                                 if (!e->duration)
     584           0 :                                         continue;
     585             :                                 if (e->start_time)
     586             :                                         start = e->start_time;
     587             : 
     588           0 :                                 dur = e->duration * (e->repeat_count+1);
     589           0 :                                 if (cumulated_dur + dur >= timeshift) {
     590           0 :                                         u32 nb_segs = (u32) ( (timeshift - cumulated_dur) / e->duration );
     591           0 :                                         seg_idx += nb_segs;
     592           0 :                                         break;
     593             :                                 }
     594             :                                 cumulated_dur += dur;
     595             :                                 start += dur;
     596           0 :                                 seg_idx += e->repeat_count+1;
     597           0 :                                 if (group->adaptation_set->smooth_max_chunks && (seg_idx>=group->adaptation_set->smooth_max_chunks)) {
     598             :                                         seg_idx = group->adaptation_set->smooth_max_chunks;
     599             :                                         break;
     600             :                                 }
     601             :                         }
     602             :                 }
     603           0 :                 group->download_segment_index = (seg_idx>1) ? seg_idx - 1 : 0;
     604           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Smooth group tuned in at segment %d\n", group->download_segment_index));
     605             :                 return;
     606             :         }
     607             : 
     608          81 :         if (group->broken_timing )
     609             :                 return;
     610             : 
     611             : 
     612             :         /*if no AST, do not use NTP sync */
     613          81 :         if (! group->dash->mpd->availabilityStartTime) {
     614           0 :                 group->broken_timing = GF_TRUE;
     615           0 :                 return;
     616             :         }
     617             : 
     618          81 :         if (!fetch_time) {
     619             :                 //when we initialize the timeline without an explicit fetch time, use our local clock - this allows for better precision
     620             :                 //when trying to locate the live edge
     621          81 :                 fetch_time = gf_net_get_utc();
     622             :         }
     623             :         //if ROUTE and clock not setup, do it
     624          87 : setup_route:
     625          84 :         val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route");
     626          84 :         if (val && !group->dash->utc_drift_estimate) {
     627             :                 u32 i;
     628             :                 GF_MPD_Period *dyn_period=NULL;
     629             :                 u32 found = 0;
     630             :                 u64 timeline_offset_ms=0;
     631          40 :                 if (!group->dash->route_clock_state) {
     632           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ROUTE DASH service ID %s\n", val));
     633           0 :                         group->dash->route_clock_state = 1;
     634             :                 }
     635             : 
     636           0 :                 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
     637          40 :                         dyn_period = gf_list_get(group->dash->mpd->periods, i);
     638          40 :                         if (!dyn_period->xlink_href && !dyn_period->origin_base_url) break;
     639           0 :                         if (dyn_period->xlink_href && !dyn_period->origin_base_url && gf_list_count(dyn_period->adaptation_sets) ) break;
     640             :                         dyn_period = NULL;
     641             :                 }
     642          40 :                 if (!dyn_period) {
     643           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE with no dynamic period, cannot init clock yet\n"));
     644             :                         return;
     645             :                 }
     646             : 
     647             :                 //for m3u8 we force refreshing the root manifest, because the download session might be tuned on a child playlist
     648             :                 //which will not have the x-route-first-seg set
     649          40 :                 if (group->dash->is_m3u8) {
     650           0 :                         gf_dash_download_resource(group->dash, &(group->dash->mpd_dnload), group->dash->base_url, 0, 0, 1, NULL);
     651             :                 }
     652          40 :                 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-first-seg");
     653          40 :                 if (!val) {
     654          35 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Waiting for ROUTE clock ...\n"));
     655             :                         return;
     656             :                 }
     657             : 
     658           0 :                 for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) {
     659             :                         u64 sr, seg_dur;
     660             :                         u32 j, len, nb_space=0;
     661             :                         GF_MPD_AdaptationSet *set;
     662           5 :                         char *sep, *start, *end, *seg_url = NULL;
     663             : 
     664           5 :                         set = gf_list_get(dyn_period->adaptation_sets, i);
     665           5 :                         for (j=0; j<gf_list_count(set->representations); j++) {
     666           5 :                                 u64 dur = dyn_period->duration;
     667           5 :                                 rep = gf_list_get(set->representations, j);
     668             : 
     669           5 :                                 dyn_period->duration = 0;
     670             : 
     671           5 :                                 if (group->dash->is_m3u8) {
     672             :                                         u32 k, count;
     673           0 :                                         if (found) break;
     674           0 :                                         if (!rep->segment_list)
     675           0 :                                                 continue;
     676           0 :                                         count = gf_list_count(rep->segment_list->segment_URLs);
     677           0 :                                         for (k=0; k<count; k++) {
     678           0 :                                                 GF_MPD_SegmentURL *surl = gf_list_get(rep->segment_list->segment_URLs, k);
     679           0 :                                                 if (surl->media && strstr(surl->media, val)) {
     680           0 :                                                         found = k+1;
     681           0 :                                                         break;
     682             :                                                 }
     683             :                                         }
     684           0 :                                         continue;
     685             :                                 }
     686             : 
     687           5 :                                 gf_mpd_resolve_url(group->dash->mpd, rep, set, dyn_period, "./", 0, GF_MPD_RESOLVE_URL_MEDIA_NOSTART, 9876, 0, &seg_url, &sr, &sr, &seg_dur, NULL, NULL, NULL, NULL);
     688             : 
     689           5 :                                 dyn_period->duration = dur;
     690             : 
     691           5 :                                 sep = seg_url ? strstr(seg_url, "987") : NULL;
     692           5 :                                 if (!sep) {
     693           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Failed to resolve template for segment #9876 on rep #%d\n", j+1));
     694           0 :                                         if (seg_url) gf_free(seg_url);
     695           0 :                                         continue;
     696             :                                 }
     697             :                                 start = sep;
     698           5 :                                 end = sep+4;
     699           5 :                                 while (start>seg_url && (*(start-1)=='0')) { start--; nb_space++;}
     700           5 :                                 start[0]=0;
     701           5 :                                 len = (u32) strlen(seg_url)-2;
     702           5 :                                 if (!strncmp(val, seg_url+2, len)) {
     703           5 :                                         u32 number=0;
     704             :                                         char szTemplate[100];
     705             : 
     706           5 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Resolve ROUTE clock on bootstrap segment URL %s template %s\n", val, seg_url+2));
     707             : 
     708           5 :                                         strcpy(szTemplate, seg_url+2);
     709             :                                         strcat(szTemplate, "%");
     710           5 :                                         if (nb_space) {
     711             :                                                 char szFmt[20];
     712           1 :                                                 sprintf(szFmt, "0%d", nb_space+4);
     713             :                                                 strcat(szTemplate, szFmt);
     714             :                                         }
     715             :                                         strcat(szTemplate, "d");
     716             :                                         strcat(szTemplate, end);
     717           5 :                                         if (sscanf(val, szTemplate, &number) == 1) {
     718             :                                                 u32 startNum = 1;
     719           5 :                                                 if (dyn_period->segment_template) startNum = dyn_period->segment_template->start_number;
     720           5 :                                                 if (set->segment_template) startNum = set->segment_template->start_number;
     721           5 :                                                 if (rep->segment_template) startNum = rep->segment_template->start_number;
     722           5 :                                                 if (number>=startNum) {
     723             :                             //clock is init which means the segment is available, so the timeline offset must match the AST of the segment (includes seg dur)
     724             : 
     725           5 :                             const char *ll_val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-ll");
     726           5 :                             if (ll_val && !strcmp(ll_val, "yes")) {
     727             :                                 //low latency case, we are currently receiving the segment
     728           0 :                                 group->dash->route_low_latency = GF_TRUE;
     729           0 :                                 number--;
     730             :                             }
     731             :                             
     732           5 :                             timeline_offset_ms = seg_dur * ( 1 + number - startNum);
     733             :                                                 }
     734             :                                                 found = 1;
     735             :                                         }
     736             :                                 } else {
     737           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] ROUTE bootstrap segment URL %s does not match template %s for rep #%d\n", val, seg_url+2, j+1));
     738             :                                 }
     739           5 :                                 gf_free(seg_url);
     740           5 :                                 if (found) break;
     741             :                         }
     742           5 :                         if (found) break;
     743             :                 }
     744           5 :                 if (found) {
     745           5 :                         if (group->dash->is_m3u8) {
     746             :                                 //purge segments (we assume we roughly are in the same state on all child playlists, we could keep one for safety)
     747           0 :                                 for (i=0; i<gf_list_count(dyn_period->adaptation_sets); i++) {
     748             :                                         u32 j;
     749           0 :                                         GF_MPD_AdaptationSet *set = gf_list_get(dyn_period->adaptation_sets, i);
     750           0 :                                         for (j=0; j<gf_list_count(set->representations); j++) {
     751             :                                                 u32 to_rem;
     752           0 :                                                 rep = gf_list_get(set->representations, j);
     753           0 :                                                 if (!rep->segment_list) continue;
     754           0 :                                                 to_rem = found-1;
     755           0 :                                                 while (to_rem) {
     756           0 :                                                         GF_MPD_SegmentURL *surl = gf_list_pop_front(rep->segment_list->segment_URLs);
     757           0 :                                                         gf_mpd_segment_url_free(surl);
     758           0 :                                                         to_rem--;
     759             :                                                 }
     760             :                                         }
     761             :                                 }
     762             :                         } else {
     763             :                                 //adjust so that nb_seg = current_time/segdur = (fetch-ast)/seg_dur;
     764             :                                 // = (fetch- ( mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate) / segdur;
     765             :                                 //hence nb_seg*seg_dur = fetch - mpd->availabilityStartTime - group->dash->utc_shift - group->dash->utc_drift_estimate
     766             :                                 //so group->dash->utc_drift_estimate = fetch - (mpd->availabilityStartTime + nb_seg*seg_dur)
     767             : 
     768             : 
     769           5 :                                 u64 utc = mpd->availabilityStartTime + dyn_period->start + timeline_offset_ms;
     770           5 :                                 group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
     771           5 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff of ROUTE broadcast "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU" - bootstraping on segment %s\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime, val));
     772             :                         }
     773           5 :                         group->dash->route_clock_state = 2;
     774             :                 } else {
     775           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to setup ROUTE clock from segment template with bootstrap URL %s, using NTP\n", val));
     776           0 :                         group->dash->route_clock_state = 3;
     777             :                 }
     778           5 :                 if (mpd->type==GF_MPD_TYPE_STATIC) {
     779           1 :                         if (found)
     780           1 :                                 group->dash->route_skip_segments_ms = (u32) timeline_offset_ms;
     781           1 :                         group->timeline_setup = GF_TRUE;
     782           1 :                         return;
     783             :                 }
     784             :         }
     785          44 :         else if (val) {
     786           1 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] ROUTE clock already setup - UTC diff of ROUTE broadcast "LLD" ms\n", group->dash->utc_drift_estimate));
     787             :         } else {
     788          43 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] No ROUTE entity on HTPP request\n"));
     789             :         }
     790             : 
     791          48 :         if (!group->dash->route_clock_state || (group->dash->route_clock_state>2)) {
     792          43 :                 GF_MPD_ProducerReferenceTime *pref = gf_list_get(group->adaptation_set->producer_reference_time, 0);
     793          43 :                 if (pref)
     794           0 :                         utc_timing = pref->utc_timing;
     795           0 :                 if (!utc_timing)
     796          43 :                         utc_timing = gf_list_get(group->dash->mpd->utc_timings, 0);
     797             :         }
     798             : 
     799          43 :         if (utc_timing && utc_timing->scheme_id_uri) {
     800             :                 Bool res = GF_FALSE;
     801           0 :                 u64 utc=0;
     802             :                 s64 drift_estimate;
     803             : 
     804           0 :                 if (!strcmp(utc_timing->scheme_id_uri, "urn:mpeg:dash:utc:direct:2014")) {
     805           0 :                         utc = gf_net_parse_date(utc_timing->value);
     806             :                         res = GF_TRUE;
     807             :                 } else {
     808           0 :                         char *time_refs = utc_timing->value;
     809             :                         utc = 0;
     810           0 :                         while (time_refs) {
     811           0 :                                 char *sep = strchr(time_refs, ' ');
     812           0 :                                 if (sep) sep[0] = 0;
     813             : 
     814           0 :                                 res = gf_dash_get_date(group->dash, utc_timing->scheme_id_uri, time_refs, &utc);
     815             : 
     816             :                                 time_refs = NULL;
     817           0 :                                 if (sep) {
     818           0 :                                         sep[0] = ' ';
     819           0 :                                         time_refs = sep+1;
     820             :                                 }
     821           0 :                                 if (res) break;
     822             :                         }
     823             :                 }
     824           0 :                 if (res) {
     825           0 :                         drift_estimate = ((s64) fetch_time - (s64) utc);
     826           0 :                         group->dash->utc_drift_estimate = drift_estimate;
     827           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server (%s): "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", utc_timing->value, group->dash->utc_drift_estimate, fetch_time, utc,
     828             :                                 group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
     829             :                 } else {
     830             :                         utc_timing = NULL;
     831             :                 }
     832             :         }
     833             : 
     834          48 :         if ((!group->dash->route_clock_state || (group->dash->route_clock_state>2))
     835          43 :                 && !group->dash->ntp_forced
     836          43 :                 && group->dash->estimate_utc_drift
     837          43 :                 && !group->dash->utc_drift_estimate
     838          37 :                 && group->dash->mpd_dnload
     839           8 :                 && group->dash->dash_io->get_header_value
     840           8 :                 && !utc_timing
     841             :         ) {
     842           8 :                 val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Server-UTC");
     843           8 :                 if (val) {
     844             :                         u64 utc;
     845           2 :                         sscanf(val, LLU, &utc);
     846           2 :                         group->dash->utc_drift_estimate = ((s64) fetch_time - (s64) utc);
     847           2 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
     848             :                 } else {
     849             :                         s64 drift_estimate = 0;
     850             :                         u64 utc = 0;
     851           6 :                         val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "Date");
     852           6 :                         if (val)
     853           6 :                                 utc = gf_net_parse_date(val);
     854           6 :                         if (utc)
     855           6 :                                 drift_estimate = ((s64) fetch_time - (s64) utc);
     856             : 
     857             :                         //HTTP date is in second - if the clock diff is less than 1 sec, we cannot infer anything
     858           6 :                         if (ABS(drift_estimate) > 1000) {
     859           3 :                                 group->dash->utc_drift_estimate = 1 + drift_estimate;
     860           3 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Estimated UTC diff between client and server "LLD" ms (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", group->dash->utc_drift_estimate, fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
     861             :                         } else {
     862           3 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] No UTC diff between client and server (UTC fetch "LLU" - server UTC "LLU" - MPD AST "LLU" - MPD PublishTime "LLU"\n", fetch_time, utc, group->dash->mpd->availabilityStartTime, group->dash->mpd->publishTime));
     863           3 :                                 group->dash->utc_drift_estimate = 1;
     864             :                         }
     865             :                 }
     866             :         }
     867             : 
     868             :         availabilityStartTime = 0;
     869          48 :         if ((s64) mpd->availabilityStartTime + group->dash->utc_shift > (s64) - group->dash->utc_drift_estimate) {
     870          48 :                 availabilityStartTime = mpd->availabilityStartTime + group->dash->utc_shift + group->dash->utc_drift_estimate;
     871             :         }
     872             : 
     873             : 
     874             : #ifdef FORCE_DESYNC
     875             :         availabilityStartTime -= FORCE_DESYNC;
     876             : #endif
     877             : 
     878          48 :         ast_diff = (u32) (availabilityStartTime - group->dash->mpd->availabilityStartTime);
     879             :         current_time = fetch_time;
     880             : 
     881          48 :         if (current_time < availabilityStartTime) {
     882             :                 //if more than 1 sec consider we have a pb
     883           0 :                 if (availabilityStartTime - current_time >= 1000) {
     884             :                         Bool broken_timing = GF_TRUE;
     885             : #ifndef _WIN32_WCE
     886             :                         time_t gtime1, gtime2;
     887             :                         struct tm *t1, *t2;
     888           0 :                         gtime1 = current_time / 1000;
     889           0 :                         t1 = gf_gmtime(&gtime1);
     890           0 :                         gtime2 = availabilityStartTime / 1000;
     891           0 :                         t2 = gf_gmtime(&gtime2);
     892           0 :                         if (t1 == t2) {
     893           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Slight drift in UTC clock at time %d-%02d-%02dT%02d:%02d:%02dZ: diff AST - now %d ms\n", 1900+t1->tm_year, t1->tm_mon+1, t1->tm_mday, t1->tm_hour, t1->tm_min, t1->tm_sec, (s32) (availabilityStartTime - current_time) ));
     894             :                                 current_time = 0;
     895             :                                 broken_timing = GF_FALSE;
     896             :                         }
     897           0 :                         else if (t1 && t2) {
     898           0 :                                 t1->tm_year = t2->tm_year;
     899           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in UTC clock: current time %d-%02d-%02dT%02d:%02d:%02dZ is less than AST %d-%02d-%02dT%02d:%02d:%02dZ - diff AST-now %d ms\n",
     900             :                                                                    1900+t1->tm_year, t1->tm_mon+1, t1->tm_mday, t1->tm_hour, t1->tm_min, t1->tm_sec,
     901             :                                                                    1900+t2->tm_year, t2->tm_mon+1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec,
     902             :                                                                    (u32) (availabilityStartTime - current_time)
     903             :                                                                   ));
     904             :                         } else {
     905           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error in UTC clock: could not retrieve time!\n"));
     906             :                         }
     907             : 
     908             : #endif
     909             :                         if (broken_timing) {
     910           0 :                                 if (group->dash->utc_shift + group->dash->utc_drift_estimate > 0) {
     911             :                                         availabilityStartTime = current_time;
     912             :                                 } else {
     913           0 :                                         group->broken_timing = GF_TRUE;
     914           0 :                                         return;
     915             :                                 }
     916             :                         }
     917             :                 } else {
     918             :                         availabilityStartTime = current_time;
     919             :                         current_time = 0;
     920             :                 }
     921             :         }
     922          48 :         else current_time -= availabilityStartTime;
     923             : 
     924          48 :         if (gf_list_count(group->dash->mpd->periods)) {
     925             :                 u64 seg_start_ms = current_time;
     926          48 :                 u64 seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
     927             :                 u32 i;
     928             :                 u64 start = 0;
     929          53 :                 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
     930          53 :                         GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
     931          53 :                         if (ap->start) start = ap->start;
     932             : 
     933          53 :                         if (group->dash->initial_period_tunein
     934          30 :                                 && (seg_start_ms>=ap->start)
     935          27 :                                 && (!ap->duration || (seg_end_ms<=start + ap->duration))
     936             :                         ) {
     937          24 :                                 if (i != group->dash->active_period_index) {
     938           0 :                                         group->dash->reinit_period_index = 1+i;
     939           0 :                                         group->dash->start_range_period = (Double) seg_start_ms;
     940           0 :                                         group->dash->start_range_period -= ap->start;
     941           0 :                                         group->dash->start_range_period /= 1000;
     942           0 :                                         return;
     943             :                                 }
     944             :                         }
     945             : 
     946          53 :                         if (!ap->duration) break;
     947           5 :                         start += ap->duration;
     948             :                 }
     949             :         }
     950             : 
     951             :         //compute current time in period
     952          48 :         if (current_time < group->period->start) {
     953           4 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Period will start in %d ms\n", group->period->start - current_time));
     954             :                 current_time = 0;
     955             :         } else {
     956          44 :                 if (group->dash->initial_period_tunein || group->force_timeline_reeval) {
     957          44 :                         current_time -= group->period->start;
     958             :                 } else {
     959             :                         //initial period was setup, consider we are moving to a new period, so time in this period is 0
     960             :                         current_time = 0;
     961           0 :                         if (group->start_playback_range) current_time = (u64) (group->start_playback_range*1000);
     962             :                 }
     963             :         }
     964             : 
     965             :         current_time_no_timeshift = current_time;
     966          48 :         if ( ((s32) mpd->time_shift_buffer_depth>=0)) {
     967             : 
     968          19 :                 if (group->dash->initial_time_shift_value) {
     969           0 :                         if (group->dash->initial_time_shift_value<=100) {
     970             :                                 shift = mpd->time_shift_buffer_depth;
     971           0 :                                 shift *= group->dash->initial_time_shift_value;
     972           0 :                                 shift /= 100;
     973             :                         } else {
     974             :                                 shift = (u32) group->dash->initial_time_shift_value;
     975           0 :                                 if (shift > mpd->time_shift_buffer_depth) shift = mpd->time_shift_buffer_depth;
     976             :                         }
     977             : 
     978           0 :                         if (current_time < shift) current_time = 0;
     979           0 :                         else current_time -= shift;
     980             :                 }
     981             :         }
     982          48 :         group->dash->time_in_tsb = group->dash->prev_time_in_tsb = 0;
     983             : 
     984             :         timeline = NULL;
     985             :         timescale=1;
     986             :         start_number=0;
     987          48 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
     988             : 
     989          48 :         if (group->period->segment_list) {
     990           0 :                 if (group->period->segment_list->segment_timeline) timeline = group->period->segment_list->segment_timeline;
     991           0 :                 if (group->period->segment_list->timescale) timescale = group->period->segment_list->timescale;
     992           0 :                 if (group->period->segment_list->start_number) start_number = group->period->segment_list->start_number;
     993           0 :                 if (group->period->segment_list->availability_time_offset) ast_offset = group->period->segment_list->availability_time_offset;
     994             :         }
     995          48 :         if (group->adaptation_set->segment_list) {
     996           0 :                 if (group->adaptation_set->segment_list->segment_timeline) timeline = group->adaptation_set->segment_list->segment_timeline;
     997           0 :                 if (group->adaptation_set->segment_list->timescale) timescale = group->adaptation_set->segment_list->timescale;
     998           0 :                 if (group->adaptation_set->segment_list->start_number) start_number = group->adaptation_set->segment_list->start_number;
     999           0 :                 if (group->adaptation_set->segment_list->availability_time_offset) ast_offset = group->adaptation_set->segment_list->availability_time_offset;
    1000             :         }
    1001          48 :         if (rep->segment_list) {
    1002           0 :                 if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
    1003           0 :                 if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
    1004           0 :                 if (rep->segment_list->start_number) start_number = rep->segment_list->start_number;
    1005           0 :                 if (rep->segment_list->availability_time_offset) ast_offset = rep->segment_list->availability_time_offset;
    1006             :         }
    1007             : 
    1008          48 :         if (group->period->segment_template) {
    1009           0 :                 if (group->period->segment_template->segment_timeline) timeline = group->period->segment_template->segment_timeline;
    1010           0 :                 if (group->period->segment_template->timescale) timescale = group->period->segment_template->timescale;
    1011           0 :                 if (group->period->segment_template->start_number) start_number = group->period->segment_template->start_number;
    1012           0 :                 if (group->period->segment_template->availability_time_offset) ast_offset = group->period->segment_template->availability_time_offset;
    1013             :         }
    1014          48 :         if (group->adaptation_set->segment_template) {
    1015          38 :                 if (group->adaptation_set->segment_template->segment_timeline) timeline = group->adaptation_set->segment_template->segment_timeline;
    1016          38 :                 if (group->adaptation_set->segment_template->timescale) timescale = group->adaptation_set->segment_template->timescale;
    1017          38 :                 if (group->adaptation_set->segment_template->start_number) start_number = group->adaptation_set->segment_template->start_number;
    1018          38 :                 if (group->adaptation_set->segment_template->availability_time_offset) ast_offset = group->adaptation_set->segment_template->availability_time_offset;
    1019             :         }
    1020          48 :         if (rep->segment_template) {
    1021          10 :                 if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
    1022          10 :                 if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
    1023          10 :                 if (rep->segment_template->start_number) start_number = rep->segment_template->start_number;
    1024          10 :                 if (rep->segment_template->availability_time_offset) ast_offset = rep->segment_template->availability_time_offset;
    1025             :         }
    1026             : 
    1027          48 :         group->is_low_latency = GF_FALSE;
    1028          48 :         if (group->dash->low_latency_mode==GF_DASH_LL_DISABLE) {
    1029             :                 ast_offset = 0;
    1030          48 :         } else if (ast_offset>0) {
    1031          13 :                 group->is_low_latency = GF_TRUE;
    1032             :         }
    1033          48 :         if (timeline) {
    1034             :                 u64 start_segtime = 0;
    1035             :                 u64 segtime = 0;
    1036             :                 u64 current_time_rescale;
    1037             :                 u64 timeline_duration = 0;
    1038             :                 u32 count;
    1039             :                 u64 last_s_dur=0;
    1040             :                 u32 i, seg_idx = 0;
    1041             : 
    1042             :                 current_time_rescale = current_time;
    1043           4 :                 current_time_rescale *= timescale;
    1044           4 :                 current_time_rescale /= 1000;
    1045             : 
    1046           4 :                 count = gf_list_count(timeline->entries);
    1047         160 :                 for (i=0; i<count; i++) {
    1048         152 :                         GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
    1049             : 
    1050         152 :                         if (!i && (current_time_rescale + ent->duration < ent->start_time)) {
    1051           0 :                                 current_time_rescale = current_time_no_timeshift * timescale / 1000;
    1052             :                         }
    1053         152 :                         timeline_duration += (1+ent->repeat_count)*ent->duration;
    1054             : 
    1055         152 :                         if (i+1 == count) timeline_duration -= ent->duration;
    1056         152 :                         last_s_dur=ent->duration;
    1057             :                 }
    1058             : 
    1059             : 
    1060           4 :                 if (!group->dash->mpd->minimum_update_period) {
    1061           1 :                         last_s_dur *= 1000;
    1062           1 :                         last_s_dur /= timescale;
    1063           1 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] dynamic MPD but no update period specified and SegmentTimeline used - will use segment duration %d ms as default update rate\n", last_s_dur));
    1064             : 
    1065           1 :                         group->dash->mpd->minimum_update_period = (u32) last_s_dur;
    1066             :                 }
    1067             : 
    1068         152 :                 for (i=0; i<count; i++) {
    1069             :                         u32 repeat;
    1070         152 :                         GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
    1071         152 :                         if (!segtime) {
    1072           4 :                                 start_segtime = segtime = ent->start_time;
    1073             : 
    1074             :                                 //if current time is before the start of the previous segment, consider our timing is broken
    1075           4 :                                 if (current_time_rescale + ent->duration < segtime) {
    1076           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] current time "LLU" is before start time "LLU" of first segment in timeline (timescale %d) by %g sec - using first segment as starting point\n", current_time_rescale, segtime, timescale, (segtime-current_time_rescale)*1.0/timescale));
    1077           0 :                                         group->download_segment_index = seg_idx;
    1078           0 :                                         group->nb_segments_in_rep = count;
    1079           0 :                                         group->start_playback_range = (segtime)*1.0/timescale;
    1080           0 :                                         group->ast_at_init = availabilityStartTime;
    1081           0 :                                         group->ast_offset = (u32) (ast_offset*1000);
    1082           0 :                                         group->broken_timing = GF_TRUE;
    1083           0 :                                         return;
    1084             :                                 }
    1085             :                         }
    1086             : 
    1087         152 :                         repeat = 1+ent->repeat_count;
    1088         904 :                         while (repeat) {
    1089         600 :                                 if ((current_time_rescale >= segtime) && (current_time_rescale < segtime + ent->duration)) {
    1090           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Found segment %d for current time "LLU" is in SegmentTimeline ["LLU"-"LLU"] (timecale %d - current index %d - startNumber %d)\n", seg_idx, current_time_rescale, start_segtime, segtime + ent->duration, timescale, group->download_segment_index, start_number));
    1091             : 
    1092           0 :                                         group->download_segment_index = seg_idx;
    1093           0 :                                         group->nb_segments_in_rep = seg_idx + count - i;
    1094           0 :                                         group->start_playback_range = (current_time)/1000.0;
    1095           0 :                                         group->ast_at_init = availabilityStartTime;
    1096           0 :                                         group->ast_offset = (u32) (ast_offset*1000);
    1097             : 
    1098             :                                         //to remove - this is a hack to speedup starting for some strange MPDs which announce the live point as the first segment but have already produced the complete timeline
    1099           0 :                                         if (group->dash->utc_drift_estimate<0) {
    1100           0 :                                                 group->ast_at_init -= (timeline_duration - (segtime-start_segtime)) *1000/timescale;
    1101             :                                         }
    1102             :                                         return;
    1103             :                                 }
    1104         600 :                                 segtime += ent->duration;
    1105         600 :                                 repeat--;
    1106         600 :                                 seg_idx++;
    1107             :                                 last_s_dur=ent->duration;
    1108             :                         }
    1109             :                 }
    1110             :                 //check if we're ahead of time but "reasonnably" ahead (max 1 min) - otherwise consider the timing is broken
    1111           4 :                 if ((current_time_rescale + last_s_dur >= segtime) && (current_time_rescale <= segtime + 60*timescale)) {
    1112           4 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] current time "LLU" is greater than last SegmentTimeline end "LLU" - defaulting to last entry in SegmentTimeline\n", current_time_rescale, segtime));
    1113           4 :                         group->download_segment_index = seg_idx-1;
    1114           4 :                         group->nb_segments_in_rep = seg_idx;
    1115             :                         //we can't trust our UTC check, play from last segment with start_range=0 (eg from start of first segment)
    1116           4 :                         group->start_playback_range = 0;
    1117             : 
    1118           4 :                         group->ast_at_init = availabilityStartTime;
    1119           4 :                         group->ast_offset = (u32) (ast_offset*1000);
    1120             :                         //force an update in half the target period
    1121           4 :                         group->dash->last_update_time = gf_sys_clock() + group->dash->mpd->minimum_update_period/2;
    1122             :                 } else {
    1123             :                         //NOT FOUND !!
    1124           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] current time "LLU" is NOT in SegmentTimeline ["LLU"-"LLU"] - cannot estimate current startNumber, default to 0 ...\n", current_time_rescale, start_segtime, segtime));
    1125           0 :                         group->download_segment_index = 0;
    1126           0 :                         group->nb_segments_in_rep = 10;
    1127           0 :                         group->broken_timing = GF_TRUE;
    1128             :                 }
    1129             : 
    1130             :                 return;
    1131             :         }
    1132             : 
    1133          44 :         if (group->segment_duration) {
    1134          44 :                 u32 nb_segs_in_update = (u32) (mpd->minimum_update_period / (1000*group->segment_duration) );
    1135          44 :                 Double nb_seg = (Double) current_time;
    1136          44 :                 nb_seg /= 1000;
    1137          44 :                 nb_seg /= group->segment_duration;
    1138          44 :                 shift = (u32) nb_seg;
    1139             : 
    1140          44 :                 if ((group->dash->route_clock_state == 2) && shift) {
    1141             :                         //shift currently points to the next segment after the one used for clock bootstrap
    1142           5 :             if (!group->dash->route_low_latency)
    1143           5 :                 shift--;
    1144             :             //avoid querying too early the cache since segments do not usually arrive exactly on time ...
    1145           5 :                         availabilityStartTime += group->dash->route_ast_shift;
    1146             :                 }
    1147             : 
    1148          44 :                 if (group->dash->initial_period_tunein || group->force_timeline_reeval) {
    1149             :                         u64 seg_start_ms, seg_end_ms;
    1150          44 :                         if (group->force_timeline_reeval) {
    1151          19 :                                 group->start_number_at_last_ast = 0;
    1152          19 :                                 group->force_timeline_reeval = GF_FALSE;
    1153             :                         }
    1154          44 :                         seg_start_ms = (u64) (group->segment_duration * (shift+start_number) * 1000);
    1155          44 :                         seg_end_ms = (u64) (seg_start_ms + group->segment_duration*1000);
    1156             :                         //we are in the right period
    1157          44 :                         if (seg_start_ms>=group->period->start && (!group->period->duration || (seg_end_ms<=group->period->start+group->period->duration)) ) {
    1158             :                         } else {
    1159             :                                 u32 i;
    1160             :                                 u64 start = 0;
    1161           1 :                                 for (i=0; i<gf_list_count(group->dash->mpd->periods); i++) {
    1162           2 :                                         GF_MPD_Period *ap = gf_list_get(group->dash->mpd->periods, i);
    1163           2 :                                         if (ap->start) start = ap->start;
    1164             : 
    1165           2 :                                         if ((seg_start_ms>=ap->start) && (!ap->duration || (seg_end_ms<=start + ap->duration))) {
    1166           1 :                                                 group->dash->reinit_period_index = 1+i;
    1167           1 :                                                 group->dash->start_range_period = (Double) seg_start_ms;
    1168           1 :                                                 group->dash->start_range_period -= ap->start;
    1169           1 :                                                 group->dash->start_range_period /= 1000;
    1170           1 :                                                 return;
    1171             :                                         }
    1172             : 
    1173           1 :                                         if (!ap->duration) break;
    1174           1 :                                         start += ap->duration;
    1175             :                                 }
    1176             :                         }
    1177             :                 }
    1178             : 
    1179             :                 //not time shifting, we are at the live edge, we must stick to start of segment otherwise we won't have enough data to play until next segment is ready
    1180             : 
    1181          43 :                 if (!group->dash->initial_time_shift_value) {
    1182             :                         Double time_in_seg;
    1183             :                         //by default playback starts at beginning of segment
    1184          43 :                         group->start_playback_range = shift * group->segment_duration;
    1185             : 
    1186             :                         time_in_seg = (Double) current_time/1000.0;
    1187          43 :                         time_in_seg -= group->start_playback_range;
    1188             : 
    1189             :                         //if low latency, try to adjust
    1190          43 :                         if (ast_offset) {
    1191             :                                 Double ast_diff_d;
    1192          13 :                                 if (ast_offset>group->segment_duration) ast_offset = group->segment_duration;
    1193          13 :                                 ast_diff_d = group->segment_duration - ast_offset;
    1194             : 
    1195             :                                 //we assume that in low latency mode, chunks are made available every (group->segment_duration - ast_offset)
    1196             :                                 //we need to seek such that the remaining time R satisfies now + R = NextSegAST
    1197             :                                 //hence S(n) + ms_in_seg + R = S(n+1) + Aoffset
    1198             :                                 //which gives us R = S(n+1) + Aoffset - S(n) - ms_in_seg = D + Aoffset - ms_in_seg
    1199             :                                 //seek = D - R = D - (D + Aoffset - ms_in_seg) = ms_in_seg - Ao
    1200          13 :                                 if (time_in_seg > ast_diff_d) {
    1201          13 :                                         group->start_playback_range += time_in_seg - ast_diff_d;
    1202             :                                 }
    1203             :                         }
    1204             :                 } else {
    1205           0 :                         group->start_playback_range = (Double) current_time / 1000.0;
    1206             :                 }
    1207             : 
    1208          43 :                 if (!group->start_number_at_last_ast) {
    1209          43 :                         group->download_segment_index = shift;
    1210          43 :                         group->start_number_at_last_ast = start_number;
    1211             : 
    1212          43 :                         group->ast_at_init = availabilityStartTime;
    1213          43 :                         group->ast_offset = (u32) (ast_offset*1000);
    1214          43 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AST at init "LLD"\n", group->ast_at_init));
    1215             : 
    1216          43 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] At current time "LLD" ms: Initializing Timeline: startNumber=%d segmentNumber=%d segmentDuration=%f - %.03f seconds in segment (start range %g)\n", current_time, start_number, shift, group->segment_duration, group->start_playback_range ? group->start_playback_range - shift*group->segment_duration : 0, group->start_playback_range));
    1217             :                 } else {
    1218           0 :                         group->download_segment_index += start_number;
    1219           0 :                         if (group->download_segment_index > group->start_number_at_last_ast) {
    1220           0 :                                 group->download_segment_index -= group->start_number_at_last_ast;
    1221           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At current time %d ms: Updating Timeline: startNumber=%d segmentNumber=%d downloadSegmentIndex=%d segmentDuration=%g AST_diff=%d\n", current_time, start_number, shift, group->download_segment_index, group->segment_duration, ast_diff));
    1222             :                         } else {
    1223           0 :                                 group->download_segment_index = shift;
    1224           0 :                                 group->ast_at_init = availabilityStartTime;
    1225           0 :                                 group->ast_offset = (u32) (ast_offset*1000);
    1226           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] At current time "LLU" ms: Re-Initializing Timeline: startNumber=%d segmentNumber=%d segmentDuration=%g AST_diff=%d\n", current_time, start_number, shift, group->segment_duration, ast_diff));
    1227             :                         }
    1228           0 :                         group->start_number_at_last_ast = start_number;
    1229             :                 }
    1230          43 :                 if (group->nb_segments_in_rep) {
    1231           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] UTC time indicates first segment in period is %d, MPD indicates %d segments are available\n", group->download_segment_index , group->nb_segments_in_rep));
    1232             :                 } else {
    1233          43 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] UTC time indicates first segment in period is %d\n", group->download_segment_index));
    1234             :                 }
    1235             : 
    1236          43 :                 if (group->nb_segments_in_rep && (group->download_segment_index + nb_segs_in_update > group->nb_segments_in_rep)) {
    1237           0 :                         if (group->download_segment_index < (s32)group->nb_segments_in_rep) {
    1238             : 
    1239             :                         } else {
    1240           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Not enough segments (%d needed vs %d indicated) to reach period endTime indicated in MPD - ignoring MPD duration\n", nb_segs_in_update, group->nb_segments_in_rep - group->download_segment_index ));
    1241           0 :                                 group->nb_segments_in_rep = shift + nb_segs_in_update;
    1242           0 :                                 group->dash->ignore_mpd_duration = GF_TRUE;
    1243             :                         }
    1244             :                 }
    1245          43 :                 group->prev_segment_ok = GF_TRUE;
    1246             :         } else {
    1247           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment duration unknown - cannot estimate current startNumber\n"));
    1248             :         }
    1249             : }
    1250             : 
    1251             : 
    1252             : /*!
    1253             : * Returns true if mime type of a given URL is an M3U8 mime-type
    1254             : \param url The url to check
    1255             : \param mime The mime-type to check
    1256             : \return true if mime-type is OK for M3U8
    1257             : */
    1258           8 : static Bool gf_dash_is_m3u8_mime(const char *url, const char * mime) {
    1259             :         u32 i;
    1260           8 :         if (!url || !mime)
    1261             :                 return GF_FALSE;
    1262           8 :         if (strstr(url, ".mpd") || strstr(url, ".MPD"))
    1263             :                 return GF_FALSE;
    1264             : 
    1265           0 :         for (i = 0 ; GF_DASH_M3U8_MIME_TYPES[i] ; i++) {
    1266           0 :                 if ( !stricmp(mime, GF_DASH_M3U8_MIME_TYPES[i]))
    1267             :                         return GF_TRUE;
    1268             :         }
    1269             :         return GF_FALSE;
    1270             : }
    1271             : 
    1272             : GF_EXPORT
    1273         118 : GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 group_idx, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start)
    1274             : {
    1275             :         s32 res;
    1276         118 :         GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    1277         118 :         if (!group) return GF_BAD_PARAM;
    1278             : 
    1279         118 :         if (! dash->rate_adaptation_download_monitor) return GF_OK;
    1280             :         //do not abort if other groups depend on this one
    1281         118 :         if (group->groups_depending_on) return GF_OK;
    1282         118 :         if (group->dash->disable_switching) return GF_OK;
    1283         118 :         if (!total_bytes || !bytes_done || !bits_per_sec) return GF_OK;
    1284         118 :         if (total_bytes == bytes_done) return GF_OK;
    1285             : 
    1286             :         //force a call go query buffer
    1287          10 :         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, group_idx, GF_OK);
    1288             : 
    1289          10 :         res = dash->rate_adaptation_download_monitor(dash, group, bits_per_sec, total_bytes, bytes_done, us_since_start, group->buffer_occupancy_ms, (u32) group->current_downloaded_segment_duration);
    1290             : 
    1291          10 :         if (res==-1) return GF_OK;
    1292             : 
    1293           0 :         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_ABORT_DOWNLOAD, gf_list_find(dash->groups, group), GF_OK);
    1294             : 
    1295             :         //internal return value, switching has already been setup
    1296           0 :         if (res<0) return GF_OK;
    1297             : 
    1298           0 :         group->force_segment_switch = GF_TRUE;
    1299           0 :         group->force_representation_idx_plus_one = (u32) res + 1;
    1300           0 :         return GF_OK;
    1301             : }
    1302             : 
    1303             : /*!
    1304             : * Download a file with possible retry if GF_IP_CONNECTION_FAILURE|GF_IP_NETWORK_FAILURE
    1305             : * (I discovered that with my WIFI connection, I had many issues with BFM-TV downloads)
    1306             : * Similar to gf_service_download_new() and gf_dm_sess_process().
    1307             : * Parameters are identical to the ones of gf_service_download_new.
    1308             : * \see gf_service_download_new()
    1309             : */
    1310         110 : GF_Err gf_dash_download_resource(GF_DashClient *dash, GF_DASHFileIOSession *sess, const char *url, u64 start_range, u64 end_range, u32 persistent_mode, GF_DASH_Group *group)
    1311             : {
    1312             :         s32 group_idx = -1;
    1313             :         Bool had_sess = GF_FALSE;
    1314             :         Bool retry = GF_TRUE;
    1315             :         GF_Err e;
    1316         110 :         GF_DASHFileIO *dash_io = dash->dash_io;
    1317             : 
    1318         110 :         if (!dash_io) return GF_BAD_PARAM;
    1319         110 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading %s starting at UTC "LLU" ms\n", url, gf_net_get_utc() ));
    1320             : 
    1321         110 :         if (group) {
    1322          50 :                 group_idx = gf_list_find(group->dash->groups, group);
    1323             :         }
    1324             : 
    1325         110 :         if (! *sess) {
    1326          52 :                 *sess = dash_io->create(dash_io, persistent_mode ? 1 : 0, url, group_idx);
    1327          52 :                 if (!(*sess)) {
    1328           2 :                         if (dash->route_clock_state)
    1329             :                                 return GF_IP_NETWORK_EMPTY;
    1330           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot try to download %s... out of memory ?\n", url));
    1331             :                         return GF_OUT_OF_MEM;
    1332             :                 }
    1333             :         } else {
    1334             :                 had_sess = GF_TRUE;
    1335          58 :                 if (persistent_mode!=2) {
    1336          18 :                         e = dash_io->setup_from_url(dash_io, *sess, url, group_idx);
    1337          18 :                         if (e) {
    1338             :                                 //with ROUTE we may have 404 right away if nothing in cache yet, not an error
    1339           0 :                                 GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) ));
    1340             :                                 return e;
    1341             :                         }
    1342             :                 }
    1343             :         }
    1344             : 
    1345          58 : retry:
    1346             : 
    1347         108 :         if (end_range) {
    1348          50 :                 e = dash_io->set_range(dash_io, *sess, start_range, end_range, (persistent_mode==2) ? GF_FALSE : GF_TRUE);
    1349          50 :                 if (e) {
    1350           0 :                         if (had_sess) {
    1351           0 :                                 dash_io->del(dash_io, *sess);
    1352           0 :                                 *sess = NULL;
    1353           0 :                                 return gf_dash_download_resource(dash, sess, url, start_range, end_range, persistent_mode ? 1 : 0, group);
    1354             :                         }
    1355           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot setup byte-range download for %s: %s\n", url, gf_error_to_string(e) ));
    1356             :                         return e;
    1357             :                 }
    1358             :         }
    1359             :         assert(*sess);
    1360             : 
    1361             :         /*issue HTTP GET for headers only*/
    1362         108 :         e = dash_io->init(dash_io, *sess);
    1363             : 
    1364         108 :         if (e>=GF_OK) {
    1365             :                 /*check mime type of the adaptation set if not provided*/
    1366         108 :                 if (group) {
    1367          50 :                         const char *mime = *sess ? dash_io->get_mime(dash_io, *sess) : NULL;
    1368          50 :                         if (mime && !group->service_mime) {
    1369           2 :                                 group->service_mime = gf_strdup(mime);
    1370             :                         }
    1371             : 
    1372             : 
    1373             :                         /*file cannot be cached on disk !*/
    1374          50 :                         if (dash_io->get_cache_name(dash_io, *sess ) == NULL) {
    1375           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Segment %s cannot be cached on disk, will use direct streaming\n", url));
    1376           0 :                                 group->segment_must_be_streamed = GF_TRUE;
    1377           0 :                                 return GF_OK;
    1378             :                         }
    1379          50 :                         group->segment_must_be_streamed = GF_FALSE;
    1380             :                 }
    1381             : 
    1382             :                 //release dl_mutex while downloading segment
    1383             :                 /*we can download the file*/
    1384         108 :                 e = dash_io->run(dash_io, *sess);
    1385             :         } else {
    1386           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] At "LLU" error %s - released dl_mutex\n", gf_net_get_utc(), gf_error_to_string(e)));
    1387             :         }
    1388             : 
    1389         108 :         switch (e) {
    1390           0 :         case GF_IP_CONNECTION_FAILURE:
    1391             :         case GF_IP_NETWORK_FAILURE:
    1392           0 :                 if (!dash->in_error || group) {
    1393           0 :                         dash_io->del(dash_io, *sess);
    1394           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] failed to download, retrying once with %s...\n", url));
    1395           0 :                         *sess = dash_io->create(dash_io, 0, url, group_idx);
    1396           0 :                         if (! (*sess)) {
    1397           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot retry to download %s... out of memory ?\n", url));
    1398             :                                 return GF_OUT_OF_MEM;
    1399             :                         }
    1400             : 
    1401           0 :                         if (retry) {
    1402             :                                 retry = GF_FALSE;
    1403             :                                 goto retry;
    1404             :                         }
    1405           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] two consecutive failures, aborting the download %s.\n", url));
    1406             :                 } else if (dash->in_error) {
    1407           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download still in error for %s.\n", url));
    1408             :                 }
    1409             :                 break;
    1410         108 :         case GF_OK:
    1411         108 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Download %s complete at UTC "LLU" ms\n", url, gf_net_get_utc() ));
    1412             :                 break;
    1413           0 :         default:
    1414             :                 //log as warning, maybe the dash client can recover from this error
    1415           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Failed to download %s: %s\n", url, gf_error_to_string(e)));
    1416             :                 break;
    1417             :         }
    1418             :         return e;
    1419             : }
    1420             : 
    1421          37 : static void gf_dash_get_timeline_duration(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_SegmentTimeline *timeline, u32 timescale, u32 *nb_segments, Double *max_seg_duration)
    1422             : {
    1423             :         u32 i, count;
    1424             :         u64 period_duration, start_time, dur;
    1425          37 :         if (period->duration) {
    1426             :                 period_duration = period->duration;
    1427             :         } else {
    1428          37 :                 period_duration = mpd->media_presentation_duration - period->start;
    1429             :         }
    1430          37 :         period_duration *= timescale;
    1431          37 :         period_duration /= 1000;
    1432             : 
    1433          37 :         *nb_segments = 0;
    1434          37 :         if (max_seg_duration) *max_seg_duration = 0;
    1435             :         start_time = 0;
    1436             :         dur = 0;
    1437          37 :         count = gf_list_count(timeline->entries);
    1438        1185 :         for (i=0; i<count; i++) {
    1439        1185 :                 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
    1440        1185 :                 if ((s32)ent->repeat_count >=0) {
    1441        1185 :                         *nb_segments += 1 + ent->repeat_count;
    1442        1185 :                         if (ent->start_time) {
    1443             :                                 start_time = ent->start_time;
    1444          12 :                                 dur = (1 + ent->repeat_count);
    1445             :                         } else {
    1446        1173 :                                 dur += (1 + ent->repeat_count);
    1447             :                         }
    1448        1185 :                         dur     *= ent->duration;
    1449             :                 } else {
    1450             :                         u32 nb_seg = 0;
    1451           0 :                         if (i+1<count) {
    1452           0 :                                 GF_MPD_SegmentTimelineEntry *ent2 = gf_list_get(timeline->entries, i+1);
    1453           0 :                                 if (ent2->start_time>0) {
    1454           0 :                                         nb_seg = (u32) ( (ent2->start_time - start_time - dur) / ent->duration);
    1455           0 :                                         dur += ((u64)nb_seg) * ent->duration;
    1456             :                                 }
    1457             :                         }
    1458           0 :                         if (!nb_seg) {
    1459           0 :                                 nb_seg = (u32) ( (period_duration - start_time) / ent->duration );
    1460           0 :                                 dur += ((u64)nb_seg) * ent->duration;
    1461             :                         }
    1462           0 :                         *nb_segments += nb_seg;
    1463             :                 }
    1464        1185 :                 if (max_seg_duration && (*max_seg_duration < ent->duration)) *max_seg_duration = ent->duration;
    1465             :         }
    1466          37 : }
    1467             : 
    1468         801 : static void gf_dash_get_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, GF_MPD *mpd, u32 *nb_segments, Double *max_seg_duration)
    1469             : {
    1470             :         Double mediaDuration;
    1471             :         Bool single_segment = GF_FALSE;
    1472             :         u32 timescale;
    1473             :         u64 duration;
    1474             :         GF_MPD_SegmentTimeline *timeline = NULL;
    1475         801 :         *nb_segments = timescale = 0;
    1476             :         duration = 0;
    1477             : 
    1478         801 :         if (rep->segment_list || set->segment_list || period->segment_list) {
    1479             :                 GF_List *segments = NULL;
    1480         155 :                 if (period->segment_list) {
    1481           0 :                         if (period->segment_list->duration) duration = period->segment_list->duration;
    1482           0 :                         if (period->segment_list->timescale) timescale = period->segment_list->timescale;
    1483           0 :                         if (period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
    1484           0 :                         if (period->segment_list->segment_timeline) timeline = period->segment_list->segment_timeline;
    1485             :                 }
    1486         155 :                 if (set->segment_list) {
    1487           0 :                         if (set->segment_list->duration) duration = set->segment_list->duration;
    1488           0 :                         if (set->segment_list->timescale) timescale = set->segment_list->timescale;
    1489           0 :                         if (set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
    1490           0 :                         if (set->segment_list->segment_timeline) timeline = set->segment_list->segment_timeline;
    1491             :                 }
    1492         155 :                 if (rep->segment_list) {
    1493         155 :                         if (rep->segment_list->duration) duration = rep->segment_list->duration;
    1494         155 :                         if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
    1495         155 :                         if (rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
    1496         155 :                         if (rep->segment_list->segment_timeline) timeline = rep->segment_list->segment_timeline;
    1497             :                 }
    1498         155 :                 if (!timescale) timescale=1;
    1499             : 
    1500         155 :                 if (timeline) {
    1501           0 :                         gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
    1502           0 :                         if (max_seg_duration) *max_seg_duration /= timescale;
    1503             :                 } else {
    1504         155 :                         if (segments)
    1505         103 :                                 *nb_segments = gf_list_count(segments);
    1506         155 :                         if (max_seg_duration) {
    1507         155 :                                 *max_seg_duration = (Double) duration;
    1508         155 :                                 *max_seg_duration /= timescale;
    1509             :                         }
    1510             :                 }
    1511             :                 return;
    1512             :         }
    1513             : 
    1514             :         /*single segment*/
    1515         646 :         if (rep->segment_base || set->segment_base || period->segment_base) {
    1516          18 :                 *max_seg_duration = (Double)mpd->media_presentation_duration;
    1517          18 :                 *max_seg_duration /= 1000;
    1518          18 :                 *nb_segments = 1;
    1519          18 :                 return;
    1520             :         }
    1521             : 
    1522             :         single_segment = GF_TRUE;
    1523         628 :         if (period->segment_template) {
    1524             :                 single_segment = GF_FALSE;
    1525           0 :                 if (period->segment_template->duration) duration = period->segment_template->duration;
    1526           0 :                 if (period->segment_template->timescale) timescale = period->segment_template->timescale;
    1527           0 :                 if (period->segment_template->segment_timeline) timeline = period->segment_template->segment_timeline;
    1528             :         }
    1529         628 :         if (set->segment_template) {
    1530             :                 single_segment = GF_FALSE;
    1531         537 :                 if (set->segment_template->duration) duration = set->segment_template->duration;
    1532         537 :                 if (set->segment_template->timescale) timescale = set->segment_template->timescale;
    1533         537 :                 if (set->segment_template->segment_timeline) timeline = set->segment_template->segment_timeline;
    1534             :         }
    1535         628 :         if (rep->segment_template) {
    1536             :                 single_segment = GF_FALSE;
    1537         222 :                 if (rep->segment_template->duration) duration = rep->segment_template->duration;
    1538         222 :                 if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
    1539         222 :                 if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
    1540             :         }
    1541         628 :         if (!timescale) timescale=1;
    1542             : 
    1543             :         /*if no SegmentXXX is found, this is a single segment representation (onDemand profile)*/
    1544         628 :         if (single_segment) {
    1545           0 :                 *max_seg_duration = (Double)mpd->media_presentation_duration;
    1546           0 :                 *max_seg_duration /= 1000;
    1547           0 :                 *nb_segments = 1;
    1548           0 :                 return;
    1549             :         }
    1550             : 
    1551         628 :         if (timeline) {
    1552          37 :                 gf_dash_get_timeline_duration(mpd, period, timeline, timescale, nb_segments, max_seg_duration);
    1553          37 :                 if (max_seg_duration) *max_seg_duration /= timescale;
    1554             :         } else {
    1555         591 :                 if (max_seg_duration) {
    1556         591 :                         *max_seg_duration = (Double) duration;
    1557         591 :                         *max_seg_duration /= timescale;
    1558             :                 }
    1559         591 :                 mediaDuration = (Double)period->duration;
    1560         591 :                 if (!mediaDuration && mpd->media_presentation_duration) {
    1561           2 :                         u32 i, count = gf_list_count(mpd->periods);
    1562             :                         Double start = 0;
    1563           4 :                         for (i=0; i<count; i++) {
    1564           4 :                                 GF_MPD_Period *ap = gf_list_get(mpd->periods, i);
    1565           4 :                                 if (ap==period) break;
    1566           2 :                                 if (ap->start) start = (Double)ap->start;
    1567           2 :                                 start += ap->duration;
    1568             :                         }
    1569           2 :                         mediaDuration = mpd->media_presentation_duration - start;
    1570             :                 }
    1571         591 :                 if (mediaDuration && duration) {
    1572             :                         Double nb_seg = (Double) mediaDuration;
    1573             :                         /*duration is given in ms*/
    1574         540 :                         nb_seg /= 1000;
    1575         540 :                         nb_seg *= timescale;
    1576         540 :                         nb_seg /= duration;
    1577         540 :                         *nb_segments = (u32) nb_seg;
    1578         540 :                         if (*nb_segments < nb_seg) (*nb_segments)++;
    1579             :                 }
    1580             :         }
    1581             : }
    1582             : 
    1583             : 
    1584        4420 : static u64 gf_dash_get_segment_start_time_with_timescale(GF_DASH_Group *group, u64 *segment_duration, u32 *scale)
    1585             : {
    1586        4420 :         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    1587        4420 :         GF_MPD_AdaptationSet *set = group->adaptation_set;
    1588        4420 :         GF_MPD_Period *period = group->period;
    1589        4420 :         s32 segment_index = group->download_segment_index;
    1590        4420 :         u64 start_time = 0;
    1591             : 
    1592        4420 :         gf_mpd_get_segment_start_time_with_timescale(segment_index,
    1593             :                 period, set, rep,
    1594             :                 &start_time, segment_duration, scale);
    1595             : 
    1596        4420 :         return start_time;
    1597             : }
    1598             : 
    1599        1365 : static Double gf_dash_get_segment_start_time(GF_DASH_Group *group, Double *segment_duration)
    1600             : {
    1601             :         u64 start = 0;
    1602        1376 :         u64 dur = 0;
    1603        1376 :         u32 scale = 1000;
    1604             : 
    1605        1376 :         start = gf_dash_get_segment_start_time_with_timescale(group, &dur, &scale);
    1606        1365 :         if (segment_duration) {
    1607        1365 :                 *segment_duration = (Double) dur;
    1608        1365 :                 *segment_duration /= scale;
    1609             :         }
    1610        1376 :         return ((Double)start)/scale;
    1611             : }
    1612             : 
    1613        1365 : static u64 gf_dash_get_segment_availability_start_time(GF_MPD *mpd, GF_DASH_Group *group, u32 segment_index, u32 *seg_dur_ms)
    1614             : {
    1615        1365 :         Double seg_ast, seg_dur=0.0;
    1616        1365 :         seg_ast = gf_dash_get_segment_start_time(group, &seg_dur);
    1617        1365 :         if (seg_dur_ms) *seg_dur_ms = (u32) (seg_dur * 1000);
    1618             : 
    1619        1365 :         seg_ast += seg_dur;
    1620        1365 :         seg_ast *= 1000;
    1621        1365 :         seg_ast += group->period->start + group->ast_at_init;
    1622        1365 :         seg_ast -= group->ast_offset;
    1623        1365 :         return (u64) seg_ast;
    1624             : }
    1625             : 
    1626           8 : static u32 gf_dash_get_index_in_timeline(GF_MPD_SegmentTimeline *timeline, u64 segment_start, u64 start_timescale, u64 timescale)
    1627             : {
    1628             :         u64 start_time = 0;
    1629             :         u32 idx = 0;
    1630             :         u32 i, count, repeat;
    1631           8 :         count = gf_list_count(timeline->entries);
    1632         306 :         for (i=0; i<count; i++) {
    1633         305 :                 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
    1634             : 
    1635         305 :                 if (!i || ent->start_time) start_time = ent->start_time;
    1636             : 
    1637         305 :                 repeat = ent->repeat_count+1;
    1638        1806 :                 while (repeat) {
    1639        1203 :                         if (start_timescale==timescale) {
    1640        1203 :                                 if (start_time == segment_start )
    1641             :                                         return idx;
    1642        1196 :                                 if (start_time > segment_start) {
    1643           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Warning: segment timeline entry start "LLU" greater than segment start "LLU", using current entry\n", start_time, segment_start));
    1644             :                                         return idx;
    1645             :                                 }
    1646             :                         } else {
    1647           0 :                                 if (start_time*start_timescale == segment_start * timescale) return idx;
    1648           0 :                                 if (start_time*start_timescale > segment_start * timescale) {
    1649           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Warning: segment timeline entry start "LLU" greater than segment start "LLU", using current entry\n", start_time, segment_start));
    1650             :                                         return idx;
    1651             :                                 }
    1652             :                         }
    1653        1196 :                         start_time+=ent->duration;
    1654        1196 :                         repeat--;
    1655        1196 :                         idx++;
    1656             :                 }
    1657             :         }
    1658             :         //end of list in regular case: segment was the last one of the previous list and no changes happend
    1659           1 :         if (start_timescale==timescale) {
    1660           1 :                 if (start_time == segment_start )
    1661             :                         return idx;
    1662             :         } else {
    1663           0 :                 if (start_time*start_timescale == segment_start * timescale)
    1664             :                         return idx;
    1665             :         }
    1666             : 
    1667           0 :         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: could not find previous segment start in current timeline ! seeking to end of timeline\n"));
    1668             :         return idx;
    1669             : }
    1670             : 
    1671             : 
    1672          96 : static GF_Err gf_dash_merge_segment_timeline(GF_DASH_Group *group, GF_DashClient *dash, GF_MPD_SegmentList *old_list, GF_MPD_SegmentTemplate *old_template, GF_MPD_SegmentList *new_list, GF_MPD_SegmentTemplate *new_template, Double min_start_time)
    1673             : {
    1674             :         GF_MPD_SegmentTimeline *old_timeline, *new_timeline;
    1675             :         u32 i, idx, timescale, nb_new_segs;
    1676             :         GF_MPD_SegmentTimelineEntry *ent;
    1677             : 
    1678             :         old_timeline = new_timeline = NULL;
    1679          96 :         if (old_list && old_list->segment_timeline) {
    1680           0 :                 if (!new_list || !new_list->segment_timeline) {
    1681           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentList\n"));
    1682             :                         return GF_NON_COMPLIANT_BITSTREAM;
    1683             :                 }
    1684             :                 old_timeline = old_list->segment_timeline;
    1685             :                 new_timeline = new_list->segment_timeline;
    1686           0 :                 timescale = new_list->timescale;
    1687          96 :         } else if (old_template && old_template->segment_timeline) {
    1688           8 :                 if (!new_template || !new_template->segment_timeline) {
    1689           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: segment timeline not present in new MPD segmentTemplate\n"));
    1690             :                         return GF_NON_COMPLIANT_BITSTREAM;
    1691             :                 }
    1692             :                 old_timeline = old_template->segment_timeline;
    1693             :                 new_timeline = new_template->segment_timeline;
    1694           8 :                 timescale = new_template->timescale;
    1695             :         }
    1696          96 :         if (!old_timeline && !new_timeline) return GF_OK;
    1697             : 
    1698           8 :         if (group) {
    1699           8 :                 group->current_start_time = gf_dash_get_segment_start_time_with_timescale(group, NULL, &group->current_timescale);
    1700             :         } else {
    1701           0 :                 for (i=0; i<gf_list_count(dash->groups); i++) {
    1702           0 :                         GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
    1703           0 :                         a_group->current_start_time = gf_dash_get_segment_start_time_with_timescale(a_group, NULL, &a_group->current_timescale);
    1704             :                 }
    1705             :         }
    1706             : 
    1707             :         nb_new_segs = 0;
    1708           8 :         idx=0;
    1709         314 :         while ((ent = gf_list_enum(new_timeline->entries, &idx))) {
    1710         306 :                 nb_new_segs += 1 + ent->repeat_count;
    1711             :         }
    1712             : 
    1713           8 :         if (group) {
    1714             : #ifndef GPAC_DISABLE_LOG
    1715           8 :                 u32 prev_idx = group->download_segment_index;
    1716             : #endif
    1717           8 :                 group->nb_segments_in_rep = nb_new_segs;
    1718           8 :                 group->download_segment_index = gf_dash_get_index_in_timeline(new_timeline, group->current_start_time, group->current_timescale, timescale ? timescale : group->current_timescale);
    1719             : 
    1720           8 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated SegmentTimeline: New segment number %d - old %d - start time "LLD"\n", group->download_segment_index , prev_idx, group->current_start_time));
    1721             :         } else {
    1722           0 :                 for (i=0; i<gf_list_count(dash->groups); i++) {
    1723           0 :                         GF_DASH_Group *a_group = gf_list_get(dash->groups, i);
    1724             : #ifndef GPAC_DISABLE_LOG
    1725           0 :                         u32 prev_idx = a_group->download_segment_index;
    1726             : #endif
    1727           0 :                         a_group->nb_segments_in_rep = nb_new_segs;
    1728           0 :                         a_group->download_segment_index = gf_dash_get_index_in_timeline(new_timeline, a_group->current_start_time, a_group->current_timescale, timescale ? timescale : a_group->current_timescale);
    1729             : 
    1730           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated SegmentTimeline: New segment number %d - old %d - start time "LLD"\n", a_group->download_segment_index , prev_idx, a_group->current_start_time));
    1731             :                 }
    1732             :         }
    1733             : 
    1734             : 
    1735             : #ifndef GPAC_DISABLE_LOG
    1736           8 :         if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO) ) {
    1737             :                 u64 start = 0;
    1738           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Old SegmentTimeline: \n"));
    1739           0 :                 for (idx=0; idx<gf_list_count(old_timeline->entries); idx++) {
    1740           0 :                         ent = gf_list_get(old_timeline->entries, idx);
    1741           0 :                         if (ent->start_time) start = ent->start_time;
    1742           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tt="LLU" d=%d r=%d (current start "LLU")\n", ent->start_time, ent->duration, ent->repeat_count, start));
    1743           0 :                         start += ent->duration * (1+ent->repeat_count);
    1744             :                 }
    1745             : 
    1746             : 
    1747             :                 start = 0;
    1748           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] New SegmentTimeline: \n"));
    1749           0 :                 for (idx=0; idx<gf_list_count(new_timeline->entries); idx++) {
    1750           0 :                         ent = gf_list_get(new_timeline->entries, idx);
    1751           0 :                         if (ent->start_time) start = ent->start_time;
    1752           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tt="LLU" d=%d r=%d (current start "LLU")\n", ent->start_time, ent->duration, ent->repeat_count, start));
    1753           0 :                         start += ent->duration * (1+ent->repeat_count);
    1754             :                 }
    1755             :         }
    1756             : #endif
    1757             : 
    1758             :         return GF_OK;
    1759             : }
    1760             : 
    1761           8 : static u32 gf_dash_purge_segment_timeline(GF_DASH_Group *group, Double min_start_time)
    1762             : {
    1763             :         u32 nb_removed, time_scale;
    1764             :         u64 start_time, min_start, duration;
    1765           8 :         GF_MPD_SegmentTimeline *timeline=NULL;
    1766           8 :         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    1767             : 
    1768           8 :         if (!min_start_time) return 0;
    1769           8 :         gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, &time_scale, NULL, &timeline);
    1770           8 :         if (!timeline) return 0;
    1771             : 
    1772           8 :         min_start = (u64) (min_start_time*time_scale);
    1773             :         start_time = 0;
    1774             :         nb_removed=0;
    1775           0 :         while (1) {
    1776           8 :                 GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, 0);
    1777           8 :                 if (!ent) break;
    1778           8 :                 if (ent->start_time) start_time = ent->start_time;
    1779             : 
    1780           8 :                 while (start_time + ent->duration < min_start) {
    1781           0 :                         if (! ent->repeat_count) break;
    1782           0 :                         ent->repeat_count--;
    1783           0 :                         nb_removed++;
    1784             :                         start_time += ent->duration;
    1785             :                 }
    1786             :                 /*this entry is in our range, keep it and make sure it has a start time*/
    1787           8 :                 if (start_time + ent->duration >= min_start) {
    1788           8 :                         if (!ent->start_time)
    1789           0 :                                 ent->start_time = start_time;
    1790             :                         break;
    1791             :                 }
    1792             :                 start_time += ent->duration;
    1793           0 :                 gf_list_rem(timeline->entries, 0);
    1794           0 :                 gf_free(ent);
    1795           0 :                 nb_removed++;
    1796             :         }
    1797           8 :         if (nb_removed) {
    1798             :                 GF_MPD_SegmentList *segment_list;
    1799             :                 /*update next download index*/
    1800           0 :                 group->download_segment_index -= nb_removed;
    1801             :                 assert(group->nb_segments_in_rep >= nb_removed);
    1802           0 :                 group->nb_segments_in_rep -= nb_removed;
    1803             :                 /*clean segmentList*/
    1804             :                 segment_list = NULL;
    1805           0 :                 if (group->period && group->period->segment_list) segment_list = group->period->segment_list;
    1806           0 :                 if (group->adaptation_set && group->adaptation_set->segment_list) segment_list = group->adaptation_set->segment_list;
    1807           0 :                 if (rep && rep->segment_list) segment_list = rep->segment_list;
    1808             : 
    1809           0 :                 if (segment_list) {
    1810             :                         u32 i = nb_removed;
    1811           0 :                         while (i) {
    1812           0 :                                 GF_MPD_SegmentURL *seg_url = gf_list_get(segment_list->segment_URLs, 0);
    1813           0 :                                 gf_list_rem(segment_list->segment_URLs, 0);
    1814           0 :                                 gf_mpd_segment_url_free(seg_url);
    1815           0 :                                 i--;
    1816             :                         }
    1817             :                 }
    1818           0 :                 group->nb_segments_purged += nb_removed;
    1819             :         }
    1820             :         return nb_removed;
    1821             : }
    1822             : 
    1823           0 : static GF_Err gf_dash_solve_representation_xlink(GF_DashClient *dash, GF_MPD_Representation *rep, u8 last_sig[GF_SHA1_DIGEST_SIZE])
    1824             : {
    1825             :         u32 count, i;
    1826             :         GF_Err e;
    1827             :         Bool is_local=GF_FALSE;
    1828             :         const char *local_url;
    1829             :         char *url;
    1830             :         GF_DOMParser *parser;
    1831             :         u8 signature[GF_SHA1_DIGEST_SIZE];
    1832           0 :         if (!rep->segment_list->xlink_href) return GF_BAD_PARAM;
    1833             : 
    1834           0 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving Representation SegmentList XLINK %s\n", rep->segment_list->xlink_href));
    1835             : 
    1836             :         //SPEC: If this value is present, the element containing the xlink:href attribute and all @xlink attributes included in the element containing @xlink:href shall be removed at the time when the resolution is due.
    1837           0 :         if (!strcmp(rep->segment_list->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
    1838           0 :                 gf_mpd_delete_segment_list(rep->segment_list);
    1839           0 :                 rep->segment_list = NULL;
    1840             :                 return GF_OK;
    1841             :         }
    1842             : 
    1843             :         //xlink relative to our MPD base URL
    1844           0 :         url = gf_url_concatenate(dash->base_url, rep->segment_list->xlink_href);
    1845             : 
    1846           0 :         if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
    1847             :                 local_url = url;
    1848             :                 is_local=GF_TRUE;
    1849             :                 e = GF_OK;
    1850             :         } else {
    1851             :                 /*use non-persistent connection for MPD*/
    1852           0 :                 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), url ? url : rep->segment_list->xlink_href, 0, 0, 0, NULL);
    1853           0 :                 gf_free(url);
    1854             :         }
    1855             : 
    1856           0 :         if (e) {
    1857           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot download Representation SegmentList XLINK %s: error %s\n", rep->segment_list->xlink_href, gf_error_to_string(e)));
    1858           0 :                 gf_free(rep->segment_list->xlink_href);
    1859           0 :                 rep->segment_list->xlink_href = NULL;
    1860             :                 return e;
    1861             :         }
    1862             : 
    1863           0 :         if (!is_local) {
    1864             :                 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
    1865           0 :                 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
    1866             :         }
    1867             : 
    1868           0 :         gf_sha1_file(local_url, signature);
    1869           0 :         if (! memcmp(signature, last_sig, GF_SHA1_DIGEST_SIZE)) {
    1870           0 :                 if (is_local) gf_free(url);
    1871             :                 return GF_EOS;
    1872             :         }
    1873             :         memcpy(last_sig, signature, GF_SHA1_DIGEST_SIZE);
    1874             : 
    1875           0 :         parser = gf_xml_dom_new();
    1876           0 :         e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
    1877           0 :         if (is_local) gf_free(url);
    1878             : 
    1879           0 :         if (e != GF_OK) {
    1880           0 :                 gf_xml_dom_del(parser);
    1881           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot parse Representation SegmentList XLINK: error in XML parsing %s\n", gf_error_to_string(e)));
    1882           0 :                 gf_free(rep->segment_list->xlink_href);
    1883           0 :                 rep->segment_list->xlink_href = NULL;
    1884             :                 return GF_NON_COMPLIANT_BITSTREAM;
    1885             :         }
    1886             : 
    1887           0 :         count = gf_xml_dom_get_root_nodes_count(parser);
    1888           0 :         if (count > 1) {
    1889           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] XLINK %s has more than one segment list - ignoring it\n", rep->segment_list->xlink_href));
    1890           0 :                 gf_mpd_delete_segment_list(rep->segment_list);
    1891           0 :                 rep->segment_list = NULL;
    1892             :                 return GF_NON_COMPLIANT_BITSTREAM;
    1893             :         }
    1894           0 :         for (i=0; i<count; i++) {
    1895           0 :                 GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
    1896           0 :                 if (!strcmp(root->name, "SegmentList")) {
    1897           0 :                         GF_MPD_SegmentList *new_seg_list = gf_mpd_solve_segment_list_xlink(dash->mpd, root);
    1898             :                         //forbiden
    1899           0 :                         if (new_seg_list && new_seg_list->xlink_href) {
    1900           0 :                                 if (new_seg_list->xlink_actuate_on_load) {
    1901           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] XLINK %s references to remote element entities that contain another @xlink:href attribute with xlink:actuate set to onLoad - forbiden\n", rep->segment_list->xlink_href));
    1902           0 :                                         gf_mpd_delete_segment_list(new_seg_list);
    1903             :                                         new_seg_list = NULL;
    1904             :                                 } else {
    1905           0 :                                         new_seg_list->consecutive_xlink_count = rep->segment_list->consecutive_xlink_count + 1;
    1906             :                                 }
    1907             :                         }
    1908             :                         //replace current segment list by the one from remote element entity (located by xlink:href)
    1909           0 :                         gf_mpd_delete_segment_list(rep->segment_list);
    1910           0 :                         rep->segment_list = new_seg_list;
    1911             :                 }
    1912             :                 else
    1913           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] XML node %s is not a representation segmentlist - ignoring it\n", root->name));
    1914             :         }
    1915             :         return GF_OK;
    1916             : }
    1917             : 
    1918           0 : static void dash_purge_xlink(GF_MPD *new_mpd)
    1919             : {
    1920           0 :         u32 i, count = gf_list_count(new_mpd->periods);
    1921           0 :         for (i=0; i<count; i++) {
    1922           0 :                 GF_MPD_Period *period = gf_list_get(new_mpd->periods, i);
    1923           0 :                 if (!gf_list_count(period->adaptation_sets)) continue;
    1924           0 :                 if (!period->xlink_href) continue;
    1925           0 :                 gf_free(period->xlink_href);
    1926           0 :                 period->xlink_href = NULL;
    1927             :         }
    1928           0 : }
    1929             : 
    1930         107 : static void gf_dash_mark_group_done(GF_DASH_Group *group)
    1931             : {
    1932         107 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d group is done\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set) ));
    1933         107 :         group->done = GF_TRUE;
    1934         107 : }
    1935             : 
    1936             : 
    1937        3380 : static const char *dash_strip_base_url(const char *url, const char *base_url)
    1938             : {
    1939        3380 :         const char *url_start = gf_url_get_path(url);
    1940        3380 :         const char *base_url_start = gf_url_get_path(base_url);
    1941        3380 :         const char *base_url_end = base_url_start ? strrchr(base_url_start, '/') : NULL;
    1942             : 
    1943        3380 :         if (base_url_start && url_start) {
    1944        3351 :                 u32 diff = (u32) (base_url_end - base_url_start);
    1945        3351 :                 if (!strncmp(url_start, base_url_start, diff))
    1946        3345 :                         return url_start + diff + 1;
    1947             :         }
    1948             :         return url;
    1949             : }
    1950             : 
    1951         130 : static GF_Err gf_dash_solve_m3u8_representation_xlink(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool *is_static, u64 *duration, u8 signature[GF_SHA1_DIGEST_SIZE])
    1952             : {
    1953             :         GF_Err e;
    1954             :         char *xlink_copy;
    1955             :         const char *name, *local_url;
    1956         130 :         GF_DashClient *dash = group->dash;
    1957         130 :         if (!dash->dash_io->manifest_updated) {
    1958         127 :                 return gf_m3u8_solve_representation_xlink(rep, &dash->getter, is_static, duration, signature);
    1959             :         }
    1960             : 
    1961           3 :         xlink_copy = gf_strdup(rep->segment_list->xlink_href);;
    1962           3 :         e = gf_m3u8_solve_representation_xlink(rep, &dash->getter, is_static, duration, signature);
    1963             :         //do not notify m3u8 update if same as last one
    1964           3 :         if (e==GF_EOS) {
    1965           0 :                 gf_free(xlink_copy);
    1966           0 :                 return e;
    1967             :         }
    1968             :         
    1969           3 :         if (gf_url_is_local(xlink_copy)) {
    1970             :                 local_url = xlink_copy;
    1971             :         } else {
    1972           0 :                 local_url = dash->getter.get_cache_name(&dash->getter);
    1973             :         }
    1974           3 :         name = dash_strip_base_url(xlink_copy, dash->base_url);
    1975           3 :         dash->dash_io->manifest_updated(dash->dash_io, name, local_url, gf_list_find(dash->groups, group));
    1976           3 :         gf_free(xlink_copy);
    1977           3 :         return e;
    1978             : }
    1979             : 
    1980          68 : static u32 ls_hls_purge_segments(s32 live_edge_idx, GF_List *l)
    1981             : {
    1982          68 :         u32 i=0, count = gf_list_count(l);
    1983             :         u32 nb_removed_before_live = 0;
    1984             : 
    1985             :         //first remove all ll chunks until live edge if set
    1986          68 :         if (live_edge_idx>=0) {
    1987          21 :                 count = live_edge_idx;
    1988             : 
    1989         148 :                 for (i=0; i<count; i++) {
    1990         127 :                         GF_MPD_SegmentURL *seg = gf_list_get(l, i);
    1991         127 :                         if (!seg->hls_ll_chunk_type)
    1992          19 :                                 continue;
    1993             : 
    1994         108 :                         gf_list_rem(l, i);
    1995         108 :                         gf_mpd_segment_url_free(seg);
    1996         108 :                         i--;
    1997         108 :                         count--;
    1998         108 :                         live_edge_idx--;
    1999         108 :                         nb_removed_before_live++;
    2000             :                 }
    2001          21 :                 if (live_edge_idx<0) return nb_removed_before_live;
    2002          21 :                 count = gf_list_count(l);
    2003             :                 //skip live edge
    2004          21 :                 i++;
    2005             : 
    2006             :                 //skip parts following live edge
    2007          77 :                 while (i<count) {
    2008          35 :                         GF_MPD_SegmentURL *seg = gf_list_get(l, i);
    2009          35 :                         if (!seg->hls_ll_chunk_type)
    2010             :                                 break;
    2011          35 :                         i++;
    2012             :                 }
    2013             :         }
    2014             : 
    2015             :         //locate last full seg
    2016          68 :         while (i<count) {
    2017          47 :                 GF_MPD_SegmentURL *seg = gf_list_get(l, count-1);
    2018          47 :                 if (!seg->hls_ll_chunk_type)
    2019             :                         break;
    2020             :                 count--;
    2021           0 :                 if (!count) break;
    2022             :         }
    2023             :         //purge the rest
    2024         126 :         for (; i<count; i++) {
    2025         126 :                 GF_MPD_SegmentURL *seg = gf_list_get(l, i);
    2026         126 :                 if (!seg->hls_ll_chunk_type)
    2027         120 :                         continue;
    2028             : 
    2029           6 :                 gf_list_rem(l, i);
    2030           6 :                 gf_mpd_segment_url_free(seg);
    2031           6 :                 i--;
    2032           6 :                 count--;
    2033             :         }
    2034             :         return nb_removed_before_live;
    2035             : }
    2036             : 
    2037         100 : static GF_Err gf_dash_update_manifest(GF_DashClient *dash)
    2038             : {
    2039             :         GF_Err e;
    2040             :         Bool force_timeline_setup = GF_FALSE;
    2041             :         u32 group_idx, rep_idx, i, j;
    2042             :         u64 fetch_time=0;
    2043             :         GF_DOMParser *mpd_parser;
    2044             :         u8 signature[GF_SHA1_DIGEST_SIZE];
    2045             :         GF_MPD_Period *period=NULL, *new_period=NULL;
    2046             :         const char *local_url;
    2047             :         char mime[128];
    2048             :         char * purl;
    2049             :         Double timeline_start_time=0;
    2050             :         GF_MPD *new_mpd=NULL;
    2051             :         Bool fetch_only = GF_FALSE;
    2052             :         u32 nb_group_unchanged = 0;
    2053             :         Bool has_reps_unchanged = GF_FALSE;
    2054             : 
    2055             :         //HLS: do not reload the playlist, directly update the reps
    2056         100 :         if (dash->is_m3u8 && !dash->m3u8_reload_master) {
    2057             :                 new_mpd = NULL;
    2058             :                 new_period = NULL;
    2059          84 :                 fetch_time = dash_get_fetch_time(dash);
    2060          84 :                 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    2061          84 :                 goto process_m3u8_manifest;
    2062             :         }
    2063             : 
    2064          16 :         if (!dash->mpd_dnload) {
    2065             :                 local_url = purl = NULL;
    2066           8 :                 if (!gf_list_count(dash->mpd->locations)) {
    2067           8 :                         if (gf_file_exists(dash->base_url)) {
    2068           8 :                                 local_url = dash->base_url;
    2069             :                         }
    2070           8 :                         if (!local_url) {
    2071             :                                 /*we will no longer attempt to update the MPD ...*/
    2072           0 :                                 dash->mpd->minimum_update_period = 0;
    2073           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: no HTTP source for MPD could be found\n"));
    2074             :                                 return GF_BAD_PARAM;
    2075             :                         }
    2076             :                 }
    2077             :                 if (!local_url) {
    2078           0 :                         purl = gf_strdup(gf_list_get(dash->mpd->locations, 0));
    2079             : 
    2080             :                         /*if no absolute URL, use <Location> to get MPD in case baseURL is relative...*/
    2081           0 :                         if (!strstr(dash->base_url, "://")) {
    2082           0 :                                 gf_free(dash->base_url);
    2083           0 :                                 dash->base_url = gf_strdup(purl);
    2084             :                         }
    2085             :                         fetch_only = 1;
    2086             :                 }
    2087             :         } else {
    2088           8 :                 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
    2089           8 :                 if (local_url) {
    2090           8 :                         gf_file_delete(local_url);
    2091             :                 }
    2092             :                 //use the redirected url stored in base URL - DO NOT USE the redirected URL of the session since
    2093             :                 //the session may have been reused for period XLINK dowload.
    2094           8 :                 purl = gf_strdup( dash->base_url );
    2095             :         }
    2096             : 
    2097             :         /*if update location is specified, update - spec does not say whether location is a relative or absoute URL*/
    2098          16 :         if (gf_list_count(dash->mpd->locations)) {
    2099           0 :                 char *update_loc = gf_list_get(dash->mpd->locations, 0);
    2100           0 :                 char *update_url = gf_url_concatenate(purl, update_loc);
    2101           0 :                 if (update_url) {
    2102           0 :                         gf_free(purl);
    2103             :                         purl = update_url;
    2104             :                 }
    2105             :         }
    2106             : 
    2107          16 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating Playlist %s...\n", purl ? purl : local_url));
    2108          16 :         if (purl) {
    2109             :                 const char *mime_type;
    2110             :                 /*use non-persistent connection for MPD*/
    2111           8 :                 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), purl, 0, 0, 0, NULL);
    2112           8 :                 if (e!=GF_OK) {
    2113           0 :                         if (!dash->in_error) {
    2114           0 :                                 if (e==GF_URL_ERROR) {
    2115           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update manifest: %s, aborting\n", gf_error_to_string(e)));
    2116           0 :                                         dash->in_error = GF_TRUE;
    2117             :                                 } else {
    2118           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Error while fetching new manifest (%s) - will retry later\n", gf_error_to_string(e)));
    2119             :                                 }
    2120             :                         }
    2121           0 :                         gf_free(purl);
    2122             :                         //try to refetch MPD every second
    2123           0 :                         dash->last_update_time+=1000;
    2124           0 :                         return e;
    2125             :                 } else {
    2126           8 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist %s updated with success\n", purl));
    2127             :                 }
    2128             : 
    2129           8 :                 mime_type = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload) ;
    2130           8 :                 strcpy(mime, mime_type ? mime_type : "");
    2131           8 :                 strlwr(mime);
    2132             : 
    2133             :                 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
    2134           8 :                 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
    2135             : 
    2136             :                 /* Some servers, for instance http://tv.freebox.fr, serve m3u8 as text/plain */
    2137           8 :                 if (gf_dash_is_m3u8_mime(purl, mime) || strstr(purl, ".m3u8")) {
    2138           0 :                         new_mpd = gf_mpd_new();
    2139           0 :                         e = gf_m3u8_to_mpd(local_url, purl, NULL, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, new_mpd, GF_FALSE, dash->keep_files);
    2140           0 :                         if (e) {
    2141           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
    2142           0 :                                 gf_mpd_del(new_mpd);
    2143           0 :                                 return GF_NON_COMPLIANT_BITSTREAM;
    2144             :                         }
    2145             :                 }
    2146             : 
    2147           8 :                 gf_free(purl);
    2148             : 
    2149           8 :                 purl = (char *) dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload) ;
    2150             : 
    2151             :                 /*if relocated, reassign MPD base URL*/
    2152           8 :                 if (strcmp(purl, dash->base_url)) {
    2153           0 :                         gf_free(dash->base_url);
    2154           0 :                         dash->base_url = gf_strdup(purl);
    2155             : 
    2156             :                 }
    2157             :                 purl = NULL;
    2158             :         }
    2159             : 
    2160          16 :         fetch_time = dash_get_fetch_time(dash);
    2161             : 
    2162             :         // parse the mpd file for filling the GF_MPD structure. Note: for m3u8, MPD has been fetched above
    2163          16 :         if (!new_mpd) {
    2164          16 :                 u32 res = gf_dash_check_mpd_root_type(local_url);
    2165          16 :                 if ((res==1) && dash->is_smooth) res = 0;
    2166          16 :                 else if ((res==2) && !dash->is_smooth) res = 0;
    2167             : 
    2168          16 :                 if (!res) {
    2169           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: MPD file type is not correct %s\n", local_url));
    2170             :                         return GF_NON_COMPLIANT_BITSTREAM;
    2171             :                 }
    2172             : 
    2173          16 :                 if (gf_sha1_file( local_url, signature) != GF_OK) {
    2174           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] : cannot SHA1 file %s\n", local_url));
    2175             :                         return GF_IO_ERR;
    2176             :                 }
    2177             : 
    2178          16 :                 if (!dash->in_error && ! memcmp( signature, dash->lastMPDSignature, GF_SHA1_DIGEST_SIZE)) {
    2179             : 
    2180           1 :                         dash->reload_count++;
    2181           1 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] MPD file did not change for %d consecutive reloads\n", dash->reload_count));
    2182             :                         /*if the MPD did not change, we should refresh "soon" but cannot wait a full refresh cycle in case of
    2183             :                         low latencies as we could miss a segment*/
    2184             : 
    2185           1 :                         if (dash->is_m3u8) {
    2186           0 :                                 dash->last_update_time += dash->mpd->minimum_update_period/2;
    2187             :                         } else {
    2188           1 :                                 dash->last_update_time = gf_sys_clock();
    2189             :                         }
    2190             : 
    2191           1 :                         dash->mpd_fetch_time = fetch_time;
    2192           1 :                         return GF_OK;
    2193             :                 }
    2194             : 
    2195             :                 force_timeline_setup = dash->in_error;
    2196          15 :                 dash->in_error = GF_FALSE;
    2197          15 :                 dash->reload_count = 0;
    2198          15 :                 memcpy(dash->lastMPDSignature, signature, GF_SHA1_DIGEST_SIZE);
    2199             : 
    2200          15 :                 if (dash->dash_io->manifest_updated) {
    2201           0 :                         char *szName = gf_file_basename(dash->base_url);
    2202           0 :                         dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1);
    2203             :                 }
    2204             : 
    2205             :                 /* It means we have to reparse the file ... */
    2206             :                 /* parse the MPD */
    2207          15 :                 mpd_parser = gf_xml_dom_new();
    2208          15 :                 e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
    2209          15 :                 if (e != GF_OK) {
    2210           0 :                         gf_xml_dom_del(mpd_parser);
    2211           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in XML parsing %s\n", gf_error_to_string(e)));
    2212             :                         return GF_NON_COMPLIANT_BITSTREAM;
    2213             :                 }
    2214          15 :                 new_mpd = gf_mpd_new();
    2215          15 :                 if (dash->is_smooth)
    2216           0 :                         e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(mpd_parser), new_mpd, purl);
    2217             :                 else
    2218          15 :                         e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), new_mpd, purl);
    2219          15 :                 gf_xml_dom_del(mpd_parser);
    2220          15 :                 if (e) {
    2221           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: error in MPD creation %s\n", gf_error_to_string(e)));
    2222           0 :                         gf_mpd_del(new_mpd);
    2223           0 :                         return GF_NON_COMPLIANT_BITSTREAM;
    2224             :                 }
    2225          15 :                 if (dash->ignore_xlink)
    2226           0 :                         dash_purge_xlink(new_mpd);
    2227             : 
    2228          15 :                 if (!e && dash->split_adaptation_set)
    2229           0 :                         gf_mpd_split_adaptation_sets(new_mpd);
    2230             : 
    2231             :         }
    2232             : 
    2233             :         assert(new_mpd);
    2234             : 
    2235          15 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    2236          15 :         if (fetch_only  && !period) goto exit;
    2237             : 
    2238             : #ifndef GPAC_DISABLE_LOG
    2239          15 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Updated manifest:\n"));
    2240          18 :         for (i=0; i<gf_list_count(new_mpd->periods); i++) {
    2241          18 :                 GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
    2242          18 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tP#%d: start "LLU" - duration " LLU" - xlink %s\n", i+1, ap->start, ap->duration, ap->xlink_href ? ap->xlink_href : "none"));
    2243             :         }
    2244             : #endif
    2245             : 
    2246             :         //if current period was a remote period, do a pass on the new manifest periods, check for xlink
    2247          30 :         if (period->origin_base_url || period->broken_xlink) {
    2248           0 : restart_period_check:
    2249           0 :                 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
    2250           0 :                         new_period = gf_list_get(new_mpd->periods, i);
    2251           0 :                         if (!new_period->xlink_href) continue;
    2252             : 
    2253           0 :                         if ((new_period->start<=period->start) &&
    2254           0 :                                 ( (new_period->start+new_period->duration >= period->start + period->duration) || (!new_period->duration))
    2255             :                         ) {
    2256             :                                 const char *base_url;
    2257             :                                 u32 insert_idx;
    2258           0 :                                 if (period->type == GF_MPD_TYPE_DYNAMIC) {
    2259           0 :                                         gf_dash_solve_period_xlink(dash, new_mpd->periods, i);
    2260           0 :                                         goto restart_period_check;
    2261             :                                 }
    2262             :                                 //this is a static period xlink, no need to further update the mpd, jsut swap the old periods in the new MPD
    2263             : 
    2264           0 :                                 base_url = period->origin_base_url ? period->origin_base_url : period->broken_xlink;
    2265           0 :                                 if (!base_url) {
    2266           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - found new Xlink period overlapping a non-xlink period in original manifest\n"));
    2267           0 :                                         gf_mpd_del(new_mpd);
    2268           0 :                                         return GF_NON_COMPLIANT_BITSTREAM;
    2269             :                                 }
    2270             :                                 insert_idx = i;
    2271           0 :                                 gf_list_rem(new_mpd->periods, i);
    2272           0 :                                 for (j=0; j<gf_list_count(dash->mpd->periods); j++) {
    2273           0 :                                         GF_MPD_Period *ap = gf_list_get(dash->mpd->periods, j);
    2274           0 :                                         if (!ap->origin_base_url && !ap->broken_xlink) continue;
    2275           0 :                                         if (ap->origin_base_url && strcmp(ap->origin_base_url, base_url)) continue;
    2276           0 :                                         if (ap->broken_xlink && strcmp(ap->broken_xlink, base_url)) continue;
    2277           0 :                                         gf_list_rem(dash->mpd->periods, j);
    2278           0 :                                         j--;
    2279           0 :                                         gf_list_insert(new_mpd->periods, ap, insert_idx);
    2280           0 :                                         insert_idx++;
    2281             :                                 }
    2282             :                                 //update active period index in new list
    2283           0 :                                 dash->active_period_index = gf_list_find(new_mpd->periods, period);
    2284             :                                 assert((s32)dash->active_period_index >= 0);
    2285             :                                 //this will do the garbage collection
    2286           0 :                                 gf_list_add(dash->mpd->periods, new_period);
    2287           0 :                                 goto exit;
    2288             :                         }
    2289             :                         new_period=NULL;
    2290             :                 }
    2291             :         }
    2292             : 
    2293             :         new_period = NULL;
    2294           1 :         for (i=0; i<gf_list_count(new_mpd->periods); i++) {
    2295          16 :                 new_period = gf_list_get(new_mpd->periods, i);
    2296          16 :                 if (new_period->start==period->start) break;
    2297             :                 new_period=NULL;
    2298             :         }
    2299             : 
    2300          15 :         if (!new_period) {
    2301           0 :                 if (dash->mpd->type == GF_MPD_TYPE_DYNAMIC) {
    2302           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] current active period not found in MPD update - assuming end of active period and switching to first period in new MPD\n"));
    2303             : 
    2304             :                         //assume the old period is no longer valid
    2305           0 :                         dash->active_period_index = 0;
    2306           0 :                         dash->request_period_switch = GF_TRUE;
    2307           0 :                         for (i=0; i<gf_list_count(dash->groups); i++) {
    2308           0 :                                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    2309           0 :                                 gf_dash_mark_group_done(group);
    2310           0 :                                 group->adaptation_set = NULL;
    2311             :                         }
    2312             :                         goto exit;
    2313             :                 }
    2314           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing period\n"));
    2315           0 :                 gf_mpd_del(new_mpd);
    2316           0 :                 return GF_NON_COMPLIANT_BITSTREAM;
    2317             :         }
    2318             : 
    2319          15 :         dash->active_period_index = gf_list_find(new_mpd->periods, new_period);
    2320             :         assert((s32)dash->active_period_index >= 0);
    2321             : 
    2322          15 :         if (gf_list_count(period->adaptation_sets) != gf_list_count(new_period->adaptation_sets)) {
    2323           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing AdaptationSet\n"));
    2324           0 :                 gf_mpd_del(new_mpd);
    2325           0 :                 return GF_NON_COMPLIANT_BITSTREAM;
    2326             :         }
    2327             : 
    2328          15 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updating playlist at UTC time "LLU" - availabilityStartTime "LLU"\n", fetch_time, new_mpd->availabilityStartTime));
    2329             : 
    2330             :         timeline_start_time = 0;
    2331             :         /*if not infinity for timeShift, compute min media time before merge and adjust it*/
    2332          15 :         if (dash->mpd->time_shift_buffer_depth != (u32) -1) {
    2333           7 :                 Double timeshift = dash->mpd->time_shift_buffer_depth;
    2334           7 :                 timeshift /= 1000;
    2335             : 
    2336          18 :                 for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
    2337          11 :                         GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    2338          11 :                         if (group->selection!=GF_DASH_GROUP_NOT_SELECTABLE) {
    2339             :                                 Double group_start = gf_dash_get_segment_start_time(group, NULL);
    2340          11 :                                 if (!group_idx || (timeline_start_time > group_start) ) timeline_start_time = group_start;
    2341             :                         }
    2342             :                 }
    2343             :                 /*we can rewind our segments from timeshift*/
    2344           7 :                 if (timeline_start_time > timeshift) timeline_start_time -= timeshift;
    2345             :                 /*we can rewind all segments*/
    2346             :                 else timeline_start_time = 0;
    2347             :         }
    2348             : 
    2349             :         /*update segmentTimeline at Period level*/
    2350          15 :         e = gf_dash_merge_segment_timeline(NULL, dash, period->segment_list, period->segment_template, new_period->segment_list, new_period->segment_template, timeline_start_time);
    2351          15 :         if (e) {
    2352           0 :                 gf_mpd_del(new_mpd);
    2353           0 :                 return e;
    2354             :         }
    2355             : 
    2356         114 : process_m3u8_manifest:
    2357             : 
    2358         196 :         for (group_idx=0; group_idx<gf_list_count(dash->groups); group_idx++) {
    2359             :                 GF_MPD_AdaptationSet *set, *new_set=NULL;
    2360             :                 u32 rep_i;
    2361             :                 u32 nb_rep_unchanged = 0;
    2362          97 :                 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    2363             : 
    2364             :                 /*update info even if the group is not selected !*/
    2365          97 :                 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
    2366           0 :                         continue;
    2367             : 
    2368          97 :                 set = group->adaptation_set;
    2369             : 
    2370          97 :                 if (new_period) {
    2371          13 :                         new_set = gf_list_get(new_period->adaptation_sets, group_idx);
    2372             : 
    2373             :                         //sort by bandwidth and quality
    2374          13 :                         for (rep_i = 1; rep_i < gf_list_count(new_set->representations); rep_i++) {
    2375             :                                 Bool swap=GF_FALSE;
    2376           0 :                                 GF_MPD_Representation *r2 = gf_list_get(new_set->representations, rep_i);
    2377           0 :                                 GF_MPD_Representation *r1 = gf_list_get(new_set->representations, rep_i-1);
    2378           0 :                                 if (r1->bandwidth > r2->bandwidth) {
    2379             :                                         swap=GF_TRUE;
    2380           0 :                                 } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
    2381             :                                         swap=GF_TRUE;
    2382             :                                 }
    2383             :                                 if (swap) {
    2384           0 :                                         gf_list_rem(new_set->representations, rep_i);
    2385           0 :                                         gf_list_insert(new_set->representations, r2, rep_i-1);
    2386             :                                         rep_i=0;
    2387             :                                 }
    2388             :                         }
    2389             : 
    2390          13 :                         if (gf_list_count(new_set->representations) != gf_list_count(group->adaptation_set->representations)) {
    2391           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: missing representation in adaptation set\n"));
    2392           0 :                                 gf_mpd_del(new_mpd);
    2393           0 :                                 return GF_NON_COMPLIANT_BITSTREAM;
    2394             :                         }
    2395             :                 }
    2396             : 
    2397             :                 /*get all representations in both periods*/
    2398         267 :                 for (rep_idx = 0; rep_idx <gf_list_count(group->adaptation_set->representations); rep_idx++) {
    2399             :                         GF_List *segments, *new_segments;
    2400         170 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_idx);
    2401         170 :                         GF_MPD_Representation *new_rep = new_set ? gf_list_get(new_set->representations, rep_idx) : NULL;
    2402             :                         GF_MPD_Representation *hls_temp_rep = NULL;
    2403             : 
    2404         170 :                         rep->playback.not_modified = GF_FALSE;
    2405             : 
    2406         170 :                         if (rep->segment_base || group->adaptation_set->segment_base || period->segment_base) {
    2407             :                                 assert(new_rep);
    2408           0 :                                 if (!new_rep->segment_base && !new_set->segment_base && !new_period->segment_base) {
    2409           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment base as previous version\n"));
    2410           0 :                                         gf_mpd_del(new_mpd);
    2411           0 :                                         return GF_NON_COMPLIANT_BITSTREAM;
    2412             :                                 }
    2413             :                                 /*what else should we check ??*/
    2414             : 
    2415             :                                 /*OK, this rep is fine*/
    2416             :                         }
    2417             : 
    2418         170 :                         else if (rep->segment_template || group->adaptation_set->segment_template || period->segment_template) {
    2419             :                                 assert(new_rep);
    2420          13 :                                 if (!new_rep->segment_template && !new_set->segment_template && !new_period->segment_template) {
    2421           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment template as previous version\n"));
    2422           0 :                                         gf_mpd_del(new_mpd);
    2423           0 :                                         return GF_NON_COMPLIANT_BITSTREAM;
    2424             :                                 }
    2425             :                                 //if no segment timeline, adjust current idx of the group if start number changes (not needed if SegmentTimeline, otherwise, we will look for the index with the same starttime in the timeline)
    2426          13 :                                 if ((period->segment_template && period->segment_template->segment_timeline)
    2427          13 :                                         || (set->segment_template && set->segment_template->segment_timeline)
    2428           5 :                                         || (rep->segment_template && rep->segment_template->segment_timeline)
    2429             :                                    ) {
    2430             :                                 } else {
    2431             :                                         s32 sn_diff = 0;
    2432             : 
    2433           5 :                                         if (period->segment_template && (period->segment_template->start_number != (u32) -1) ) sn_diff = period->segment_template->start_number;
    2434           5 :                                         else if (set->segment_template && (set->segment_template->start_number != (u32) -1) ) sn_diff = set->segment_template->start_number;
    2435           0 :                                         else if (rep->segment_template && (rep->segment_template->start_number != (u32) -1) ) sn_diff = rep->segment_template->start_number;
    2436             : 
    2437           5 :                                         if (new_period->segment_template && (new_period->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_period->segment_template->start_number;
    2438           5 :                                         else if (new_set->segment_template && (new_set->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_set->segment_template->start_number;
    2439           0 :                                         else if (new_rep->segment_template && (new_rep->segment_template->start_number != (u32) -1) ) sn_diff -= (s32) new_rep->segment_template->start_number;
    2440             : 
    2441           5 :                                         if (sn_diff != 0) {
    2442           0 :                                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] startNumber change for SegmentTemplate without SegmentTimeline - adjusting current segment index by %d\n", sn_diff));
    2443           0 :                                                 group->download_segment_index += sn_diff;
    2444             :                                         }
    2445             :                                 }
    2446             :                                 /*OK, this rep is fine*/
    2447             :                         }
    2448             :                         else {
    2449             :                                 Bool same_rep = GF_FALSE;
    2450             :                                 /*we're using segment list*/
    2451             :                                 assert(rep->segment_list || group->adaptation_set->segment_list || period->segment_list);
    2452             : 
    2453             :                                 //HLS case
    2454         157 :                                 if (!new_rep) {
    2455             :                                         assert(rep->segment_list);
    2456             :                                         assert(rep->segment_list->previous_xlink_href || rep->segment_list->xlink_href);
    2457         157 :                                         hls_temp_rep = gf_mpd_representation_new();
    2458         157 :                                         GF_SAFEALLOC(hls_temp_rep->segment_list, GF_MPD_SegmentList);
    2459         157 :                                         hls_temp_rep->segment_list->segment_URLs = gf_list_new();
    2460         157 :                                         if (rep->segment_list->xlink_href)
    2461           0 :                                                 hls_temp_rep->segment_list->xlink_href = gf_strdup(rep->segment_list->xlink_href);
    2462             :                                         else
    2463         157 :                                                 hls_temp_rep->segment_list->xlink_href = gf_strdup(rep->segment_list->previous_xlink_href);
    2464             : 
    2465             :                                         new_rep = hls_temp_rep;
    2466             :                                 }
    2467         157 :                                 if (group->active_rep_index!=rep_idx)
    2468             :                                         same_rep = GF_TRUE;
    2469             : 
    2470             :                                 //if we have a xlink_href in segment_list, solve it for the active quality only
    2471         212 :                                 while (new_rep->segment_list->xlink_href && !same_rep) {
    2472             :                                         u32 retry=10;
    2473          84 :                                         Bool is_static = GF_FALSE;
    2474          84 :                                         u64 dur = 0;
    2475             : 
    2476          84 :                                         if (new_rep->segment_list->consecutive_xlink_count) {
    2477           0 :                                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Resolving a XLINK pointed from another XLINK (%d consecutive XLINK in segment list)\n", new_rep->segment_list->consecutive_xlink_count));
    2478             :                                         }
    2479             : 
    2480          84 :                                         while (retry) {
    2481          84 :                                                 if (dash->is_m3u8) {
    2482          84 :                                                         if (!rep_idx)
    2483             :                                                                 rep_idx = 0;
    2484          84 :                                                         e = gf_dash_solve_m3u8_representation_xlink(group, new_rep, &is_static, &dur, rep->playback.xlink_digest);
    2485             :                                                 } else {
    2486           0 :                                                         e = gf_dash_solve_representation_xlink(group->dash, new_rep, rep->playback.xlink_digest);
    2487             :                                                 }
    2488          84 :                                                 if (e==GF_OK) break;
    2489          29 :                                                 if (e==GF_EOS) {
    2490             :                                                         same_rep = GF_TRUE;
    2491             :                                                         e = GF_OK;
    2492             :                                                         break;
    2493             :                                                 }
    2494           0 :                                                 if (e==GF_NON_COMPLIANT_BITSTREAM) break;
    2495           0 :                                                 if (e==GF_OUT_OF_MEM) break;
    2496           0 :                                                 if (group->dash->dash_state != GF_DASH_STATE_RUNNING)
    2497             :                                                         break;
    2498             : 
    2499           0 :                                                 gf_sleep(100);
    2500           0 :                                                 retry --;
    2501             :                                         }
    2502             : 
    2503          84 :                                         if (same_rep) break;
    2504             : 
    2505          55 :                                         if (e==GF_OK) {
    2506          55 :                                                 if (dash->is_m3u8 && is_static) {
    2507           0 :                                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[m3u8] MPD type changed from dynamic to static\n"));
    2508           0 :                                                         group->dash->mpd->type = GF_MPD_TYPE_STATIC;
    2509           0 :                                                         group->dash->mpd->minimum_update_period = 0;
    2510           0 :                                                         if (group->dash->mpd->media_presentation_duration < dur)
    2511           0 :                                                                 group->dash->mpd->media_presentation_duration = dur;
    2512           0 :                                                         if (group->period->duration < dur)
    2513           0 :                                                                 group->period->duration = dur;
    2514             :                                                 }
    2515             :                                         }
    2516             :                                 }
    2517             : 
    2518         157 :                                 if (same_rep==GF_TRUE) {
    2519         102 :                                         rep->playback.not_modified = GF_TRUE;
    2520         102 :                                         nb_rep_unchanged ++;
    2521             :                                         has_reps_unchanged = GF_TRUE;
    2522         102 :                                         if (hls_temp_rep)
    2523         102 :                                                 gf_mpd_representation_free(hls_temp_rep);
    2524         102 :                                         if (group->hls_next_seq_num) {
    2525          90 :                                                 HLS_MIN_RELOAD_TIME(dash)
    2526             :                                         }
    2527         102 :                                         continue;
    2528             :                                 }
    2529             : 
    2530          55 :                                 if (!new_rep->segment_list && (!new_set || !new_set->segment_list) && (!new_period || !new_period->segment_list)) {
    2531           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update playlist: representation does not use segment list as previous version\n"));
    2532           0 :                                         gf_mpd_del(new_mpd);
    2533           0 :                                         return GF_NON_COMPLIANT_BITSTREAM;
    2534             :                                 }
    2535             :                                 /*get the segment list*/
    2536             :                                 segments = new_segments = NULL;
    2537          55 :                                 if (period->segment_list && period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
    2538          55 :                                 if (set->segment_list && set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
    2539          55 :                                 if (rep->segment_list && rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
    2540             : 
    2541          55 :                                 if (new_period && new_period->segment_list && new_period->segment_list->segment_URLs) new_segments = new_period->segment_list->segment_URLs;
    2542          55 :                                 if (new_set && new_set->segment_list && new_set->segment_list->segment_URLs) new_segments = new_set->segment_list->segment_URLs;
    2543          55 :                                 if (new_rep->segment_list && new_rep->segment_list->segment_URLs) new_segments = new_rep->segment_list->segment_URLs;
    2544             : 
    2545             :                                 Bool skip_next_seg_url = GF_FALSE;
    2546             :                                 Bool has_ll_hls = GF_FALSE;
    2547             :                                 Bool live_edge_passed = GF_FALSE;
    2548             :                                 u32 dld_index_offset = 0;
    2549             :                                 GF_MPD_SegmentURL *first_added_chunk = NULL;
    2550          55 :                                 GF_MPD_SegmentURL *hls_last_chunk = group->llhls_edge_chunk;
    2551             : 
    2552          55 :                                 if (dash->is_m3u8 && group->is_low_latency) {
    2553          48 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: merging segments, current live chunk %s\n", rep_idx+1, hls_last_chunk ? hls_last_chunk->media : "none"));
    2554             : 
    2555             :                                         //active rep, figure out the diff between the scheduled last chunk and the download index
    2556             :                                         //typically if update_mpd is called after a segment is downloaded but before next segment is evaluated for download
    2557             :                                         //there is a diff of 1 seg
    2558             :                                         //we reapply this diff when updating download_segment_index below
    2559          48 :                                         if ((group->active_rep_index==rep_idx) && hls_last_chunk) {
    2560          48 :                                                 s32 pos = gf_list_find(segments, hls_last_chunk);
    2561          48 :                                                 if ((pos>=0) && (pos < group->download_segment_index))
    2562          11 :                                                         dld_index_offset = (s32) group->download_segment_index - pos;
    2563             :                                         }
    2564             :                                 }
    2565             : 
    2566             : //#define DUMP_LIST
    2567             : 
    2568          55 :                                 if (dash->is_m3u8) {
    2569             :                                         //preprocess new segment list, starting from end and locate the most recent full segment already present
    2570             :                                         // in old list before the live edge
    2571             :                                         //we start browsing from old list since it is likely the last segments in new list are not present at all in the old
    2572          55 :                                         u32 nb_new_segs = gf_list_count(new_segments);
    2573          55 :                                         u32 nb_old_segs = gf_list_count(segments);
    2574             :                                         s32 purge_segs_until = -1;
    2575             : 
    2576             : #ifdef DUMP_LIST
    2577             :                                         fprintf(stderr, "New list received:\n");
    2578             :                                         for (i=0; i<nb_new_segs; i++) {
    2579             :                                                 GF_MPD_SegmentURL *surl = gf_list_get(new_segments, i);
    2580             :                                                 fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
    2581             :                                         }
    2582             : #endif
    2583             : 
    2584             : 
    2585         168 :                                         for (j=nb_old_segs; j>0; j--) {
    2586         100 :                                                 GF_MPD_SegmentURL *old_seg = gf_list_get(segments, j-1);
    2587             :                                                 //ignore chunks
    2588         100 :                                                 if (old_seg->hls_ll_chunk_type) continue;
    2589             :                                                 //if live edge dont't trash anything after it, append and parts merge is done below
    2590          79 :                                                 if (hls_last_chunk && (hls_last_chunk->hls_seq_num <= old_seg->hls_seq_num)) continue;
    2591         256 :                                                 for (i=nb_new_segs; i>0; i--) {
    2592         256 :                                                         GF_MPD_SegmentURL *new_seg = gf_list_get(new_segments, i-1);
    2593             :                                                         //ignore chunks
    2594         256 :                                                         if (new_seg->hls_ll_chunk_type) continue;
    2595             : 
    2596             :                                                         //match, we will trash everything in new list until this point
    2597          94 :                                                         if (old_seg->hls_seq_num == new_seg->hls_seq_num) {
    2598          42 :                                                                 purge_segs_until = (s32) i-1;
    2599          42 :                                                                 break;
    2600             :                                                         }
    2601             :                                                 }
    2602          42 :                                                 if (purge_segs_until>=0) break;
    2603             :                                         }
    2604         449 :                                         while (purge_segs_until>=0) {
    2605         394 :                                                 GF_MPD_SegmentURL *new_seg = gf_list_pop_front(new_segments);
    2606         394 :                                                 gf_mpd_segment_url_free(new_seg);
    2607         394 :                                                 purge_segs_until--;
    2608             :                                         }
    2609             :                                 }
    2610             : 
    2611             :                                 //browse new list - this is for both DASH or HLS
    2612         325 :                                 for (i=0; i<gf_list_count(new_segments); i++) {
    2613         325 :                                         GF_MPD_SegmentURL *new_seg = gf_list_get(new_segments, i);
    2614         325 :                                         u32 nb_segs = gf_list_count(segments);
    2615             :                                         Bool found = GF_FALSE;
    2616             : 
    2617             :                                         //part of a segment
    2618         325 :                                         if (new_seg->hls_ll_chunk_type) {
    2619             :                                                 has_ll_hls = GF_TRUE;
    2620             : 
    2621             :                                                 //remaining chunk of our live edge, add it (skip loop below)
    2622         254 :                                                 if (skip_next_seg_url)
    2623             :                                                         nb_segs = 0;
    2624             :                                         }
    2625             : 
    2626             :                                         //find this segment in the old list
    2627         958 :                                         for (j = 0; j<nb_segs; j++) {
    2628         696 :                                                 GF_MPD_SegmentURL *seg = gf_list_get(segments, j);
    2629             : 
    2630             :                                                 //compare only segs or parts
    2631         696 :                                                 if (new_seg->hls_ll_chunk_type && !seg->hls_ll_chunk_type)
    2632         435 :                                                         continue;
    2633         261 :                                                 if (!new_seg->hls_ll_chunk_type && seg->hls_ll_chunk_type)
    2634          29 :                                                         continue;
    2635             : 
    2636             :                                                 //full segment
    2637         232 :                                                 if (!seg->media || !new_seg->media || strcmp(seg->media, new_seg->media))
    2638         133 :                                                         continue;
    2639             : 
    2640          99 :                                                 if (seg->media_range && new_seg->media_range) {
    2641          58 :                                                         if ((seg->media_range->start_range != new_seg->media_range->start_range) ||
    2642          11 :                                                                 (seg->media_range->end_range != new_seg->media_range->end_range)
    2643             :                                                         ) {
    2644          36 :                                                                 continue;
    2645             :                                                         }
    2646             :                                                 }
    2647             :                                                 //we already had the segment, do not add
    2648             :                                                 found = 1;
    2649             : 
    2650             :                                                 //this is the live edge !
    2651          63 :                                                 if (seg==hls_last_chunk) {
    2652             :                                                         skip_next_seg_url = GF_TRUE;
    2653             :                                                 }
    2654             :                                                 break;
    2655             :                                         }
    2656             : 
    2657             :                                         //remove all part before live edge - parts after live edge with a full seg following will be purged once merged
    2658         325 :                                         if (!found && !skip_next_seg_url && new_seg->hls_ll_chunk_type && !live_edge_passed) {
    2659             :                                                 found = GF_TRUE;
    2660             :                                         }
    2661             :                                         //first full seg after our live edge, insert before the first fragment of this segment still in our list
    2662         325 :                                         if (!new_seg->hls_ll_chunk_type && skip_next_seg_url && !found) {
    2663             :                                                 //starting from our current live edge, rewind and insert after the first full segment found
    2664           5 :                                                 s32 pos = group->download_segment_index;
    2665          33 :                                                 while (pos>0) {
    2666          25 :                                                         GF_MPD_SegmentURL *prev = gf_list_get(segments, pos);
    2667          25 :                                                         if (!prev) break;
    2668          25 :                                                         if (!prev->hls_ll_chunk_type) {
    2669           2 :                                                                 gf_list_insert(segments, new_seg, pos+1);
    2670             :                                                                 pos = pos+1;
    2671           2 :                                                                 break;
    2672             :                                                         }
    2673          23 :                                                         pos--;
    2674             :                                                 }
    2675             :                                                 assert(pos>=0);
    2676           5 :                                                 if (pos==0) {
    2677           3 :                                                         gf_list_insert(segments, new_seg, 0);
    2678             :                                                 }
    2679             :                                                 //remove from new segments
    2680           5 :                                                 gf_list_rem(new_segments, i);
    2681           5 :                                                 i--;
    2682             : 
    2683           5 :                                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: Injecting segment %s before LL chunk\n", rep_idx+1, new_seg->media));
    2684             :                                                 found = GF_TRUE;
    2685             :                                                 //no longer at live edge point
    2686             :                                                 skip_next_seg_url = GF_FALSE;
    2687             :                                                 //edge is now passed, do not discard following parts
    2688             :                                                 live_edge_passed = GF_TRUE;
    2689             :                                                 first_added_chunk = NULL;
    2690             :                                         }
    2691             : 
    2692             :                                         /*this is a new segment, merge it: we remove from new list and push to old one, before doing a final swap
    2693             :                                         this ensures that indexing in the segment_list is still correct after merging*/
    2694         325 :                                         if (!found) {
    2695          59 :                                                 gf_list_rem(new_segments, i);
    2696          59 :                                                 i--;
    2697             : 
    2698          59 :                                                 if (new_seg->hls_ll_chunk_type) {
    2699          35 :                                                         if (!first_added_chunk) {
    2700             :                                                                 first_added_chunk = new_seg;
    2701             :                                                         }
    2702             :                                                 } else {
    2703             :                                                         //we just flushed a new full seg, remove all parts
    2704          24 :                                                         if (first_added_chunk) {
    2705           0 :                                                                 s32 pos = gf_list_find(segments, first_added_chunk);
    2706             :                                                                 assert(pos>=0);
    2707           0 :                                                                 while (pos < (s32) gf_list_count(segments)) {
    2708           0 :                                                                         GF_MPD_SegmentURL *surl = gf_list_pop_back(segments);
    2709             :                                                                         assert(surl->hls_ll_chunk_type);
    2710           0 :                                                                         gf_mpd_segment_url_free(surl);
    2711             :                                                                 }
    2712             :                                                                 first_added_chunk = NULL;
    2713             :                                                         }
    2714             :                                                 }
    2715             : 
    2716          59 :                                                 gf_list_add(segments, new_seg);
    2717          59 :                                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation #%d: Adding new segment %s\n", rep_idx+1, new_seg->media));
    2718             :                                         }
    2719             :                                 }
    2720             :                                 /*what else should we check ?*/
    2721             : 
    2722             : #ifdef DUMP_LIST
    2723             :                                 fprintf(stderr, "updated segment list before purge\n");
    2724             :                                 for (i=0; i<gf_list_count(segments); i++) {
    2725             :                                         GF_MPD_SegmentURL *surl = gf_list_get(segments, i);
    2726             :                                         fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
    2727             :                                 }
    2728             : #endif
    2729             : 
    2730          55 :                                 if (dash->is_m3u8) {
    2731             :                                         //also remove all segments older than min seq num in new rep
    2732           0 :                                         while (1) {
    2733          55 :                                                 GF_MPD_SegmentURL *old_seg = gf_list_get(segments, 0);
    2734          55 :                                                 if (!old_seg) break;
    2735          55 :                                                 if (old_seg->hls_seq_num >= new_rep->m3u8_media_seq_min)
    2736             :                                                         break;
    2737             : 
    2738           0 :                                                 gf_list_rem(segments, 0);
    2739           0 :                                                 gf_mpd_segment_url_free(old_seg);
    2740             :                                         }
    2741             :                                 } else {
    2742             :                                         //todo for MPD with rep update on xlink (no existing profile use this)
    2743           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation using segment list in live: purging not implemented, patch welcome\n"));
    2744             :                                 }
    2745             : 
    2746             :                                 /*swap segment list content*/
    2747          55 :                                 gf_list_swap(new_segments, segments);
    2748             : 
    2749             :                                 /*LL-HLS, cleanup everything except part around "our" live edge and part at manifest live edge*/
    2750          55 :                                 if (has_ll_hls) {
    2751             :                                         s32 live_edge_idx = -1;
    2752             :                                         //active rep, find live edge index before purge
    2753          46 :                                         if (group->llhls_edge_chunk && (group->active_rep_index==rep_idx)) {
    2754          46 :                                                 live_edge_idx = gf_list_find(new_segments, group->llhls_edge_chunk);
    2755             :                                         }
    2756          46 :                                         ls_hls_purge_segments(live_edge_idx, new_segments);
    2757             : 
    2758             :                                         //active rep, update download_segment_index after purge
    2759          46 :                                         if (group->llhls_edge_chunk && (group->active_rep_index==rep_idx)) {
    2760          46 :                                                 live_edge_idx = gf_list_find(new_segments, group->llhls_edge_chunk);
    2761          46 :                                                 if (live_edge_idx>=0)
    2762          11 :                                                         group->download_segment_index = (u32) live_edge_idx + dld_index_offset;
    2763             :                                         }
    2764             :                                 }
    2765             : 
    2766             : #ifdef DUMP_LIST
    2767             :                                 fprintf(stderr, "%d updated segment list - min/max seq num in new list %d / %d\n", gf_sys_clock(), new_rep->m3u8_media_seq_min, new_rep->m3u8_media_seq_max);
    2768             : 
    2769             :                                 for (i=0; i<gf_list_count(new_segments); i++) {
    2770             :                                         GF_MPD_SegmentURL *surl = gf_list_get(new_segments, i);
    2771             :                                         fprintf(stderr, "\tsegment %s - chunk type %d\n", surl->media, surl->hls_ll_chunk_type);
    2772             :                                 }
    2773             : #endif
    2774             : 
    2775             :                                 //HLS live: if a new time is set (active group only), we just switched between qualities
    2776             :                                 //locate the segment with the same start time in the manifest, and purge previous ones
    2777             :                                 //it may happen that the manifest does still not contain the segment we are looking for, force an MPD update
    2778          55 :                                 if (group->hls_next_seq_num && (group->active_rep_index==rep_idx)) {
    2779             :                                         Bool found = GF_FALSE;
    2780          40 :                                         u32 k, count = gf_list_count(new_segments);
    2781             : 
    2782             :                                         //browse new segment list in reverse order, looking for our desired seq num
    2783          80 :                                         for (k=count; k>0; k--) {
    2784          40 :                                                 GF_MPD_SegmentURL *segu = (GF_MPD_SegmentURL *) gf_list_get(new_segments, k-1);
    2785          40 :                                                 if (segu->hls_ll_chunk_type && !segu->is_first_part)
    2786           0 :                                                         continue;
    2787             : 
    2788          40 :                                                 if (group->hls_next_seq_num == segu->hls_seq_num) {
    2789          12 :                                                         group->download_segment_index = k-1;
    2790             :                                                         found = GF_TRUE;
    2791          12 :                                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] HLS switching qualities on %s%s\n", segu->media, segu->hls_ll_chunk_type ? " - live edge" : ""));
    2792             :                                                         break;
    2793             :                                                 }
    2794             :                                                 /*
    2795             :                                                 "In order to play the presentation normally, the next Media Segment to load is the one with the
    2796             :                                                 lowest Media Sequence Number that is greater than the Media Sequence Number of the last Media Segment loaded."
    2797             : 
    2798             :                                                 so we store this one, but continue until we find an exact match, if any
    2799             :                                                 */
    2800          28 :                                                 else if (segu->hls_seq_num > group->hls_next_seq_num) {
    2801           0 :                                                         group->download_segment_index = k-1;
    2802             :                                                         found = GF_TRUE;
    2803             :                                                 }
    2804             :                                                 //seg num is lower than our requested next, abort browsing
    2805             :                                                 else
    2806             :                                                         break;
    2807             :                                         }
    2808             :                                         //not yet available
    2809          28 :                                         if (!found) {
    2810             :                                                 //use group last modification time
    2811          28 :                                                 u32 timer = gf_sys_clock() - group->last_mpd_change_time;
    2812          28 :                                                 if (!group->segment_duration || (timer < group->segment_duration * 2000) ) {
    2813          28 :                                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Cannot find segment for given HLS SN %d - forcing manifest update\n", group->hls_next_seq_num));
    2814          28 :                                                         HLS_MIN_RELOAD_TIME(dash)
    2815             :                                                 } else {
    2816           0 :                                                         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", timer));
    2817           0 :                                                         gf_dash_mark_group_done(group);
    2818           0 :                                                         group->hls_next_seq_num = 0;
    2819             :                                                 }
    2820             :                                         } else {
    2821          12 :                                                 group->hls_next_seq_num = 0;
    2822             :                                         }
    2823             :                                 }
    2824             : 
    2825             :                                 /*current representation is the active one in the group - update the number of segments*/
    2826          55 :                                 if (group->active_rep_index==rep_idx) {
    2827          55 :                                         group->nb_segments_in_rep = gf_list_count(new_segments);
    2828             :                                 }
    2829             :                         }
    2830             : 
    2831          68 :                         e = gf_dash_merge_segment_timeline(group, NULL, rep->segment_list, rep->segment_template, new_rep->segment_list, new_rep->segment_template, timeline_start_time);
    2832          68 :                         if (e) {
    2833           0 :                                 gf_mpd_del(new_mpd);
    2834           0 :                                 return e;
    2835             :                         }
    2836             : 
    2837             :                         //move redirections in representations base URLs (we could do that on AS as well )
    2838          68 :                         if (rep->base_URLs && new_rep->base_URLs) {
    2839             :                                 u32 k;
    2840           0 :                                 for (i=0; i<gf_list_count(new_rep->base_URLs); i++) {
    2841           0 :                                         GF_MPD_BaseURL *n_url = gf_list_get(new_rep->base_URLs, i);
    2842           0 :                                         if (!n_url->URL) continue;
    2843             : 
    2844           0 :                                         for (k=0; k<gf_list_count(rep->base_URLs); k++) {
    2845           0 :                                                 GF_MPD_BaseURL *o_url = gf_list_get(rep->base_URLs, k);
    2846           0 :                                                 if (o_url->URL && !strcmp(o_url->URL, n_url->URL)) {
    2847           0 :                                                         n_url->redirection = o_url->redirection;
    2848           0 :                                                         o_url->redirection = NULL;
    2849             :                                                 }
    2850             :                                         }
    2851             :                                 }
    2852             :                         }
    2853             : 
    2854             :                         /*what else should we check ??*/
    2855             : 
    2856             : 
    2857          68 :                         if (hls_temp_rep) {
    2858             :                                 //reswap segment lists: new segments contain the actual list
    2859          55 :                                 rep->segment_list->segment_URLs = new_segments;
    2860          55 :                                 new_rep->segment_list->segment_URLs = segments;
    2861             :                                 //gf_list_swap(new_segments, segments);
    2862             : 
    2863             :                                 //destroy temporary rep
    2864          55 :                                 gf_mpd_representation_free(hls_temp_rep);
    2865          55 :                                 group->last_mpd_change_time = gf_sys_clock();
    2866             :                         } else {
    2867             :                                 /*switch all internal GPAC stuff*/
    2868          13 :                                 memcpy(&new_rep->playback, &rep->playback, sizeof(GF_DASH_RepresentationPlayback));
    2869          13 :                                 if (rep->playback.cached_init_segment_url) rep->playback.cached_init_segment_url = NULL;
    2870             : 
    2871          13 :                                 if (!new_rep->mime_type) {
    2872           8 :                                         new_rep->mime_type = rep->mime_type;
    2873           8 :                                         rep->mime_type = NULL;
    2874             :                                 }
    2875             :                         }
    2876             :                 }
    2877             : 
    2878             :                 /*update segmentTimeline at AdaptationSet level before switching the set (old setup needed to compute current timing of each group) */
    2879          97 :                 if (new_set) {
    2880          13 :                         e = gf_dash_merge_segment_timeline(group, NULL, set->segment_list, set->segment_template, new_set->segment_list, new_set->segment_template, timeline_start_time);
    2881          13 :                         if (e) {
    2882           0 :                                 gf_mpd_del(new_mpd);
    2883           0 :                                 return e;
    2884             :                         }
    2885             : 
    2886          13 :                         if (nb_rep_unchanged == gf_list_count(new_set->representations))
    2887             :                                 nb_group_unchanged++;
    2888             :                         else
    2889          13 :                                 group->last_mpd_change_time = gf_sys_clock();
    2890             :                 }
    2891             :         }
    2892             :         //good to go, switch pointers
    2893         110 :         for (group_idx=0; group_idx<gf_list_count(dash->groups) && new_period; group_idx++) {
    2894             :                 Double seg_dur;
    2895             :                 Bool reset_segment_count;
    2896             :                 GF_MPD_AdaptationSet *new_as;
    2897          13 :                 GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    2898             : 
    2899             :                 /*update group/period to new period*/
    2900          13 :                 j = gf_list_find(group->period->adaptation_sets, group->adaptation_set);
    2901          13 :                 new_as = gf_list_get(new_period->adaptation_sets, j);
    2902             :                 assert(new_as);
    2903             : 
    2904          13 :                 if (has_reps_unchanged) {
    2905             :                         //swap all unchanged reps from old MPD to new MPD
    2906           0 :                         for (j=0; j<gf_list_count(group->adaptation_set->representations); j++) {
    2907           0 :                                 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, j);
    2908           0 :                                 GF_MPD_Representation *new_rep = gf_list_get(new_as->representations, j);
    2909           0 :                                 if (!rep->playback.not_modified) continue;
    2910           0 :                                 gf_list_rem(group->adaptation_set->representations, j);
    2911           0 :                                 gf_list_rem(new_as->representations, j);
    2912           0 :                                 gf_list_insert(group->adaptation_set->representations, new_rep, j);
    2913           0 :                                 gf_list_insert(new_as->representations, rep, j);
    2914           0 :                                 rep->playback.not_modified = GF_FALSE;
    2915             :                         }
    2916             :                 }
    2917             : 
    2918             :                 /*swap group period/AS to new period/AS*/
    2919          13 :                 group->adaptation_set = new_as;
    2920          13 :                 group->period = new_period;
    2921             : 
    2922          13 :                 j = gf_list_count(group->adaptation_set->representations);
    2923             :                 assert(j);
    2924             : 
    2925             :                 /*now that all possible SegmentXXX have been updated, purge them if needed: all segments ending before timeline_start_time
    2926             :                 will be removed from MPD*/
    2927          13 :                 if (timeline_start_time) {
    2928           8 :                         u32 nb_segments_removed = gf_dash_purge_segment_timeline(group, timeline_start_time);
    2929           8 :                         if (nb_segments_removed) {
    2930           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AdaptationSet %d - removed %d segments from timeline (%d since start of the period)\n", group_idx+1, nb_segments_removed, group->nb_segments_purged));
    2931             :                         }
    2932             :                 }
    2933             : 
    2934          13 :                 if (force_timeline_setup) {
    2935           0 :                         group->timeline_setup = GF_FALSE;
    2936           0 :                         group->start_number_at_last_ast = 0;
    2937           0 :                         gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
    2938             :                 }
    2939          13 :                 else if (new_mpd->availabilityStartTime != dash->mpd->availabilityStartTime) {
    2940             :                         s64 diff = new_mpd->availabilityStartTime;
    2941           2 :                         diff -= dash->mpd->availabilityStartTime;
    2942           2 :                         if (diff < 0) diff = -diff;
    2943           2 :                         if (diff>3000)
    2944           2 :                                 gf_dash_group_timeline_setup(new_mpd, group, fetch_time);
    2945             :                 }
    2946             : 
    2947          13 :                 group->maybe_end_of_stream = 0;
    2948             :                 reset_segment_count = GF_FALSE;
    2949             :                 /*compute fetchTime + minUpdatePeriod and check period end time*/
    2950          13 :                 if (new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
    2951           0 :                         u64 endTime = fetch_time - new_mpd->availabilityStartTime - period->start;
    2952           0 :                         if (endTime > new_mpd->media_presentation_duration) {
    2953           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period EndTime is signaled to "LLU", less than fetch time "LLU" ! Ignoring mediaPresentationDuration\n", new_mpd->media_presentation_duration, endTime));
    2954           0 :                                 new_mpd->media_presentation_duration = 0;
    2955             :                                 reset_segment_count = GF_TRUE;
    2956             :                         } else {
    2957           0 :                                 endTime += new_mpd->minimum_update_period;
    2958           0 :                                 if (endTime > new_mpd->media_presentation_duration) {
    2959           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period EndTime is signaled to "LLU", less than fetch time + next update "LLU" - maybe end of stream ?\n", new_mpd->availabilityStartTime, endTime));
    2960           0 :                                         group->maybe_end_of_stream = 1;
    2961             :                                 }
    2962             :                         }
    2963             :                 }
    2964             : 
    2965             :                 /*update number of segments in active rep*/
    2966          13 :                 gf_dash_get_segment_duration(gf_list_get(group->adaptation_set->representations, group->active_rep_index), group->adaptation_set, group->period, new_mpd, &group->nb_segments_in_rep, &seg_dur);
    2967             : 
    2968          13 :                 if (reset_segment_count) {
    2969           0 :                         u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
    2970           0 :                         group->nb_segments_in_rep = group->download_segment_index + nb_segs_in_mpd_period;
    2971             :                 }
    2972             :                 /*check if number of segments are coherent ...*/
    2973          13 :                 else if (!group->maybe_end_of_stream && new_mpd->minimum_update_period && new_mpd->media_presentation_duration) {
    2974           0 :                         u32 nb_segs_in_mpd_period = (u32) (dash->mpd->minimum_update_period / (1000*seg_dur) );
    2975             : 
    2976           0 :                         if (group->download_segment_index + nb_segs_in_mpd_period >= group->nb_segments_in_rep) {
    2977           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Period has %d segments but %d are needed until next refresh. Maybe end of stream is near ?\n", group->nb_segments_in_rep, group->download_segment_index + nb_segs_in_mpd_period));
    2978           0 :                                 group->maybe_end_of_stream = 1;
    2979             :                         }
    2980             :                 }
    2981             : 
    2982          13 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated AdaptationSet %d - %d segments\n", group_idx+1, group->nb_segments_in_rep));
    2983             : 
    2984          13 :                 if (!period->duration && new_period->duration) {
    2985           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("End of period upcoming, current segment index for group #%d: %d\n", group_idx+1, group->download_segment_index));
    2986           0 :                         if (group->download_segment_index > (s32) group->nb_segments_in_rep)
    2987           0 :                                 gf_dash_mark_group_done(group);
    2988             :                 }
    2989             : 
    2990             :         }
    2991             : 
    2992          99 : exit:
    2993             :         /*swap MPDs*/
    2994          99 :         if (new_mpd) {
    2995          15 :                 if (dash->mpd) {
    2996          15 :                         if (!new_mpd->minimum_update_period && (new_mpd->type==GF_MPD_TYPE_DYNAMIC))
    2997           4 :                                 new_mpd->minimum_update_period = dash->mpd->minimum_update_period;
    2998          15 :                         gf_mpd_del(dash->mpd);
    2999             :                 }
    3000          15 :                 dash->mpd = new_mpd;
    3001             :         }
    3002          99 :         dash->last_update_time = gf_sys_clock();
    3003          99 :         dash->mpd_fetch_time = fetch_time;
    3004             : 
    3005             : #ifndef GPAC_DISABLE_LOG
    3006          99 :         if (new_period) {
    3007          15 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Manifest after update:\n"));
    3008          18 :                 for (i=0; i<gf_list_count(new_mpd->periods); i++) {
    3009          18 :                         GF_MPD_Period *ap = gf_list_get(new_mpd->periods, i);
    3010          18 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\tP#%d: start "LLU" - duration " LLU" - xlink %s\n", i+1, ap->start, ap->duration, ap->xlink_href ? ap->xlink_href : ap->origin_base_url ? ap->origin_base_url : "none"));
    3011             :                 }
    3012             :         }
    3013             : #endif
    3014             : 
    3015             :         return GF_OK;
    3016             : }
    3017             : 
    3018          11 : static void m3u8_setup_timeline(GF_DASH_Group *group, GF_MPD_Representation *rep)
    3019             : {
    3020             :         u64 timeshift = 0;
    3021             :         u64 tsb_depth = 0;
    3022             :         u32 i, count;
    3023             : 
    3024          11 :         if (!group->dash->initial_time_shift_value || !rep->segment_list) return;
    3025             : 
    3026           0 :         count = gf_list_count(rep->segment_list->segment_URLs);
    3027           0 :         for (i=0; i<count; i++) {
    3028           0 :                 GF_MPD_SegmentURL *s = gf_list_get(rep->segment_list->segment_URLs, i);
    3029           0 :                 tsb_depth += s->duration;
    3030             :         }
    3031             : 
    3032           0 :         if (group->dash->initial_time_shift_value<=100) {
    3033             :                 timeshift = tsb_depth;
    3034           0 :                 timeshift *= group->dash->initial_time_shift_value;
    3035           0 :                 timeshift /= 100;
    3036             :         } else {
    3037           0 :                 timeshift = (u32) group->dash->initial_time_shift_value;
    3038           0 :                 timeshift *= rep->segment_list->timescale;
    3039           0 :                 timeshift /= 1000;
    3040           0 :                 if (timeshift > tsb_depth) timeshift = tsb_depth;
    3041             :         }
    3042             :         tsb_depth = 0;
    3043           0 :         for (i=count; i>0; i--) {
    3044           0 :                 GF_MPD_SegmentURL *s = gf_list_get(rep->segment_list->segment_URLs, i-1);
    3045           0 :                 tsb_depth += s->duration;
    3046             :                 //only check on independent parts
    3047           0 :                 if (s->hls_ll_chunk_type == 1)
    3048           0 :                         continue;
    3049           0 :                 if (tsb_depth > timeshift) {
    3050           0 :                         group->download_segment_index = i-1;
    3051             :                         break;
    3052             :                 }
    3053             :         }
    3054             : }
    3055             : 
    3056             : 
    3057        3501 : static GF_Err gf_dash_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_DASH_Group *group, const char *mpd_url, GF_MPD_URLResolveType resolve_type, u32 item_index, char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *out_key_iv, Bool *data_url_process, u32 *out_start_number)
    3058             : {
    3059             :         GF_Err e;
    3060        3501 :         GF_MPD_AdaptationSet *set = group->adaptation_set;
    3061        3501 :         GF_MPD_Period *period = group->period;
    3062             :         u32 timescale;
    3063             : 
    3064        3501 :         if (!mpd_url) return GF_BAD_PARAM;
    3065             : 
    3066        3501 :         if (!strncmp(mpd_url, "gfio://", 7))
    3067           9 :                 mpd_url = gf_file_basename(gf_fileio_translate_url(mpd_url));
    3068             : 
    3069        3501 :         if (!group->timeline_setup) {
    3070         296 :                 gf_dash_group_timeline_setup(mpd, group, 0);
    3071             :                 //we must wait for ROUTE clock to initialize, even if first period is static remote (we need to know when to tune)
    3072         296 :                 if (group->dash->route_clock_state==1)
    3073             :                         return GF_IP_NETWORK_EMPTY;
    3074             : 
    3075         261 :                 if (group->dash->reinit_period_index)
    3076             :                         return GF_IP_NETWORK_EMPTY;
    3077         260 :                 group->timeline_setup = GF_TRUE;
    3078         260 :                 item_index = group->download_segment_index;
    3079             :         }
    3080             : 
    3081        3465 :         gf_mpd_resolve_segment_duration(rep, set, period, segment_duration, &timescale, NULL, NULL);
    3082        3465 :         *segment_duration = (resolve_type==GF_MPD_RESOLVE_URL_MEDIA) ? (u32) ((Double) ((*segment_duration) * 1000.0) / timescale) : 0;
    3083        3465 :         e = gf_mpd_resolve_url(mpd, rep, set, period, mpd_url, group->current_base_url_idx, resolve_type, item_index, group->nb_segments_purged, out_url, out_range_start, out_range_end, segment_duration, is_in_base_url, out_key_url, out_key_iv, out_start_number);
    3084             : 
    3085             : 
    3086             :         if (e == GF_NON_COMPLIANT_BITSTREAM) {
    3087             : //              group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
    3088             :         }
    3089        3465 :         if (!*out_url) {
    3090             :                 return e;
    3091             :         }
    3092             : 
    3093        3435 :         if (*out_url && data_url_process && !strncmp(*out_url, "data:", 5)) {
    3094             :                 char *sep;
    3095           0 :                 sep = strstr(*out_url, ";base64,");
    3096           0 :                 if (sep) {
    3097             :                         GF_Blob *blob;
    3098             :                         u32 len;
    3099           0 :                         sep+=8;
    3100           0 :                         len = (u32)strlen(sep) + 1;
    3101           0 :                         GF_SAFEALLOC(blob, GF_Blob);
    3102           0 :                         if (!blob) return GF_OUT_OF_MEM;
    3103             : 
    3104           0 :                         blob->data = (char *)gf_malloc(len);
    3105           0 :                         blob->size = gf_base64_decode(sep, len, blob->data, len);
    3106           0 :                         sprintf(*out_url, "gmem://%p", blob);
    3107           0 :                         *data_url_process = GF_TRUE;
    3108             :                 } else {
    3109           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("data scheme with encoding different from base64 not supported\n"));
    3110             :                 }
    3111             :         }
    3112             : 
    3113             :         return e;
    3114             : }
    3115             : 
    3116             : 
    3117         395 : static void gf_dash_set_group_representation(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool is_next_schedule)
    3118             : {
    3119             : #ifndef GPAC_DISABLE_LOG
    3120             :         u32 width=0, height=0, samplerate=0;
    3121             :         GF_MPD_Fractional *framerate=NULL;
    3122             : #endif
    3123             :         u32 k;
    3124             :         s32 timeshift;
    3125             :         GF_MPD_AdaptationSet *set;
    3126             :         GF_MPD_Period *period;
    3127             :         u32 ol_nb_segs_in_rep;
    3128         395 :         u32 i = gf_list_find(group->adaptation_set->representations, rep);
    3129         395 :         u32 prev_active_rep_index = group->active_rep_index;
    3130         395 :         u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
    3131             :         assert((s32) i >= 0);
    3132             : 
    3133         395 :         if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
    3134           0 :                 group->llhls_switch_request = i;
    3135             :                 return;
    3136             :         }
    3137         395 :         group->llhls_switch_request = -1;
    3138             : 
    3139             :         //we do not support switching in the middle of a segment
    3140         395 :         if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
    3141             :                 return;
    3142             :         }
    3143             : 
    3144             :         /* in case of dependent representations: we set max_complementary_rep_index than active_rep_index*/
    3145         395 :         if (group->base_rep_index_plus_one)
    3146           0 :                 group->max_complementary_rep_index = i;
    3147             :         else {
    3148         395 :                 group->active_rep_index = i;
    3149             : //                      if (group->timeline_setup)
    3150             : //                              group->llhls_edge_chunk = NULL;
    3151             :         }
    3152         395 :         group->active_bitrate = rep->bandwidth;
    3153         395 :         group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
    3154         395 :         ol_nb_segs_in_rep = group->nb_segments_in_rep;
    3155             : 
    3156         395 :         group->min_bandwidth_selected = GF_TRUE;
    3157         379 :         for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
    3158         512 :                 GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
    3159         512 :                 if (group->active_bitrate > arep->bandwidth) {
    3160         133 :                         group->min_bandwidth_selected = GF_FALSE;
    3161             :                         break;
    3162             :                 }
    3163             :         }
    3164             : 
    3165         441 :         while (rep->segment_list && rep->segment_list->xlink_href) {
    3166          46 :                 Bool is_static = GF_FALSE;
    3167          46 :                 u64 dur = 0;
    3168             :                 GF_Err e=GF_OK;
    3169             : 
    3170          46 :                 if (rep->segment_list->consecutive_xlink_count) {
    3171           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Resolving a XLINK pointed from another XLINK (%d consecutive XLINK in segment list)\n", rep->segment_list->consecutive_xlink_count));
    3172             :                 }
    3173             : 
    3174          46 :                 if (group->dash->is_m3u8) {
    3175          46 :                         e = gf_dash_solve_m3u8_representation_xlink(group, rep, &is_static, &dur, rep->playback.xlink_digest);
    3176             :                 } else {
    3177           0 :                         e = gf_dash_solve_representation_xlink(group->dash, rep, rep->playback.xlink_digest);
    3178             :                 }
    3179          46 :                 if (is_static)
    3180          31 :                         group->dash->mpd->type = GF_MPD_TYPE_STATIC;
    3181             : 
    3182             :                 //after resolving xlink: if this represenstation is marked as disabled, we have nothing to do
    3183          46 :                 if (rep->playback.disabled)
    3184           0 :                         return;
    3185             : 
    3186          46 :                 if (e) {
    3187           0 :                         if (group->dash->dash_state != GF_DASH_STATE_RUNNING) {
    3188           0 :                                 group->dash->force_period_reload = 1;
    3189           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Could not reslove XLINK %s of initial rep, will retry\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "", gf_error_to_string(e) ));
    3190             :                         } else {
    3191           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Could not reslove XLINK %s in time: %s - using old representation\n", (rep->segment_list && rep->segment_list->xlink_href) ? rep->segment_list->xlink_href : "", gf_error_to_string(e) ));
    3192           0 :                                 group->active_rep_index = prev_active_rep_index;
    3193             :                         }
    3194             :                         return;
    3195             :                 }
    3196             : 
    3197             :                 //we only know this is a static or dynamic MPD after parsing the first subplaylist
    3198             :                 //if this is static, we need to update infos in mpd and period
    3199          46 :                 if (group->dash->is_m3u8 && is_static) {
    3200          31 :                         group->dash->mpd->type = GF_MPD_TYPE_STATIC;
    3201          31 :                         group->dash->mpd->minimum_update_period = 0;
    3202          31 :                         if (group->dash->mpd->media_presentation_duration < dur)
    3203          11 :                                 group->dash->mpd->media_presentation_duration = dur;
    3204          31 :                         if (group->period->duration < dur)
    3205          11 :                                 group->period->duration = dur;
    3206             :                 }
    3207             :         }
    3208             : 
    3209         395 :         if (group->dash->is_m3u8) {
    3210             :                 //here we change to another representation: we need to remove all URLs from segment list and adjust the download segment index for this group
    3211          63 :                 if (group->dash->dash_state == GF_DASH_STATE_RUNNING) {
    3212          26 :                         u32 group_dld_index = group->download_segment_index;
    3213             :                         u32 next_media_seq;
    3214             :                         Bool found = GF_FALSE;
    3215          26 :                         Bool is_dynamic = (group->dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE;
    3216             :                         u32 nb_segs;
    3217          26 :                         GF_MPD_Representation *prev_active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, prev_active_rep_index);
    3218             :                         GF_MPD_SegmentURL *last_seg_url;
    3219             :                         assert(rep->segment_list);
    3220             :                         assert(prev_active_rep->segment_list);
    3221             : 
    3222          26 :                         last_seg_url = gf_list_get(prev_active_rep->segment_list->segment_URLs, group_dld_index);
    3223          26 :                         if (last_seg_url)
    3224           6 :                                 next_media_seq = last_seg_url->hls_seq_num;
    3225             :                         else {
    3226          20 :                                 last_seg_url = gf_list_get(prev_active_rep->segment_list->segment_URLs, group_dld_index-1);
    3227          20 :                                 if (last_seg_url)
    3228          20 :                                         next_media_seq = last_seg_url->hls_seq_num+1;
    3229             :                                 else
    3230           0 :                                         next_media_seq = rep->m3u8_media_seq_max;
    3231             :                         }
    3232             : 
    3233          26 :                         nb_segs = gf_list_count(rep->segment_list->segment_URLs);
    3234         223 :                         for (k=nb_segs; k>0; k--) {
    3235         223 :                                 GF_MPD_SegmentURL *seg_url = (GF_MPD_SegmentURL *) gf_list_get(rep->segment_list->segment_URLs, k-1);
    3236         223 :                                 if (seg_url->hls_ll_chunk_type) continue;
    3237             : 
    3238         223 :                                 if (next_media_seq == seg_url->hls_seq_num) {
    3239           6 :                                         group->download_segment_index = k-1;
    3240             :                                         found = GF_TRUE;
    3241             :                                         break;
    3242             :                                 }
    3243             :                                 /*
    3244             :                                 "In order to play the presentation normally, the next Media Segment to load is the one with the
    3245             :                                 lowest Media Sequence Number that is greater than the Media Sequence Number of the last Media Segment loaded."
    3246             : 
    3247             :                                 so we store this one, but continue until we find an exact match, if any
    3248             :                                 */
    3249         217 :                                 else if (next_media_seq < seg_url->hls_seq_num) {
    3250         197 :                                         group->download_segment_index = k-1;
    3251             :                                         found = GF_TRUE;
    3252             :                                 }
    3253             :                                 //segment before our current target, abort
    3254             :                                 else {
    3255             :                                         break;
    3256             :                                 }
    3257             :                         }
    3258             : 
    3259          20 :                         if (!found) {
    3260          20 :                                 if (is_dynamic) {
    3261             :                                         //we switch quality but next seg is not known, force an update NOW
    3262          16 :                                         group->dash->force_mpd_update = GF_TRUE;
    3263          16 :                                         group->hls_next_seq_num = next_media_seq;
    3264             :                                 } else {
    3265           4 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] next media segment %d not found in new variant stream (min %d - max %d), aborting\n", next_media_seq, rep->m3u8_media_seq_min, rep->m3u8_media_seq_max));
    3266           4 :                                         group->done = GF_TRUE;
    3267             :                                 }
    3268             :                         } else {
    3269           6 :                                 if (is_dynamic) {
    3270           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] HLS next seg %d found in new rep, no manifest update\n", next_media_seq));
    3271             :                                 }
    3272             :                         }
    3273             :                 }
    3274             : 
    3275             :                 //switching to a rep for which we didn't solve the init segment yet
    3276          63 :                 if (!rep->playback.cached_init_segment_url) {
    3277             :                         GF_Err e;
    3278          46 :                         Bool timeline_setup = group->timeline_setup;
    3279          46 :                         char *r_base_init_url = NULL;
    3280          46 :                         u64 r_start = 0, r_end = 0, r_dur = 0;
    3281             : 
    3282          46 :                         e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &r_base_init_url, &r_start, &r_end, &r_dur, NULL, &rep->playback.key_url, &rep->playback.key_IV, &rep->playback.owned_gmem, NULL);
    3283             : 
    3284          46 :                         group->timeline_setup = timeline_setup;
    3285          46 :                         if (!e && r_base_init_url) {
    3286          45 :                                 rep->playback.cached_init_segment_url = r_base_init_url;
    3287          45 :                                 rep->playback.init_start_range = r_start;
    3288          45 :                                 rep->playback.init_end_range = r_end;
    3289          45 :                                 rep->playback.init_seg_name_start = dash_strip_base_url(r_base_init_url, group->dash->base_url);
    3290           1 :                         } else if (e) {
    3291           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
    3292           0 :                                 rep->playback.disabled = 1;
    3293             :                         }
    3294             :                 }
    3295             : 
    3296          63 :                 group->m3u8_start_media_seq = rep->m3u8_media_seq_min;
    3297          63 :                 if (rep->m3u8_low_latency)
    3298          22 :                         group->is_low_latency = GF_TRUE;
    3299          63 :                 if (group->dash->mpd->type==GF_MPD_TYPE_STATIC)
    3300          36 :                         group->timeline_setup = GF_TRUE;
    3301             : 
    3302          63 :                 if (!group->current_downloaded_segment_duration && rep->segment_list && rep->segment_list->timescale)
    3303          38 :                         group->current_downloaded_segment_duration = rep->segment_list->duration * 1000 / rep->segment_list->timescale;
    3304             : 
    3305             :                 //setup tune point
    3306          63 :                 if (!group->timeline_setup) {
    3307             :                         //tune to last entry (live edge)
    3308          11 :                         group->download_segment_index = rep->m3u8_media_seq_indep_last;
    3309          11 :                         if (rep->m3u8_low_latency && rep->segment_list) {
    3310          10 :                                 u32 nb_removed = ls_hls_purge_segments(group->download_segment_index, rep->segment_list->segment_URLs);
    3311          10 :                                 group->download_segment_index -= nb_removed;
    3312             :                         }
    3313             :                         //if TSB set, roll back
    3314          11 :                         m3u8_setup_timeline(group, rep);
    3315          11 :                         group->timeline_setup = GF_TRUE;
    3316          11 :                         group->first_hls_chunk = GF_TRUE;
    3317             :                 } else {
    3318          52 :                         if (rep->m3u8_low_latency && rep->segment_list) {
    3319          12 :                                 ls_hls_purge_segments(-1, rep->segment_list->segment_URLs);
    3320             :                         }
    3321             :                 }
    3322             :         }
    3323             : 
    3324         395 :         set = group->adaptation_set;
    3325         395 :         period = group->period;
    3326             : 
    3327             : #ifndef GPAC_DISABLE_LOG
    3328             : 
    3329             : #define GET_REP_ATTR(_a)        _a = rep->_a; if (!_a) _a = set->_a;
    3330             : 
    3331         395 :         GET_REP_ATTR(width);
    3332         395 :         GET_REP_ATTR(height);
    3333         395 :         GET_REP_ATTR(samplerate);
    3334         395 :         GET_REP_ATTR(framerate);
    3335             : 
    3336         395 :         if (width || height) {
    3337             :                 u32 num=25, den=1;
    3338         301 :                 if (framerate) {
    3339         237 :                         num = framerate->num;
    3340         237 :                         if (framerate->den) den = framerate->den;
    3341             :                 }
    3342             : 
    3343         301 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps - Width %d Height %d FPS %d/%d (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, width, height, num, den, group->dash->speed));
    3344             :         }
    3345          94 :         else if (samplerate) {
    3346          76 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps - sample rate %u (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, samplerate, group->dash->speed));
    3347             :         }
    3348             :         else {
    3349          18 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d changed quality to bitrate %d kbps (playback speed %g)\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth/1000, group->dash->speed));
    3350             :         }
    3351             : #endif
    3352             : 
    3353         395 :         gf_dash_get_segment_duration(rep, set, period, group->dash->mpd, &group->nb_segments_in_rep, &group->segment_duration);
    3354             : 
    3355             :         /*if broken indication in duration restore previous seg count*/
    3356         395 :         if (group->dash->ignore_mpd_duration)
    3357           0 :                 group->nb_segments_in_rep = ol_nb_segs_in_rep;
    3358             : 
    3359         395 :         timeshift = (s32) (rep->segment_base ? rep->segment_base->time_shift_buffer_depth : (rep->segment_list ? rep->segment_list->time_shift_buffer_depth : (rep->segment_template ? rep->segment_template->time_shift_buffer_depth : -1) ) );
    3360         191 :         if (timeshift == -1) timeshift = (s32) (set->segment_base ? set->segment_base->time_shift_buffer_depth : (set->segment_list ? set->segment_list->time_shift_buffer_depth : (set->segment_template ? set->segment_template->time_shift_buffer_depth : -1) ) );
    3361         395 :         if (timeshift == -1) timeshift = (s32) (period->segment_base ? period->segment_base->time_shift_buffer_depth : (period->segment_list ? period->segment_list->time_shift_buffer_depth : (period->segment_template ? period->segment_template->time_shift_buffer_depth : -1) ) );
    3362             : 
    3363         395 :         if (timeshift == -1) timeshift = (s32) group->dash->mpd->time_shift_buffer_depth;
    3364         395 :         group->time_shift_buffer_depth = (u32) timeshift;
    3365             : 
    3366         395 :         group->dash->dash_io->on_dash_event(group->dash->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, gf_list_find(group->dash->groups, group), GF_OK);
    3367             : }
    3368             : 
    3369           2 : static void gf_dash_switch_group_representation(GF_DashClient *mpd, GF_DASH_Group *group)
    3370             : {
    3371             :         u32 i, bandwidth, min_bandwidth;
    3372             :         GF_MPD_Representation *rep_sel = NULL;
    3373             :         GF_MPD_Representation *min_rep_sel = NULL;
    3374             :         Bool min_bandwidth_selected = GF_FALSE;
    3375             :         bandwidth = 0;
    3376             :         min_bandwidth = (u32) -1;
    3377             : 
    3378           2 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Checking representations between %d and %d kbps\n", group->min_bitrate/1000, group->max_bitrate/1000));
    3379             : 
    3380           2 :         if (group->force_representation_idx_plus_one) {
    3381           2 :                 rep_sel = gf_list_get(group->adaptation_set->representations, group->force_representation_idx_plus_one - 1);
    3382           2 :                 group->force_representation_idx_plus_one = 0;
    3383             :         }
    3384             : 
    3385           2 :         if (!rep_sel) {
    3386           0 :                 for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
    3387           0 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
    3388           0 :                         if (rep->playback.disabled) continue;
    3389           0 :                         if ((rep->bandwidth > bandwidth) && (rep->bandwidth < group->max_bitrate )) {
    3390             :                                 rep_sel = rep;
    3391             :                                 bandwidth = rep->bandwidth;
    3392             :                         }
    3393           0 :                         if (rep->bandwidth < min_bandwidth) {
    3394             :                                 min_rep_sel = rep;
    3395             :                                 min_bandwidth = rep->bandwidth;
    3396             :                         }
    3397             :                 }
    3398             :         }
    3399             : 
    3400           2 :         if (!rep_sel) {
    3401           0 :                 if (!min_rep_sel) {
    3402           0 :                         min_rep_sel = gf_list_get(group->adaptation_set->representations, 0);
    3403             :                 }
    3404             :                 rep_sel = min_rep_sel;
    3405             :                 min_bandwidth_selected = 1;
    3406             :         }
    3407             :         assert(rep_sel);
    3408           2 :         i = gf_list_find(group->adaptation_set->representations, rep_sel);
    3409             : 
    3410             :         assert((s32) i >= 0);
    3411             : 
    3412           2 :         group->force_switch_bandwidth = 0;
    3413           2 :         group->max_bitrate = 0;
    3414           2 :         group->min_bitrate = (u32) -1;
    3415             : 
    3416           2 :         if (i != group->active_rep_index) {
    3417           2 :                 if (min_bandwidth_selected) {
    3418           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No representation found with bandwidth below %d kbps - using representation @ %d kbps\n", group->max_bitrate/1000, rep_sel->bandwidth/1000));
    3419             :                 }
    3420           2 :                 gf_dash_set_group_representation(group, rep_sel, GF_FALSE);
    3421             :         }
    3422           2 : }
    3423             : 
    3424             : /* Estimate the maximum speed that we can play, using our statistic. If it is below the max_playout_rate in MPD, return max_playout_rate*/
    3425        2409 : static Double gf_dash_get_max_available_speed(GF_DashClient *dash, GF_DASH_Group *group, GF_MPD_Representation *rep)
    3426             : {
    3427             :         Double max_available_speed = 0;
    3428             :         Double max_dl_speed, max_decoding_speed;
    3429             :         u32 framerate;
    3430             :         u32 bytes_per_sec;
    3431             : 
    3432        2409 :         if (!group->irap_max_dec_time && !group->avg_dec_time) {
    3433             :                 return 0;
    3434             :         }
    3435          64 :         bytes_per_sec = group->backup_Bps;
    3436          64 :         max_dl_speed = 8.0*bytes_per_sec / rep->bandwidth;
    3437             : 
    3438             :         //if framerate is not in MPD, suppose that it is 25 fps
    3439             :         framerate = 25;
    3440          64 :         if (rep->framerate) {
    3441          64 :                 framerate = rep->framerate->num;
    3442          64 :                 if (rep->framerate->den) {
    3443          64 :                         framerate /= rep->framerate->den;
    3444             :                 }
    3445             :         }
    3446             : 
    3447          64 :         if (group->decode_only_rap)
    3448           0 :                 max_decoding_speed = group->irap_max_dec_time ? 1000000.0 / group->irap_max_dec_time : 0;
    3449             :         else
    3450          64 :                 max_decoding_speed = group->avg_dec_time ? 1000000.0 / (group->max_dec_time + group->avg_dec_time*(framerate - 1)) : 0;
    3451          64 :         max_available_speed = max_decoding_speed > max_dl_speed ? max_dl_speed : max_decoding_speed;
    3452          64 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Representation %s max playout rate: in MPD %f - calculated by stat: %f\n", rep->id, rep->max_playout_rate, max_available_speed));
    3453             : 
    3454             :         return max_available_speed;
    3455             : }
    3456             : 
    3457        2981 : static void dash_store_stats(GF_DashClient *dash, GF_DASH_Group *group, u32 bytes_per_sec, u32 file_size, Bool is_broadcast, u32 cur_dep_idx_plus_one, u64 us_since_start)
    3458             : {
    3459             : #ifndef GPAC_DISABLE_LOG
    3460             :         const char *url=NULL, *full_url=NULL;
    3461             : #endif
    3462             :         GF_MPD_Representation *rep;
    3463             : 
    3464        2981 :         if (!group->nb_cached_segments)
    3465             :                 return;
    3466             : 
    3467             : #ifndef GPAC_DISABLE_LOG
    3468        2981 :         if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
    3469          48 :                 if (cur_dep_idx_plus_one) {
    3470             :                         u32 i=0;
    3471          49 :                         while (i<group->nb_cached_segments) {
    3472          48 :                                 full_url =  group->cached[i].url;
    3473          48 :                                 if (group->cached[i].representation_index==cur_dep_idx_plus_one-1)
    3474             :                                         break;
    3475           1 :                                 i++;
    3476             :                         }
    3477             :                 } else {
    3478           0 :                         full_url =  group->cached[group->nb_cached_segments-1].url;
    3479             :                 }
    3480          48 :                 url = strrchr(full_url, '/');
    3481          48 :                 if (!url) url = strrchr(full_url, '\\');
    3482          48 :                 if (url) url+=1;
    3483             :                 else url = full_url;
    3484             :         }
    3485             : #endif
    3486             : 
    3487             :         
    3488        2981 :         if (!bytes_per_sec && group->local_files) {
    3489             :                 bytes_per_sec = (u32) -1;
    3490             :                 bytes_per_sec /= 8;
    3491             :         }
    3492             : 
    3493        2981 :         group->total_size = file_size;
    3494             :         //in broadcast mode, just store the rate
    3495        2981 :         if (is_broadcast) group->bytes_per_sec = bytes_per_sec;
    3496             :         //otherwise store the min rate we got (to deal with complementary representations)
    3497        2981 :         else if (!group->bytes_per_sec || group->bytes_per_sec > bytes_per_sec)
    3498        2794 :                 group->bytes_per_sec = bytes_per_sec;
    3499             : 
    3500        2981 :         group->last_segment_time = gf_sys_clock();
    3501        2981 :         group->nb_segments_since_switch ++;
    3502             : 
    3503        2981 :         group->prev_segment_ok = GF_TRUE;
    3504        2981 :         if (group->time_at_first_failure) {
    3505             : #ifndef GPAC_DISABLE_LOG
    3506           0 :                 if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
    3507           0 :                         if (group->current_base_url_idx) {
    3508           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 by switching baseURL\n", url));
    3509             :                         } else {
    3510           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Recovered segment %s after 404 - was our download schedule %d too early ?\n", url, group->time_at_last_request - group->time_at_first_failure));
    3511             :                         }
    3512             :                 }
    3513             : #endif
    3514           0 :                 group->time_at_first_failure = 0;
    3515             :         }
    3516        2981 :         group->nb_consecutive_segments_lost = 0;
    3517        2981 :         group->current_base_url_idx = 0;
    3518             : 
    3519             : 
    3520        2981 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    3521        2981 :         rep->playback.broadcast_flag = is_broadcast;
    3522             : 
    3523             :         /*
    3524             :         we "merged" the following segments with the current one in a single open byte-range: the downloaded segment duration indicated
    3525             :         is the one of the PART, NOT of the segment
    3526             :         We need to compute (for ABR logic) the segment duration but we likely don't have the information since we issued this merge
    3527             :         on the live edge and have not performed a forced manifest update since then, so last PARTs are not known
    3528             : 
    3529             :         We therefore assume a default duration of the average indicated, and check (in case we are lucky) if we have the full segment in the
    3530             :         list (i.e. before live edge)
    3531             :         if not, we SHOULD force an update just to fetch this duration before calling the ABR, but if the ABR decides to change right now
    3532             :         we would fetch this manifest for nothing, and we are already fetching a LOT of manifests in HLS...
    3533             :         So for the time being, we assume the target duration is a good approximation
    3534             :         */
    3535        2981 :         if (dash->llhls_single_range && group->llhls_last_was_merged) {
    3536             :                 u64 duration;
    3537             :                 assert(rep->segment_list);
    3538             :                 assert(rep->segment_list->timescale);
    3539           1 :                 duration = rep->segment_list->duration;
    3540           1 :                 if (group->llhls_edge_chunk) {
    3541           1 :                         s32 pos = group->download_segment_index-1;
    3542           3 :                         while (pos>=0) {
    3543           2 :                                 GF_MPD_SegmentURL *surl = gf_list_get(rep->segment_list->segment_URLs, pos);
    3544           2 :                                 if (surl->hls_seq_num < group->llhls_edge_chunk->hls_seq_num) break;
    3545           1 :                                 if (!surl->hls_ll_chunk_type) {
    3546           0 :                                         duration = surl->duration;
    3547           0 :                                         break;
    3548             :                                 }
    3549           1 :                                 pos--;
    3550             :                         }
    3551           1 :                         group->current_downloaded_segment_duration = duration * 1000 / rep->segment_list->timescale;
    3552             :                 }
    3553             :         }
    3554             : 
    3555             : #ifndef GPAC_DISABLE_LOG
    3556        2981 :         if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
    3557             :                 u32 i, buffer_ms = 0;
    3558             :                 Double bitrate, time_sec;
    3559             :                 //force a call go query buffer
    3560          48 :                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(dash->groups, group), GF_OK);
    3561          48 :                 buffer_ms = group->buffer_occupancy_ms;
    3562          96 :                 for (i=0; i < group->nb_cached_segments; i++) {
    3563          48 :                         buffer_ms += group->cached[i].duration;
    3564             :                 }
    3565             : 
    3566             :                 bitrate=0;
    3567             :                 time_sec=0;
    3568          48 :                 if (group->current_downloaded_segment_duration) {
    3569          48 :                         bitrate = 8*group->total_size;
    3570          48 :                         bitrate /= group->current_downloaded_segment_duration;
    3571             :                 }
    3572             : 
    3573          48 :                 if (!us_since_start) {
    3574           0 :                         if (bytes_per_sec) {
    3575           0 :                                 time_sec = group->total_size;
    3576           0 :                                 time_sec /= bytes_per_sec;
    3577             :                         }
    3578             :                 } else {
    3579          48 :                         time_sec = (Double) us_since_start;
    3580          48 :                         time_sec /= 1000000;
    3581             :                 }
    3582             : 
    3583          48 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AS#%d got %s stats: %d bytes in %.03g sec at %d kbps - dur %g sec - bitrate: %d (avg %d) kbps - buffer %d ms\n", 1+gf_list_find(group->period->adaptation_sets, group->adaptation_set), url, group->total_size, time_sec, 8*bytes_per_sec/1000, group->current_downloaded_segment_duration/1000.0, (u32) bitrate, rep->bandwidth/1000, buffer_ms));
    3584             :         }
    3585             : #endif
    3586             : }
    3587             : 
    3588           0 : static s32 dash_do_rate_monitor_default(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur)
    3589             : {
    3590             :         Bool default_switch_mode;
    3591             :         u32 set_idx, time_until_end;
    3592             : 
    3593             :         //do not abort if we are downloading faster than current rate
    3594           0 :         if (bits_per_sec > group->active_bitrate) {
    3595             :                 return -1;
    3596             :         }
    3597             : 
    3598           0 :         set_idx = gf_list_find(group->period->adaptation_sets, group->adaptation_set)+1;
    3599             : 
    3600           0 :         if (group->min_bandwidth_selected) {
    3601           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but media bitrate is"
    3602             :                                 " %d kbps - no lower bitrate available ...\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000 ));
    3603             :                 return -1;
    3604             :         }
    3605             : 
    3606             :         //we start checking after 100ms
    3607           0 :         if (us_since_start < 100000) {
    3608           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps (media bitrate %d kbps) but 100ms only ellapsed, waiting\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000 ));
    3609             :                 return -1;
    3610             :         }
    3611             : 
    3612           0 :         time_until_end = (u32) (8000*(total_bytes-bytes_done) / bits_per_sec);
    3613             : 
    3614           0 :         if (bits_per_sec<group->min_bitrate)
    3615           0 :                 group->min_bitrate = bits_per_sec;
    3616           0 :         if (bits_per_sec>group->max_bitrate)
    3617           0 :                 group->max_bitrate = bits_per_sec;
    3618             : 
    3619             :         //we have enough cache data to go until end of this download, perform rate switching at next segment
    3620           0 :         if (time_until_end < buffer_dur_ms) {
    3621           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Downloading from set #%ds at rate %d kbps (media bitrate %d kbps) - %d ms until end of download and %d ms in buffer, not aborting\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000, time_until_end, buffer_dur_ms));
    3622             :                 return -1;
    3623             :         }
    3624             : 
    3625           0 :         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Downloading from set #%d at rate %d kbps but media bitrate is %d kbps - %d ms until end of download but %d ms in buffer - aborting segment download\n", set_idx, bits_per_sec/1000, group->active_bitrate/1000, buffer_dur_ms));
    3626             : 
    3627             :         //in live we just abort current download and go to next. In onDemand, we may want to rebuffer
    3628           0 :         default_switch_mode = (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_FALSE : GF_TRUE;
    3629             : 
    3630           0 :         us_since_start /= 1000;
    3631             :         //if we have time to download from another rep ?
    3632           0 :         if (current_seg_dur <= us_since_start) {
    3633             :                 //don't force bandwidth switch (it's too late anyway, consider we lost the segment), let the rate adaptation decide
    3634           0 :                 group->force_switch_bandwidth = default_switch_mode;
    3635             : 
    3636           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download time longer than segment duration - trying to resync on next segment\n"));
    3637             :         } else {
    3638             :                 u32 target_rate;
    3639             :                 //compute min bitrate needed to fetch the segment in another rep, with the time remaining
    3640           0 :                 Double ratio = (Double) ((u32)current_seg_dur - us_since_start);
    3641           0 :                 ratio /= (u32) current_seg_dur;
    3642             : 
    3643           0 :                 target_rate = (u32) (bits_per_sec * ratio);
    3644             : 
    3645           0 :                 if (target_rate < group->min_representation_bitrate) {
    3646           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Download rate lower than min available rate ...\n"));
    3647           0 :                         target_rate = group->min_representation_bitrate;
    3648             :                         //don't force bandwidth switch, we won't have time to redownload the segment.
    3649           0 :                         group->force_switch_bandwidth = default_switch_mode;
    3650             :                 } else {
    3651           0 :                         group->force_switch_bandwidth = GF_TRUE;
    3652           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Attempting to re-download at target rate %d\n", target_rate));
    3653             :                 }
    3654             :                 //cap max bitrate for next rate adaptation pass
    3655           0 :                 group->max_bitrate = target_rate;
    3656             :         }
    3657             :         return -2;
    3658             : }
    3659             : 
    3660        2116 : static s32 dash_do_rate_adaptation_legacy_rate(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
    3661             :                                                                                                 u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
    3662             :                                                                                                 GF_MPD_Representation *rep, Bool go_up_bitrate)
    3663             : {
    3664             :         u32 k;
    3665             :         Bool do_switch;
    3666             :         GF_MPD_Representation *new_rep;
    3667        2116 :         s32 new_index = group->active_rep_index;
    3668             : 
    3669             :         /* records the number of representations between the current one and the next chosen one */
    3670             :         u32 nb_inter_rep = 0;
    3671             : 
    3672             :         /* We assume that there will be a change in quality */
    3673             :         do_switch = GF_TRUE;
    3674             : 
    3675             :         /*find best bandwidth that fits our bitrate and playing speed*/
    3676             :         new_rep = NULL;
    3677             : 
    3678             :         /* for each playable representation, if we still need to switch, evaluate it */
    3679        4939 :         for (k = 0; k<gf_list_count(group->adaptation_set->representations) && do_switch; k++) {
    3680        2823 :                 GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
    3681        2823 :                 if (!arep->playback.prev_max_available_speed) {
    3682         233 :                         arep->playback.prev_max_available_speed = 1.0;
    3683             :                 }
    3684        2823 :                 if (arep->playback.disabled) {
    3685           0 :                         continue;
    3686             :                 }
    3687        2823 :                 if (arep->playback.prev_max_available_speed && (speed > arep->playback.prev_max_available_speed)) {
    3688           0 :                         continue;
    3689             :                 }
    3690             :                 /* Only try to switch to a representation, if download rate is greater than its bitrate */
    3691        2823 :                 if (dl_rate >= arep->bandwidth) {
    3692             : 
    3693             :                         /* First check if we are adapting to CPU */
    3694        2798 :                         if (!dash->disable_speed_adaptation && force_lower_complexity) {
    3695             : 
    3696             :                                 /*try to switch to highest quality below the current one*/
    3697           0 :                                 if ((arep->quality_ranking < rep->quality_ranking) ||
    3698           0 :                                         (arep->width < rep->width) || (arep->height < rep->height)) {
    3699             : 
    3700             :                                         /* If we hadn't found a new representation, use it
    3701             :                                            otherwise use it only if current representation is better*/
    3702           0 :                                         if (!new_rep) {
    3703             :                                                 new_rep = arep;
    3704           0 :                                                 new_index = k;
    3705             :                                         }
    3706           0 :                                         else if ((arep->quality_ranking > new_rep->quality_ranking) ||
    3707           0 :                                                 (arep->width > new_rep->width) || (arep->height > new_rep->height)) {
    3708             :                                                 new_rep = arep;
    3709           0 :                                                 new_index = k;
    3710             :                                         }
    3711             :                                 }
    3712           0 :                                 rep->playback.prev_max_available_speed = max_available_speed;
    3713           0 :                                 go_up_bitrate = GF_FALSE;
    3714             :                         }
    3715             :                         else {
    3716             :                                 /* if speed adaptation is not used or used, but the new representation can be played at the right speed */
    3717             : 
    3718        2798 :                                 if (!new_rep) {
    3719             :                                         new_rep = arep;
    3720        2113 :                                         new_index = k;
    3721             :                                 }
    3722         685 :                                 else if (go_up_bitrate) {
    3723             : 
    3724             :                                         /* agressive switching is configured in the GPAC configuration */
    3725         635 :                                         if (dash->agressive_switching) {
    3726             :                                                 /*be agressive, try to switch to highest bitrate below available download rate*/
    3727           0 :                                                 if (arep->bandwidth > new_rep->bandwidth) {
    3728           0 :                                                         if (new_rep->bandwidth > rep->bandwidth) {
    3729           0 :                                                                 nb_inter_rep++;
    3730             :                                                         }
    3731             :                                                         new_rep = arep;
    3732           0 :                                                         new_index = k;
    3733             :                                                 }
    3734           0 :                                                 else if (arep->bandwidth > rep->bandwidth) {
    3735           0 :                                                         nb_inter_rep++;
    3736             :                                                 }
    3737             :                                         }
    3738             :                                         else {
    3739             :                                                 /*don't be agressive, try to switch to lowest bitrate above our current rep*/
    3740         635 :                                                 if (new_rep->bandwidth <= rep->bandwidth) {
    3741             :                                                         new_rep = arep;
    3742         625 :                                                         new_index = k;
    3743             :                                                 }
    3744          10 :                                                 else if ((arep->bandwidth < new_rep->bandwidth) && (arep->bandwidth > rep->bandwidth)) {
    3745             :                                                         new_rep = arep;
    3746           0 :                                                         new_index = k;
    3747             :                                                 }
    3748             :                                         }
    3749             :                                 }
    3750             :                                 else {
    3751             :                                         /* go_up_bitrate is GF_FALSE */
    3752             :                                         /*try to switch to highest bitrate below available download rate*/
    3753          50 :                                         if (arep->bandwidth > new_rep->bandwidth) {
    3754             :                                                 new_rep = arep;
    3755          50 :                                                 new_index = k;
    3756             :                                         }
    3757             :                                 }
    3758             :                         }
    3759             :                 }
    3760             :         }
    3761             : 
    3762        2116 :         if (!new_rep || (new_rep == rep)) {
    3763        2097 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d no better match for requested bandwidth %d - not switching (AS bitrate %d)!\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), dl_rate, rep->bandwidth));
    3764             :                 do_switch = GF_FALSE;
    3765             :         }
    3766             : 
    3767             :         if (do_switch) {
    3768             :                 //if we're switching to the next upper bitrate (no intermediate bitrates), do not immediately switch
    3769             :                 //but for a given number of segments - this avoids fluctuation in the quality
    3770          19 :                 if (go_up_bitrate && !nb_inter_rep) {
    3771          11 :                         new_rep->playback.probe_switch_count++;
    3772          11 :                         if (new_rep->playback.probe_switch_count > dash->probe_times_before_switch) {
    3773           4 :                                 new_rep->playback.probe_switch_count = 0;
    3774             :                         } else {
    3775           7 :                                 new_index = group->active_rep_index;
    3776             :                         }
    3777             :                 }
    3778             :         }
    3779        2116 :         return new_index;
    3780             : }
    3781             : 
    3782        2248 : static s32 dash_do_rate_adaptation_legacy_buffer(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
    3783             :                                                                                                   u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
    3784             :                                                                                                   GF_MPD_Representation *rep, Bool go_up_bitrate)
    3785             : {
    3786             :         Bool do_switch;
    3787        2248 :         s32 new_index = group->active_rep_index;
    3788             : 
    3789             :         /* We assume that there will be a change in quality
    3790             :            and then set it to no change or increase if this is not the case */
    3791             :         do_switch = GF_TRUE;
    3792             : 
    3793             :         /* if the current representation bitrate is smaller than the measured bandwidth,
    3794             :            tentatively start to increase bitrate */
    3795        2248 :         if (rep->bandwidth < dl_rate) {
    3796             :                 go_up_bitrate = GF_TRUE;
    3797             :         }
    3798             : 
    3799             :         /* clamp download bitrate to the lowest representation rate, to allow choosing it */
    3800        2248 :         if (dl_rate < group->min_representation_bitrate) {
    3801             :                 dl_rate = group->min_representation_bitrate;
    3802             :         }
    3803             : 
    3804             :         //we have buffered output
    3805        2248 :         if (group->buffer_max_ms) {
    3806             :                 u32 buf_high_threshold, buf_low_threshold;
    3807             :                 s32 occ;
    3808             : 
    3809         808 :                 if (group->current_downloaded_segment_duration && (group->buffer_max_ms > group->current_downloaded_segment_duration)) {
    3810         100 :                         buf_high_threshold = group->buffer_max_ms - (u32)group->current_downloaded_segment_duration;
    3811             :                 }
    3812             :                 else {
    3813         708 :                         buf_high_threshold = 2 * group->buffer_max_ms / 3;
    3814             :                 }
    3815         808 :                 buf_low_threshold = (group->current_downloaded_segment_duration && (group->buffer_min_ms>10)) ? group->buffer_min_ms : (u32)group->current_downloaded_segment_duration;
    3816         808 :                 if (buf_low_threshold > group->buffer_max_ms) buf_low_threshold = 1 * group->buffer_max_ms / 3;
    3817             : 
    3818             :                 //compute how much we managed to refill (current state minus previous state)
    3819         808 :                 occ = (s32)group->buffer_occupancy_ms;
    3820         808 :                 occ -= (s32)group->buffer_occupancy_at_last_seg;
    3821             :                 //if above max buffer force occ>0 since a segment may still be pending and not dispatched (buffer regulation)
    3822         808 :                 if (group->buffer_occupancy_ms>group->buffer_max_ms) occ = 1;
    3823             : 
    3824             :                 //switch down if current buffer falls below min threshold
    3825         808 :                 if ((s32)group->buffer_occupancy_ms < (s32)buf_low_threshold) {
    3826          13 :                         if (!group->buffer_occupancy_ms) {
    3827             :                                 dl_rate = group->min_representation_bitrate;
    3828             :                         }
    3829             :                         else {
    3830          11 :                                 dl_rate = (rep->bandwidth > 10) ? rep->bandwidth - 10 : 1;
    3831             :                         }
    3832             :                         go_up_bitrate = GF_FALSE;
    3833          13 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running low, switching down, target rate %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate));
    3834             :                 }
    3835             :                 //switch up if above max threshold and buffer refill is fast enough
    3836         795 :                 else if ((occ>0) && (group->buffer_occupancy_ms > buf_high_threshold)) {
    3837         649 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - running high, will try to switch up, target rate %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ, dl_rate));
    3838             :                         go_up_bitrate = GF_TRUE;
    3839             :                 }
    3840             :                 //don't do anything in the middle range of the buffer or if refill not fast enough
    3841             :                 else {
    3842             :                         do_switch = GF_FALSE;
    3843         146 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d bitrate %d bps buffer max %d current %d refill since last %d - steady\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), rep->bandwidth, group->buffer_max_ms, group->buffer_occupancy_ms, occ));
    3844             :                 }
    3845             :         }
    3846             : 
    3847             :         /* Unless the switching has been turned off (e.g. middle buffer range),
    3848             :            we apply rate-based adaptation */
    3849        2248 :         if (do_switch) {
    3850        2102 :                 new_index = dash_do_rate_adaptation_legacy_rate(dash, group, base_group, dl_rate, speed, max_available_speed, force_lower_complexity, rep, go_up_bitrate);
    3851             :         }
    3852             : 
    3853        2248 :         return new_index;
    3854             : }
    3855             : 
    3856             : // returns the bitrate and index of the representation having the minimum bitrate above the given rate
    3857           9 : static u32 get_min_rate_above(GF_List *representations, double rate, s32 *index) {
    3858             :         u32 k;
    3859             :         u32 min_rate = GF_INT_MAX;
    3860             :         GF_MPD_Representation *rep;
    3861             : 
    3862           9 :         u32 nb_reps = gf_list_count(representations);
    3863          24 :         for (k = 0; k < nb_reps; k++) {
    3864          24 :                 rep = gf_list_get(representations, k);
    3865          24 :                 if ((rep->bandwidth < min_rate) && (rep->bandwidth > rate)) {
    3866             :                         min_rate = rep->bandwidth;
    3867           9 :                         if (index) {
    3868           0 :                                 *index = k;
    3869             :                         }
    3870             :                         return min_rate; // representations are sorted by bandwidth
    3871             :                 }
    3872             :         }
    3873             :         return min_rate;
    3874             : }
    3875             : 
    3876             : // returns the bitrate and index of the representation having the maximum bitrate below the given rate
    3877          19 : static u32 get_max_rate_below(GF_List *representations, double rate, s32 *index) {
    3878             :         s32 k;
    3879             :         u32 max_rate = 0;
    3880             :         GF_MPD_Representation *rep;
    3881             : 
    3882          19 :         u32 nb_reps = gf_list_count(representations);
    3883          44 :         for (k = (s32) nb_reps-1; k >=0 ; k--) {
    3884          44 :                 rep = gf_list_get(representations, k);
    3885          44 :                 if ((rep->bandwidth > max_rate) && (rep->bandwidth < rate)) {
    3886             :                         max_rate = rep->bandwidth;
    3887          19 :                         if (index) {
    3888           5 :                                 *index = k;
    3889             :                         }
    3890             :                         return max_rate; // representations are sorted by bandwidth
    3891             :                 }
    3892             :         }
    3893             :         return max_rate;
    3894             : }
    3895             : 
    3896             : /**
    3897             : Adaptation Algorithm as described in
    3898             :         T.-Y. Huang et al. 2014. A buffer-based approach to rate adaptation: evidence from a large video streaming service.
    3899             :         In Proceedings of the 2014 ACM conference on SIGCOMM (SIGCOMM '14).
    3900             : */
    3901          41 : static s32 dash_do_rate_adaptation_bba0(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
    3902             :                                                                                                   u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
    3903             :                                                                                                   GF_MPD_Representation *rep, Bool go_up_bitrate)
    3904             : {
    3905             :         u32 rate_plus;
    3906             :         u32 rate_minus;
    3907          41 :         u32 rate_prev = group->active_bitrate;
    3908             :         u32 rate_max;
    3909             :         u32 rate_min;
    3910             :         s32 new_index;
    3911             :         u32 r; // reservoir
    3912             :         u32 cu; // cushion
    3913          41 :         u32 buf_now = group->buffer_occupancy_ms;
    3914          41 :         u32 buf_max = group->buffer_max_ms;
    3915             :         double f_buf_now;
    3916             : 
    3917             :         /* We don't use the segment duration as advertised in the MPD because it may not be there due to segment timeline*/
    3918          41 :         u32 segment_duration_ms = (u32)group->current_downloaded_segment_duration;
    3919             : 
    3920          41 :         rate_min = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, 0))->bandwidth;
    3921          41 :         rate_max = ((GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, gf_list_count(group->adaptation_set->representations) - 1))->bandwidth;
    3922             : 
    3923          41 :         if (!buf_max) buf_max = 3*segment_duration_ms;
    3924             : 
    3925             :         /* buffer level higher than max buffer, keep high quality*/
    3926          41 :         if (group->buffer_occupancy_ms > buf_max) {
    3927           0 :                 return gf_list_count(group->adaptation_set->representations) - 1;
    3928             :         }
    3929             :         /* we cannot run bba if segments are longer than the max buffer*/
    3930          41 :         if (buf_max < segment_duration_ms) {
    3931           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] BBA-0: max buffer %d shorter than segment duration %d, cannot adapt - will use current quality\n", buf_max, group->buffer_occupancy_ms));
    3932           0 :                 return group->active_rep_index;
    3933             :         }
    3934             :         /* if the current buffer cannot hold an entire new segment, we indicate that we don't want to download it now
    3935             :            NOTE: This is not described in the paper
    3936             :         */
    3937          41 :         if (group->buffer_occupancy_ms + segment_duration_ms > buf_max) {
    3938          22 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] BBA-0: not enough space to download new segment: %d\n", group->buffer_occupancy_ms));
    3939             :                 return -1;
    3940             :         }
    3941             : 
    3942          19 :     if (rate_prev == rate_max) {
    3943             :         rate_plus = rate_max;
    3944             :     } else {
    3945           9 :                 rate_plus = get_min_rate_above(group->adaptation_set->representations, rate_prev, NULL);
    3946             :     }
    3947             : 
    3948          19 :     if (rate_prev == rate_min) {
    3949             :         rate_minus = rate_min;
    3950             :     } else {
    3951          14 :                 rate_minus = get_max_rate_below(group->adaptation_set->representations, rate_prev, NULL);
    3952             :     }
    3953             : 
    3954             :     /*
    3955             :      * the size of the reservoir is 37.5% of the buffer size, but at least = 1 chunk duration)
    3956             :          * the size of the upper reservoir is 10% of the buffer size
    3957             :      * the size of cushion is between 37.5% and 90% of the buffer size
    3958             :          * the rate map is piece-wise
    3959             :      */
    3960          19 :         if (buf_max <= segment_duration_ms) {
    3961           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] BBA-0: cannot initialize BBA-0 given the buffer size (%d) and segment duration (%d)\n", group->buffer_max_ms, group->segment_duration*1000));
    3962             :                 return -1;
    3963             :         }
    3964          19 :         r = (u32)(37.5*buf_max / 100);
    3965          19 :         if (r < segment_duration_ms) {
    3966             :                 r = segment_duration_ms;
    3967             :         }
    3968          19 :         cu = (u32)((90-37.5)*buf_max / 100);
    3969             : 
    3970          19 :         if (buf_now <= r) {
    3971           4 :                 f_buf_now = rate_min;
    3972             :         }
    3973          15 :         else if (buf_now >= (cu + r)) {
    3974           1 :                 f_buf_now = rate_max;
    3975             :         }
    3976             :         else {
    3977          14 :                 f_buf_now = rate_min + (rate_max - rate_min)*((buf_now - r) * 1.0 / cu);
    3978             :         }
    3979             : 
    3980          19 :         if (f_buf_now == rate_max) {
    3981             :                 // rate_next = rate_max;
    3982           1 :                 new_index = gf_list_count(group->adaptation_set->representations) - 1;
    3983             :         }
    3984          18 :         else if (f_buf_now == rate_min) {
    3985             :                 // rate_next = rate_min;
    3986           4 :                 new_index = 0;
    3987             :         }
    3988          14 :         else if (f_buf_now >= rate_plus) {
    3989             :                 // rate_next = max of Ri st. Ri < f_buf_now
    3990           2 :                 new_index = 0;
    3991           2 :                 get_max_rate_below(group->adaptation_set->representations, f_buf_now, &new_index);
    3992             :         }
    3993          12 :         else if (f_buf_now <= rate_minus) {
    3994             :                 // rate_next = min of Ri st. Ri > f_buf_now
    3995           0 :                 new_index = gf_list_count(group->adaptation_set->representations) - 1;
    3996           0 :                 get_min_rate_above(group->adaptation_set->representations, f_buf_now, &new_index);
    3997             :         }
    3998             :         else {
    3999             :                 // no change
    4000          12 :                 new_index = group->active_rep_index;
    4001             :         }
    4002             : 
    4003          19 :         if (new_index != -1) {
    4004             : #ifndef GPAC_DISABLE_LOG
    4005          19 :                 GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
    4006             : #endif
    4007             :                 // increment the segment number for debug purposes
    4008          19 :                 group->current_index++;
    4009          19 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] BBA-0: buffer %d ms, segment number %d, new quality %d with rate %d\n", group->buffer_occupancy_ms, group->current_index, new_index, result->bandwidth));
    4010             :         }
    4011          19 :         return new_index;
    4012             : }
    4013             : 
    4014             : /* returns the index of the representation which maximises BOLA utility function
    4015             :    based on the relative log-based utility of each representation compared to the one with the lowest bitrate
    4016             :    NOTE: V can represent V (in BOLA BASIC) or V_D (in other modes of BOLA) */
    4017          63 : static s32 bola_find_max_utility_index(GF_List *representations, Double V, Double gamma, Double p, Double Q) {
    4018             :         u32 k;
    4019             :         Double max_utility = GF_MIN_DOUBLE;
    4020          63 :         u32 nb_reps = gf_list_count(representations);
    4021             :         s32 new_index = -1;
    4022             : 
    4023         269 :         for (k = 0; k < nb_reps; k++) {
    4024         206 :                 GF_MPD_Representation *rep = gf_list_get(representations, k);
    4025         206 :                 Double utility = (V * rep->playback.bola_v + V*gamma*p - Q) / (rep->bandwidth*p);
    4026         206 :                 if (utility >= max_utility) {
    4027             :                         max_utility = utility;
    4028         164 :                         new_index = k;
    4029             :                 }
    4030             :         }
    4031          63 :         return new_index;
    4032             : }
    4033             : 
    4034             : /**
    4035             : Adaptation Algorithm as described in
    4036             : K. Spiteri et al. 2016. BOLA: Near-Optimal Bitrate Adaptation for Online Videos
    4037             : Arxiv.org
    4038             : */
    4039          63 : static s32 dash_do_rate_adaptation_bola(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
    4040             :                                                                                   u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
    4041             :                                                                                           GF_MPD_Representation *rep, Bool go_up_bitrate)
    4042             : {
    4043             :         s32 new_index = -1;
    4044             :         u32 k;
    4045          63 :         Double p = group->current_downloaded_segment_duration / 1000.0;      // segment duration
    4046          63 :         Double gamma = (double)5/(double)p;
    4047          63 :         Double Qmax = group->buffer_max_ms / 1000.0 / p;             // max nb of segments in the buffer
    4048          63 :         Double Q = group->buffer_occupancy_ms / 1000.0 / p;          // current buffer occupancy in number of segments
    4049             : 
    4050             :         GF_MPD_Representation *min_rep;
    4051             :         GF_MPD_Representation *max_rep;
    4052             :         u32 nb_reps;
    4053             : 
    4054          63 :         if (dash->mpd->type != GF_MPD_TYPE_STATIC) {
    4055           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] BOLA: Cannot be used for live MPD\n"));
    4056             :                 return -1;
    4057             :         }
    4058             : 
    4059          63 :         nb_reps = gf_list_count(group->adaptation_set->representations);
    4060          63 :         min_rep = gf_list_get(group->adaptation_set->representations, 0);
    4061          63 :         max_rep = gf_list_get(group->adaptation_set->representations, nb_reps - 1);
    4062             : 
    4063             :         // Computing the log-based utility of each segment (recomputing each time for period changes)
    4064         269 :         for (k = 0; k < nb_reps; k++) {
    4065         206 :                 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
    4066         206 :                 a_rep->playback.bola_v = log(((Double)a_rep->bandwidth) / min_rep->bandwidth);
    4067             :         }
    4068             : 
    4069          63 :         if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_BASIC) {
    4070             :                 /* BOLA Basic is the variant of BOLA that assumes infinite duration streams (no wind-up/down, no rate use, no oscillation control)
    4071             :                    It simply consists in finding the maximum utility */
    4072             :                 // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
    4073          18 :                 Double V = (Qmax - 1) / (gamma * p + max_rep->playback.bola_v);
    4074          18 :                 new_index = bola_find_max_utility_index(group->adaptation_set->representations, V, gamma, p, Q);
    4075             :         }
    4076          45 :         else if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_FINITE ||
    4077          15 :                 dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O ||
    4078             :                 dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
    4079             :                 /* BOLA FINITE is the same as BOLA Basic with the wind-up and down phases */
    4080             :                 /* BOLA O and U add extra steps to BOLA FINITE */
    4081             :                 Double t_bgn; //play time from begin
    4082             :                 Double t_end; //play time to the end
    4083             :                 Double t;
    4084             :                 Double t_prime;
    4085             :                 Double Q_Dmax;
    4086             :                 Double V_D;
    4087          45 :                 Double N = dash->mpd->media_presentation_duration / p;
    4088             : 
    4089          45 :                 t_bgn = p*group->current_index;
    4090          45 :                 t_end = (N - group->current_index)*p;
    4091          45 :                 t = MIN(t_bgn, t_end);
    4092          45 :                 t_prime = MAX(t / 2, 3 * p);
    4093          45 :                 Q_Dmax = MIN(Qmax, t_prime / p);
    4094          45 :                 V_D = (Q_Dmax - 1) / (gamma * p + max_rep->playback.bola_v);
    4095             : 
    4096          45 :                 new_index = bola_find_max_utility_index(group->adaptation_set->representations, V_D, gamma, p, Q);
    4097             : 
    4098          45 :                 if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U || dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_O) {
    4099             :                         //Bola U algorithm
    4100          30 :                         if ((new_index != -1) && ((u32)new_index > group->active_rep_index)) {
    4101           3 :                                 u32 r = group->bytes_per_sec*8;
    4102             : 
    4103             :                                 // index_prime the min m such that (Sm[m]/p)<= max(bandwidth_previous,S_M/p))
    4104             :                                 // NOTE in BOLA, representation indices decrease when the quality increases [1 = best quality]
    4105           3 :                                 u32 m_prime = 0;
    4106           3 :                                 get_max_rate_below(group->adaptation_set->representations, MAX(r, min_rep->bandwidth), &m_prime);
    4107           3 :                                 if (m_prime >= (u32)new_index) {
    4108           1 :                                         m_prime = new_index;
    4109             :                                 }
    4110           2 :                                 else if (m_prime < group->active_rep_index) {
    4111           0 :                                         m_prime = group->active_rep_index;
    4112             :                                 }
    4113             :                                 else {
    4114           2 :                                         if (dash->adaptation_algorithm == GF_DASH_ALGO_BOLA_U) {
    4115           1 :                                                 m_prime++;
    4116             :                                         }
    4117             :                                         else { //GF_DASH_ALGO_BOLA_O
    4118             : #if 0
    4119             :                                                 GF_MPD_Representation *rep_m_prime, *rep_m_prime_plus_one;
    4120             :                                                 Double Sm_prime, Sm_prime_plus_one, f_m_prime, f_m_prime_1, bola_o_pause;
    4121             : 
    4122             :                                                 assert(m_prime >= 0 && m_prime < nb_reps - 2);
    4123             :                                                 rep_m_prime = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime);
    4124             :                                                 rep_m_prime_plus_one = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, m_prime+1);
    4125             :                                                 Sm_prime = rep_m_prime->bandwidth*p;
    4126             :                                                 Sm_prime_plus_one = rep_m_prime_plus_one->bandwidth*p;
    4127             :                                                 f_m_prime = V_D*(rep_m_prime->playback.bola_v + gamma*p) / Sm_prime;
    4128             :                                                 f_m_prime_1 = V_D*(rep_m_prime_plus_one->playback.bola_v + gamma*p) / Sm_prime_plus_one;
    4129             :                                                 // TODO wait for bola_o_pause before making the download
    4130             :                                                 bola_o_pause = Q - (f_m_prime - f_m_prime_1) / (1 / Sm_prime - 1 / Sm_prime_plus_one);
    4131             : #endif
    4132             :                                         }
    4133             :                                 }
    4134           3 :                                 new_index = m_prime;
    4135             :                         }
    4136             :                 }
    4137             :                 // TODO trigger pause for max(p*(Q-Q_Dmax+1), 0)
    4138             :         }
    4139             : 
    4140          63 :         if (new_index != -1) {
    4141             : #ifndef GPAC_DISABLE_LOG
    4142          63 :                 GF_MPD_Representation *result = gf_list_get(group->adaptation_set->representations, (u32)new_index);
    4143             : #endif
    4144             :                 // increment the segment number for debug purposes
    4145          63 :                 group->current_index++;
    4146          63 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] BOLA: buffer %d ms, segment number %d, new quality %d with rate %d\n", group->buffer_occupancy_ms, group->current_index, new_index, result->bandwidth));
    4147             :         }
    4148             :         return new_index;
    4149             : }
    4150             : 
    4151             : /* This function is called each time a new segment has been downloaded */
    4152        2728 : static void dash_do_rate_adaptation(GF_DashClient *dash, GF_DASH_Group *group)
    4153             : {
    4154             :         Double speed;
    4155             :         Double max_available_speed;
    4156             :         u32 dl_rate;
    4157             :         u32 k;
    4158             :         s32 new_index, old_index;
    4159             :         GF_DASH_Group *base_group;
    4160             :         GF_MPD_Representation *rep;
    4161             :         Bool force_lower_complexity;
    4162             : 
    4163             :         /* Don't do adaptation if configured switching to happen systematically (debug) */
    4164        2728 :         if (dash->auto_switch_count) {
    4165             :                 return;
    4166             :         }
    4167             :         /* Don't do adaptation if GPAC config disabled switching */
    4168        2627 :         if (group->dash->disable_switching) {
    4169             :                 return;
    4170             :         }
    4171             : 
    4172             :         /* The bytes_per_sec field is set each time a segment is downloaded,
    4173             :            (this may need to be adjusted in the future to accomodate algorithms
    4174             :            that smooth download rate over several segments)
    4175             :            if set to 0, this means that no segment was downloaded since the last call
    4176             :            because this AdaptationSet is not selected
    4177             :            So no rate adaptation should be done*/
    4178        2409 :         if (!group->bytes_per_sec) {
    4179             :                 return;
    4180             :         }
    4181             : 
    4182             :         /* Find the AdaptationSet on which this AdaptationSet depends, if any
    4183             :            (e.g. used for specific coding schemes: scalable streams, tiled streams, ...)*/
    4184             :         base_group = group;
    4185        3849 :         while (base_group->depend_on_group) {
    4186             :                 base_group = base_group->depend_on_group;
    4187             :         }
    4188             : 
    4189             :         /* adjust the download rate according to the playback speed
    4190             :            All adaptation algorithms should use this value */
    4191        2409 :         speed = dash->speed;
    4192        2409 :         if (speed<0) speed = -speed;
    4193        2409 :         dl_rate = (u32) (8 * (u64) group->bytes_per_sec / speed);
    4194        2409 :         if ((s32) dl_rate < 0)
    4195             :                 dl_rate = GF_INT_MAX;
    4196             : 
    4197             :         /* Get the active representation in the AdaptationSet */
    4198        2409 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    4199             : 
    4200             :         /*check whether we can play with this speed (i.e. achieve target frame rate);
    4201             :         if not force, let algorithm know that they should switch to a lower resolution*/
    4202        2409 :         max_available_speed = gf_dash_get_max_available_speed(dash, base_group, rep);
    4203        2409 :         if (!dash->disable_speed_adaptation && !rep->playback.waiting_codec_reset) {
    4204           0 :                 if (max_available_speed && (0.9 * speed > max_available_speed)) {
    4205           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Forcing a lower complexity to achieve desired playback speed\n"));
    4206             :                         force_lower_complexity = GF_TRUE;
    4207             :                 } else {
    4208             :                         force_lower_complexity = GF_FALSE;
    4209             :                 }
    4210             :         } else {
    4211             :                 force_lower_complexity = GF_FALSE;
    4212             :         }
    4213             : 
    4214             :         /*query codec and buffer statistics for buffer-based algorithms */
    4215        2409 :         group->buffer_max_ms = 0;
    4216        2409 :         group->buffer_occupancy_ms = 0;
    4217        2409 :         group->codec_reset = 0;
    4218             :         /* the DASH Client asks the player for its buffer level
    4219             :           (uses a function pointer to avoid depenencies on the player code, to reuse the DASH client in different situations)*/
    4220        2409 :         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CODEC_STAT_QUERY, gf_list_find(group->dash->groups, group), GF_OK);
    4221             : 
    4222             :         /* If the playback for the current representation was waiting for a codec reset and it happened,
    4223             :            indicate that this representation does not need a reset anymore */
    4224        2409 :         if (rep->playback.waiting_codec_reset && group->codec_reset) {
    4225           0 :                 rep->playback.waiting_codec_reset = GF_FALSE;
    4226             :         }
    4227             : 
    4228        2409 :         old_index = group->active_rep_index;
    4229             :         //scalable case, force the rate algo to consider the active rep is the max rep
    4230        2409 :         if (group->base_rep_index_plus_one) {
    4231           0 :                 group->active_rep_index = group->max_complementary_rep_index;
    4232             :         }
    4233        2409 :         if (group->dash->route_clock_state) {
    4234           0 :                 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    4235           0 :                 if (rep->playback.broadcast_flag && (dl_rate < rep->bandwidth)) {
    4236           0 :                         dl_rate = rep->bandwidth+1;
    4237           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d representation %d segment sent over broadcast, forcing bandwidth to %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set), group->active_rep_index, dl_rate));
    4238             :                 }
    4239             :         }
    4240             : 
    4241             :         /* Call a specific adaptation algorithm (see GPAC configuration)
    4242             :         Each algorithm should:
    4243             :         - return the new_index value to the desired quality
    4244             : 
    4245             :         It can use:
    4246             :         - the information about each available representation (group->adaptation_set->representations, e.g. bandwidth required for that representation)
    4247             :         - the information of the current representation (rep)
    4248             :         - the download_rate dl_rate (computed on the previously downloaded segment, and adjusted to the playback speed),
    4249             :         - the buffer levels:
    4250             :             - current: group->buffer_occupancy_ms,
    4251             :                 - previous: group->buffer_occupancy_at_last_seg
    4252             :                 - max: group->buffer_max_ms,
    4253             :         - the playback speed,
    4254             :         - the maximum achievable speed at the current resolution,
    4255             :         - the indicator that the current representation is too demanding CPU-wise (force_lower_complexity)
    4256             : 
    4257             :         Private algorithm information should be stored in the dash object if global to all AdaptationSets,
    4258             :         or in the group if local to an AdaptationSet.
    4259             : 
    4260             :         TODO: document how to access other possible parameters (e.g. segment sizes if available, ...)
    4261             :         */
    4262        2409 :         new_index = group->active_rep_index;
    4263        2409 :         if (dash->rate_adaptation_algo) {
    4264        2409 :                 new_index = dash->rate_adaptation_algo(dash, group, base_group,
    4265             :                                                                                                                   dl_rate, speed, max_available_speed, force_lower_complexity,
    4266             :                                                                                                                   rep, GF_FALSE);
    4267             :         }
    4268             : 
    4269        2409 :         if (new_index==-1) {
    4270          22 :                 group->active_rep_index = old_index;
    4271          22 :                 group->rate_adaptation_postponed = GF_TRUE;
    4272          22 :                 return;
    4273             :         }
    4274        2387 :         group->rate_adaptation_postponed = GF_FALSE;
    4275        2387 :         if (new_index < 0) {
    4276           0 :                 if (new_index == -2) {
    4277           0 :                         group->disabled = GF_TRUE;
    4278             :                 }
    4279           0 :                 group->active_rep_index = old_index;
    4280           0 :                 return;
    4281             :         }
    4282        2387 :         if (new_index != group->active_rep_index) {
    4283          34 :                 GF_MPD_Representation *new_rep = gf_list_get(group->adaptation_set->representations, (u32)new_index);
    4284          34 :                 group->disabled = GF_FALSE;
    4285          34 :                 if (!new_rep) {
    4286           0 :                         group->active_rep_index = old_index;
    4287           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Cannot find new representation index %d, using previous one\n", new_index));
    4288             :                         return;
    4289             :                 }
    4290             : 
    4291          34 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] AS#%d switching after playing %d segments, from rep %d to rep %d\n", 1 + gf_list_find(group->period->adaptation_sets, group->adaptation_set),
    4292             :                                 group->nb_segments_since_switch, group->active_rep_index, new_index));
    4293          34 :                 group->nb_segments_since_switch = 0;
    4294             : 
    4295          34 :                 if (force_lower_complexity) {
    4296           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Requesting codec reset\n"));
    4297           0 :                         new_rep->playback.waiting_codec_reset = GF_TRUE;
    4298             :                 }
    4299             :                 /* request downloads for the new representation */
    4300          34 :                 gf_dash_set_group_representation(group, new_rep, GF_FALSE);
    4301             : 
    4302             :                 /* Reset smoothing of switches
    4303             :                 (note: should really apply only to algorithms using the switch_probe_count (smoothing the aggressiveness of the change)
    4304             :                 for now: only GF_DASH_ALGO_GPAC_LEGACY_RATE and  GF_DASH_ALGO_GPAC_LEGACY_BUFFER */
    4305         166 :                 for (k = 0; k < gf_list_count(group->adaptation_set->representations); k++) {
    4306         132 :                         GF_MPD_Representation *arep = gf_list_get(group->adaptation_set->representations, k);
    4307         132 :                         if (new_rep == arep) continue;
    4308          98 :                         arep->playback.probe_switch_count = 0;
    4309             :                 }
    4310             : 
    4311             :         } else {
    4312        2353 :                 group->active_rep_index = old_index;
    4313        2353 :                 if (force_lower_complexity) {
    4314           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Forced to lower quality/rate because of playback speed %f higher than max speed possible %f, but no other quality available: cannot switch down\n", speed, max_available_speed));
    4315             :                         /*FIXME: should do something here*/
    4316             :                 }
    4317             :         }
    4318             : 
    4319             :         /* Remembering the buffer level for the processing of the next segment */
    4320        2387 :         group->buffer_occupancy_at_last_seg = group->buffer_occupancy_ms;
    4321             : }
    4322             : 
    4323         262 : static char *gf_dash_get_fileio_url(const char *base_url, char *res_url)
    4324             : {
    4325             :         const char *new_res;
    4326             :         GF_FileIO *gfio;
    4327         262 :         if (!base_url)
    4328             :                 return NULL;
    4329         262 :         if (strncmp(base_url, "gfio://", 7))
    4330             :                 return res_url;
    4331             : 
    4332           1 :         gfio = gf_fileio_from_url(base_url);
    4333             : 
    4334           1 :         new_res = gf_fileio_factory(gfio, res_url);
    4335           1 :         if (!new_res) return res_url;
    4336           1 :         gf_free(res_url);
    4337           1 :         return gf_strdup(new_res);
    4338             : }
    4339             : 
    4340         298 : static GF_Err gf_dash_download_init_segment(GF_DashClient *dash, GF_DASH_Group *group)
    4341             : {
    4342             :         GF_Err e;
    4343             :         char *base_init_url;
    4344             :         GF_MPD_Representation *rep;
    4345             :         u64 start_range, end_range;
    4346         298 :         Bool data_url_processed = GF_FALSE;
    4347             :         /* This variable is 0 if there is a initURL, the index of first segment downloaded otherwise */
    4348             :         u32 nb_segment_read = 0;
    4349             :         char *base_url=NULL;
    4350             :         char *base_url_orig=NULL;
    4351         298 :         char *key_url=NULL;
    4352             :         bin128 key_iv;
    4353         298 :         u32 start_number = 0;
    4354             : 
    4355         298 :         if (!dash || !group)
    4356             :                 return GF_BAD_PARAM;
    4357             : 
    4358             :         assert(group->adaptation_set && group->adaptation_set->representations);
    4359         298 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    4360         298 :         if (!rep) {
    4361           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to find any representation, aborting.\n"));
    4362             :                 return GF_IO_ERR;
    4363             :         }
    4364         298 :         start_range = end_range = 0;
    4365         298 :         base_url = dash->base_url;
    4366         298 :         if (group->period->origin_base_url) base_url = group->period->origin_base_url;
    4367             : 
    4368             :         base_url_orig = base_url;
    4369         298 :         if (base_url && !strncmp(base_url, "gfio://", 7)) {
    4370           1 :                 GF_FileIO *gfio = gf_fileio_from_url(base_url);
    4371           1 :                 base_url = (char *) gf_file_basename(gf_fileio_resource_url(gfio));
    4372             :         }
    4373             : 
    4374         298 :         e = gf_dash_resolve_url(dash->mpd, rep, group, base_url, GF_MPD_RESOLVE_URL_INIT, 0, &base_init_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, &data_url_processed, NULL);
    4375         298 :         if (e) {
    4376          36 :                 if (e != GF_IP_NETWORK_EMPTY) {
    4377           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve initialization URL: %s\n", gf_error_to_string(e) ));
    4378             :                 }
    4379             :                 return e;
    4380             :         }
    4381             : 
    4382         262 :         if (!base_init_url && rep->dependency_id) {
    4383             :                 return GF_OK;
    4384             :         }
    4385             : 
    4386             :         /*no error and no init segment, go for media segment - this is needed for TS so that the set of media streams can be
    4387             :         declared to the player */
    4388         262 :         if (!base_init_url) {
    4389           6 :                 e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA, group->download_segment_index, &base_init_url, &start_range, &end_range, &group->current_downloaded_segment_duration, NULL, &key_url, &key_iv, NULL, &start_number);
    4390           6 :                 if (e) {
    4391           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media URL: %s\n", gf_error_to_string(e) ));
    4392             :                         return e;
    4393             :                 }
    4394             :                 nb_segment_read = 1;
    4395             :         }
    4396             : 
    4397             :         base_url = base_url_orig;
    4398         262 :         base_init_url = gf_dash_get_fileio_url(base_url, base_init_url);
    4399             : 
    4400         262 :         if (!strstr(base_init_url, "://") || !strnicmp(base_init_url, "file://", 7) || !strnicmp(base_init_url, "gmem://", 7)
    4401          70 :                 || !strnicmp(base_init_url, "views://", 8) || !strnicmp(base_init_url, "mosaic://", 9)
    4402          70 :                 || !strnicmp(base_init_url, "isobmff://", 10) || !strnicmp(base_init_url, "gfio://", 7)
    4403             :         ) {
    4404             :                 //if file-based, check if file exists, if not switch base URL
    4405         198 :                 if ( strnicmp(base_init_url, "gmem://", 7) && strnicmp(base_init_url, "gfio://", 7)) {
    4406         197 :                         if (! gf_file_exists(base_init_url) ) {
    4407           5 :                                 if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
    4408           0 :                                         group->current_base_url_idx++;
    4409           0 :                                         gf_free(base_init_url);
    4410           0 :                                         return gf_dash_download_init_segment(dash, group);
    4411             :                                 }
    4412             :                         }
    4413             :                 }
    4414             :                 //we don't reset the baseURL index until we are done fetching all init segments
    4415             : 
    4416             :                 assert(!group->nb_cached_segments);
    4417             :                 //transfer mem
    4418         198 :                 group->cached[0].url = base_init_url;
    4419         198 :                 group->cached[0].representation_index = group->active_rep_index;
    4420         198 :                 group->prev_active_rep_index = group->active_rep_index;
    4421         198 :                 if (key_url) {
    4422           0 :                         group->cached[0].key_url = key_url;
    4423           0 :                         memcpy(group->cached[0].key_IV, key_iv, sizeof(bin128));
    4424             :                 }
    4425         198 :                 group->cached[0].seg_number = start_number + group->download_segment_index;
    4426             : 
    4427         198 :                 group->nb_cached_segments = 1;
    4428             :                 /*do not erase local files*/
    4429         198 :                 group->local_files = group->was_segment_base ? 0 : 1;
    4430             : 
    4431         198 :                 group->download_segment_index += nb_segment_read;
    4432         198 :                 if (group->bitstream_switching) {
    4433          40 :                         group->bs_switching_init_segment_url = gf_strdup(base_init_url);
    4434          40 :                         group->bs_switching_init_segment_url_start_range = start_range;
    4435          40 :                         group->bs_switching_init_segment_url_end_range = end_range;
    4436          40 :                         group->bs_switching_init_segment_url_name_start = dash_strip_base_url(group->bs_switching_init_segment_url, base_url);
    4437          40 :                         if (data_url_processed) {
    4438           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
    4439             :                         }
    4440             :                 } else {
    4441         158 :                         if (rep->playback.cached_init_segment_url) gf_free(rep->playback.cached_init_segment_url);
    4442         158 :                         rep->playback.cached_init_segment_url = gf_strdup(base_init_url);
    4443         158 :                         rep->playback.owned_gmem = data_url_processed;
    4444         158 :                         rep->playback.init_start_range = start_range;
    4445         158 :                         rep->playback.init_end_range = end_range;
    4446         158 :                         rep->playback.init_seg_name_start = dash_strip_base_url(rep->playback.cached_init_segment_url, base_url);
    4447         158 :                         if (key_url) {
    4448           0 :                                 rep->playback.key_url = gf_strdup(key_url);
    4449           0 :                                 memcpy(rep->playback.key_IV, key_iv, sizeof(bin128) );
    4450             :                         }
    4451             :                 }
    4452             : 
    4453             : 
    4454             :                 /*finally download all init segments if any*/
    4455         198 :                 if (!group->bitstream_switching) {
    4456             :                         u32 k;
    4457         183 :                         for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
    4458         183 :                                 char *a_base_init_url = NULL;
    4459         183 :                                 u64 a_start = 0, a_end = 0, a_dur = 0;
    4460         183 :                                 GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
    4461         341 :                                 if (a_rep==rep) continue;
    4462          25 :                                 if (a_rep->playback.disabled) continue;
    4463             : 
    4464          25 :                                 e = gf_dash_resolve_url(dash->mpd, a_rep, group, dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &a_base_init_url, &a_start, &a_end, &a_dur, NULL, &a_rep->playback.key_url, &a_rep->playback.key_IV, &a_rep->playback.owned_gmem, NULL);
    4465          25 :                                 if (!e && a_base_init_url) {
    4466          21 :                                         if (a_rep->playback.cached_init_segment_url) gf_free(a_rep->playback.cached_init_segment_url);
    4467          21 :                                         a_rep->playback.cached_init_segment_url = a_base_init_url;
    4468          21 :                                         a_rep->playback.init_start_range = a_start;
    4469          21 :                                         a_rep->playback.init_end_range = a_end;
    4470          21 :                                         a_rep->playback.init_seg_name_start = dash_strip_base_url(a_base_init_url, base_url);
    4471           4 :                                 } else if (e) {
    4472           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - discarding representation\n", gf_error_to_string(e) ));
    4473           0 :                                         a_rep->playback.disabled = 1;
    4474             :                                 }
    4475             :                         }
    4476             :                 }
    4477         198 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] First segment is %s \n", base_init_url));
    4478             :                 //do NOT free base_init_url, it is now in group->cached[0].url
    4479         198 :                 group->current_base_url_idx=0;
    4480         198 :                 return GF_OK;
    4481             :         }
    4482             : 
    4483          64 :         group->max_bitrate = 0;
    4484          64 :         group->min_bitrate = (u32)-1;
    4485             : 
    4486             : 
    4487          64 :         if (dash->route_clock_state && !group->period->origin_base_url) {
    4488           8 :                 GF_DASHFileIOSession sess = NULL;
    4489             :                 /*check the init segment has been received*/
    4490           8 :                 e = gf_dash_download_resource(dash, &sess, base_init_url, start_range, end_range, 1, NULL);
    4491           8 :                 dash->dash_io->del(dash->dash_io, sess);
    4492             : 
    4493           8 :                 if (e!=GF_OK) {
    4494           2 :                         gf_free(base_init_url);
    4495           2 :                         return e;
    4496             :                 }
    4497             :         }
    4498             : 
    4499             :         assert(!group->nb_cached_segments);
    4500          62 :         group->cached[0].url = base_init_url;
    4501          62 :         group->cached[0].representation_index = group->active_rep_index;
    4502          62 :         group->cached[0].duration = (u32) group->current_downloaded_segment_duration;
    4503             : 
    4504          62 :         if (group->bitstream_switching) {
    4505          31 :                 group->bs_switching_init_segment_url = gf_strdup(base_init_url);
    4506          31 :                 group->bs_switching_init_segment_url_name_start = dash_strip_base_url(group->bs_switching_init_segment_url, base_url);
    4507             : 
    4508          31 :                 group->bs_switching_init_segment_url_start_range = start_range;
    4509          31 :                 group->bs_switching_init_segment_url_end_range = end_range;
    4510          31 :                 if (data_url_processed) {
    4511           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("URL with data scheme not handled for Bistream Switching Segments, probable memory leak"));
    4512             :                 }
    4513             :         } else {
    4514          31 :                 if (rep->playback.cached_init_segment_url) gf_free(rep->playback.cached_init_segment_url);
    4515          31 :                 rep->playback.cached_init_segment_url = gf_strdup(base_init_url);
    4516          31 :                 rep->playback.init_start_range = start_range;
    4517          31 :                 rep->playback.init_end_range = end_range;
    4518          31 :                 rep->playback.owned_gmem = data_url_processed;
    4519          31 :                 rep->playback.init_seg_name_start = dash_strip_base_url(rep->playback.cached_init_segment_url, base_url);
    4520             :         }
    4521          62 :         group->nb_cached_segments = 1;
    4522          62 :         group->download_segment_index += nb_segment_read;
    4523             : 
    4524             :         /*download all init segments if any*/
    4525          62 :         if (!group->bitstream_switching) {
    4526             :                 u32 k;
    4527          57 :                 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
    4528          57 :                         char *a_base_init_url = NULL;
    4529             :                         u64 a_start, a_end, a_dur;
    4530          57 :                         GF_MPD_Representation *a_rep = gf_list_get(group->adaptation_set->representations, k);
    4531          88 :                         if (a_rep==rep) continue;
    4532          26 :                         if (a_rep->playback.disabled) continue;
    4533             : 
    4534          26 :                         e = gf_dash_resolve_url(dash->mpd, a_rep, group, dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &a_base_init_url, &a_start, &a_end, &a_dur, NULL, &a_rep->playback.key_url, &a_rep->playback.key_IV, &a_rep->playback.owned_gmem, NULL);
    4535          26 :                         if (!e && a_base_init_url) {
    4536          15 :                                 if (a_rep->playback.cached_init_segment_url) gf_free(a_rep->playback.cached_init_segment_url);
    4537          15 :                                 a_rep->playback.cached_init_segment_url = a_base_init_url;
    4538          15 :                                 a_rep->playback.init_start_range = a_start;
    4539          15 :                                 a_rep->playback.init_end_range = a_end;
    4540          15 :                                 a_rep->playback.init_seg_name_start = dash_strip_base_url(a_rep->playback.cached_init_segment_url, base_url);
    4541          11 :                         } else if (e) {
    4542           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot solve initialization segment for representation: %s - disabling representation\n", gf_error_to_string(e) ));
    4543           0 :                                 a_rep->playback.disabled = 1;
    4544             :                         }
    4545             :                 }
    4546             :         }
    4547             :         return GF_OK;
    4548             : }
    4549             : 
    4550         120 : static void gf_dash_skip_disabled_representation(GF_DASH_Group *group, GF_MPD_Representation *rep, Bool for_autoswitch)
    4551             : {
    4552             :         s32 rep_idx, orig_idx;
    4553             :         u32 bandwidth = 0xFFFFFFFF;
    4554             : 
    4555         120 :         rep_idx = orig_idx = gf_list_find(group->adaptation_set->representations, rep);
    4556             :         while (1) {
    4557         120 :                 rep_idx++;
    4558         120 :                 if (rep_idx==gf_list_count(group->adaptation_set->representations)) rep_idx = 0;
    4559             :                 //none other than current one
    4560         120 :                 if (orig_idx==rep_idx) return;
    4561             : 
    4562          91 :                 rep = gf_list_get(group->adaptation_set->representations, rep_idx);
    4563          91 :                 if (rep->playback.disabled) continue;
    4564             : 
    4565             :                 if (rep->bandwidth<=bandwidth) break;
    4566             :                 assert(for_autoswitch);
    4567             :                 //go to next rep
    4568             :         }
    4569             :         assert(rep && !rep->playback.disabled);
    4570          91 :         gf_dash_set_group_representation(group, rep, GF_FALSE);
    4571             : }
    4572             : 
    4573             : 
    4574        3287 : static void gf_dash_group_reset_cache_entry(segment_cache_entry *cached)
    4575             : {
    4576        3287 :         gf_free(cached->url);
    4577        3287 :         if (cached->key_url) gf_free(cached->key_url);
    4578             :         memset(cached, 0, sizeof(segment_cache_entry));
    4579        3287 : }
    4580             : 
    4581         284 : static void gf_dash_group_reset(GF_DashClient *dash, GF_DASH_Group *group)
    4582             : {
    4583         441 :         while (group->nb_cached_segments) {
    4584         157 :                 group->nb_cached_segments--;
    4585         157 :                 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
    4586             :         }
    4587         284 :         group->llhls_edge_chunk = NULL;
    4588             : 
    4589         284 :         group->timeline_setup = GF_FALSE;
    4590         284 : }
    4591             : 
    4592         244 : static void gf_dash_reset_groups(GF_DashClient *dash)
    4593             : {
    4594             :         /*send playback destroy event*/
    4595         244 :         if (dash->dash_io) dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_DESTROY_PLAYBACK, -1, GF_OK);
    4596             : 
    4597         506 :         while (gf_list_count(dash->groups)) {
    4598         262 :                 GF_DASH_Group *group = gf_list_last(dash->groups);
    4599         262 :                 gf_list_rem_last(dash->groups);
    4600             : 
    4601         262 :                 gf_dash_group_reset(dash, group);
    4602             : 
    4603         262 :                 gf_list_del(group->groups_depending_on);
    4604         262 :                 gf_free(group->cached);
    4605         262 :                 if (group->service_mime)
    4606           2 :                         gf_free(group->service_mime);
    4607             : 
    4608         262 :                 if (group->bs_switching_init_segment_url)
    4609          71 :                         gf_free(group->bs_switching_init_segment_url);
    4610             : 
    4611         262 :                 gf_free(group);
    4612             :         }
    4613         244 :         gf_list_del(dash->groups);
    4614         244 :         dash->groups = NULL;
    4615             : 
    4616         495 :         while (gf_list_count(dash->SRDs)) {
    4617           7 :                 struct _dash_srd_desc *srd = gf_list_last(dash->SRDs);
    4618           7 :                 gf_list_rem_last(dash->SRDs);
    4619           7 :                 gf_free(srd);
    4620             :         }
    4621         244 :         gf_list_del(dash->SRDs);
    4622         244 :         dash->SRDs = NULL;
    4623         244 : }
    4624             : 
    4625             : #ifndef GPAC_DISABLE_LOG
    4626         126 : static u32 gf_dash_get_start_number(GF_DASH_Group *group, GF_MPD_Representation *rep)
    4627             : {
    4628         126 :         if (rep->segment_list && rep->segment_list->start_number) return rep->segment_list->start_number;
    4629         126 :         if (group->adaptation_set->segment_list && group->adaptation_set->segment_list->start_number) return group->adaptation_set->segment_list->start_number;
    4630         126 :         if (group->period->segment_list && group->period->segment_list->start_number) return group->period->segment_list->start_number;
    4631             : 
    4632         126 :         if (rep->segment_template && rep->segment_template->start_number) return rep->segment_template->start_number;
    4633         104 :         if (group->adaptation_set->segment_template && group->adaptation_set->segment_template->start_number) return group->adaptation_set->segment_template->start_number;
    4634           0 :         if (group->period->segment_template && group->period->segment_template->start_number) return group->period->segment_template->start_number;
    4635             : 
    4636             :         return 0;
    4637             : }
    4638             : #endif
    4639             : 
    4640          63 : static GF_MPD_Representation *gf_dash_find_rep(GF_DashClient *dash, const char *dependency_id, GF_DASH_Group **rep_group)
    4641             : {
    4642             :         u32 i, j, nb_groups, nb_reps;
    4643             :         GF_MPD_Representation *rep;
    4644             : 
    4645          63 :         if (rep_group) *rep_group = NULL;
    4646             : 
    4647          63 :         if (!dependency_id) return NULL;
    4648             : 
    4649          63 :         nb_groups = gf_list_count(dash->groups);
    4650           0 :         for (i=0; i<nb_groups; i++) {
    4651          63 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    4652          63 :                 nb_reps = gf_list_count(group->adaptation_set->representations);
    4653           0 :                 for (j=0; j<nb_reps; j++) {
    4654          63 :                         rep = gf_list_get(group->adaptation_set->representations, j);
    4655          63 :                         if (rep->id && !strcmp(rep->id, dependency_id)) {
    4656          63 :                                 if (rep_group) *rep_group = group;
    4657             :                                 return rep;
    4658             :                         }
    4659             :                 }
    4660             :         }
    4661             :         return NULL;
    4662             : }
    4663             : 
    4664             : static
    4665         262 : s32 gf_dash_group_get_dependency_group(GF_DashClient *dash, u32 idx)
    4666             : {
    4667             :         GF_MPD_Representation *rep;
    4668         262 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    4669         262 :         if (!group) return idx;
    4670             : 
    4671         262 :         rep = gf_list_get(group->adaptation_set->representations, 0);
    4672             : 
    4673         587 :         while (rep && rep->dependency_id) {
    4674          63 :                 char *sep = strchr(rep->dependency_id, ' ');
    4675          63 :                 if (sep) sep[0] = 0;
    4676          63 :                 rep = gf_dash_find_rep(dash, rep->dependency_id, &group);
    4677          63 :                 if (sep) sep[0] = ' ';
    4678             :         }
    4679         262 :         return gf_list_find(dash->groups, group);
    4680             : }
    4681             : 
    4682             : GF_EXPORT
    4683         528 : s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 idx)
    4684             : {
    4685         528 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    4686         528 :         if (!group) return GF_FALSE;
    4687         528 :         return group->depend_on_group ? gf_list_find(dash->groups, group->depend_on_group) : -1;
    4688             : }
    4689             : 
    4690             : GF_EXPORT
    4691         197 : u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 idx)
    4692             : {
    4693         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    4694         197 :         if (!group) return 0;
    4695         197 :         return group->groups_depending_on ? gf_list_count(group->groups_depending_on) : 0;
    4696             : }
    4697             : 
    4698             : GF_EXPORT
    4699           0 : s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 idx, u32 group_depending_on_dep_idx)
    4700             : {
    4701             :         GF_DASH_Group *group_depending_on;
    4702           0 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    4703           0 :         if (!group || !group->groups_depending_on) return -1;
    4704           0 :         group_depending_on = gf_list_get(group->groups_depending_on, group_depending_on_dep_idx);
    4705           0 :         if (!group_depending_on) return -1;
    4706           0 :         return gf_list_find(dash->groups, group_depending_on);
    4707             : }
    4708             : 
    4709             : /* create groups (implementation of adaptations set) */
    4710         123 : GF_Err gf_dash_setup_groups(GF_DashClient *dash)
    4711             : {
    4712             :         GF_Err e;
    4713             :         u32 i, j, count, nb_dependent_rep;
    4714             :         GF_MPD_Period *period;
    4715             : 
    4716         123 :         if (!dash->groups) {
    4717         123 :                 dash->groups = gf_list_new();
    4718         123 :                 if (!dash->groups) return GF_OUT_OF_MEM;
    4719             :         }
    4720             : 
    4721         123 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    4722         123 :         if (!period) return GF_BAD_PARAM;
    4723             : 
    4724         123 :         count = gf_list_count(period->adaptation_sets);
    4725         385 :         for (i=0; i<count; i++) {
    4726             :                 Double seg_dur;
    4727             :                 GF_DASH_Group *group;
    4728             :                 Bool found = GF_FALSE;
    4729             :                 Bool has_dependent_representations = GF_FALSE;
    4730         262 :                 GF_MPD_AdaptationSet *set = gf_list_get(period->adaptation_sets, i);
    4731         669 :                 for (j=0; j<gf_list_count(dash->groups); j++) {
    4732         407 :                         group = gf_list_get(dash->groups, j);
    4733         407 :                         if (group->adaptation_set==set) {
    4734             :                                 found = 1;
    4735             :                                 break;
    4736             :                         }
    4737             :                 }
    4738             : 
    4739         262 :                 if (found) continue;
    4740             : 
    4741         262 :                 if (! gf_list_count(set->representations)) {
    4742           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Empty adaptation set found (ID %d) - ignoring\n", set->id));
    4743           0 :                         continue;
    4744             :                 }
    4745             : 
    4746             : 
    4747         262 :                 GF_SAFEALLOC(group, GF_DASH_Group);
    4748         262 :                 if (!group) return GF_OUT_OF_MEM;
    4749         262 :                 group->dash = dash;
    4750         262 :                 group->adaptation_set = set;
    4751         262 :                 group->period = period;
    4752         262 :                 group->bitstream_switching = (set->bitstream_switching || period->bitstream_switching) ? GF_TRUE : GF_FALSE;
    4753             : 
    4754             :                 seg_dur = 0;
    4755             :                 nb_dependent_rep = 0;
    4756         655 :                 for (j=0; j<gf_list_count(set->representations); j++) {
    4757             :                         Double dur;
    4758             :                         u32 nb_seg, k;
    4759             :                         Bool cp_supported;
    4760         393 :                         GF_MPD_Representation *rep = gf_list_get(set->representations, j);
    4761         393 :                         gf_dash_get_segment_duration(rep, set, period, dash->mpd, &nb_seg, &dur);
    4762         393 :                         if (dur>seg_dur) seg_dur = dur;
    4763             : 
    4764         393 :                         if (group->bitstream_switching && (set->segment_base || period->segment_base || rep->segment_base) ) {
    4765           0 :                                 group->bitstream_switching = GF_FALSE;
    4766           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] bitstreamSwitching set for onDemand content - ignoring flag\n"));
    4767             :                         }
    4768             : 
    4769         393 :                         if (dash->dash_io->dash_codec_supported) {
    4770           0 :                                 Bool res = dash->dash_io->dash_codec_supported(dash->dash_io, rep->codecs, rep->width, rep->height, (rep->scan_type==GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0, rep->framerate ? rep->framerate->num : 0, rep->framerate ? rep->framerate->den : 0, rep->samplerate);
    4771           0 :                                 if (!res) {
    4772           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation not supported by playback engine - ignoring\n"));
    4773           0 :                                         rep->playback.disabled = 1;
    4774           0 :                                         continue;
    4775             :                                 }
    4776             :                         }
    4777             :                         //filter out everything above HD
    4778         393 :                         if ((dash->max_width>2000) && (dash->max_height>2000)) {
    4779           0 :                                 if ((rep->width>dash->max_width) || (rep->height>dash->max_height)) {
    4780           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation size %dx%d exceeds max display size allowed %dx%d - ignoring\n", rep->width, rep->height, dash->max_width, dash->max_height));
    4781           0 :                                         rep->playback.disabled = 1;
    4782           0 :                                         continue;
    4783             :                                 }
    4784             :                         }
    4785         393 :                         if (rep->codecs && (dash->max_bit_per_pixel > 8) ) {
    4786           0 :                                 char *vid_type = strstr(rep->codecs, "hvc");
    4787           0 :                                 if (!vid_type) vid_type = strstr(rep->codecs, "hev");
    4788           0 :                                 if (!vid_type) vid_type = strstr(rep->codecs, "avc");
    4789           0 :                                 if (!vid_type) vid_type = strstr(rep->codecs, "svc");
    4790           0 :                                 if (!vid_type) vid_type = strstr(rep->codecs, "mvc");
    4791             :                                 //HEVC
    4792           0 :                                 if (vid_type && (!strnicmp(rep->codecs, "hvc", 3) || !strnicmp(rep->codecs, "hev", 3))) {
    4793           0 :                                         char *pidc = rep->codecs+5;
    4794           0 :                                         if ((pidc[0]=='A') || (pidc[0]=='B') || (pidc[0]=='C')) pidc++;
    4795             :                                         //Main 10 !!
    4796           0 :                                         if (!strncmp(pidc, "2.", 2)) {
    4797           0 :                                                 rep->playback.disabled = 1;
    4798           0 :                                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
    4799           0 :                                                 continue;
    4800             :                                         }
    4801             :                                 }
    4802             :                                 //AVC
    4803           0 :                                 if (vid_type && (!strnicmp(rep->codecs, "avc", 3) || !strnicmp(rep->codecs, "svc", 3) || !strnicmp(rep->codecs, "mvc", 3))) {
    4804             :                                         char prof_string[3];
    4805             :                                         u8 prof;
    4806           0 :                                         strncpy(prof_string, vid_type+5, 2);
    4807           0 :                                         prof_string[2]=0;
    4808           0 :                                         prof = atoi(prof_string);
    4809             :                                         //Main 10
    4810           0 :                                         if (prof==0x6E) {
    4811           0 :                                                 rep->playback.disabled = 1;
    4812           0 :                                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation bit depth higher than max display bit depth - ignoring\n"));
    4813           0 :                                                 continue;
    4814             :                                         }
    4815             :                                 }
    4816             :                         }
    4817             : 
    4818           0 :                         for (k=0; k<gf_list_count(rep->essential_properties); k++) {
    4819           0 :                                 GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->essential_properties, k);
    4820             : 
    4821             :                                 //we don't know any defined scheme for now
    4822           0 :                                 if (! strstr(mpd_desc->scheme_id_uri, "gpac") ) {
    4823           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
    4824           0 :                                         rep->playback.disabled = 1;
    4825           0 :                                         break;
    4826             :                                 }
    4827             :                         }
    4828         393 :                         if (rep->playback.disabled) {
    4829           0 :                                 continue;
    4830             :                         }
    4831             : 
    4832             :                         cp_supported = 1;
    4833           0 :                         for (k=0; k<gf_list_count(rep->content_protection); k++) {
    4834           0 :                                 GF_MPD_Descriptor *mpd_desc = gf_list_get(rep->content_protection, k);
    4835             :                                 //we don't know any defined scheme for now
    4836           0 :                                 if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
    4837           0 :                                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Representation with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
    4838             :                                         cp_supported = 0;
    4839             :                                 } else {
    4840             :                                         cp_supported = 1;
    4841             :                                         break;
    4842             :                                 }
    4843             :                         }
    4844         393 :                         if (!cp_supported) {
    4845           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Representation with no supported ContentProtection\n"));
    4846           0 :                                 rep->playback.disabled = 1;
    4847           0 :                                 continue;
    4848             :                         }
    4849             : 
    4850         393 :                         rep->playback.disabled = 0;
    4851         393 :                         if (rep->width>set->max_width) {
    4852          36 :                                 set->max_width = rep->width;
    4853          36 :                                 set->max_height = rep->height;
    4854             :                         }
    4855         393 :                         if (rep->dependency_id && strlen(rep->dependency_id))
    4856             :                                 has_dependent_representations = GF_TRUE;
    4857             :                         else
    4858         303 :                                 group->base_rep_index_plus_one = j+1;
    4859         393 :                         rep->playback.enhancement_rep_index_plus_one = 0;
    4860        1276 :                         for (k = 0; k < gf_list_count(set->representations); k++) {
    4861         883 :                                 GF_MPD_Representation *a_rep = gf_list_get(set->representations, k);
    4862         883 :                                 if (a_rep->dependency_id) {
    4863         144 :                                         char * tmp = strrchr(a_rep->dependency_id, ' ');
    4864         144 :                                         if (tmp)
    4865           0 :                                                 tmp = tmp + 1;
    4866             :                                         else
    4867             :                                                 tmp = a_rep->dependency_id;
    4868         144 :                                         if (!strcmp(tmp, rep->id))
    4869           0 :                                                 rep->playback.enhancement_rep_index_plus_one = k + 1;
    4870             :                                 }
    4871             :                         }
    4872         393 :                         if (!rep->playback.enhancement_rep_index_plus_one)
    4873         393 :                                 group->max_complementary_rep_index = j;
    4874         393 :                         if (!rep->playback.disabled && rep->dependency_id)
    4875          90 :                                 nb_dependent_rep++;
    4876             :                 }
    4877             : 
    4878         262 :                 if (!seg_dur && !dash->is_m3u8) {
    4879           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Cannot compute default segment duration\n"));
    4880             :                 }
    4881             : 
    4882         262 :                 group->cache_duration = dash->max_cache_duration;
    4883         262 :                 if (group->cache_duration < dash->mpd->min_buffer_time)
    4884         257 :                         group->cache_duration = dash->mpd->min_buffer_time;
    4885             : 
    4886         262 :                 group->max_cached_segments = (nb_dependent_rep+1);
    4887             : 
    4888         262 :                 if (!has_dependent_representations)
    4889         199 :                         group->base_rep_index_plus_one = 0; // all representations in this group are independent
    4890             : 
    4891         262 :                 if (group->max_cached_segments>50) {
    4892           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Too many cached segments (%d), segment duration %g - using 50 max\n", group->max_cached_segments, seg_dur));
    4893           0 :                         group->max_cached_segments = 50;
    4894             :                 }
    4895         262 :                 group->cached = gf_malloc(sizeof(segment_cache_entry)*group->max_cached_segments);
    4896         262 :                 memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
    4897         262 :                 if (!group->cached) {
    4898           0 :                         gf_free(group);
    4899           0 :                         return GF_OUT_OF_MEM;
    4900             :                 }
    4901         262 :                 e = gf_list_add(dash->groups, group);
    4902         262 :                 if (e) {
    4903           0 :                         gf_free(group->cached);
    4904           0 :                         gf_free(group);
    4905           0 :                         return e;
    4906             :                 }
    4907             :         }
    4908             : 
    4909             : 
    4910         123 :         count = gf_list_count(dash->groups);
    4911         385 :         for (i=0; i<count; i++) {
    4912         262 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    4913         262 :                 j = gf_dash_group_get_dependency_group(dash, i);
    4914         262 :                 if (i != j) {
    4915          63 :                         GF_DASH_Group *base_group = gf_list_get(dash->groups, j);
    4916             :                         assert(base_group);
    4917          63 :                         group->depend_on_group = base_group;
    4918          63 :                         if (!base_group->groups_depending_on) {
    4919           7 :                                 base_group->groups_depending_on = gf_list_new();
    4920             :                         }
    4921          63 :                         gf_list_add(base_group->groups_depending_on, group);
    4922             :                 }
    4923             :         }
    4924             : 
    4925         262 :         for (i=0; i<count; i++) {
    4926         262 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    4927         262 :                 if (group->groups_depending_on) {
    4928           7 :                         u32 nb_dep_groups = gf_list_count(group->groups_depending_on);
    4929             :                         //all dependent groups will be stored in the base group
    4930           7 :                         group->max_cached_segments *= (1+nb_dep_groups);
    4931           7 :                         group->cached = gf_realloc(group->cached, sizeof(segment_cache_entry)*group->max_cached_segments);
    4932           7 :                         memset(group->cached, 0, sizeof(segment_cache_entry)*group->max_cached_segments);
    4933             : 
    4934          70 :                         for (j=0; j<nb_dep_groups; j++) {
    4935          63 :                                 GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, j);
    4936             : 
    4937          63 :                                 dep_group->max_cached_segments = 0;
    4938             : 
    4939             :                                 /* the rest of the code assumes that at least group->cached[0] is allocated */
    4940          63 :                                 dep_group->cached = gf_realloc(dep_group->cached, sizeof(segment_cache_entry));
    4941             :                                 memset(dep_group->cached, 0, sizeof(segment_cache_entry));
    4942             : 
    4943             :                         }
    4944             :                 }
    4945             :         }
    4946             : 
    4947             :         return GF_OK;
    4948             : }
    4949             : 
    4950          14 : static GF_Err gf_dash_load_sidx(GF_BitStream *bs, GF_MPD_Representation *rep, Bool separate_index, u64 sidx_offset)
    4951             : {
    4952             : #ifdef GPAC_DISABLE_ISOM
    4953             :         return GF_NOT_SUPPORTED;
    4954             : #else
    4955             :         u64 anchor_position, prev_pos;
    4956          14 :         GF_SegmentIndexBox *sidx = NULL;
    4957             :         u32 i, size, type;
    4958             :         GF_Err e;
    4959             :         u64 offset;
    4960             : 
    4961          14 :         prev_pos = gf_bs_get_position(bs);
    4962          14 :         gf_bs_seek(bs, sidx_offset);
    4963          14 :         size = gf_bs_read_u32(bs);
    4964          14 :         type = gf_bs_read_u32(bs);
    4965          14 :         if (type != GF_ISOM_BOX_TYPE_SIDX) {
    4966           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error parsing SIDX: type is %s (box start offset "LLD")\n", gf_4cc_to_str(type), gf_bs_get_position(bs)-8 ));
    4967             :                 return GF_ISOM_INVALID_FILE;
    4968             :         }
    4969             : 
    4970          14 :         gf_bs_seek(bs, sidx_offset);
    4971             : 
    4972          14 :         anchor_position = sidx_offset + size;
    4973          14 :         if (separate_index)
    4974             :                 anchor_position = 0;
    4975             : 
    4976          14 :         e = gf_isom_box_parse((GF_Box **) &sidx, bs);
    4977          14 :         if (e) return e;
    4978             : 
    4979          14 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Loading SIDX - %d entries - Earliest Presentation Time "LLD"\n", sidx->nb_refs, sidx->earliest_presentation_time));
    4980             : 
    4981          14 :         offset = sidx->first_offset + anchor_position;
    4982          14 :         rep->segment_list->timescale = sidx->timescale;
    4983         621 :         for (i=0; i<sidx->nb_refs; i++) {
    4984         607 :                 if (sidx->refs[i].reference_type) {
    4985           0 :                         e = gf_dash_load_sidx(bs, rep, separate_index, offset);
    4986           0 :                         if (e) {
    4987             :                                 break;
    4988             :                         }
    4989             :                 } else {
    4990             :                         GF_MPD_SegmentURL *seg;
    4991         607 :                         GF_SAFEALLOC(seg, GF_MPD_SegmentURL);
    4992         607 :                         if (!seg) return GF_OUT_OF_MEM;
    4993         607 :                         GF_SAFEALLOC(seg->media_range, GF_MPD_ByteRange);
    4994         607 :                         if (!seg->media_range) return GF_OUT_OF_MEM;
    4995         607 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Found media segment size %d - duration %d - start with SAP: %d - SAP type %d - SAP Deltat Time %d\n",
    4996             :                                                            sidx->refs[i].reference_size, sidx->refs[i].subsegment_duration, sidx->refs[i].starts_with_SAP, sidx->refs[i].SAP_type, sidx->refs[i].SAP_delta_time));
    4997             : 
    4998         607 :                         seg->media_range->start_range = offset;
    4999         607 :                         offset += sidx->refs[i].reference_size;
    5000         607 :                         seg->media_range->end_range = offset - 1;
    5001         607 :                         seg->duration = sidx->refs[i].subsegment_duration;
    5002         607 :                         gf_list_add(rep->segment_list->segment_URLs, seg);
    5003             :                 }
    5004             :         }
    5005          14 :         gf_isom_box_del((GF_Box*)sidx);
    5006          14 :         gf_bs_seek(bs, prev_pos);
    5007          14 :         return e;
    5008             : #endif
    5009             : }
    5010             : 
    5011          18 : static GF_Err gf_dash_load_representation_sidx(GF_DASH_Group *group, GF_MPD_Representation *rep, const char *cache_name, Bool separate_index, Bool needs_mov_range)
    5012             : {
    5013             :         GF_Err e;
    5014             :         GF_BitStream *bs;
    5015             :         FILE *f=NULL;
    5016          18 :         if (!cache_name) return GF_BAD_PARAM;
    5017             : 
    5018          18 :         if (!strncmp(cache_name, "gmem://", 7)) {
    5019             :                 u32 size;
    5020             :                 u8 *mem_address;
    5021          10 :                 e = gf_blob_get(cache_name, &mem_address, &size, NULL);
    5022          10 :                 if (e) return e;
    5023             : 
    5024          10 :                 bs = gf_bs_new(mem_address, size, GF_BITSTREAM_READ);
    5025          10 :         gf_blob_release(cache_name);
    5026             :         } else {
    5027           8 :                 f = gf_fopen(cache_name, "rb");
    5028           8 :                 if (!f) return GF_IO_ERR;
    5029           8 :                 bs = gf_bs_from_file(f, GF_BITSTREAM_READ);
    5030             :         }
    5031             :         e = GF_OK;
    5032          87 :         while (gf_bs_available(bs)) {
    5033          83 :                 u32 size = gf_bs_read_u32(bs);
    5034          83 :                 u32 type = gf_bs_read_u32(bs);
    5035          83 :                 if (type != GF_ISOM_BOX_TYPE_SIDX) {
    5036          69 :                         gf_bs_skip_bytes(bs, size-8);
    5037             : 
    5038          69 :                         if (needs_mov_range && (type==GF_ISOM_BOX_TYPE_MOOV )) {
    5039           4 :                                 GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
    5040           4 :                                 if (rep->segment_list->initialization_segment->byte_range)
    5041           4 :                                         rep->segment_list->initialization_segment->byte_range->end_range = gf_bs_get_position(bs);
    5042             :                         }
    5043          69 :                         continue;
    5044             :                 }
    5045          14 :                 gf_bs_seek(bs, gf_bs_get_position(bs)-8);
    5046          14 :                 e = gf_dash_load_sidx(bs, rep, separate_index, gf_bs_get_position(bs) );
    5047             : 
    5048             :                 /*we could also parse the sub sidx*/
    5049             :                 break;
    5050             :         }
    5051          18 :         gf_bs_del(bs);
    5052          18 :         if (f) gf_fclose(f);
    5053             :         return e;
    5054             : }
    5055             : 
    5056          50 : static GF_Err dash_load_box_type(const char *cache_name, u32 offset, u32 *box_type, u32 *box_size)
    5057             : {
    5058          50 :         *box_type = *box_size = 0;
    5059          50 :         if (!strncmp(cache_name, "gmem://", 7)) {
    5060             :                 GF_Err e;
    5061             :                 u32 size;
    5062             :                 u8 *mem_address;
    5063          50 :                 e = gf_blob_get(cache_name, &mem_address, &size, NULL);
    5064          50 :                 if (e) return e;
    5065          50 :         gf_blob_release(cache_name);
    5066          50 :                 if (offset+8 > size)
    5067             :                         return GF_IO_ERR;
    5068          50 :                 mem_address += offset;
    5069          50 :                 *box_size = GF_4CC(mem_address[0], mem_address[1], mem_address[2], mem_address[3]);
    5070          50 :                 *box_type = GF_4CC(mem_address[4], mem_address[5], mem_address[6], mem_address[7]);
    5071             :         } else {
    5072             :                 unsigned char data[4];
    5073           0 :                 FILE *f = gf_fopen(cache_name, "rb");
    5074           0 :                 if (!f) return GF_IO_ERR;
    5075           0 :                 if (gf_fseek(f, offset, SEEK_SET))
    5076             :                         return GF_IO_ERR;
    5077           0 :                 if (gf_fread(data, 4, f) == 4) {
    5078           0 :                         *box_size = GF_4CC(data[0], data[1], data[2], data[3]);
    5079           0 :                         if (gf_fread(data, 4, f) == 4) {
    5080           0 :                                 *box_type = GF_4CC(data[0], data[1], data[2], data[3]);
    5081             :                         }
    5082             :                 }
    5083           0 :                 gf_fclose(f);
    5084             :         }
    5085             :         return GF_OK;
    5086             : }
    5087             : 
    5088             : extern void gf_mpd_segment_template_free(void *_item);
    5089         262 : static GF_Err gf_dash_setup_single_index_mode(GF_DASH_Group *group)
    5090             : {
    5091             :         u32 i;
    5092             :         GF_Err e = GF_OK;
    5093         262 :         char *init_url = NULL;
    5094         262 :         char *index_url = NULL;
    5095             :         GF_DASHFileIOSession *download_sess;
    5096         262 :         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
    5097             : 
    5098         262 :         download_sess = &group->dash->mpd_dnload;
    5099             : 
    5100         262 :         if (!rep->segment_base && !group->adaptation_set->segment_base && !group->period->segment_base) {
    5101         250 :                 if (rep->segment_template || group->adaptation_set->segment_template || group->period->segment_template) return GF_OK;
    5102          50 :                 if (rep->segment_list || group->adaptation_set->segment_list || group->period->segment_list) return GF_OK;
    5103             :         } else {
    5104          12 :                 char *profile = rep->profiles;
    5105          12 :                 if (!profile) profile = group->adaptation_set->profiles;
    5106          12 :                 if (!profile) profile = group->dash->mpd->profiles;
    5107             : 
    5108             :                 //if on-demand cleanup all segment templates and segment list if we have base URLs
    5109          12 :                 if (profile && strstr(profile, "on-demand")) {
    5110             :                         u32 nb_rem=0;
    5111          12 :                         if (rep->segment_template) {
    5112             :                                 nb_rem++;
    5113           0 :                                 gf_mpd_segment_template_free(rep->segment_template);
    5114           0 :                                 rep->segment_template = NULL;
    5115             :                         }
    5116          12 :                         if (group->adaptation_set->segment_template) {
    5117           0 :                                 nb_rem++;
    5118           0 :                                 gf_mpd_segment_template_free(group->adaptation_set->segment_template);
    5119           0 :                                 group->adaptation_set->segment_template = NULL;
    5120             :                         }
    5121             : 
    5122          12 :                         if (group->period->segment_template) {
    5123           0 :                                 nb_rem++;
    5124           0 :                                 gf_mpd_segment_template_free(group->period->segment_template);
    5125           0 :                                 group->period->segment_template = NULL;
    5126             :                         }
    5127          12 :                         if (nb_rem) {
    5128           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentTemplate present for on-demand with SegmentBase present - skipping SegmentTemplate\n"));
    5129             :                         }
    5130             :                         nb_rem=0;
    5131          12 :                         if (rep->segment_list) {
    5132             :                                 nb_rem++;
    5133           0 :                                 gf_mpd_segment_template_free(rep->segment_list);
    5134           0 :                                 rep->segment_list = NULL;
    5135             :                         }
    5136          12 :                         if (group->adaptation_set->segment_list) {
    5137           0 :                                 nb_rem++;
    5138           0 :                                 gf_mpd_segment_template_free(group->adaptation_set->segment_list);
    5139           0 :                                 group->adaptation_set->segment_list = NULL;
    5140             :                         }
    5141          12 :                         if (group->period->segment_list) {
    5142           0 :                                 nb_rem++;
    5143           0 :                                 gf_mpd_segment_template_free(group->period->segment_list);
    5144           0 :                                 group->period->segment_list = NULL;
    5145             :                         }
    5146          12 :                         if (nb_rem) {
    5147           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] SegmentList present for on-demand with SegmentBase present - skipping SegmentList\n"));
    5148             :                         }
    5149             :                 }
    5150             :         }
    5151             : 
    5152             :         /*OK we are in single-file mode, download all required indexes & co*/
    5153          18 :         for (i=0; i<gf_list_count(group->adaptation_set->representations); i++) {
    5154             :                 char *sidx_file = NULL;
    5155          18 :                 u64 duration, index_start_range = 0, index_end_range = 0, init_start_range, init_end_range;
    5156             :                 Bool index_in_base, init_in_base;
    5157             :                 Bool init_needs_byte_range = GF_FALSE;
    5158             :                 Bool has_seen_sidx = GF_FALSE;
    5159             :                 Bool is_isom = GF_TRUE;
    5160          18 :                 rep = gf_list_get(group->adaptation_set->representations, i);
    5161             : 
    5162          18 :                 index_in_base = init_in_base = GF_FALSE;
    5163          18 :                 e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INIT, 0, &init_url, &init_start_range, &init_end_range, &duration, &init_in_base, NULL, NULL, NULL, NULL);
    5164          18 :                 if (e) goto exit;
    5165             : 
    5166          18 :                 e = gf_dash_resolve_url(group->dash->mpd, rep, group, group->dash->base_url, GF_MPD_RESOLVE_URL_INDEX, 0, &index_url, &index_start_range, &index_end_range, &duration, &index_in_base, NULL, NULL, NULL, NULL);
    5167          18 :                 if (e) goto exit;
    5168             : 
    5169             : 
    5170          18 :                 if (is_isom && (init_in_base || index_in_base)) {
    5171          18 :                         if (!strstr(init_url, "://") || (!strnicmp(init_url, "file://", 7) ) ) {
    5172           8 :                                 GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
    5173           8 :                                 if (!rep->segment_list) {
    5174             :                                         e = GF_OUT_OF_MEM;
    5175             :                                         goto exit;
    5176             :                                 }
    5177           8 :                                 rep->segment_list->segment_URLs = gf_list_new();
    5178             : 
    5179           8 :                                 if (rep->segment_base) rep->segment_list->presentation_time_offset = rep->segment_base->presentation_time_offset;
    5180           0 :                                 else if (group->adaptation_set->segment_base) rep->segment_list->presentation_time_offset = group->adaptation_set->segment_base->presentation_time_offset;
    5181           0 :                                 else if (group->period->segment_base) rep->segment_list->presentation_time_offset = group->period->segment_base->presentation_time_offset;
    5182             : 
    5183           8 :                                 if (init_in_base) {
    5184           8 :                                         GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
    5185           8 :                                         if (!rep->segment_list->initialization_segment) {
    5186             :                                                 e = GF_OUT_OF_MEM;
    5187             :                                                 goto exit;
    5188             :                                         }
    5189           8 :                                         rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
    5190           8 :                                         rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
    5191             :                                         /*we don't want to load the entire movie */
    5192             :                                         init_needs_byte_range = 1;
    5193             :                                 }
    5194           8 :                                 if (index_in_base) {
    5195           8 :                                         sidx_file = (char *)init_url;
    5196             :                                 }
    5197             :                         }
    5198             :                         /*we need to download the init segment, at least partially*/
    5199             :                         else {
    5200             :                                 u32 offset = 0;
    5201          10 :                                 u32 box_type = 0;
    5202          10 :                                 u32 box_size = 0;
    5203             :                                 u32 sidx_start = 0;
    5204             :                                 const char *cache_name;
    5205             : 
    5206          10 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Downloading init segment and SIDX for representation %s\n", init_url));
    5207             : 
    5208             :                                 /*download first 8 bytes and check if we do have a box starting there*/
    5209          10 :                                 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, 7, 1, group);
    5210          10 :                                 if (e) goto exit;
    5211          10 :                                 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
    5212          10 :                                 e = dash_load_box_type(cache_name, offset, &box_type, &box_size);
    5213             :                                 offset = 8;
    5214          50 :                                 while (box_type) {
    5215             :                                         /*we got the moov, stop here */
    5216          40 :                                         if (!index_in_base && (box_type==GF_ISOM_BOX_TYPE_MOOV)) {
    5217           0 :                                                 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-9, 2, group);
    5218           0 :                                                 break;
    5219             :                                         } else {
    5220             :                                                 const u32 offset_ori = offset;
    5221          40 :                                                 e = gf_dash_download_resource(group->dash, download_sess, init_url, offset, offset+box_size-1, 2, group);
    5222          40 :                                                 if (e < 0) goto exit;
    5223          40 :                                                 offset += box_size;
    5224             :                                                 /*we need to refresh the cache name because of our memory astorage thing ...*/
    5225          40 :                                                 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
    5226          40 :                                                 e = dash_load_box_type(cache_name, offset-8, &box_type, &box_size);
    5227          40 :                                                 if (e == GF_IO_ERR) {
    5228             :                                                         /*if the socket was closed then gf_dash_download_resource() with gmem:// was reset - retry*/
    5229           0 :                                                         e = dash_load_box_type(cache_name, offset-offset_ori-8, &box_type, &box_size);
    5230           0 :                                                         if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
    5231             :                                                                 offset -= 8;
    5232             :                                                                 /*FIXME sidx found, reload the full resource*/
    5233           0 :                                                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] have to re-downloading init and SIDX for rep %s\n", init_url));
    5234           0 :                                                                 e = gf_dash_download_resource(group->dash, download_sess, init_url, 0, offset+box_size-1, 2, group);
    5235           0 :                                                                 break;
    5236             :                                                         }
    5237             :                                                 }
    5238             : 
    5239          40 :                                                 if (box_type == GF_ISOM_BOX_TYPE_SIDX) {
    5240          10 :                                                         if (!sidx_start) sidx_start = offset;
    5241             :                                                         has_seen_sidx = 1;
    5242          30 :                                                 } else if (has_seen_sidx)
    5243             :                                                         break;
    5244             :                                         }
    5245             :                                 }
    5246          10 :                                 if (e < 0) goto exit;
    5247             : 
    5248          10 :                                 if (box_type == 0) {
    5249             :                                         e = GF_ISOM_INVALID_FILE;
    5250             :                                         goto exit;
    5251             :                                 }
    5252          10 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Done downloading init segment and SIDX\n"));
    5253             : 
    5254          10 :                                 GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
    5255          10 :                                 if (!rep->segment_list) {
    5256             :                                         e = GF_OUT_OF_MEM;
    5257             :                                         goto exit;
    5258             :                                 }
    5259          10 :                                 rep->segment_list->segment_URLs = gf_list_new();
    5260             : 
    5261          10 :                                 cache_name = group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
    5262          10 :                                 if (init_in_base) {
    5263             :                                         char szName[100];
    5264          10 :                                         GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
    5265          10 :                                         if (!rep->segment_list->initialization_segment) {
    5266             :                                                 e = GF_OUT_OF_MEM;
    5267           0 :                                                 goto exit;
    5268             :                                         }
    5269             : 
    5270          10 :                                         rep->segment_list->initialization_segment->sourceURL = gf_strdup(init_url);
    5271          10 :                                         GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
    5272          10 :                                         if (rep->segment_list->initialization_segment->byte_range) {
    5273          10 :                                                 rep->segment_list->initialization_segment->byte_range->start_range = init_start_range;
    5274          10 :                                                 rep->segment_list->initialization_segment->byte_range->end_range = init_end_range ? init_end_range : (sidx_start-1);
    5275             :                                         }
    5276             : 
    5277             :                                         //we need to store the init segment since it has the same name as the rest of the segments and will be destroyed when cleaning up the cache ..
    5278           0 :                                         else if (!strnicmp(cache_name, "gmem://", 7)) {
    5279             :                                                 u8 *mem_address;
    5280           0 :                                                 e = gf_blob_get(cache_name, &mem_address, &rep->playback.init_segment.size, NULL);
    5281           0 :                                                 if (e) {
    5282             :                                                         goto exit;
    5283             :                                                 }
    5284           0 :                                                 rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
    5285           0 :                                                 memcpy(rep->playback.init_segment.data, mem_address, sizeof(char) * rep->playback.init_segment.size);
    5286             : 
    5287           0 :                                                 sprintf(szName, "gmem://%p", &rep->playback.init_segment);
    5288           0 :                                                 rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
    5289           0 :                                                 rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
    5290           0 :                         gf_blob_release(cache_name);
    5291             :                                         } else {
    5292           0 :                                                 FILE *t = gf_fopen(cache_name, "rb");
    5293           0 :                                                 if (t) {
    5294             :                                                         s32 res;
    5295           0 :                                                         rep->playback.init_segment.size = (u32) gf_fsize(t);
    5296             : 
    5297           0 :                                                         rep->playback.init_segment.data = gf_malloc(sizeof(char) * rep->playback.init_segment.size);
    5298           0 :                                                         res = (s32) gf_fread(rep->playback.init_segment.data, rep->playback.init_segment.size, t);
    5299           0 :                                                         if (res != rep->playback.init_segment.size) {
    5300           0 :                                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to store init segment\n"));
    5301           0 :                                                         } else if (rep->segment_list && rep->segment_list->initialization_segment) {
    5302           0 :                                                                 sprintf(szName, "gmem://%p", &rep->playback.init_segment);
    5303           0 :                                                                 rep->segment_list->initialization_segment->sourceURL = gf_strdup(szName);
    5304           0 :                                                                 rep->segment_list->initialization_segment->is_resolved = GF_TRUE;
    5305             :                                                         }
    5306             :                                                 }
    5307             :                                         }
    5308             :                                 }
    5309          10 :                                 if (index_in_base) {
    5310             :                                         sidx_file = (char *)cache_name;
    5311             :                                 }
    5312             :                         }
    5313             :                 }
    5314             :                 /*we have index url, download it*/
    5315          18 :                 if (! index_in_base) {
    5316           0 :                         e = gf_dash_download_resource(group->dash, download_sess, index_url, index_start_range, index_end_range, 1, group);
    5317           0 :                         if (e) goto exit;
    5318           0 :                         sidx_file = (char *)group->dash->dash_io->get_cache_name(group->dash->dash_io, *download_sess);
    5319             :                 }
    5320             : 
    5321             :                 /*load sidx*/
    5322          18 :                 e = gf_dash_load_representation_sidx(group, rep, sidx_file, !index_in_base, init_needs_byte_range);
    5323          18 :                 if (e) {
    5324           0 :                         rep->playback.disabled = 1;
    5325           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to load segment index for this representation - disabling\n"));
    5326             :                 }
    5327             : 
    5328             :                 //cleanup cache right away
    5329          18 :                 group->dash->dash_io->delete_cache_file(group->dash->dash_io, *download_sess, init_url);
    5330             : 
    5331             :                 /*reset all seg based stuff*/
    5332          18 :                 if (rep->segment_base) {
    5333          18 :                         gf_mpd_segment_base_free(rep->segment_base);
    5334          18 :                         rep->segment_base = NULL;
    5335             :                 }
    5336             : 
    5337          18 :                 gf_free(index_url);
    5338          18 :                 index_url = NULL;
    5339          18 :                 gf_free(init_url);
    5340          18 :                 init_url = NULL;
    5341             :         }
    5342          12 :         if (group->adaptation_set->segment_base) {
    5343           0 :                 gf_mpd_segment_base_free(group->adaptation_set->segment_base);
    5344           0 :                 group->adaptation_set->segment_base = NULL;
    5345             :         }
    5346          12 :         group->was_segment_base = 1;
    5347             : 
    5348          12 : exit:
    5349          12 :         if (init_url) gf_free(init_url);
    5350          12 :         if (index_url) gf_free(index_url);
    5351             :         return e;
    5352             : }
    5353             : 
    5354           4 : static void gf_dash_solve_period_xlink(GF_DashClient *dash, GF_List *period_list, u32 period_idx)
    5355             : {
    5356             :         u32 count, i;
    5357             :         GF_Err e;
    5358             :         u64 start = 0;
    5359             :         u64 src_duration = 0;
    5360             :         Bool is_local=GF_FALSE;
    5361             :         const char *local_url;
    5362             :         char *url, *period_xlink;
    5363             :         GF_DOMParser *parser;
    5364             :         GF_MPD *new_mpd;
    5365             :         GF_MPD_Period *period;
    5366             :         u32 nb_inserted = 0;
    5367           4 :         GF_DASHFileIOSession xlink_sess=NULL;
    5368             : 
    5369           4 :         period = gf_list_get(period_list, period_idx);
    5370           4 :         if (!period->xlink_href || (dash->route_clock_state==1)) {
    5371           0 :                 return;
    5372             :         }
    5373           4 :         start = period->start;
    5374           4 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Resolving period XLINK %s\n", period->xlink_href));
    5375             : 
    5376           4 :         if (!strcmp(period->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013")) {
    5377             :                 //spec is not very clear here, I suppose it means "remove the element"
    5378           0 :                 gf_list_rem(period_list, period_idx);
    5379           0 :                 gf_mpd_period_free(period);
    5380           0 :                 return;
    5381             :         }
    5382             : 
    5383             :         //ATSC puts a tag in front of the url ("tag:atsc.org,2016:xlink") - in case others decide to follow this crazy example, whe search for http:// or https://
    5384           4 :         period_xlink = strstr(period->xlink_href, "http://");
    5385           4 :         if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTP://");
    5386           4 :         if (!period_xlink) period_xlink = strstr(period->xlink_href, "https://");
    5387           4 :         if (!period_xlink) period_xlink = strstr(period->xlink_href, "HTTPS://");
    5388           4 :         if (!period_xlink) period_xlink = period->xlink_href;
    5389             : 
    5390             :         //xlink relative to our MPD base URL
    5391           4 :         url = gf_url_concatenate(dash->base_url, period_xlink);
    5392             : 
    5393           4 :         if (!strstr(url, "://") || !strnicmp(url, "file://", 7) ) {
    5394             :                 local_url = url;
    5395             :                 is_local=GF_TRUE;
    5396             :                 e = GF_OK;
    5397             :         } else {
    5398           3 :                 if (dash->query_string) {
    5399             :                         char *full_url;
    5400             :                         char *purl, *sep;
    5401             :                         u32 len;
    5402             :                         purl = url ? url : period_xlink;
    5403           0 :                         len = (u32) (2 + strlen(purl) + strlen(dash->query_string) + (period->ID ? strlen(period->ID) : 0 ) );
    5404           0 :                         full_url = gf_malloc(sizeof(char)*len);
    5405             : 
    5406             :                         strcpy(full_url, purl);
    5407           0 :                         if (strchr(purl, '?')) strcat(full_url, "&");
    5408             :                         else strcat(full_url, "?");
    5409             : 
    5410             :                         //append the query string
    5411           0 :                         strcat(full_url, dash->query_string);
    5412             :                         //if =PID is given, replace by period ID
    5413           0 :                         sep = strstr(dash->query_string, "=PID");
    5414           0 :                         if (sep && period->ID) {
    5415           0 :                                 char *sep2 = strstr(full_url, "=PID");
    5416             :                                 assert(sep2);
    5417           0 :                                 sep2[1] = 0;
    5418           0 :                                 strcat(full_url, period->ID);
    5419           0 :                                 strcat(full_url, sep+4);
    5420             :                         }
    5421             : 
    5422             :                         /*use non-persistent connection for MPD*/
    5423           0 :                         e = gf_dash_download_resource(dash, &xlink_sess, full_url, 0, 0, 0, NULL);
    5424           0 :                         gf_free(full_url);
    5425             : 
    5426             :                 } else {
    5427             :                         /*use non-persistent connection for MPD*/
    5428           3 :                         e = gf_dash_download_resource(dash, &xlink_sess, url ? url : period_xlink, 0, 0, 0, NULL);
    5429             :                 }
    5430             :         }
    5431             : 
    5432           4 :         if (e) {
    5433           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot download xlink from periods %s: error %s\n", period->xlink_href, gf_error_to_string(e)));
    5434           0 :                 gf_free(period->xlink_href);
    5435           0 :                 period->xlink_href = NULL;
    5436           0 :                 if (xlink_sess) dash->dash_io->del(dash->dash_io, xlink_sess);
    5437           0 :                 if (url) gf_free(url);
    5438             :                 return;
    5439             :         }
    5440             : 
    5441           4 :         if (!is_local) {
    5442             :                 /*in case the session has been restarted, local_url may have been destroyed - get it back*/
    5443           3 :                 local_url = dash->dash_io->get_cache_name(dash->dash_io, xlink_sess);
    5444             :         }
    5445             : 
    5446             :         /* parse the MPD */
    5447           4 :         parser = gf_xml_dom_new();
    5448           4 :         e = gf_xml_dom_parse(parser, local_url, NULL, NULL);
    5449           4 :         if (url) gf_free(url);
    5450             :         url = NULL;
    5451             : 
    5452           4 :         if (xlink_sess) {
    5453             :                 //get redirected URL
    5454           3 :                 url = (char *) dash->dash_io->get_url(dash->dash_io, xlink_sess);
    5455           3 :                 if (url) url = gf_strdup(url);
    5456           3 :                 dash->dash_io->del(dash->dash_io, xlink_sess);
    5457             :         }
    5458           4 :         if (e != GF_OK) {
    5459           0 :                 gf_xml_dom_del(parser);
    5460           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot parse xlink periods: error in XML parsing %s\n", gf_error_to_string(e)));
    5461           0 :                 gf_free(period->xlink_href);
    5462           0 :                 period->xlink_href = NULL;
    5463           0 :                 if (url) gf_free(url);
    5464             :                 return;
    5465             :         }
    5466           4 :         new_mpd = gf_mpd_new();
    5467             : 
    5468           4 :         count = gf_xml_dom_get_root_nodes_count(parser);
    5469           8 :         for (i=0; i<count; i++) {
    5470           4 :                 GF_XMLNode *root = gf_xml_dom_get_root_idx(parser, i);
    5471           4 :                 if (i) {
    5472           0 :                         e = gf_mpd_complete_from_dom(root, new_mpd, period->xlink_href);
    5473             :                 } else {
    5474           4 :                         e = gf_mpd_init_from_dom(root, new_mpd, period->xlink_href);
    5475             :                 }
    5476           4 :                 if (e) break;
    5477             :         }
    5478           4 :         gf_xml_dom_del(parser);
    5479           4 :         if (e) {
    5480           0 :                 gf_free(period->xlink_href);
    5481           0 :                 period->xlink_href = NULL;
    5482           0 :                 gf_mpd_del(new_mpd);
    5483           0 :                 if (url) gf_free(url);
    5484             :                 return;
    5485             :         }
    5486             : 
    5487           4 :         gf_list_rem(period_list, period_idx);
    5488             : 
    5489           4 :         if (dash->split_adaptation_set)
    5490           0 :                 gf_mpd_split_adaptation_sets(new_mpd);
    5491             : 
    5492           4 :         if (!period->duration) {
    5493           3 :                 GF_MPD_Period *next_period = gf_list_get(period_list, period_idx);
    5494           3 :                 if (next_period && next_period->start)
    5495           0 :                         period->duration = next_period->start - period->start;
    5496             :         }
    5497           4 :         src_duration = period->duration;
    5498             :         //insert all periods
    5499          11 :         while (gf_list_count(new_mpd->periods)) {
    5500           4 :                 GF_MPD_Period *inserted_period = gf_list_get(new_mpd->periods, 0);
    5501           4 :                 gf_list_rem(new_mpd->periods, 0);
    5502             :                 //forbiden
    5503           4 :                 if (inserted_period->xlink_href && inserted_period->xlink_actuate_on_load) {
    5504           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Invalid remote period with xlink:actuate=\"onLoad\" and xlink:href set, removing parent period.\n"));
    5505           0 :                         gf_mpd_period_free(inserted_period);
    5506           0 :                         continue;
    5507             :                 }
    5508           4 :                 inserted_period->origin_base_url = url ? gf_strdup(url) : NULL;
    5509           4 :                 inserted_period->start = start;
    5510           4 :                 inserted_period->type = GF_MPD_TYPE_STATIC;
    5511             : 
    5512           4 :                 gf_list_insert(period_list, inserted_period, period_idx);
    5513           4 :                 period_idx++;
    5514           4 :                 nb_inserted++;
    5515             : 
    5516           4 :                 if (period->duration) {
    5517             :                         //truncate duration
    5518           1 :                         if (inserted_period->duration > src_duration) {
    5519           1 :                                 inserted_period->duration = src_duration;
    5520           1 :                                 break;
    5521             :                         } else {
    5522           0 :                                 src_duration -= inserted_period->duration;
    5523             :                         }
    5524             :                 }
    5525           3 :                 start += inserted_period->duration;
    5526             :         }
    5527           4 :         if (url) gf_free(url);
    5528             : 
    5529           4 :         if (!nb_inserted && gf_list_count(period->adaptation_sets)) {
    5530           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No periods inserted during ad insertion, but origin period not empty - ignoring ad insertion.\n"));
    5531           0 :                 gf_list_insert(period_list, period, period_idx);
    5532           0 :                 period->broken_xlink = period->xlink_href;
    5533           0 :                 period->xlink_href = NULL;
    5534             :         } else {
    5535             :                 //this will do the garbage collection
    5536           4 :                 gf_list_add(new_mpd->periods, period);
    5537             :         }
    5538           4 :         gf_mpd_del(new_mpd);
    5539             : }
    5540             : 
    5541       11740 : static u32 gf_dash_get_tiles_quality_rank(GF_DashClient *dash, GF_DASH_Group *tile_group)
    5542             : {
    5543             :         s32 res, res2;
    5544       11740 :         struct _dash_srd_desc *srd = tile_group->srd_desc;
    5545             : 
    5546             :         //no SRD is max quality for now
    5547       11740 :         if (!srd) return 0;
    5548        7826 :         if (!tile_group->srd_w || !tile_group->srd_h) return 0;
    5549             : 
    5550        7045 :         if (tile_group->quality_degradation_hint) {
    5551          48 :                 u32 v = tile_group->quality_degradation_hint * MAX(srd->srd_nb_rows, srd->srd_nb_cols);
    5552          48 :                 v/=100;
    5553          48 :                 if (dash->disable_low_quality_tiles)
    5554           0 :                         tile_group->disabled = GF_TRUE;
    5555             :                 return v;
    5556             :         }
    5557        6997 :         tile_group->disabled = GF_FALSE;
    5558             : 
    5559             :         //TODO - use visibility rect as well
    5560             : 
    5561        6997 :         switch (dash->tile_adapt_mode) {
    5562             :         case GF_DASH_ADAPT_TILE_NONE:
    5563             :                 return 0;
    5564           0 :         case GF_DASH_ADAPT_TILE_ROWS:
    5565           0 :                 return tile_group->srd_row_idx;
    5566           0 :         case GF_DASH_ADAPT_TILE_ROWS_REVERSE:
    5567           0 :                 return srd->srd_nb_rows - 1 - tile_group->srd_row_idx;
    5568           0 :         case GF_DASH_ADAPT_TILE_ROWS_MIDDLE:
    5569           0 :                 res = srd->srd_nb_rows/2;
    5570           0 :                 res -= tile_group->srd_row_idx;
    5571           0 :                 return ABS(res);
    5572           0 :         case GF_DASH_ADAPT_TILE_COLUMNS:
    5573           0 :                 return tile_group->srd_col_idx;
    5574           0 :         case GF_DASH_ADAPT_TILE_COLUMNS_REVERSE:
    5575           0 :                 return srd->srd_nb_cols - 1 - tile_group->srd_col_idx;
    5576           0 :         case GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE:
    5577           0 :                 res = srd->srd_nb_cols/2;
    5578           0 :                 res -= tile_group->srd_col_idx;
    5579           0 :                 return ABS(res);
    5580           0 :         case GF_DASH_ADAPT_TILE_CENTER:
    5581           0 :                 res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
    5582           0 :                 res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
    5583           0 :                 return MAX( ABS(res), ABS(res2) );
    5584           0 :         case GF_DASH_ADAPT_TILE_EDGES:
    5585           0 :                 res = srd->srd_nb_rows/2 - tile_group->srd_row_idx;
    5586           0 :                 res = srd->srd_nb_rows/2 - ABS(res);
    5587           0 :                 res2 = srd->srd_nb_cols/2 - tile_group->srd_col_idx;
    5588           0 :                 res2 = srd->srd_nb_cols/2 - ABS(res2);
    5589           0 :                 return MIN( res, res2 );
    5590             :         }
    5591             :         return 0;
    5592             : }
    5593             : 
    5594             : //used upon startup of the session only
    5595           9 : static void gf_dash_set_tiles_quality(GF_DashClient *dash, struct _dash_srd_desc *srd, Bool force_all)
    5596             : {
    5597             :         u32 i, count;
    5598           9 :         Bool tiles_use_lowest = (dash->first_select_mode==GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES) ? GF_TRUE : GF_FALSE;
    5599             : 
    5600           9 :         count = gf_list_count(dash->groups);
    5601          99 :         for (i=0; i<count; i++) {
    5602          90 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    5603             :                 u32 lower_quality;
    5604          90 :                 if (group->srd_desc != srd) continue;
    5605             : 
    5606             :                 //dynamic changes of qualities, only update if changed
    5607          90 :                 if (!force_all) {
    5608          20 :                         if (!group->update_tile_qualities) continue;
    5609          16 :                         group->update_tile_qualities = GF_FALSE;
    5610             :                 }
    5611             : 
    5612          86 :                 lower_quality = gf_dash_get_tiles_quality_rank(dash, group);
    5613          86 :                 if (!lower_quality) continue;
    5614             : 
    5615           6 :                 if (tiles_use_lowest && (group->active_rep_index >= lower_quality)) {
    5616           0 :                         lower_quality = group->active_rep_index - lower_quality;
    5617             :                 } else {
    5618             :                         lower_quality = 0;
    5619             :                 }
    5620           6 :                 gf_dash_set_group_representation(group, gf_list_get(group->adaptation_set->representations, lower_quality), GF_FALSE);
    5621             :         }
    5622           9 : }
    5623             : 
    5624          70 : static struct _dash_srd_desc *gf_dash_get_srd_desc(GF_DashClient *dash, u32 srd_id, Bool do_create)
    5625             : {
    5626             :         u32 i, count;
    5627             :         struct _dash_srd_desc *srd;
    5628          70 :         count = dash->SRDs ? gf_list_count(dash->SRDs) : 0;
    5629           0 :         for (i=0; i<count; i++) {
    5630          63 :                 srd = gf_list_get(dash->SRDs, i);
    5631          63 :                 if (srd->id==srd_id) return srd;
    5632             :         }
    5633           7 :         if (!do_create) return NULL;
    5634           7 :         GF_SAFEALLOC(srd, struct _dash_srd_desc);
    5635           7 :         if (!srd) return NULL;
    5636           7 :         srd->id = srd_id;
    5637           7 :         if (!dash->SRDs) dash->SRDs = gf_list_new();
    5638           7 :         gf_list_add(dash->SRDs, srd);
    5639             :         return srd;
    5640             : }
    5641             : 
    5642         123 : static GF_Err gf_dash_setup_period(GF_DashClient *dash)
    5643             : {
    5644             :         GF_MPD_Period *period;
    5645             :         u32 rep_i, as_i, group_i, j, nb_groups_ok;
    5646             :         u32 retry = 10;
    5647             : 
    5648             :         //solve xlink - if
    5649         247 :         while (retry) {
    5650         124 :                 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    5651         124 :                 if (!period) return GF_EOS;
    5652         124 :                 if (!period->xlink_href) break;
    5653           1 :                 gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
    5654           1 :                 retry --;
    5655             :         }
    5656         123 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    5657         123 :         if (period->xlink_href && (dash->route_clock_state!=1) ) {
    5658           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Too many xlink indirections on the same period - not supported\n"));
    5659             :                 return GF_NOT_SUPPORTED;
    5660             :         }
    5661             : 
    5662         123 :         if (!period->duration) {
    5663          39 :                 GF_MPD_Period *next_period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
    5664          39 :                 if (next_period && next_period->start)
    5665           1 :                         period->duration = next_period->start - period->start;
    5666             :         }
    5667             : 
    5668             :         /*we are not able to process webm dash (youtube)*/
    5669         123 :         j = gf_list_count(period->adaptation_sets);
    5670         385 :         for (as_i=0; as_i<j; as_i++) {
    5671         262 :                 GF_MPD_AdaptationSet *set = (GF_MPD_AdaptationSet*)gf_list_get(period->adaptation_sets, as_i);
    5672         262 :                 if (set->mime_type && strstr(set->mime_type, "webm")) {
    5673             :                         u32 k;
    5674           0 :                         for (k=0; k<gf_list_count(set->representations); ++k) {
    5675           0 :                                 GF_MPD_Representation *rep = (GF_MPD_Representation*)gf_list_get(set->representations, k);
    5676           0 :                                 rep->playback.disabled = GF_TRUE;
    5677             :                         }
    5678             :                 }
    5679             :         }
    5680             : 
    5681         123 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Setting up period start "LLU" duration "LLU" xlink %s ID %s\n", period->start, period->duration, period->origin_base_url ? period->origin_base_url : "none", period->ID ? period->ID : "none"));
    5682             : 
    5683             :         /*setup all groups*/
    5684         123 :         gf_dash_setup_groups(dash);
    5685             : 
    5686             :         nb_groups_ok = 0;
    5687         385 :         for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
    5688             :                 GF_MPD_Representation *rep_sel;
    5689             :                 u32 active_rep, nb_rep;
    5690             :                 const char *mime_type;
    5691             :                 u32 nb_rep_ok = 0;
    5692             :                 Bool group_has_video = GF_FALSE;
    5693             :                 Bool disabled = GF_FALSE;
    5694             :                 Bool cp_supported = GF_FALSE;
    5695         262 :                 GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
    5696             :                 Bool active_rep_found;
    5697             : 
    5698             :                 active_rep = 0;
    5699             : 
    5700         262 :                 if (dash->dbg_grps_index) {
    5701             :                         Bool disable = GF_TRUE;
    5702             :                         u32 gidx;
    5703           0 :                         for (gidx=0; gidx<dash->nb_dbg_grps; gidx++) {
    5704           0 :                                 if (group_i == dash->dbg_grps_index[gidx]) {
    5705             :                                         disable = GF_FALSE;
    5706             :                                         break;
    5707             :                                 }
    5708             :                         }
    5709           0 :                         if (disable) {
    5710           0 :                                 group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
    5711           0 :                                 continue;
    5712             :                         }
    5713             :                 }
    5714             : 
    5715         262 :                 nb_rep = gf_list_count(group->adaptation_set->representations);
    5716             : 
    5717             :                 //on HLS get rid of audio only adaptation set if not in fMP4 mode
    5718         262 :                 if (dash->is_m3u8
    5719          37 :                         && !group->adaptation_set->max_width
    5720          13 :                         && !group->adaptation_set->max_height
    5721          13 :                         && (gf_list_count(dash->groups)>1)
    5722             :                 ) {
    5723          13 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, 0);
    5724          13 :                         if ((!rep->segment_template || !rep->segment_template->initialization)
    5725          13 :                                 && (!rep->segment_list || (!rep->segment_list->initialization_segment && !rep->segment_list->xlink_href))
    5726             :                         ) {
    5727           0 :                                 group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
    5728           0 :                                 continue;
    5729             :                         }
    5730             :                 }
    5731             : 
    5732         262 :                 if ((nb_rep>1) && !group->adaptation_set->segment_alignment && !group->adaptation_set->subsegment_alignment) {
    5733           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet without segmentAlignment flag set - may result in broken adaptation\n"));
    5734             :                 }
    5735         262 :                 if (group->adaptation_set->xlink_href) {
    5736           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with xlink:href to %s - ignoring because not supported\n", group->adaptation_set->xlink_href));
    5737           0 :                         continue;
    5738             :                 }
    5739             : 
    5740         276 :                 for (j=0; j<gf_list_count(group->adaptation_set->essential_properties); j++) {
    5741           7 :                         GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->essential_properties, j);
    5742           7 :                         if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
    5743             :                                 u32 id, w, h, res;
    5744           7 :                                 w = h = 0;
    5745           7 :                                 res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h, &w, &h);
    5746           7 :                                 if (res != 7) {
    5747           0 :                                         res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
    5748           0 :                                         if (res!=5) res=0;
    5749             :                                 }
    5750           7 :                                 if (res) {
    5751           7 :                                         group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
    5752           7 :                                         if (!w) w = group->srd_x + group->srd_w;
    5753           7 :                                         if (!h) h = group->srd_y + group->srd_h;
    5754             : 
    5755           7 :                                         if (w>group->srd_desc->srd_fw)
    5756           7 :                                                 group->srd_desc->srd_fw = w;
    5757           7 :                                         if (h>group->srd_desc->srd_fh)
    5758           7 :                                                 group->srd_desc->srd_fh = h;
    5759             :                                 }
    5760             : 
    5761             :                         } else {
    5762             :                                 //we don't know any defined scheme for now
    5763           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized EssentialProperty %s - ignoring because not supported\n", mpd_desc->scheme_id_uri));
    5764             :                                 disabled = 1;
    5765             :                                 break;
    5766             :                         }
    5767             :                 }
    5768             :                 if (disabled) {
    5769           0 :                         continue;
    5770             :                 }
    5771             : 
    5772             :                 cp_supported = 1;
    5773           0 :                 for (j=0; j<gf_list_count(group->adaptation_set->content_protection); j++) {
    5774          11 :                         GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->content_protection, j);
    5775             :                         //we don't know any defined scheme for now
    5776          11 :                         if (strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:mp4protection:2011")) {
    5777           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] AdaptationSet with unrecognized ContentProtection %s\n", mpd_desc->scheme_id_uri));
    5778             :                                 cp_supported = 0;
    5779             :                         } else {
    5780             :                                 cp_supported = 1;
    5781             :                                 break;
    5782             :                         }
    5783             :                 }
    5784         262 :                 if (!cp_supported) {
    5785           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] AdaptationSet with no supported ContentProtection - ignoring\n"));
    5786           0 :                         continue;
    5787             :                 }
    5788             : 
    5789          63 :                 for (j=0; j<gf_list_count(group->adaptation_set->supplemental_properties); j++) {
    5790          63 :                         GF_MPD_Descriptor *mpd_desc = gf_list_get(group->adaptation_set->supplemental_properties, j);
    5791          63 :                         if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:srd:2014")) {
    5792             :                                 u32 id, w, h, res;
    5793          63 :                                 w = h = 0;
    5794          63 :                                 res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h, &w, &h);
    5795          63 :                                 if (res != 7) {
    5796          63 :                                         res = sscanf(mpd_desc->value, "%d,%d,%d,%d,%d", &id, &group->srd_x, &group->srd_y, &group->srd_w, &group->srd_h);
    5797          63 :                                         if (res != 5) res=0;
    5798             :                                 }
    5799          63 :                                 if (res) {
    5800          63 :                                         group->srd_desc = gf_dash_get_srd_desc(dash, id, GF_TRUE);
    5801          63 :                                         if (!w) w = group->srd_x + group->srd_w;
    5802          63 :                                         if (!h) h = group->srd_y + group->srd_h;
    5803             : 
    5804          63 :                                         if (w>group->srd_desc->srd_fw)
    5805           0 :                                                 group->srd_desc->srd_fw = w;
    5806          63 :                                         if (h>group->srd_desc->srd_fh)
    5807           0 :                                                 group->srd_desc->srd_fh = h;
    5808             :                                 }
    5809             :                         }
    5810             :                 }
    5811             : 
    5812             :                 /*translate from single-indexed file to SegmentList*/
    5813         262 :                 gf_dash_setup_single_index_mode(group);
    5814             : 
    5815             :                 /* Select the appropriate representation in the given period */
    5816         655 :                 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
    5817         393 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
    5818         393 :                         if (rep->width && rep->height) group_has_video = GF_TRUE;
    5819             :                 }
    5820             : 
    5821             :                 //sort by ascending bandwidth and quality
    5822         406 :                 for (rep_i = 1; rep_i < nb_rep; rep_i++) {
    5823             :                         Bool swap=GF_FALSE;
    5824         406 :                         GF_MPD_Representation *r2 = gf_list_get(group->adaptation_set->representations, rep_i);
    5825         406 :                         GF_MPD_Representation *r1 = gf_list_get(group->adaptation_set->representations, rep_i-1);
    5826         406 :                         if (r1->bandwidth > r2->bandwidth) {
    5827             :                                 swap=GF_TRUE;
    5828         299 :                         } else if ((r1->bandwidth == r2->bandwidth) && (r1->quality_ranking<r2->quality_ranking)) {
    5829             :                                 swap=GF_TRUE;
    5830             :                         }
    5831             :                         if (swap) {
    5832         107 :                                 gf_list_rem(group->adaptation_set->representations, rep_i);
    5833         107 :                                 gf_list_insert(group->adaptation_set->representations, r2, rep_i-1);
    5834             :                                 rep_i=0;
    5835             :                         }
    5836             :                 }
    5837             : 
    5838         262 : select_active_rep:
    5839         262 :                 group->min_representation_bitrate = (u32) -1;
    5840             :                 active_rep_found = GF_FALSE;
    5841         655 :                 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
    5842         393 :                         u32 first_select_mode = dash->first_select_mode;
    5843         393 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
    5844         393 :                         if (rep->playback.disabled)
    5845           0 :                                 continue;
    5846         393 :                         if (!active_rep_found) {
    5847             :                                 active_rep = rep_i;
    5848             :                                 active_rep_found = GF_TRUE;
    5849             :                         }
    5850             : 
    5851         393 :                         rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
    5852             : 
    5853         393 :                         if (group_has_video && !rep->width && !rep->height) {
    5854           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Adaptation %s: non-video in a video group - disabling it\n", rep->id));
    5855           0 :                                 rep->playback.disabled = 1;
    5856           0 :                                 continue;
    5857             :                         }
    5858             : 
    5859         393 :                         if (group_has_video && !rep_sel->width && !rep_sel->height && rep->width && rep->height) {
    5860             :                                 rep_sel = rep;
    5861             :                         }
    5862             : 
    5863         393 :                         if (rep->bandwidth < group->min_representation_bitrate) {
    5864         262 :                                 group->min_representation_bitrate = rep->bandwidth;
    5865             :                         }
    5866             : 
    5867         393 :                         if (rep_i) {
    5868             :                                 Bool ok;
    5869         131 :                                 if (rep->codecs && rep_sel->codecs) {
    5870         117 :                                         char *sep = strchr(rep_sel->codecs, '.');
    5871         117 :                                         if (sep) sep[0] = 0;
    5872         117 :                                         ok = !strnicmp(rep->codecs, rep_sel->codecs, strlen(rep_sel->codecs) );
    5873             :                                         //check for scalable coding
    5874         117 :                                         if (!ok && rep->dependency_id) {
    5875           0 :                                                 if (!strncmp(rep_sel->codecs, "avc", 3)) {
    5876             :                                                         //we accept LHVC with different configs as enhancement for AVC
    5877           0 :                                                         if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
    5878             :                                                         //we accept SVC and MVC as enhancement for AVC
    5879           0 :                                                         else if (!strncmp(rep->codecs, "svc", 3) || !strncmp(rep->codecs, "mvc", 3) ) ok = 1;
    5880             :                                                 }
    5881           0 :                                                 else if (!strncmp(rep_sel->codecs, "hvc", 3) || !strncmp(rep_sel->codecs, "hev", 3)) {
    5882             :                                                         //we accept HEVC and HEVC+LHVC with different configs
    5883           0 :                                                         if (!strncmp(rep->codecs, "hvc", 3) || !strncmp(rep->codecs, "hev", 3) ) ok = 1;
    5884             :                                                         //we accept LHVC with different configs
    5885           0 :                                                         else if (!strncmp(rep->codecs, "lhv", 3) || !strncmp(rep->codecs, "lhe", 3) ) ok = 1;
    5886             :                                                 }
    5887             :                                         }
    5888             : 
    5889         117 :                                         if (sep) sep[0] = '.';
    5890         117 :                                         if (!ok) {
    5891           0 :                                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Different codec types (%s vs %s) in same AdaptationSet - disabling rep %s\n", rep_sel->codecs, rep->codecs, rep->codecs));
    5892             :                                                 //we don(t support mixes
    5893           0 :                                                 rep->playback.disabled = 1;
    5894           0 :                                                 continue;
    5895             :                                         }
    5896             :                                 }
    5897             :                         }
    5898             :                         //move to highest rate if ROUTE session and rep is not a remote one (baseURL set)
    5899         393 :                         if (dash->route_clock_state && (first_select_mode==GF_DASH_SELECT_BANDWIDTH_LOWEST) && !gf_list_count(rep->base_URLs))
    5900             :                                 first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST;
    5901             : 
    5902         393 :                         switch (first_select_mode) {
    5903           0 :                         case GF_DASH_SELECT_QUALITY_LOWEST:
    5904           0 :                                 if (rep->quality_ranking && (rep->quality_ranking < rep_sel->quality_ranking)) {
    5905             :                                         active_rep = rep_i;
    5906             :                                         break;
    5907             :                                 }/*fallthrough if quality is not indicated*/
    5908             :                         case GF_DASH_SELECT_BANDWIDTH_LOWEST:
    5909          12 :                                 if ((rep->width&&rep->height) || !group_has_video) {
    5910          12 :                                         if (rep->bandwidth < rep_sel->bandwidth) {
    5911             :                                                 active_rep = rep_i;
    5912             :                                         }
    5913             :                                 }
    5914             :                                 break;
    5915           0 :                         case GF_DASH_SELECT_QUALITY_HIGHEST:
    5916           0 :                                 if (rep->quality_ranking > rep_sel->quality_ranking) {
    5917             :                                         active_rep = rep_i;
    5918             :                                         break;
    5919             :                                 }
    5920             :                                 /*fallthrough if quality is not indicated*/
    5921             :                         case GF_DASH_SELECT_BANDWIDTH_HIGHEST:
    5922         381 :                                 if (rep->bandwidth > rep_sel->bandwidth) {
    5923             :                                         active_rep = rep_i;
    5924             :                                 }
    5925             :                                 break;
    5926             :                         default:
    5927             :                                 break;
    5928             :                         }
    5929             :                 }
    5930         393 :                 for (rep_i = 0; rep_i < nb_rep; rep_i++) {
    5931         393 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, rep_i);
    5932         393 :                         if (!rep->playback.disabled)
    5933         393 :                                 nb_rep_ok++;
    5934             :                 }
    5935             : 
    5936         262 :                 if (! nb_rep_ok) {
    5937           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No valid representation in this group - disabling\n"));
    5938           0 :                         group->selection = GF_DASH_GROUP_NOT_SELECTABLE;
    5939           0 :                         continue;
    5940             :                 }
    5941             : 
    5942         262 :                 rep_sel = gf_list_get(group->adaptation_set->representations, active_rep);
    5943             : 
    5944         262 :                 gf_dash_set_group_representation(group, rep_sel, GF_FALSE);
    5945         262 :                 if (group->dash->force_period_reload) {
    5946           0 :                         gf_dash_reset_groups(dash);
    5947           0 :                         dash->period_groups_setup = GF_FALSE;
    5948           0 :                         dash->dash_state = GF_DASH_STATE_SETUP;
    5949           0 :                         return GF_OK;
    5950             :                 }
    5951             :                 
    5952             :                 // active representation is marked as disabled, we need to redo the selection
    5953         262 :                 if (rep_sel->playback.disabled)
    5954             :                         goto select_active_rep;
    5955             : 
    5956             :                 //adjust seek
    5957         262 :                 if (dash->start_range_period) {
    5958           2 :                         gf_dash_seek_group(dash, group, dash->start_range_period, 0);
    5959             :                 }
    5960             : 
    5961         262 :                 mime_type = gf_dash_get_mime_type(NULL, rep_sel, group->adaptation_set);
    5962             : 
    5963         262 :                 if (!mime_type) {
    5964           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Missing MIME type for AdaptationSet - skipping\n"));
    5965           0 :                         continue;
    5966             :                 }
    5967             : 
    5968             :                 /* TODO: Generate segment names if urltemplates are used */
    5969         262 :                 if (!rep_sel->segment_base && !rep_sel->segment_list && !rep_sel->segment_template
    5970         134 :                         && !group->adaptation_set->segment_base && !group->adaptation_set->segment_list && !group->adaptation_set->segment_template
    5971           0 :                         && !group->period->segment_base && !group->period->segment_list && !group->period->segment_template
    5972           0 :                         && !gf_list_count(rep_sel->base_URLs)
    5973             :                    ) {
    5974           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment URLs are not present for AdaptationSet - skipping\n"));
    5975           0 :                         continue;
    5976             :                 }
    5977             : 
    5978         262 :                 group->selection = GF_DASH_GROUP_NOT_SELECTED;
    5979         262 :                 nb_groups_ok++;
    5980             :         }
    5981         123 :         dash->start_range_period = 0;
    5982             : 
    5983             :         //setup SRDs
    5984         130 :         for (as_i = 0; as_i<gf_list_count(dash->SRDs); as_i++) {
    5985             :                 u32 cols[10], rows[10];
    5986           7 :                 struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, as_i);
    5987             : 
    5988           7 :                 srd->srd_nb_rows = srd->srd_nb_cols = 0;
    5989             : 
    5990             :                 //sort SRDs
    5991          70 :                 for (j=1; j < gf_list_count(dash->groups); j++) {
    5992          63 :                         GF_DASH_Group *dg2 = gf_list_get(dash->groups, j);
    5993          63 :                         GF_DASH_Group *dg1 = gf_list_get(dash->groups, j-1);
    5994          63 :                         u32 dg1_weight = dg1->srd_y << 16 | dg1->srd_x;
    5995          63 :                         u32 dg2_weight = dg2->srd_y << 16 | dg2->srd_x;
    5996             : 
    5997          63 :                         if (dg1->srd_desc != srd) continue;
    5998          63 :                         if (dg2->srd_desc != srd) continue;
    5999             : 
    6000          63 :                         if (dg1_weight > dg2_weight) {
    6001           0 :                                 gf_list_rem(dash->groups, j);
    6002           0 :                                 gf_list_insert(dash->groups, dg2, j-1);
    6003             :                                 j=0;
    6004             :                         }
    6005             :                 }
    6006             : 
    6007             :                 //groups are now sorted for this srd, locate col/row positions
    6008          70 :                 for (group_i=0; group_i<gf_list_count(dash->groups); group_i++) {
    6009             :                         u32 k;
    6010             :                         Bool found = GF_FALSE;
    6011          70 :                         GF_DASH_Group *group = gf_list_get(dash->groups, group_i);
    6012          70 :                         if (group->srd_desc != srd) continue;
    6013             : 
    6014          70 :                         if (!group->srd_w || !group->srd_h) continue;
    6015             : 
    6016          63 :                         for (k=0; k<srd->srd_nb_cols; k++) {
    6017         105 :                                 if (cols[k]==group->srd_x) {
    6018             :                                         found=GF_TRUE;
    6019             :                                         break;
    6020             :                                 }
    6021             :                         }
    6022          63 :                         if (!found) {
    6023          21 :                                 cols[srd->srd_nb_cols] = group->srd_x;
    6024          21 :                                 group->srd_col_idx = srd->srd_nb_cols;
    6025          21 :                                 srd->srd_nb_cols++;
    6026             : 
    6027          21 :                                 srd->width += group->adaptation_set->max_width;
    6028             : 
    6029             :                         } else {
    6030          42 :                                 group->srd_col_idx = k;
    6031             :                         }
    6032             : 
    6033             :                         found = GF_FALSE;
    6034         126 :                         for (k=0; k<srd->srd_nb_rows; k++) {
    6035         105 :                                 if (rows[k]==group->srd_y) {
    6036             :                                         found=GF_TRUE;
    6037             :                                         break;
    6038             :                                 }
    6039             :                         }
    6040          63 :                         if (!found) {
    6041          21 :                                 rows[srd->srd_nb_rows] = group->srd_y;
    6042          21 :                                 group->srd_row_idx = srd->srd_nb_rows;
    6043          21 :                                 srd->srd_nb_rows++;
    6044          21 :                                 srd->height += group->adaptation_set->max_height;
    6045             :                         } else {
    6046          42 :                                 group->srd_row_idx = k;
    6047             :                         }
    6048             : 
    6049             :                 }
    6050           7 :                 gf_dash_set_tiles_quality(dash, srd, GF_TRUE);
    6051             :         }
    6052             : 
    6053         123 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    6054             : 
    6055         123 :         if (period->segment_base) {
    6056           0 :                 gf_mpd_segment_base_free(period->segment_base);
    6057           0 :                 period->segment_base = NULL;
    6058             :         }
    6059             : 
    6060         123 :         if (!nb_groups_ok) {
    6061           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No AdaptationSet could be selected in the MPD - Cannot play\n"));
    6062             :                 return GF_NON_COMPLIANT_BITSTREAM;
    6063             :         }
    6064             : 
    6065             :         /*and seek if needed*/
    6066             :         return GF_OK;
    6067             : }
    6068             : 
    6069             : 
    6070        1587 : static void gf_dash_group_check_time(GF_DASH_Group *group)
    6071             : {
    6072             :         s64 check_time;
    6073             :         u32 nb_dropped;
    6074             : 
    6075        1587 :         if (group->dash->is_m3u8) return;
    6076        1251 :         if (! group->timeline_setup) return;
    6077        1239 :         if (group->broken_timing) return;
    6078             : 
    6079        1239 :         check_time = (s64) gf_net_get_utc();
    6080             :         nb_dropped = 0;
    6081             : 
    6082             :         while (1) {
    6083             :                 u32 seg_dur_ms;
    6084        1239 :                 u64 seg_ast = gf_dash_get_segment_availability_start_time(group->dash->mpd, group, group->download_segment_index, &seg_dur_ms);
    6085             : 
    6086        1239 :                 s64 now = check_time + (s64) seg_dur_ms;
    6087        1239 :                 if (now <= (s64) seg_ast) {
    6088          49 :                         group->dash->tsb_exceeded = (u32) -1;
    6089        1288 :                         return;
    6090             :                 }
    6091             : 
    6092        1190 :                 now -= (s64) seg_ast;
    6093        1190 :                 if (now <= (s64) seg_dur_ms) {
    6094         223 :                         group->dash->tsb_exceeded = (u32) -1;
    6095         223 :                         return;
    6096             :                 }
    6097         967 :                 if (((s32) group->time_shift_buffer_depth > 0) && (now > group->time_shift_buffer_depth)) {
    6098           0 :                         group->download_segment_index++;
    6099           0 :                         nb_dropped ++;
    6100           0 :                         group->dash->time_in_tsb = 0;
    6101           0 :                         continue;
    6102             :                 }
    6103             : 
    6104         967 :                 if (nb_dropped > group->dash->tsb_exceeded) {
    6105           0 :                         group->dash->tsb_exceeded = nb_dropped;
    6106             :                 }
    6107             : 
    6108         967 :                 now -= group->dash->user_buffer_ms;
    6109         967 :                 if (now<0) return;
    6110             : 
    6111         498 :                 if (now>group->dash->time_in_tsb)
    6112         497 :                         group->dash->time_in_tsb = (u32) now;
    6113             :                 return;
    6114             :         }
    6115             : }
    6116             : 
    6117             : typedef enum
    6118             : {
    6119             :         GF_DASH_DownloadCancel,
    6120             :         GF_DASH_DownloadRestart,
    6121             :         GF_DASH_DownloadSuccess,
    6122             : } DownloadGroupStatus;
    6123             : 
    6124             : 
    6125             : static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following);
    6126             : 
    6127             : static GFINLINE void dash_set_min_wait(GF_DashClient *dash, u32 min_wait)
    6128             : {
    6129          77 :         if (!dash->min_wait_ms_before_next_request || (min_wait < dash->min_wait_ms_before_next_request)) {
    6130          54 :                 dash->min_wait_ms_before_next_request = min_wait;
    6131          54 :                 dash->min_wait_sys_clock = gf_sys_clock();
    6132             :         }
    6133             : }
    6134             : 
    6135             : /*TODO decide what is the best, fetch from another representation or ignore ...*/
    6136         139 : static DownloadGroupStatus on_group_download_error(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, GF_Err e, GF_MPD_Representation *rep, char *new_base_seg_url, char *key_url, Bool has_dep_following)
    6137             : {
    6138             :         u32 clock_time;
    6139             :         Bool will_retry = GF_FALSE;
    6140             :         Bool is_live = GF_FALSE;
    6141             :     u32 min_wait;
    6142         139 :         if (!dash || !group)
    6143             :                 return GF_DASH_DownloadCancel;
    6144             : 
    6145          25 :         clock_time = gf_sys_clock();
    6146             : 
    6147          25 :     min_wait = dash->min_timeout_between_404;
    6148          25 :     if (dash->route_clock_state) {
    6149           2 :         if (!group->period->origin_base_url)
    6150             :             min_wait = 50; //50 ms between retries if route and not a remote period
    6151             :     }
    6152             : 
    6153             :     dash_set_min_wait(dash, min_wait);
    6154             : 
    6155          25 :         group->retry_after_utc = min_wait + gf_net_get_utc();
    6156          25 :         if (!group->period->origin_base_url && (dash->mpd->type==GF_MPD_TYPE_DYNAMIC))
    6157             :                 is_live = GF_TRUE;
    6158             : 
    6159          25 :         if (e==GF_REMOTE_SERVICE_ERROR) {
    6160           0 :                 gf_dash_mark_group_done(group);
    6161             :         }
    6162             :         //failure on last segment in non dynamic mode: likely due to rounding in dash segment duration, assume no error
    6163             :         //in dynamic mode, we need to check if this is a download schedule issue
    6164          25 :         else if (!is_live && group->period->duration && (group->download_segment_index + 1 >= (s32) group->nb_segments_in_rep) ) {
    6165           2 :                 gf_dash_mark_group_done(group);
    6166             :         }
    6167          23 :         else if (group->maybe_end_of_stream) {
    6168           0 :                 if (group->maybe_end_of_stream==2) {
    6169           0 :                         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)));
    6170           0 :                         group->maybe_end_of_stream = 0;
    6171           0 :                         gf_dash_mark_group_done(group);
    6172             :                 }
    6173           0 :                 group->maybe_end_of_stream++;
    6174          23 :         } else if (group->segment_in_valid_range) {
    6175           0 :                 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)));
    6176           0 :                 if (dash->speed >= 0) {
    6177           0 :                         group->download_segment_index++;
    6178           0 :                 } else if (group->download_segment_index) {
    6179           0 :                         group->download_segment_index--;
    6180             :                 } else {
    6181           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
    6182           0 :                         gf_dash_mark_group_done(group);
    6183             :                 }
    6184           0 :                 group->segment_in_valid_range=0;
    6185             :     }
    6186             :     //ROUTE case, the file was removed from cache by the route demuxer
    6187          23 :     else if (e==GF_URL_REMOVED) {
    6188           0 :         if (dash->speed >= 0) {
    6189           0 :             group->download_segment_index++;
    6190           0 :         } else if (group->download_segment_index) {
    6191           0 :             group->download_segment_index--;
    6192             :         }
    6193          23 :         } else if (group->prev_segment_ok && !group->time_at_first_failure && !group->loop_detected) {
    6194           4 :         group->time_at_first_failure = clock_time;
    6195           4 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Error in downloading new segment: %s %s - starting countdown for %d ms (delay between retry %d ms)\n", new_base_seg_url, gf_error_to_string(e), group->current_downloaded_segment_duration, min_wait));
    6196             : 
    6197             :         will_retry = GF_TRUE;
    6198             :         }
    6199             :         //if multiple baseURL, try switching the base
    6200          19 :         else if ((e==GF_URL_ERROR) && (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) )) {
    6201           0 :                 group->current_base_url_idx++;
    6202           0 :                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6203           0 :                 if (key_url) gf_free(key_url);
    6204           0 :                 return dash_download_group_download(dash, group, base_group, has_dep_following);
    6205             :         }
    6206             :         //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 some default safety safety
    6207          19 :         else if (group->prev_segment_ok && (clock_time - group->time_at_first_failure <= group->current_downloaded_segment_duration + dash->segment_lost_after_ms )) {
    6208             :                 will_retry = GF_TRUE;
    6209             :         } else {
    6210           9 :                 if ((group->dash->route_clock_state==2) && (e==GF_URL_ERROR)) {
    6211           0 :                         const char *val = group->dash->dash_io->get_header_value(group->dash->dash_io, group->dash->mpd_dnload, "x-route-loop");
    6212           0 :                         Bool is_loop = (val && !strcmp(val, "yes")) ? GF_TRUE : GF_FALSE;
    6213             :                         //if explicit loop or more than 5 consecutive seg lost restart synchro
    6214           0 :                         if ((group->nb_consecutive_segments_lost >= 5) || is_loop) {
    6215           0 :                                 u32 i=0;
    6216           0 :                                 if (is_loop) {
    6217           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE loop detected, reseting timeline\n"));
    6218             :                                 } else {
    6219           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] ROUTE lost %d consecutive segments, resetup tune-in\n", group->nb_consecutive_segments_lost));
    6220             :                                 }
    6221           0 :                                 dash->utc_drift_estimate = 0;
    6222           0 :                                 dash->initial_period_tunein = GF_TRUE;
    6223           0 :                                 dash->route_clock_state = 1;
    6224           0 :                                 while ((group = gf_list_enum(dash->groups, &i))) {
    6225           0 :                                         group->start_number_at_last_ast = 0;
    6226           0 :                                         gf_dash_group_timeline_setup(dash->mpd, group, 0);
    6227           0 :                                         group->loop_detected = is_loop;
    6228           0 :                                         group->time_at_first_failure = 0;
    6229           0 :                                         group->prev_segment_ok = GF_TRUE;
    6230             :                                 }
    6231           0 :                                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6232           0 :                                 if (key_url) gf_free(key_url);
    6233             :                                 return GF_DASH_DownloadCancel;
    6234             :                         }
    6235             :                 }
    6236           9 :                 if (group->prev_segment_ok) {
    6237           3 :                         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));
    6238           3 :                         group->time_at_first_failure = 0;
    6239             :                         //for route we still consider the previous segment valid and don't attempt to resync the timeline
    6240           3 :                         if (!group->dash->route_clock_state)
    6241           3 :                                 group->prev_segment_ok = GF_FALSE;
    6242             :                 }
    6243           9 :                 group->nb_consecutive_segments_lost ++;
    6244             : 
    6245             :                 //we are lost ....
    6246           9 :                 if (group->nb_consecutive_segments_lost == 20) {
    6247           0 :                         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"));
    6248             :                         dash_set_min_wait(dash, 1000);
    6249           0 :                         group->maybe_end_of_stream = 1;
    6250             :                 } else {
    6251           9 :                         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)));
    6252           9 :                         if (dash->speed >= 0) {
    6253           9 :                                 group->download_segment_index++;
    6254           0 :                         } else if (group->download_segment_index) {
    6255           0 :                                 group->download_segment_index--;
    6256             :                         } else {
    6257           0 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
    6258           0 :                                 gf_dash_mark_group_done(group);
    6259             :                         }
    6260             :                 }
    6261             :         }
    6262             :         //if retry, do not reset dependency status
    6263             :         if (!will_retry) {
    6264          11 :                 if (rep->dependency_id) {
    6265           0 :                         segment_cache_entry *cache_entry = &base_group->cached[base_group->nb_cached_segments];
    6266           0 :                         cache_entry->flags &= ~SEG_FLAG_DEP_FOLLOWING;
    6267             :                 }
    6268             : 
    6269          11 :                 if (group->base_rep_index_plus_one) {
    6270           0 :                         group->active_rep_index = group->base_rep_index_plus_one - 1;
    6271           0 :                         group->has_pending_enhancement = GF_FALSE;
    6272             :                 }
    6273             :         }
    6274             : 
    6275          25 :         if (new_base_seg_url) gf_free(new_base_seg_url);
    6276          25 :         if (key_url) gf_free(key_url);
    6277             :         return GF_DASH_DownloadCancel;
    6278             : }
    6279             : 
    6280        5014 : static DownloadGroupStatus dash_download_group_download(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
    6281             : {
    6282             :         //commented out as we end up doing too many requets
    6283             :         GF_Err e;
    6284             :         GF_MPD_Representation *rep;
    6285        5014 :         char *new_base_seg_url=NULL;
    6286        5014 :         char *key_url=NULL;
    6287             :         bin128 key_iv;
    6288             :         u64 start_range, end_range;
    6289             :         Bool use_byterange;
    6290             :         u32 llhls_live_edge_type=0;
    6291             :         u32 representation_index;
    6292             :         u32 clock_time;
    6293             :         Bool remote_file = GF_FALSE;
    6294             :         const char *base_url = NULL;
    6295        5014 :         u32 start_number=0;
    6296             :         u64 seg_dur;
    6297             :         u32 seg_scale;
    6298             :         segment_cache_entry *cache_entry;
    6299             : 
    6300        5014 :         GF_MPD_Type dyn_type = dash->mpd->type;
    6301        5014 :         if (group->period->origin_base_url)
    6302           0 :                 dyn_type = group->period->type;
    6303             : 
    6304        5014 :         if (group->done)
    6305             :                 return GF_DASH_DownloadSuccess;
    6306        4766 :         if (!base_group)
    6307             :                 return GF_DASH_DownloadSuccess;
    6308             : 
    6309             :         //we are waiting for the playlist to be updated to find the next segment to play
    6310        4766 :         if (group->hls_next_seq_num) {
    6311             :                 return GF_DASH_DownloadCancel;
    6312             :         }
    6313             : 
    6314        4617 :         if (group->selection != GF_DASH_GROUP_SELECTED) return GF_DASH_DownloadSuccess;
    6315             : 
    6316        4617 :         if (base_group->nb_cached_segments>=base_group->max_cached_segments) {
    6317             :                 return GF_DASH_DownloadCancel;
    6318             :         }
    6319        3890 :         if (!group->timeline_setup) {
    6320         248 :                 gf_dash_group_timeline_setup(dash->mpd, group, 0);
    6321         248 :                 group->timeline_setup = GF_TRUE;
    6322             :         }
    6323             : 
    6324             :         /*remember the active rep index, since group->active_rep_index may change because of bandwidth control algorithm*/
    6325        3890 :         representation_index = group->active_rep_index;
    6326        3890 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    6327        3890 :         rep->playback.broadcast_flag = GF_FALSE;
    6328             : 
    6329        3890 : llhls_rety:
    6330             :         //special case for LL-HLS: if we have a switch request pending, check if next fragment is the first of a new seg
    6331             :         //or a complete seg (we do not switch in the middle of a segment)
    6332        3890 :         if (group->llhls_switch_request>=0) {
    6333           0 :                 GF_MPD_SegmentURL *hlsseg = gf_list_get(rep->segment_list->segment_URLs, group->download_segment_index);
    6334           0 :                 if (hlsseg && (! hlsseg->hls_ll_chunk_type || hlsseg->is_first_part)) {
    6335           0 :                         rep = gf_list_get(group->adaptation_set->representations, group->llhls_switch_request);
    6336           0 :                         group->llhls_edge_chunk = NULL;
    6337           0 :                         gf_dash_set_group_representation(group, rep, GF_TRUE);
    6338             :                         assert(group->llhls_switch_request<0);
    6339             :                         //we are waiting for playlist update, return
    6340           0 :                         if (group->hls_next_seq_num) {
    6341             :                                 return GF_DASH_DownloadCancel;
    6342             :                         }
    6343             :                         //otherwise new rep is set
    6344           0 :                         representation_index = group->active_rep_index;
    6345           0 :                         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    6346           0 :                         rep->playback.broadcast_flag = GF_FALSE;
    6347             :                 }
    6348             :         }
    6349             : 
    6350             :         /* if the index of the segment to be downloaded is greater or equal to the last segment (as seen in the playlist),
    6351             :          we need to check if a new playlist is ready */
    6352        3890 :         if (group->nb_segments_in_rep && (group->download_segment_index >= (s32) group->nb_segments_in_rep)) {
    6353         772 :                 u32 timer = gf_sys_clock() - dash->last_update_time;
    6354             :                 Bool update_playlist = 0;
    6355             : 
    6356             :                 /* this period is done*/
    6357         772 :                 if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
    6358             :                 }
    6359             :                 /* update of the playlist, only if indicated */
    6360         772 :                 else if (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period)) {
    6361             :                         update_playlist = 1;
    6362             :                 }
    6363             :                 /* if media_presentation_duration is 0 and we are in live, force a refresh (not in the spec but safety check*/
    6364         772 :                 else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->mpd->media_presentation_duration) {
    6365         680 :                         if (group->segment_duration && (timer > group->segment_duration*1000))
    6366             :                                 update_playlist = 1;
    6367             :                 }
    6368             :                 if (update_playlist) {
    6369           1 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playlist should be updated, postponing group download until playlist is updated\n"));
    6370           1 :                         dash->force_mpd_update = GF_TRUE;
    6371           1 :                         return GF_DASH_DownloadCancel;
    6372             :                 }
    6373             :                 /* Now that the playlist is up to date, we can check again */
    6374         771 :                 if (group->download_segment_index  >= (s32) group->nb_segments_in_rep) {
    6375             :                         /* if there is a specified update period, we redo the whole process */
    6376         771 :                         if (dash->mpd->minimum_update_period || dyn_type==GF_MPD_TYPE_DYNAMIC) {
    6377             : 
    6378         679 :                                 if (dyn_type==GF_MPD_TYPE_STATIC) {
    6379           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in static period (dynamic MPD) - group is done\n"));
    6380           0 :                                         gf_dash_mark_group_done(group);
    6381           0 :                                         return GF_DASH_DownloadCancel;
    6382             :                                 }
    6383         679 :                                 else if ((dyn_type==GF_MPD_TYPE_DYNAMIC) && group->period->duration) {
    6384           0 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Last segment in period (dynamic mode) - group is done\n"));
    6385           0 :                                         gf_dash_mark_group_done(group);
    6386           0 :                                         return GF_DASH_DownloadCancel;
    6387             :                                 }
    6388         679 :                                 else if (! group->maybe_end_of_stream) {
    6389         679 :                                         u32 now = gf_sys_clock();
    6390         679 :                                         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));
    6391             : 
    6392         679 :                                         if (group->nb_cached_segments) {
    6393             :                                                 return GF_DASH_DownloadCancel;
    6394             :                                         }
    6395             : 
    6396         679 :                                         if (dash->is_m3u8 && (dyn_type==GF_MPD_TYPE_DYNAMIC)) {
    6397          11 :                                                 if (!group->time_at_first_reload_required)
    6398           8 :                                                         group->time_at_first_reload_required = now;
    6399             : 
    6400             :                                                 //use group last modification time
    6401          11 :                                                 timer = now - group->last_mpd_change_time;
    6402          11 :                                                 if (timer < group->segment_duration * 2000) {
    6403             :                                                         //no more segment, force a manifest update now
    6404          11 :                                                         dash->force_mpd_update = GF_TRUE;
    6405             :                                                 } else {
    6406           0 :                                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] HLS Segment list has not been updated for more than %d ms - assuming end of session\n", now - group->time_at_first_reload_required));
    6407           0 :                                                         gf_dash_mark_group_done(group);
    6408             :                                                 }
    6409             :                                                 return GF_DASH_DownloadCancel;
    6410             :                                         }
    6411             : 
    6412         668 :                                         if (!group->time_at_first_reload_required) {
    6413           5 :                                                 group->time_at_first_reload_required = now;
    6414           5 :                                                 return GF_DASH_DownloadCancel;
    6415             :                                         }
    6416         663 :                                         if (now - group->time_at_first_reload_required < group->cache_duration)
    6417             :                                                 return GF_DASH_DownloadCancel;
    6418           1 :                                         if (dash->mpd->minimum_update_period) {
    6419           1 :                                                 if (now - group->time_at_first_reload_required < dash->mpd->minimum_update_period)
    6420             :                                                         return GF_DASH_DownloadCancel;
    6421           0 :                                         } else if (dyn_type==GF_MPD_TYPE_DYNAMIC) {
    6422             :                                                 //use group last modification time
    6423           0 :                                                 timer = now - group->last_mpd_change_time;
    6424           0 :                                                 if (timer < 2 * group->segment_duration * 2000)
    6425             :                                                         return GF_DASH_DownloadCancel;
    6426             :                                         }
    6427             : 
    6428           1 :                                         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));
    6429           1 :                                         gf_dash_mark_group_done(group);
    6430           1 :                                         return GF_DASH_DownloadCancel;
    6431             :                                 }
    6432             :                         } else {
    6433             :                                 /* if not, we are really at the end of the playlist, we can quit */
    6434          92 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] End of period reached for group\n"));
    6435          92 :                                 gf_dash_mark_group_done(group);
    6436          92 :                                 if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
    6437          87 :                                         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
    6438             :                                 return GF_DASH_DownloadCancel;
    6439             :                         }
    6440             :                 }
    6441             :         }
    6442        3118 :         group->time_at_first_reload_required = 0;
    6443             : 
    6444        3118 :         if (group->force_switch_bandwidth && !dash->auto_switch_count) {
    6445           2 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing representation switch, retesting group"));
    6446           2 :                 gf_dash_switch_group_representation(dash, group);
    6447             :                 /*restart*/
    6448           2 :                 return GF_DASH_DownloadRestart;
    6449             :         }
    6450             : 
    6451             :         /*check availablity start time of segment in Live !!*/
    6452        3116 :         if (!group->broken_timing && (dyn_type==GF_MPD_TYPE_DYNAMIC) && !dash->is_m3u8 && !dash->is_smooth) {
    6453             :                 s32 to_wait = 0;
    6454         126 :                 u32 seg_dur_ms=0;
    6455         126 :                 s64 segment_ast = (s64) gf_dash_get_segment_availability_start_time(dash->mpd, group, group->download_segment_index, &seg_dur_ms);
    6456         126 :                 s64 now = (s64) gf_net_get_utc();
    6457             : #ifndef GPAC_DISABLE_LOG
    6458         126 :                 start_number = gf_dash_get_start_number(group, rep);
    6459             : #endif
    6460             : 
    6461             : 
    6462         126 :                 if (group->retry_after_utc > (u64) now) {
    6463           8 :                         to_wait = (u32) (group->retry_after_utc - (u64) now);
    6464             :                         dash_set_min_wait(dash, (u32) to_wait);
    6465             : 
    6466          52 :                         return GF_DASH_DownloadCancel;
    6467             :                 }
    6468             : 
    6469         118 :                 clock_time = gf_sys_clock();
    6470         118 :                 to_wait = (s32) (segment_ast - now);
    6471             : 
    6472         118 :                 if (group->force_early_fetch) {
    6473           2 :                         if (to_wait>1) {
    6474           2 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Set #%d demux empty but wait time for segment %d is still %d ms, forcing scheduling\n", 1+gf_list_find(dash->groups, group), group->download_segment_index + start_number, to_wait));
    6475             :                                 to_wait = 0;
    6476             :                         } else {
    6477             :                                 //we officially reached segment AST
    6478           0 :                                 group->force_early_fetch = GF_FALSE;
    6479             :                         }
    6480             :                 }
    6481             : 
    6482             :                 /*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 ...*/
    6483         116 :                 if (to_wait > 1) {
    6484          44 :                         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", 1+gf_list_find(dash->groups, group), gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, to_wait));
    6485          44 :                         if (group->last_segment_time) {
    6486          28 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
    6487             :                         }
    6488             : 
    6489             :                         dash_set_min_wait(dash, (u32) to_wait);
    6490             : 
    6491             :                         return GF_DASH_DownloadCancel;
    6492             :                 } else {
    6493          74 :                         GF_LOG(GF_LOG_INFO, 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", 1+gf_list_find(dash->groups, group), gf_sys_clock(), group->download_segment_index + start_number, segment_ast, (segment_ast - group->period->start - group->ast_at_init + group->ast_offset)/1000.0, -to_wait));
    6494             : 
    6495          74 :                         if (group->last_segment_time) {
    6496          48 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] %d ms elapsed since previous segment download\n", clock_time - group->last_segment_time));
    6497             :                         }
    6498             :                 }
    6499          74 :         group->time_at_last_request = gf_sys_clock();
    6500             :     }
    6501             : 
    6502        3064 :         base_url = dash->base_url;
    6503        3064 :         if (group->period->origin_base_url) base_url = group->period->origin_base_url;
    6504             :         /* At this stage, there are some segments left to be downloaded */
    6505        3064 :         e = gf_dash_resolve_url(dash->mpd, rep, group, 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, NULL, &start_number);
    6506             : 
    6507             : 
    6508        3064 :         if ((e==GF_EOS) && group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
    6509             :                 //no more segments, force update now
    6510           0 :                 dash->force_mpd_update = GF_TRUE;
    6511           0 :                 return GF_DASH_DownloadCancel;
    6512             :         }
    6513             : 
    6514        3064 :         if (e || !new_base_seg_url) {
    6515           8 :                 if (e==GF_EOS) {
    6516           8 :                         gf_dash_mark_group_done(group);
    6517             :                 } else {
    6518             :                         /*do something!!*/
    6519           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error resolving URL of next segment: %s\n", gf_error_to_string(e) ));
    6520             :                 }
    6521           8 :                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6522           8 :                 if (key_url) gf_free(key_url);
    6523           8 :                 group->llhls_edge_chunk = NULL;
    6524           8 :                 return GF_DASH_DownloadCancel;
    6525             :         }
    6526             : 
    6527        3056 :         if (dash->is_m3u8 && group->is_low_latency) {
    6528             :                 assert(rep->segment_list);
    6529          42 :                 GF_MPD_SegmentURL *hlsseg = gf_list_get(rep->segment_list->segment_URLs, group->download_segment_index);
    6530             :                 assert(hlsseg);
    6531             : 
    6532          42 :                 if (dash->llhls_single_range && hlsseg->media_range && (hlsseg->can_merge || group->llhls_last_was_merged) ) {
    6533             :                         //if not very first request (tune in) and not first part of seg, if mergeable issue a single byterange
    6534           1 :                         if (!group->first_hls_chunk && hlsseg->media_range->start_range) {
    6535           0 :                                 if (!hlsseg->can_merge) {
    6536           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] LL-HLS part cannot be merged with previously open byte-range request, disabling merging !\n"));
    6537           0 :                                         dash->llhls_single_range = GF_FALSE;
    6538             :                                 }
    6539           0 :                                 group->download_segment_index++;
    6540           0 :                                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6541           0 :                                 new_base_seg_url = NULL;
    6542           0 :                                 if (key_url) gf_free(key_url);
    6543           0 :                                 key_url = NULL;
    6544           0 :                                 goto llhls_rety;
    6545             :                         }
    6546           1 :                         group->first_hls_chunk = GF_FALSE;
    6547           1 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Changing LL-HLS request %s @ "LLU"->"LLU" to open end range\n", new_base_seg_url, start_range, end_range));
    6548           1 :                         end_range = (u64) -1;
    6549             :                         llhls_live_edge_type = 2;
    6550           1 :                         group->llhls_last_was_merged = GF_TRUE;
    6551             :                 } else {
    6552          41 :                         group->llhls_last_was_merged = GF_FALSE;
    6553          41 :                         if (hlsseg->hls_ll_chunk_type)
    6554             :                                 llhls_live_edge_type = 1;
    6555             :                 }
    6556          42 :                 group->llhls_edge_chunk = hlsseg;
    6557             :         }
    6558        3056 :         use_byterange = (start_range || end_range) ? 1 : 0;
    6559             : 
    6560             : #ifndef GPAC_DISABLE_LOG
    6561        3056 :         if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_INFO)) {
    6562          53 :                 if (llhls_live_edge_type==2) {
    6563           1 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next segment: %s (live edge merged range: "LLU" -> END)\n", gf_file_basename(new_base_seg_url), start_range));
    6564          52 :                 } else if (use_byterange) {
    6565          28 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next %s: %s (range: "LLU" -> "LLU")\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment",  gf_file_basename(new_base_seg_url), start_range, end_range));
    6566             :                 } else {
    6567          24 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Queing next %s: %s\n", (llhls_live_edge_type==1) ? "LL-HLS part" : "segment", gf_file_basename(new_base_seg_url)));
    6568             :                 }
    6569             :         }
    6570             : #endif
    6571             : 
    6572             :         /*local file*/
    6573        3056 :         if (strnicmp(base_url, "gfio://", 7)
    6574        3047 :                 && (!strstr(new_base_seg_url, "://") || (!strnicmp(new_base_seg_url, "file://", 7) || !strnicmp(new_base_seg_url, "gmem://", 7) ) )
    6575             :         ) {
    6576             :                 e = GF_OK;
    6577             :                 /*do not erase local files*/
    6578        2529 :                 group->local_files = 1;
    6579        2529 :                 if (group->force_switch_bandwidth && !dash->auto_switch_count) {
    6580           0 :                         if (new_base_seg_url) gf_free(new_base_seg_url);
    6581           0 :                         gf_dash_switch_group_representation(dash, group);
    6582             :                         /*restart*/
    6583           0 :                         return GF_DASH_DownloadRestart;
    6584             :                 }
    6585        2529 :                 if (! gf_file_exists(new_base_seg_url)) {
    6586          20 :                         if (group->current_base_url_idx + 1 < gf_mpd_get_base_url_count(dash->mpd, group->period, group->adaptation_set, rep) ){
    6587           0 :                                 group->current_base_url_idx++;
    6588           0 :                                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6589           0 :                                 if (key_url) gf_free(key_url);
    6590           0 :                                 return dash_download_group_download(dash, group, base_group, has_dep_following);
    6591          20 :                         } else if (group->period->duration && (group->download_segment_index + 1 == group->nb_segments_in_rep) ) {
    6592           4 :                                 if (new_base_seg_url) gf_free(new_base_seg_url);
    6593           4 :                                 gf_dash_mark_group_done(group);
    6594           4 :                                 return GF_DASH_DownloadCancel;
    6595             :                         } else {
    6596          16 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] File %s not found on disk\n", new_base_seg_url));
    6597          16 :                                 group->current_base_url_idx = 0;
    6598          16 :                                 return on_group_download_error(dash, group, base_group, GF_NOT_FOUND, rep, new_base_seg_url, key_url, has_dep_following);
    6599             :                         }
    6600             :                 }
    6601        2509 :                 group->current_base_url_idx = 0;
    6602             :         } else {
    6603             :                 remote_file = GF_TRUE;
    6604             :         }
    6605             : 
    6606             : 
    6607             :         assert(base_group->nb_cached_segments<base_group->max_cached_segments);
    6608        3036 :         cache_entry = &base_group->cached[base_group->nb_cached_segments];
    6609             : 
    6610             :         //assign url
    6611        3036 :         cache_entry->url = new_base_seg_url;
    6612        3036 :         if (use_byterange && remote_file) {
    6613          34 :                 cache_entry->start_range = start_range;
    6614          34 :                 cache_entry->end_range = end_range;
    6615             :         } else {
    6616        3002 :                 cache_entry->start_range = 0;
    6617        3002 :                 cache_entry->end_range = 0;
    6618             :         }
    6619        3036 :         cache_entry->representation_index = representation_index;
    6620        3036 :         cache_entry->duration = (u32) group->current_downloaded_segment_duration;
    6621        3036 :         cache_entry->flags = group->loop_detected ? SEG_FLAG_LOOP_DETECTED : 0;
    6622        3036 :         if (has_dep_following) cache_entry->flags |= SEG_FLAG_DEP_FOLLOWING;
    6623        3036 :         if (group->disabled)
    6624           0 :                 cache_entry->flags |= SEG_FLAG_DISABLED;
    6625        3036 :         if (key_url) {
    6626           6 :                 cache_entry->key_url = key_url;
    6627           6 :                 memcpy(cache_entry->key_IV, key_iv, sizeof(bin128));
    6628             :                 //set to NULL since we stored it, so that it won't be freed when exiting this function
    6629           6 :                 key_url = NULL;
    6630             :         }
    6631             : 
    6632        3036 :         cache_entry->time.num = gf_dash_get_segment_start_time_with_timescale(group, &seg_dur, &seg_scale);
    6633        3036 :         cache_entry->time.den = seg_scale;
    6634             : 
    6635        3036 :         cache_entry->seg_number = group->download_segment_index + start_number;
    6636        3036 :         cache_entry->seg_name_start = dash_strip_base_url(cache_entry->url, base_url);
    6637        3036 :         group->loop_detected = GF_FALSE;
    6638             : 
    6639        3036 :         if (group->local_files && use_byterange) {
    6640         291 :                 cache_entry->start_range = start_range;
    6641         291 :                 cache_entry->end_range = end_range;
    6642             :         }
    6643        3036 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Added file to cache (%u/%u in cache): %s\n", base_group->nb_cached_segments+1, base_group->max_cached_segments, cache_entry->url));
    6644             : 
    6645        3036 :         base_group->nb_cached_segments++;
    6646             : 
    6647             :         /* download enhancement representation of this segment*/
    6648        3036 :         if ((representation_index != group->max_complementary_rep_index) && rep->playback.enhancement_rep_index_plus_one) {
    6649           0 :                 group->active_rep_index = rep->playback.enhancement_rep_index_plus_one - 1;
    6650           0 :                 group->has_pending_enhancement = GF_TRUE;
    6651             :         }
    6652             :         /* if we have downloaded all enhancement representations of this segment, restart from base representation and increase downloaded segment index by 1*/
    6653             :         else {
    6654        3036 :                 if (group->base_rep_index_plus_one) group->active_rep_index = group->base_rep_index_plus_one - 1;
    6655        3036 :                 if (dash->speed >= 0) {
    6656        3036 :                         group->download_segment_index++;
    6657           0 :                 } else if (group->download_segment_index) {
    6658           0 :                         group->download_segment_index--;
    6659             :                 } else {
    6660           0 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing in backward - start of playlist reached - assuming end of stream\n"));
    6661           0 :                         gf_dash_mark_group_done(group);
    6662             :                 }
    6663        3036 :                 group->has_pending_enhancement = GF_FALSE;
    6664             :         }
    6665        3036 :         if (dash->auto_switch_count) {
    6666         120 :                 if (group->llhls_edge_chunk && group->llhls_edge_chunk->hls_ll_chunk_type) {
    6667           0 :                         if (group->llhls_edge_chunk->is_first_part)
    6668           0 :                                 group->nb_segments_done++;
    6669             :                 } else {
    6670         120 :                         group->nb_segments_done++;
    6671             :                 }
    6672         120 :                 if (group->nb_segments_done==dash->auto_switch_count) {
    6673         120 :                         group->nb_segments_done=0;
    6674         120 :                         gf_dash_skip_disabled_representation(group, rep, GF_TRUE);
    6675             :                 }
    6676             :         }
    6677             : 
    6678             :         //do not notify segments if there is a pending period switch - since these are decided by the user, we don't
    6679             :         //want to notify old segments
    6680        3036 :         if (!dash->request_period_switch && !group->has_pending_enhancement && !has_dep_following)
    6681        1587 :                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, gf_list_find(dash->groups, base_group), GF_OK);
    6682             : 
    6683             :         //do NOT free new_base_seg_url, it is now in cache_entry->url
    6684        3036 :         if (key_url) gf_free(key_url);
    6685             :         if (e) return GF_DASH_DownloadCancel;
    6686             :         return GF_DASH_DownloadSuccess;
    6687             : }
    6688             : 
    6689             : 
    6690        5014 : static DownloadGroupStatus dash_download_group(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group, Bool has_dep_following)
    6691             : {
    6692             :         DownloadGroupStatus res;
    6693             : 
    6694        5014 :         if (!group->current_dep_idx) {
    6695        5014 :                 res = dash_download_group_download(dash, group, base_group, has_dep_following);
    6696        5014 :                 if (res==GF_DASH_DownloadRestart) return res;
    6697        5012 :                 if (res==GF_DASH_DownloadCancel) return res;
    6698        3284 :                 group->current_dep_idx = 1;
    6699             :         }
    6700             : 
    6701        3284 :         if (group->groups_depending_on) {
    6702         161 :                 u32 i, count = gf_list_count(group->groups_depending_on);
    6703         161 :                 i = group->current_dep_idx - 1;
    6704        1610 :                 for (; i<count; i++) {
    6705             : 
    6706        1449 :                         GF_DASH_Group *dep_group = gf_list_get(group->groups_depending_on, i);
    6707        1449 :                         if ((i+1==count) && !dep_group->groups_depending_on)
    6708             :                                 has_dep_following = GF_FALSE;
    6709             : 
    6710        1449 :                         res = dash_download_group(dash, dep_group, base_group, has_dep_following);
    6711        1449 :                         if (res==GF_DASH_DownloadRestart) {
    6712           0 :                                 i--;
    6713           0 :                                 continue;
    6714             :                         }
    6715        1449 :                         group->current_dep_idx = i + 1;
    6716        1449 :                         if (res==GF_DASH_DownloadCancel)
    6717             :                                 return GF_DASH_DownloadCancel;
    6718             :                 }
    6719             :         }
    6720        3284 :         group->current_dep_idx = 0;
    6721        3284 :         return GF_DASH_DownloadSuccess;
    6722             : }
    6723             : 
    6724             : //tile based adaptation
    6725        1556 : static void dash_global_rate_adaptation(GF_DashClient *dash, Bool for_postponed_only)
    6726             : {
    6727             :         u32 quality_rank;
    6728             :         u32 min_bandwidth = 0;
    6729             :         Bool force_rep_idx = GF_FALSE;
    6730             :         Bool local_file_mode = GF_FALSE;
    6731             :         Bool use_custom_algo = GF_FALSE;
    6732             :         GF_MPD_Representation *rep, *rep_new;
    6733             :         u32 total_rate, max_fsize, bandwidths[20], groups_per_quality[20], max_level;
    6734             :         u32 q_idx, nb_qualities = 0;
    6735        1556 :         u32 i, count = gf_list_count(dash->groups), local_files = 0;
    6736             : 
    6737             :         //initialize min/max bandwidth
    6738             :         min_bandwidth = 0;
    6739             :         max_level = 0;
    6740             :         total_rate = (u32) -1;
    6741             :         nb_qualities = 1;
    6742             :         max_fsize = 0;
    6743             : 
    6744             :         //get max qualities due to SRD descriptions
    6745             :         //for now, consider all non-SRDs group to run in max quality
    6746        1716 :         for (i=0; i<gf_list_count(dash->SRDs); i++) {
    6747         160 :                 struct _dash_srd_desc *srd = gf_list_get(dash->SRDs, i);
    6748         160 :                 u32 nb_q = MAX(srd->srd_nb_cols, srd->srd_nb_rows);
    6749         160 :                 if (nb_q > nb_qualities) nb_qualities = nb_q;
    6750             :         }
    6751             : 
    6752             :         //estimate bitrate
    6753        3091 :         for (i=0; i<count; i++) {
    6754        3595 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    6755        3595 :                 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    6756        3519 :                 if (group->local_files) local_files ++;
    6757        3519 :                 if (!group->bytes_per_sec) {
    6758         505 :                         if (!for_postponed_only && !group->disabled)
    6759         511 :                                 return;
    6760           1 :                         continue;
    6761             :                 }
    6762        3014 :                 if (group->done) continue;
    6763             : 
    6764             :                 //change of tile qualities
    6765        2926 :                 if (group->update_tile_qualities) {
    6766           2 :                         group->update_tile_qualities = GF_FALSE;
    6767           2 :                         if (!dash->rate_adaptation_algo_custom) {
    6768           2 :                                 if (group->srd_desc)
    6769           2 :                                         gf_dash_set_tiles_quality(dash, group->srd_desc, GF_FALSE);
    6770             :                         }
    6771             :                 }
    6772             : 
    6773             : 
    6774        2926 :                 group->backup_Bps = group->bytes_per_sec;
    6775             :                 //only count broadband ones
    6776        2926 :                 if (dash->route_clock_state && !gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs) && !group->period->origin_base_url) {
    6777             :                         u32 j;
    6778             :                         //get all active rep, count bandwidth for broadband ones
    6779           8 :                         for (j=0; j<=group->max_complementary_rep_index; j++) {
    6780           8 :                                 rep = gf_list_get(group->adaptation_set->representations, j);
    6781             :                                 //this rep is not in broadcast, add bandwidth
    6782           8 :                                 if (gf_list_count(rep->base_URLs)) {
    6783           0 :                                         total_rate = group->bytes_per_sec;
    6784             :                                 }
    6785             :                         }
    6786             :                 } else {
    6787             :                         //use rate of largest downloaded file to perform rate adaptation
    6788             :                         //TODO: we should split rate adaptation per baseURL
    6789        2918 :                         if (!max_fsize || (max_fsize<group->total_size)) {
    6790        2702 :                                 if (total_rate > group->bytes_per_sec) {
    6791             :                                         total_rate = group->bytes_per_sec;
    6792        1209 :                                         max_fsize = group->total_size;
    6793             :                                 }
    6794             :                         }
    6795             :                 }
    6796             :         }
    6797             : 
    6798        1052 :         if (total_rate == (u32) -1) {
    6799             :                 total_rate = 0;
    6800             :         }
    6801        1052 :         if (local_files==count) {
    6802         796 :                 total_rate = dash->dash_io->get_bytes_per_sec ? dash->dash_io->get_bytes_per_sec(dash->dash_io, NULL) : 0;
    6803           0 :                 if (!total_rate) local_file_mode = GF_TRUE;
    6804         256 :         } else if (!total_rate) {
    6805             :                 return;
    6806             :         }
    6807             : 
    6808        1045 :         if (dash->rate_adaptation_algo_custom) {
    6809             :                 use_custom_algo = GF_TRUE;
    6810          23 :                 dash->total_rate = total_rate;
    6811          23 :                 goto custom_algo;
    6812             :         }
    6813             : 
    6814        1342 :         for (q_idx=0; q_idx<nb_qualities; q_idx++) {
    6815        1342 :                 bandwidths[q_idx] = 0;
    6816        1342 :                 groups_per_quality[q_idx] = 0;
    6817             : 
    6818        7394 :                 for (i=0; i<count; i++) {
    6819        6052 :                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    6820        6052 :                         if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    6821        5978 :                         if (group->done) continue;
    6822             : 
    6823        5894 :                         quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
    6824        5894 :                         if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
    6825        5894 :                         if (quality_rank != q_idx) continue;
    6826             : 
    6827        2694 :                         group->target_new_rep = 0;
    6828        2694 :                         rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
    6829        2694 :                         bandwidths[q_idx] += rep->bandwidth;
    6830        2694 :                         groups_per_quality[q_idx] ++;
    6831        2694 :                         if (max_level < 1 + quality_rank) max_level = 1+quality_rank;
    6832             : 
    6833             :                         //quick trick here: if no download cap in local playback, compute the total rate based on
    6834             :                         //quality rank
    6835        2694 :                         if (local_file_mode) {
    6836        2376 :                                 u32 nb_reps = gf_list_count(group->adaptation_set->representations);
    6837             :                                 //get rep matching the given quality rank - quality 0 is the highest and our
    6838             :                                 //reps are sorted from lowest !
    6839             :                                 u32 rep_target;
    6840        2376 :                                 if (!quality_rank)
    6841        2370 :                                         rep_target = nb_reps-1;
    6842             :                                 else
    6843           6 :                                         rep_target = (nb_qualities - quality_rank) * nb_reps / nb_qualities;
    6844             : 
    6845        2376 :                                 rep = gf_list_get(group->adaptation_set->representations, rep_target);
    6846        2376 :                                 total_rate += rep->bandwidth;
    6847             :                         }
    6848             :                 }
    6849        1342 :                 min_bandwidth += bandwidths[q_idx];
    6850             :         }
    6851        1022 :         if (local_file_mode) {
    6852             :                 //total rate is in bytes per second, and add a safety of 10 bytes to ensure selection
    6853         796 :                 total_rate = 10 + total_rate / 8;
    6854             :         }
    6855             : 
    6856             :         /*no per-quality adaptation, we may have oscillations*/
    6857        1022 :         if (!dash->tile_rate_decrease) {
    6858             :         }
    6859             :         /*automatic rate alloc*/
    6860        1022 :         else if (dash->tile_rate_decrease==100) {
    6861             :                 //for each quality level (starting from highest priority), increase the bitrate if possible
    6862        1026 :                 for (q_idx=0; q_idx < max_level; q_idx++) {
    6863             :                         Bool test_pass = GF_TRUE;
    6864        1292 :                         while (1) {
    6865             :                                 u32 nb_rep_increased = 0;
    6866             :                                 u32 nb_rep_in_qidx = 0;
    6867             :                                 u32 cumulated_bw_in_pass = 0;
    6868             : 
    6869        8776 :                                 for (i=0; i<count; i++) {
    6870             :                                         u32 diff;
    6871        6458 :                                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    6872        6458 :                                         if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    6873        5958 :                                         if (group->done) continue;
    6874             : 
    6875        5760 :                                         quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
    6876        5760 :                                         if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
    6877        5760 :                                         if (quality_rank != q_idx) continue;
    6878             : 
    6879        5714 :                                         if (group->target_new_rep + 1 == gf_list_count(group->adaptation_set->representations))
    6880        3228 :                                                 continue;
    6881             : 
    6882             :                                         nb_rep_in_qidx++;
    6883             : 
    6884        2486 :                                         rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
    6885        2486 :                                         diff = rep->bandwidth;
    6886        2486 :                                         rep_new = gf_list_get(group->adaptation_set->representations, group->target_new_rep+1);
    6887        2486 :                                         diff = rep_new->bandwidth - diff;
    6888             : 
    6889        2486 :                                         if (dash->route_clock_state) {
    6890             :                                                 //if baseURL in period or adaptation set, we assume we are in broadband mode, otherwise we re in broadcast, don't count bitrate
    6891           0 :                                                 if (!gf_list_count(group->period->base_URLs) && !gf_list_count(group->adaptation_set->base_URLs)) {
    6892             :                                                         //new rep is in broadcast, force diff to 0 to select the rep
    6893           0 :                                                         if (!gf_list_count(rep_new->base_URLs)) {
    6894             :                                                                 diff = 0;
    6895             :                                                         }
    6896             :                                                         //new rep is in broadband, prev rep is in broadcast, diff is the new rep bandwidth
    6897           0 :                                                         else if (!gf_list_count(rep->base_URLs)) {
    6898           0 :                                                                 diff = rep_new->bandwidth;
    6899             :                                                         }
    6900             :                                                 }
    6901             :                                         }
    6902             : 
    6903        2486 :                                         if (test_pass) {
    6904        1259 :                                                 cumulated_bw_in_pass+= diff;
    6905        1259 :                                                 nb_rep_increased ++;
    6906        1227 :                                         } else if (min_bandwidth + diff < 8*total_rate) {
    6907             :                                                 min_bandwidth += diff;
    6908        1227 :                                                 nb_rep_increased ++;
    6909        1227 :                                                 bandwidths[q_idx] += diff;
    6910        1227 :                                                 group->target_new_rep++;
    6911             :                                         }
    6912             :                                 }
    6913        2318 :                                 if (test_pass) {
    6914             :                                         //all reps cannot be switched up in this quality level, do it
    6915        1672 :                                         if ( min_bandwidth + cumulated_bw_in_pass > 8*total_rate) {
    6916             :                                                 break;
    6917             :                                         }
    6918             :                                 }
    6919             :                                 //no more adjustement possible for this quality level
    6920        2284 :                                 if (! nb_rep_increased)
    6921             :                                         break;
    6922             : 
    6923        1292 :                                 test_pass = !test_pass;
    6924             :                         }
    6925             :                 }
    6926             : 
    6927        1022 :                 if (! for_postponed_only) {
    6928        1000 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Rate Adaptation - download rate %d kbps - %d quality levels (cumulated representations rate %d kbps)\n", 8*total_rate/1000, max_level, min_bandwidth/1000));
    6929             : 
    6930        1004 :                         for (q_idx=0; q_idx<max_level; q_idx++) {
    6931        1004 :                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH]\tLevel #%d - %d Adaptation Sets for a total %d kbps allocated\n", q_idx+1, groups_per_quality[q_idx], bandwidths[q_idx]/1000 ));
    6932             :                         }
    6933             :                 }
    6934             : 
    6935             :                 force_rep_idx = GF_TRUE;
    6936             :         }
    6937             :         /*for each quality level get tile_rate_decrease% of the available bandwidth*/
    6938             :         else {
    6939           0 :                 u32 rate = bandwidths[0] = total_rate * dash->tile_rate_decrease / 100;
    6940           0 :                 for (i=1; i<max_level; i++) {
    6941           0 :                         u32 remain = total_rate - rate;
    6942           0 :                         if (i+1==max_level) {
    6943           0 :                                 bandwidths[i] = remain;
    6944             :                         } else {
    6945           0 :                                 bandwidths[i] = remain * dash->tile_rate_decrease/100;
    6946           0 :                                 rate += bandwidths[i];
    6947             :                         }
    6948             :                 }
    6949             : 
    6950           0 :                 if (! for_postponed_only) {
    6951           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Rate Adaptation - download rate %d kbps - %d quality levels (cumulated rate %d kbps)\n", 8*total_rate/1000, max_level, 8*min_bandwidth/1000));
    6952           0 :                         for (q_idx=0; q_idx<max_level; q_idx++) {
    6953           0 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH]\tLevel #%d - %d Adaptation Sets for a total %d kbps allocated\n", q_idx+1, groups_per_quality[q_idx], 8*bandwidths[q_idx]/1000 ));
    6954             :                         }
    6955             :                 }
    6956             :         }
    6957             : 
    6958           0 : custom_algo:
    6959             : 
    6960             :         //GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("DEBUG. 2. dowload at %d \n", 8*bandwidths[q_idx]/1000));
    6961             :         //bandwitdh sharing done, perform rate adaptation with theses new numbers
    6962        3942 :         for (i=0; i<count; i++) {
    6963        2897 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    6964        2897 :                 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    6965        2821 :                 if (group->done) continue;
    6966             : 
    6967             :                 //in custom algo case, we don't change the bitrate of the group
    6968        2737 :                 if (!use_custom_algo) {
    6969        2694 :                         if (force_rep_idx) {
    6970        2694 :                                 rep = gf_list_get(group->adaptation_set->representations, group->target_new_rep);
    6971             :                                 //add 100 bytes/sec to make sure we select the target one
    6972        2694 :                                 group->bytes_per_sec = 100 + rep->bandwidth / 8;
    6973             :                         }
    6974             :                         //decrease by quality level
    6975           0 :                         else if (dash->tile_rate_decrease) {
    6976           0 :                                 quality_rank = gf_dash_get_tiles_quality_rank(dash, group);
    6977           0 :                                 if (quality_rank >= nb_qualities) quality_rank = nb_qualities-1;
    6978             :                                 assert(groups_per_quality[quality_rank]);
    6979           0 :                                 group->bytes_per_sec = bandwidths[quality_rank] / groups_per_quality[quality_rank];
    6980             :                         }
    6981             :                 }
    6982             : 
    6983        2737 :                 if (for_postponed_only) {
    6984          31 :                         if (group->rate_adaptation_postponed)
    6985          22 :                                 dash_do_rate_adaptation(dash, group);
    6986          31 :                         group->bytes_per_sec = group->backup_Bps;
    6987             :                 } else {
    6988        2706 :                         dash_do_rate_adaptation(dash, group);
    6989             :                         //reset/restore bytes_per_sec once all groups have been called
    6990             :                 }
    6991             :         }
    6992             : 
    6993        1045 :         if (!for_postponed_only) {
    6994        2853 :                 for (i=0; i<count; i++) {
    6995        2853 :                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    6996        2853 :                         if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    6997        2790 :                         if (group->done) continue;
    6998             : 
    6999        2706 :                         if (!group->rate_adaptation_postponed)
    7000        2704 :                                 group->bytes_per_sec = 0;
    7001             :                         else
    7002           2 :                                 group->bytes_per_sec = group->backup_Bps;
    7003             :                 }
    7004             :         }
    7005             : }
    7006             : 
    7007             : 
    7008             : 
    7009             : 
    7010         159 : static GF_Err dash_setup_period_and_groups(GF_DashClient *dash)
    7011             : {
    7012             :         u32 i, group_count;
    7013             :         GF_Err e;
    7014             : 
    7015             :         //don't resetup the entire period, only the broken group(s) ...
    7016         160 :         if (!dash->period_groups_setup) {
    7017             :                 /*setup period*/
    7018         123 :                 e = gf_dash_setup_period(dash);
    7019         123 :                 if (e) {
    7020             :                         //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
    7021           0 :                         dash->dash_state = GF_DASH_STATE_STOPPED;
    7022           0 :                         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
    7023           0 :                         return e;
    7024             :                 }
    7025         123 :                 if (dash->force_period_reload) return GF_OK;
    7026             : 
    7027         123 :                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SELECT_GROUPS, -1, GF_OK);
    7028             : 
    7029         123 :                 dash->period_groups_setup = GF_TRUE;
    7030         123 :                 dash->all_groups_done_notified = GF_FALSE;
    7031             :         }
    7032             : 
    7033             :         e = GF_OK;
    7034         160 :         group_count = gf_list_count(dash->groups);
    7035         422 :         for (i=0; i<group_count; i++) {
    7036         300 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    7037         300 :                 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE)
    7038           0 :                         continue;
    7039             : 
    7040         300 :                 if (group->group_setup) continue;
    7041             : 
    7042         298 :                 e = gf_dash_download_init_segment(dash, group);
    7043             : 
    7044             :                 //might happen with broadcast DASH (eg ATSC3)
    7045         298 :                 if (e == GF_IP_NETWORK_EMPTY) {
    7046          38 :                         if (dash->reinit_period_index) {
    7047           1 :                                 gf_dash_reset_groups(dash);
    7048           1 :                                 dash->active_period_index = dash->reinit_period_index-1;
    7049           1 :                                 dash->reinit_period_index = 0;
    7050           1 :                                 dash->period_groups_setup = GF_FALSE;
    7051           1 :                                 return dash_setup_period_and_groups(dash);
    7052             :                         }
    7053             : 
    7054             :                         return e;
    7055             :                 }
    7056         260 :                 group->group_setup = GF_TRUE;
    7057         260 :                 if (dash->initial_period_tunein && !dash->route_clock_state) {
    7058         248 :                         group->timeline_setup = GF_FALSE;
    7059         248 :                         group->force_timeline_reeval = GF_TRUE;
    7060             :                 }
    7061         260 :                 if (e) break;
    7062             :         }
    7063         122 :         dash->initial_period_tunein = GF_FALSE;
    7064             : 
    7065             :         /*if error signal to the user*/
    7066         122 :         if (e != GF_OK) {
    7067             :                 //move to stop state before sending the error event otherwise we might deadlock when disconnecting the dash client
    7068           0 :                 dash->dash_state = GF_DASH_STATE_STOPPED;
    7069           0 :                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_PERIOD_SETUP_ERROR, -1, e);
    7070           0 :                 return e;
    7071             :         }
    7072             : 
    7073             :         return GF_OK;
    7074             : }
    7075             : 
    7076        2247 : static void dash_do_groups(GF_DashClient *dash)
    7077             : {
    7078        2247 :         u32 i, group_count = gf_list_count(dash->groups);
    7079             : 
    7080        2247 :         dash->min_wait_ms_before_next_request = 0;
    7081             : 
    7082             :         /*for each selected groups*/
    7083        7455 :         for (i=0; i<group_count; i++) {
    7084        5208 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    7085        5208 :                 if (group->selection != GF_DASH_GROUP_SELECTED) {
    7086         128 :                         if (group->nb_cached_segments) {
    7087          22 :                                 gf_dash_group_reset(dash, group);
    7088             :                         }
    7089         128 :                         continue;
    7090             :                 }
    7091             : 
    7092        5080 :                 if (group->depend_on_group) continue;
    7093             :                 //not yet scheduled for download
    7094        3586 :                 if (group->rate_adaptation_postponed) {
    7095          21 :                         continue;
    7096             :                 }
    7097             : 
    7098             :                 DownloadGroupStatus res;
    7099        3565 :                 res = dash_download_group(dash, group, group, group->groups_depending_on ? GF_TRUE : GF_FALSE);
    7100        3565 :                 if (res==GF_DASH_DownloadRestart) {
    7101           2 :                         i--;
    7102           2 :                         continue;
    7103             :                 }
    7104             :         }
    7105        2247 : }
    7106             : 
    7107       40460 : static GF_Err dash_check_mpd_update_and_cache(GF_DashClient *dash, Bool *cache_is_full)
    7108             : {
    7109             :         GF_Err e = GF_OK;
    7110             :         u32 i, group_count;
    7111       40460 :         u32 now = gf_sys_clock();
    7112       40460 :         u32 timer = now - dash->last_update_time;
    7113             :         Bool has_postponed_rate_adaptation;
    7114             : 
    7115       40460 :         (*cache_is_full) = GF_TRUE;
    7116             :         has_postponed_rate_adaptation = GF_FALSE;
    7117             : 
    7118       40460 :         group_count = gf_list_count(dash->groups);
    7119             : 
    7120             :         /*refresh MPD*/
    7121       40460 :         if (dash->force_mpd_update
    7122             :                 //regular MPD update
    7123       40432 :                 || (dash->mpd->minimum_update_period && (timer > dash->mpd->minimum_update_period))
    7124             :                 //pending HLS playlist refresh
    7125       40424 :                 || (dash->hls_reload_time && (now > dash->hls_reload_time))
    7126             :         ) {
    7127          93 :                 if (dash->force_mpd_update) {
    7128          28 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Forcing playlist refresh (last segment reached)\n"));
    7129          65 :                 } else if (dash->mpd->minimum_update_period) {
    7130           8 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Update the playlist (%u ms elapsed since last refresh / min reload rate %u ms)\n", gf_sys_clock() , timer, dash->mpd->minimum_update_period));
    7131             :                 }
    7132          93 :                 dash->force_mpd_update = GF_FALSE;
    7133          93 :                 dash->hls_reload_time = 0;
    7134          93 :                 e = gf_dash_update_manifest(dash);
    7135          93 :                 if (e) {
    7136           0 :                         if (!dash->in_error) {
    7137           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error updating MPD %s\n", gf_error_to_string(e)));
    7138             :                         }
    7139             :                 } else {
    7140          93 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Updated MPD in %d ms\n", gf_sys_clock() - now));
    7141             :                 }
    7142             :         } else {
    7143             :                 Bool all_groups_done = GF_TRUE;
    7144             :                 Bool cache_full = GF_TRUE;
    7145             : 
    7146             :                 has_postponed_rate_adaptation = GF_FALSE;
    7147             : 
    7148             :                 /*wait if nothing is ready to be downloaded*/
    7149       40367 :                 if (dash->min_wait_ms_before_next_request > 1) {
    7150          83 :                         if (gf_sys_clock() < dash->min_wait_sys_clock + dash->min_wait_ms_before_next_request) {
    7151             :                                 return GF_EOS;
    7152             :                         }
    7153          30 :                         dash->min_wait_ms_before_next_request = 0;
    7154             :                 }
    7155             : 
    7156             :                 /*check if cache is not full*/
    7157       40314 :                 dash->tsb_exceeded = 0;
    7158       40314 :                 dash->time_in_tsb = 0;
    7159      176301 :                 for (i=0; i<group_count; i++) {
    7160      138234 :                         GF_MPD_Type type = dash->mpd->type;
    7161      138234 :                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    7162             : 
    7163      138234 :                         if ((group->selection != GF_DASH_GROUP_SELECTED)
    7164      116522 :                                 || group->depend_on_group
    7165       50363 :                                 || !group->adaptation_set
    7166       50363 :                                 || (group->done && !group->nb_cached_segments)
    7167             :                         ) {
    7168       90067 :                                 continue;
    7169             :                         }
    7170       48167 :                         if (group->period->origin_base_url)
    7171           0 :                                 type = group->period->type;
    7172             : 
    7173             :                         all_groups_done = 0;
    7174       48167 :                         if (type==GF_MPD_TYPE_DYNAMIC) {
    7175        1587 :                                 gf_dash_group_check_time(group);
    7176             :                         }
    7177             :                         //cache is full, notify client some segments are pending
    7178       48167 :                         if ((group->nb_cached_segments == group->max_cached_segments)
    7179       45920 :                                 && !dash->request_period_switch
    7180       45920 :                                 && !group->has_pending_enhancement
    7181             :                         ) {
    7182       45920 :                                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
    7183             :                         }
    7184       48167 :                         if (group->done && group->nb_cached_segments) {
    7185          32 :                                 dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_SEGMENT_AVAILABLE, i, GF_OK);
    7186             :                         }
    7187             : 
    7188       48167 :                         if (group->nb_cached_segments<group->max_cached_segments) {
    7189             :                                 cache_full = 0;
    7190             :                         }
    7191       48167 :                         if (group->rate_adaptation_postponed)
    7192             :                                 has_postponed_rate_adaptation = GF_TRUE;
    7193             : 
    7194       48167 :                         if (!cache_full)
    7195             :                                 break;
    7196             :                 }
    7197             : 
    7198       40314 :                 if (dash->tsb_exceeded) {
    7199         214 :                         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_OVERFLOW, (s32) dash->tsb_exceeded, GF_OK);
    7200         214 :                         dash->tsb_exceeded = 0;
    7201       40100 :                 } else if (dash->time_in_tsb != dash->prev_time_in_tsb) {
    7202         192 :                         dash->prev_time_in_tsb = dash->time_in_tsb;
    7203         192 :                         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_TIMESHIFT_UPDATE, 0, GF_OK);
    7204             :                 }
    7205             : 
    7206             : 
    7207       40314 :                 if (!cache_full) {
    7208        2247 :                         (*cache_is_full) = GF_FALSE;
    7209             :                 } else {
    7210             :                         //seek request
    7211       38067 :                         if (dash->request_period_switch==2) all_groups_done = 1;
    7212             : 
    7213       38067 :                         if (all_groups_done && dash->next_period_checked) {
    7214           0 :                                 dash->next_period_checked = 1;
    7215             :                         }
    7216       38067 :                         if (all_groups_done && dash->request_period_switch) {
    7217           4 :                                 gf_dash_reset_groups(dash);
    7218           4 :                                 if (dash->request_period_switch == 1) {
    7219           4 :                                         if (dash->speed<0) {
    7220           0 :                                                 if (dash->active_period_index) {
    7221           0 :                                                         dash->active_period_index--;
    7222             :                                                 }
    7223             :                                         } else {
    7224           4 :                                                 dash->active_period_index++;
    7225             :                                         }
    7226             :                                 }
    7227             : 
    7228           4 :                                 (*cache_is_full) = GF_FALSE;
    7229           4 :                                 dash->request_period_switch = 0;
    7230           4 :                                 dash->period_groups_setup = GF_FALSE;
    7231           4 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Switching to period #%d\n", dash->active_period_index+1));
    7232           4 :                                 dash->dash_state = GF_DASH_STATE_SETUP;
    7233             : 
    7234           4 :                                 if (!dash->all_groups_done_notified) {
    7235           4 :                                         dash->all_groups_done_notified = GF_TRUE;
    7236           4 :                                         dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_END_OF_PERIOD, 0, GF_OK);
    7237             :                                 }
    7238             : 
    7239             :                         } else {
    7240       38063 :                                 (*cache_is_full) = GF_TRUE;
    7241       38063 :                                 return GF_OK;
    7242             :                         }
    7243             :                 }
    7244             :         }
    7245             : 
    7246        2251 :         if (has_postponed_rate_adaptation) {
    7247          22 :                 dash_global_rate_adaptation(dash, GF_TRUE);
    7248             :         }
    7249             :         return GF_OK;
    7250             : }
    7251             : 
    7252       40619 : static GF_Err gf_dash_process_internal(GF_DashClient *dash)
    7253             : {
    7254             :         GF_Err e;
    7255             :         Bool cache_is_full;
    7256             : 
    7257       40619 :         if (dash->in_error) return GF_SERVICE_ERROR;
    7258             : 
    7259       40619 :         if (dash->force_period_reload) {
    7260           0 :                 if (gf_sys_clock() - dash->force_period_reload < 500) return GF_OK;
    7261           0 :                 dash->force_period_reload = 0;
    7262           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Retrying period reload after previous failure\n"));
    7263             :         }
    7264             : 
    7265       40619 :         switch (dash->dash_state) {
    7266         159 :         case GF_DASH_STATE_SETUP:
    7267         159 :                 dash->in_period_setup = 1;
    7268         159 :                 e = dash_setup_period_and_groups(dash);
    7269         159 :                 if (e) return e;
    7270             : 
    7271         122 :                 if (dash->force_period_reload) {
    7272           0 :                         dash->force_period_reload = gf_sys_clock();
    7273           0 :                         return GF_OK;
    7274             :                 }
    7275             : 
    7276         122 :                 dash->last_update_time = gf_sys_clock();
    7277         122 :                 dash->dash_state = GF_DASH_STATE_CONNECTING;
    7278             : 
    7279             :                 //fallthrough
    7280             :                 
    7281         122 :         case GF_DASH_STATE_CONNECTING:
    7282             :                 /*ask the user to connect to desired groups*/
    7283         122 :                 e = dash->dash_io->on_dash_event(dash->dash_io, GF_DASH_EVENT_CREATE_PLAYBACK, -1, GF_OK);
    7284         122 :                 if (e) return e;
    7285         122 :                 dash->in_period_setup = 0;
    7286         122 :                 dash->dash_state = GF_DASH_STATE_RUNNING;
    7287         122 :                 dash->min_wait_ms_before_next_request = 0;
    7288         122 :                 return GF_OK;
    7289       40460 :         case GF_DASH_STATE_RUNNING:
    7290       40460 :                 e = dash_check_mpd_update_and_cache(dash, &cache_is_full);
    7291       40460 :                 if (e || cache_is_full) {
    7292             :                         return GF_OK;
    7293             :                 }
    7294        2251 :                 if (dash->dash_state == GF_DASH_STATE_SETUP)
    7295             :                         return GF_OK;
    7296             : 
    7297        2247 :                 dash->initial_period_tunein = GF_FALSE;
    7298        2247 :                 dash_do_groups(dash);
    7299        2247 :                 return GF_OK;
    7300             :         case GF_DASH_STATE_STOPPED:
    7301             :                 return GF_EOS;
    7302             :         }
    7303           0 :         return GF_OK;
    7304             : }
    7305             : 
    7306       40619 : GF_Err gf_dash_process(GF_DashClient *dash)
    7307             : {
    7308       40619 :         return gf_dash_process_internal(dash);
    7309             : }
    7310             : 
    7311             : 
    7312         118 : static u32 gf_dash_period_index_from_time(GF_DashClient *dash, u64 time)
    7313             : {
    7314             :         u32 i, count, active_period_plus_one;
    7315             :         u64 cumul_start = 0;
    7316             :         Bool is_dyn = GF_FALSE;
    7317             :         GF_MPD_Period *period;
    7318             : 
    7319         118 :         if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
    7320             :                 u64 now, availabilityStartTime;
    7321          36 :                 availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
    7322          36 :                 availabilityStartTime += dash->utc_drift_estimate;
    7323             : 
    7324          36 :                 now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
    7325          36 :                 if (dash->initial_time_shift_value<=100) {
    7326          36 :                         now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
    7327             :                 } else {
    7328           0 :                         now -= dash->initial_time_shift_value;
    7329             :                 }
    7330          36 :                 if (!time) is_dyn = GF_TRUE;
    7331          36 :                 time += now;
    7332             :         }
    7333             : 
    7334             : 
    7335         200 : restart:
    7336         121 :         count = gf_list_count(dash->mpd->periods);
    7337             :         cumul_start = 0;
    7338             :         active_period_plus_one = 0;
    7339         252 :         for (i = 0; i<count; i++) {
    7340         134 :                 period = gf_list_get(dash->mpd->periods, i);
    7341             : 
    7342         134 :                 if (period->xlink_href && !period->duration) {
    7343           3 :                         if (!active_period_plus_one || period->xlink_actuate_on_load) {
    7344           3 :                                 gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
    7345           3 :                                 goto restart;
    7346             :                         }
    7347             :                 }
    7348             : 
    7349         131 :                 if ((period->start > time) || (cumul_start > time)) {
    7350             :                 } else {
    7351         121 :                         cumul_start += period->duration;
    7352             : 
    7353         121 :                         if (!active_period_plus_one && (time < cumul_start)) {
    7354          83 :                                 active_period_plus_one = i+1;
    7355             :                         }
    7356             :                 }
    7357             :         }
    7358         118 :         if (is_dyn) {
    7359           0 :                 for (i = 0; i<count; i++) {
    7360          36 :                         period = gf_list_get(dash->mpd->periods, i);
    7361          36 :                         if (!period->xlink_href && !period->origin_base_url) return i;
    7362             :                 }
    7363             :         }
    7364          82 :         return active_period_plus_one ? active_period_plus_one-1 : 0;
    7365             : }
    7366             : 
    7367           3 : static Bool gf_dash_seek_periods(GF_DashClient *dash, Double seek_time)
    7368             : {
    7369             :         Double start_time;
    7370             :         /*we have an arch issue here: we may get a seek request in normal multiperiod playback
    7371             :          with a seek time close but before to the active period, typically if the stream is shorter
    7372             :          than the advertised period duration. We will use a safety check of 0.5 seconds, any seek request
    7373             :          at Start(Pn) - 0.5 will resolve in seek at Start(Pn)
    7374             :          * */
    7375             :         Bool at_period_boundary=GF_FALSE;
    7376             :         u32 i, period_idx;
    7377             :         u32 nb_retry = 10;
    7378             : 
    7379           3 :         dash->start_range_period = 0;
    7380             :         start_time = 0;
    7381             :         period_idx = 0;
    7382           5 :         for (i=0; i<gf_list_count(dash->mpd->periods); i++) {
    7383           5 :                 GF_MPD_Period *period = gf_list_get(dash->mpd->periods, i);
    7384             :                 Double dur;
    7385             : 
    7386           5 :                 if (period->xlink_href) {
    7387           0 :                         gf_dash_solve_period_xlink(dash, dash->mpd->periods, i);
    7388           0 :                         if (nb_retry) {
    7389           0 :                                 nb_retry --;
    7390           0 :                                 i--;
    7391           0 :                                 continue;
    7392             :                         }
    7393           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period still resolves to XLINK %s for more than 10 consecutive retry, ignoring it ...\n", period->xlink_href));
    7394           0 :                         gf_free(period->xlink_href);
    7395           0 :                         period->xlink_href = NULL;
    7396             :                 } else {
    7397             :                         nb_retry = 10;
    7398             :                 }
    7399           5 :                 dur = (Double)period->duration;
    7400           5 :                 dur /= 1000;
    7401           5 :                 if (seek_time + 0.5 >= start_time) {
    7402           5 :                         if ((i+1==gf_list_count(dash->mpd->periods)) || (seek_time + 0.5 < start_time + dur) ) {
    7403           3 :                                 if (seek_time > start_time + dur) {
    7404             :                                         at_period_boundary = GF_TRUE;
    7405             :                                 }
    7406             :                                 period_idx = i;
    7407             :                                 break;
    7408             :                         }
    7409             :                 }
    7410           2 :                 start_time += dur;
    7411             :         }
    7412           3 :         if (period_idx != dash->active_period_index) {
    7413           1 :                 seek_time -= start_time;
    7414           1 :                 dash->active_period_index = period_idx;
    7415           1 :                 dash->request_period_switch = 2;
    7416             : 
    7417           1 :                 dash->start_range_period = seek_time;
    7418           2 :         } else if (seek_time < start_time) {
    7419             :                 at_period_boundary = GF_TRUE;
    7420             :         }
    7421             : 
    7422           3 :         if (at_period_boundary) return GF_TRUE;
    7423           3 :         return dash->request_period_switch ? 1 : 0;
    7424             : }
    7425             : 
    7426           4 : static void gf_dash_seek_group(GF_DashClient *dash, GF_DASH_Group *group, Double seek_to, Bool is_dynamic)
    7427             : {
    7428             :         GF_Err e;
    7429             :         u32 first_downloaded, last_downloaded, segment_idx, orig_idx;
    7430             : 
    7431           6 :         if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) return;
    7432             :         
    7433           2 :         group->force_segment_switch = 0;
    7434           2 :         if (!is_dynamic) {
    7435             :                 /*figure out where to seek*/
    7436           2 :                 orig_idx = group->download_segment_index;
    7437           2 :                 e = gf_mpd_seek_in_period(seek_to, MPD_SEEK_PREV, group->period, group->adaptation_set, gf_list_get(group->adaptation_set->representations, group->active_rep_index), &segment_idx, NULL);
    7438           2 :                 if (e<0)
    7439           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] An error occured while seeking to time %lf: %s\n", seek_to, gf_error_to_string(e)));
    7440             : 
    7441           2 :                 group->download_segment_index = orig_idx;
    7442             : 
    7443             :                 /*remember to seek to given duration*/
    7444           2 :                 group->start_playback_range = seek_to;
    7445             : 
    7446             :                 first_downloaded = last_downloaded = group->download_segment_index;
    7447           2 :                 if (group->download_segment_index + 1 >= (s32) group->nb_cached_segments) {
    7448           2 :                         first_downloaded = group->download_segment_index + 1 - group->nb_cached_segments;
    7449             :                 }
    7450             :                 /*we are seeking in our download range, just go on*/
    7451           2 :                 if ((segment_idx>=first_downloaded) && (segment_idx<=last_downloaded)) {
    7452             :                         return;
    7453             :                 }
    7454             : 
    7455           2 :                 group->force_segment_switch = 1;
    7456           2 :                 group->download_segment_index = segment_idx;
    7457             :         } else {
    7458           0 :                 group->start_number_at_last_ast = 0;
    7459             :                 /*remember to adjust time in timeline steup*/
    7460           0 :                 group->start_playback_range = seek_to;
    7461           0 :                 group->timeline_setup = GF_FALSE;
    7462             :         }
    7463             : 
    7464           4 :         while (group->nb_cached_segments) {
    7465           2 :                 group->nb_cached_segments--;
    7466           2 :                 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
    7467             :         }
    7468           2 :         group->done = 0;
    7469             : }
    7470             : 
    7471             : GF_EXPORT
    7472           0 : void gf_dash_group_seek(GF_DashClient *dash, u32 group_idx, Double seek_to)
    7473             : {
    7474           0 :         GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    7475           0 :         if (!group) return;
    7476           0 :         gf_dash_seek_group(dash, group, seek_to, (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE);
    7477             : }
    7478             : 
    7479           1 : static void gf_dash_seek_groups(GF_DashClient *dash, Double seek_time, Bool is_dynamic)
    7480             : {
    7481             :         u32 i;
    7482             : 
    7483           1 :         if (dash->active_period_index) {
    7484             :                 Double dur = 0;
    7485           0 :                 for (i=0; i<dash->active_period_index; i++) {
    7486           0 :                         GF_MPD_Period *period = gf_list_get(dash->mpd->periods, dash->active_period_index-1);
    7487           0 :                         dur += period->duration/1000.0;
    7488             :                 }
    7489           0 :                 seek_time -= dur;
    7490             :         }
    7491           3 :         for (i=0; i<gf_list_count(dash->groups); i++) {
    7492           2 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    7493           2 :                 gf_dash_seek_group(dash, group, seek_time, is_dynamic);
    7494             :         }
    7495           1 : }
    7496             : 
    7497             : 
    7498         101 : static GF_Err http_ifce_get(GF_FileDownload *getter, char *url)
    7499             : {
    7500             :         GF_Err e;
    7501             :         Bool owns_sess = GF_FALSE;
    7502             :         GF_DASHFileIOSession *sess;
    7503         101 :         GF_DashClient *dash = (GF_DashClient*) getter->udta;
    7504         101 :         if (!getter->session) {
    7505           8 :                 if (!dash->mpd_dnload) {
    7506           0 :                         sess = dash->dash_io->create(dash->dash_io, 1, url, -1);
    7507           0 :                         if (!sess) return GF_IO_ERR;
    7508           0 :                         getter->session = sess;
    7509             :                         e = GF_OK;
    7510             :                         owns_sess = GF_TRUE;
    7511             :                 } else {
    7512           8 :                         sess = getter->session = dash->mpd_dnload;
    7513           8 :                         e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, -1);
    7514             :                 }
    7515             :         }
    7516             :         else {
    7517             :                 u32 group_idx = -1, i;
    7518          21 :                 for (i = 0; i < gf_list_count(dash->groups); i++) {
    7519         109 :                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    7520         109 :                         if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    7521             :                         group_idx = i;
    7522             :                         break;
    7523             :                 }
    7524          93 :                 e = dash->dash_io->setup_from_url(dash->dash_io, getter->session, url, group_idx);
    7525          93 :                 if (e) {
    7526             :                         //with ROUTE we may have 404 right away if nothing in cache yet, not an error
    7527           0 :                         GF_LOG(dash->route_clock_state ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot resetup downloader for url %s: %s\n", url, gf_error_to_string(e) ));
    7528             :                         return e;
    7529             :                 }
    7530          93 :                 sess = (GF_DASHFileIOSession *)getter->session;
    7531             :         }
    7532         101 :         if (!e)
    7533         101 :                 e = dash->dash_io->init(dash->dash_io, sess);
    7534             : 
    7535         101 :         if (e) {
    7536           0 :                 if (owns_sess) {
    7537           0 :                         dash->dash_io->del(dash->dash_io, sess);
    7538           0 :                         if (getter->session == sess)
    7539           0 :                                 getter->session = NULL;
    7540             :                 }
    7541             :                 return e;
    7542             :         }
    7543         101 :         return dash->dash_io->run(dash->dash_io, sess);
    7544             : }
    7545             : 
    7546           0 : static void http_ifce_clean(GF_FileDownload *getter)
    7547             : {
    7548           0 :         GF_DashClient *dash = (GF_DashClient*) getter->udta;
    7549           0 :         if (getter->session) dash->dash_io->del(dash->dash_io, getter->session);
    7550           0 :         getter->session = NULL;
    7551           0 : }
    7552             : 
    7553         101 : static const char *http_ifce_cache_name(GF_FileDownload *getter)
    7554             : {
    7555         101 :         GF_DashClient *dash = (GF_DashClient*) getter->udta;
    7556         101 :         if (getter->session) return dash->dash_io->get_cache_name(dash->dash_io, getter->session);
    7557             :         return NULL;
    7558             : }
    7559             : 
    7560             : GF_EXPORT
    7561         118 : GF_Err gf_dash_open(GF_DashClient *dash, const char *manifest_url)
    7562             : {
    7563             :         char local_path[GF_MAX_PATH];
    7564             :         const char *local_url;
    7565             :         char *sep_cgi = NULL;
    7566             :         char *sep_frag = NULL;
    7567             :         GF_Err e;
    7568             :         GF_MPD_Period *period;
    7569             :         GF_DOMParser *mpd_parser=NULL;
    7570             :         Bool is_local = GF_FALSE;
    7571             : 
    7572         118 :         if (!dash || !manifest_url) return GF_BAD_PARAM;
    7573             : 
    7574         118 :         memset( dash->lastMPDSignature, 0, GF_SHA1_DIGEST_SIZE);
    7575         118 :         dash->reload_count = 0;
    7576             : 
    7577         118 :         if (dash->base_url) gf_free(dash->base_url);
    7578         118 :         sep_cgi = strrchr(manifest_url, '?');
    7579         118 :         if (sep_cgi) sep_cgi[0] = 0;
    7580         118 :         dash->base_url = gf_strdup(manifest_url);
    7581         118 :         if (sep_cgi) sep_cgi[0] = '?';
    7582             : 
    7583         118 :         dash->getter.udta = dash;
    7584         118 :         dash->getter.new_session = http_ifce_get;
    7585         118 :         dash->getter.del_session = http_ifce_clean;
    7586         118 :         dash->getter.get_cache_name = http_ifce_cache_name;
    7587         118 :         dash->getter.session = NULL;
    7588             : 
    7589             : 
    7590             : 
    7591         118 :         if (dash->mpd_dnload) dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7592         118 :         dash->mpd_dnload = NULL;
    7593             :         local_url = NULL;
    7594             : 
    7595         118 :         if (!strnicmp(manifest_url, "file://", 7)) {
    7596           0 :                 local_url = manifest_url + 7;
    7597             :                 is_local = 1;
    7598           0 :                 if (strstr(manifest_url, ".m3u8")) {
    7599           0 :                         dash->is_m3u8 = GF_TRUE;
    7600             :                 }
    7601         118 :         } else if (strstr(manifest_url, "://") && strncmp(manifest_url, "gfio://", 7)) {
    7602             :                 const char *reloc_url, *mtype;
    7603             :                 char mime[128];
    7604          41 :                 e = gf_dash_download_resource(dash, &(dash->mpd_dnload), manifest_url, 0, 0, 1, NULL);
    7605          41 :                 if (e!=GF_OK) {
    7606           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD downloading problem %s for %s\n", gf_error_to_string(e), manifest_url));
    7607           0 :                         dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7608           0 :                         dash->mpd_dnload = NULL;
    7609           0 :                         return e;
    7610             :                 }
    7611             : 
    7612          41 :                 mtype = dash->dash_io->get_mime(dash->dash_io, dash->mpd_dnload);
    7613          41 :                 strcpy(mime, mtype ? mtype : "");
    7614          41 :                 strlwr(mime);
    7615             : 
    7616          41 :                 reloc_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
    7617             : 
    7618             :                 /*if relocated use new URL as base URL for all requests*/
    7619          41 :                 if (strcmp(reloc_url, manifest_url)) {
    7620           8 :                         gf_free(dash->base_url);
    7621           8 :                         dash->base_url = gf_strdup(reloc_url);
    7622             :                 }
    7623             : 
    7624          41 :                 local_url = dash->dash_io->get_cache_name(dash->dash_io, dash->mpd_dnload);
    7625          41 :                 if (!local_url) {
    7626           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: cache problem %s\n", local_url));
    7627           0 :                         dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7628           0 :                         dash->mpd_dnload = NULL;
    7629           0 :                         return GF_IO_ERR;
    7630             :                 }
    7631             :         } else {
    7632             :                 local_url = manifest_url;
    7633             :                 is_local = 1;
    7634             :         }
    7635             : 
    7636          77 :         if (is_local && strncmp(manifest_url, "gfio://", 7)) {
    7637          76 :                 FILE *f = gf_fopen(local_url, "rt");
    7638          76 :                 if (!f) {
    7639           0 :                         sep_cgi = strrchr(local_url, '?');
    7640           0 :                         if (sep_cgi) sep_cgi[0] = 0;
    7641           0 :                         sep_frag = strrchr(local_url, '#');
    7642           0 :                         if (sep_frag) sep_frag[0] = 0;
    7643             : 
    7644           0 :                         f = gf_fopen(local_url, "rt");
    7645           0 :                         if (!f) {
    7646           0 :                                 if (sep_cgi) sep_cgi[0] = '?';
    7647           0 :                                 if (sep_frag) sep_frag[0] = '#';
    7648             :                                 return GF_URL_ERROR;
    7649             :                         }
    7650             :                 }
    7651          76 :                 gf_fclose(f);
    7652             :         }
    7653         118 :         dash->mpd_fetch_time = dash_get_fetch_time(dash);
    7654             : 
    7655         118 :         gf_dash_reset_groups(dash);
    7656             : 
    7657         118 :         if (dash->mpd)
    7658           0 :                 gf_mpd_del(dash->mpd);
    7659             : 
    7660         118 :         dash->mpd = gf_mpd_new();
    7661         118 :         if (!dash->mpd) {
    7662             :                 e = GF_OUT_OF_MEM;
    7663           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
    7664             :                 goto exit;
    7665             :         }
    7666         118 :         if (dash->dash_io->manifest_updated) {
    7667           7 :                 const char *szName = gf_file_basename(manifest_url);
    7668           7 :                 dash->dash_io->manifest_updated(dash->dash_io, szName, local_url, -1);
    7669             :         }
    7670             : 
    7671             :         //peek payload, check if m3u8 - MPD and SmoothStreaming are checked after
    7672             :         char szLine[100];
    7673         118 :         FILE *f = gf_fopen(local_url, "r");
    7674         118 :         if (f) {
    7675         118 :                 gf_fread(szLine, 100, f);
    7676         118 :                 gf_fclose(f);
    7677             :         }
    7678         118 :         szLine[99] = 0;
    7679         118 :         if (strstr(szLine, "#EXTM3U"))
    7680          21 :                 dash->is_m3u8 = 1;
    7681             : 
    7682         118 :         if (dash->is_m3u8) {
    7683          21 :                 if (is_local) {
    7684             :                         char *sep;
    7685             :                         strcpy(local_path, local_url);
    7686          13 :                         sep = gf_file_ext_start(local_path);
    7687          13 :                         if (sep) sep[0]=0;
    7688             :                         strcat(local_path, ".mpd");
    7689             : 
    7690          13 :                         e = gf_m3u8_to_mpd(local_url, manifest_url, local_path, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, dash->mpd, GF_FALSE, dash->keep_files);
    7691             :                 } else {
    7692           8 :                         const char *redirected_url = dash->dash_io->get_url(dash->dash_io, dash->mpd_dnload);
    7693           8 :                         if (!redirected_url) redirected_url=manifest_url;
    7694             : 
    7695           8 :                         e = gf_m3u8_to_mpd(local_url, redirected_url, NULL, dash->reload_count, dash->mimeTypeForM3U8Segments, 0, M3U8_TO_MPD_USE_TEMPLATE, M3U8_TO_MPD_USE_SEGTIMELINE, &dash->getter, dash->mpd, GF_FALSE, dash->keep_files);
    7696             :                 }
    7697             : 
    7698          21 :                 if (!e && dash->split_adaptation_set)
    7699           3 :                         gf_mpd_split_adaptation_sets(dash->mpd);
    7700             : 
    7701             :         } else {
    7702          97 :                 u32 res = gf_dash_check_mpd_root_type(local_url);
    7703          97 :                 if (res==2) {
    7704           2 :                         dash->is_smooth = 1;
    7705             :                 }
    7706          97 :                 if (!dash->is_smooth && !res) {
    7707           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: wrong file type %s\n", local_url));
    7708           0 :                         dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7709           0 :                         dash->mpd_dnload = NULL;
    7710           0 :                         return GF_URL_ERROR;
    7711             :                 }
    7712             : 
    7713          97 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] parsing %s manifest %s\n", dash->is_smooth ? "SmoothStreaming" : "DASH-MPD", local_url));
    7714             : 
    7715             :                 /* parse the MPD */
    7716          97 :                 mpd_parser = gf_xml_dom_new();
    7717          97 :                 e = gf_xml_dom_parse(mpd_parser, local_url, NULL, NULL);
    7718             : 
    7719          97 :                 if (sep_cgi) sep_cgi[0] = '?';
    7720          97 :                 if (sep_frag) sep_frag[0] = '#';
    7721             : 
    7722          97 :                 if (e != GF_OK) {
    7723           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD parsing problem %s\n", gf_xml_dom_get_error(mpd_parser) ));
    7724           0 :                         gf_xml_dom_del(mpd_parser);
    7725           0 :                         dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7726           0 :                         dash->mpd_dnload = NULL;
    7727           0 :                         return GF_URL_ERROR;
    7728             :                 }
    7729             : 
    7730          97 :                 if (dash->is_smooth) {
    7731           2 :                         e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
    7732             :                 } else {
    7733          95 :                         e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), dash->mpd, manifest_url);
    7734             :                 }
    7735          97 :                 gf_xml_dom_del(mpd_parser);
    7736             : 
    7737          97 :                 if (!e && dash->split_adaptation_set)
    7738           7 :                         gf_mpd_split_adaptation_sets(dash->mpd);
    7739             : 
    7740          97 :                 if (dash->ignore_xlink)
    7741           0 :                         dash_purge_xlink(dash->mpd);
    7742             : 
    7743             :         }
    7744             :         //for both DASH and HLS, we support ROUTE
    7745         118 :         if (!is_local) {
    7746          41 :                 const char *hdr = dash->dash_io->get_header_value(dash->dash_io, dash->mpd_dnload, "x-route");
    7747          41 :                 if (hdr) {
    7748           5 :                         if (!dash->route_clock_state) {
    7749           5 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Detected ROUTE DASH service ID %s\n", hdr));
    7750           5 :                                 dash->route_clock_state = 1;
    7751             :                         }
    7752             :                 }
    7753             :         }
    7754             : 
    7755         118 :         if (e != GF_OK) {
    7756           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot connect service: MPD creation problem %s\n", gf_error_to_string(e)));
    7757             :                 goto exit;
    7758             :         }
    7759         118 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] DASH client initialized from MPD at UTC time "LLU" - availabilityStartTime "LLU"\n", dash->mpd_fetch_time , dash->mpd->availabilityStartTime));
    7760             : 
    7761         118 :         if (is_local && dash->mpd->minimum_update_period) {
    7762           7 :                 e = gf_dash_update_manifest(dash);
    7763           7 :                 if (e != GF_OK) {
    7764           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot update MPD: %s\n", gf_error_to_string(e)));
    7765             :                         goto exit;
    7766             :                 }
    7767             :         }
    7768             : 
    7769             :         /* Get the right period from the given time */
    7770         118 :         dash->active_period_index = gf_dash_period_index_from_time(dash, 0);
    7771         118 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    7772         118 :         if (period->xlink_href) {
    7773           0 :                 gf_dash_solve_period_xlink(dash, dash->mpd->periods, dash->active_period_index);
    7774           0 :                 period = gf_list_get(dash->mpd->periods, dash->active_period_index);
    7775             :         }
    7776         118 :         if (!period || !gf_list_count(period->adaptation_sets) ) {
    7777           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error - cannot start: not enough periods or representations in MPD\n"));
    7778             :                 e = GF_URL_ERROR;
    7779             :                 goto exit;
    7780             :         }
    7781             : 
    7782         118 :         dash->dash_state = GF_DASH_STATE_SETUP;
    7783         118 :         return GF_OK;
    7784             : 
    7785           0 : exit:
    7786           0 :         if (dash->dash_io) {
    7787           0 :                 dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7788           0 :                 dash->mpd_dnload = NULL;
    7789             :         }
    7790             : 
    7791           0 :         if (dash->mpd)
    7792           0 :                 gf_mpd_del(dash->mpd);
    7793           0 :         dash->mpd = NULL;
    7794           0 :         return e;
    7795             : }
    7796             : 
    7797             : GF_EXPORT
    7798         121 : void gf_dash_close(GF_DashClient *dash)
    7799             : {
    7800             :         assert(dash);
    7801             : 
    7802         121 :         if (dash->dash_io) {
    7803         121 :                 if (dash->mpd_dnload) {
    7804          41 :                         if (dash->mpd_dnload == dash->getter.session)
    7805           8 :                                 dash->getter.session = NULL;
    7806          41 :                         dash->dash_io->del(dash->dash_io, dash->mpd_dnload);
    7807          41 :                         dash->mpd_dnload = NULL;
    7808             :                 }
    7809             : 
    7810         121 :                 if (dash->getter.del_session && dash->getter.session)
    7811           0 :                         dash->getter.del_session(&dash->getter);
    7812             :         }
    7813         121 :         if (dash->mpd) {
    7814         118 :                 gf_mpd_del(dash->mpd);
    7815         118 :                 dash->mpd = NULL;
    7816             :         }
    7817             : 
    7818         121 :         if (dash->dash_state != GF_DASH_STATE_CONNECTING)
    7819         121 :                 gf_dash_reset_groups(dash);
    7820         121 : }
    7821             : 
    7822             : GF_EXPORT
    7823         118 : void gf_dash_set_algo(GF_DashClient *dash, GF_DASHAdaptationAlgorithm algo)
    7824             : {
    7825         118 :         dash->adaptation_algorithm = algo;
    7826         118 :         switch (dash->adaptation_algorithm) {
    7827          97 :         case GF_DASH_ALGO_GPAC_LEGACY_BUFFER:
    7828          97 :                 dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_buffer;
    7829          97 :                 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
    7830          97 :                 break;
    7831           1 :         case GF_DASH_ALGO_GPAC_LEGACY_RATE:
    7832           1 :                 dash->rate_adaptation_algo = dash_do_rate_adaptation_legacy_rate;
    7833           1 :                 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
    7834           1 :                 break;
    7835           1 :         case GF_DASH_ALGO_BBA0:
    7836           1 :                 dash->rate_adaptation_algo = dash_do_rate_adaptation_bba0;
    7837           1 :                 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
    7838           1 :                 break;
    7839           4 :         case GF_DASH_ALGO_BOLA_FINITE:
    7840             :         case GF_DASH_ALGO_BOLA_BASIC:
    7841             :         case GF_DASH_ALGO_BOLA_U:
    7842             :         case GF_DASH_ALGO_BOLA_O:
    7843           4 :                 dash->rate_adaptation_algo = dash_do_rate_adaptation_bola;
    7844           4 :                 dash->rate_adaptation_download_monitor = dash_do_rate_monitor_default;
    7845           4 :                 break;
    7846          15 :         case GF_DASH_ALGO_NONE:
    7847             :         default:
    7848          15 :                 dash->rate_adaptation_algo = NULL;
    7849          15 :                 break;
    7850             :         }
    7851         118 : }
    7852             : 
    7853          43 : static s32 dash_do_rate_adaptation_custom(GF_DashClient *dash, GF_DASH_Group *group, GF_DASH_Group *base_group,
    7854             :                                                                                   u32 dl_rate, Double speed, Double max_available_speed, Bool force_lower_complexity,
    7855             :                                                                                           GF_MPD_Representation *rep, Bool go_up_bitrate)
    7856             : {
    7857             :         GF_DASHCustomAlgoInfo stats;
    7858          43 :         u32 g_idx = gf_list_find(dash->groups, group);
    7859          43 :         u32 b_idx = gf_list_find(dash->groups, base_group);
    7860             : 
    7861          43 :         stats.download_rate = dl_rate;
    7862          43 :         stats.file_size = group->total_size;
    7863          43 :         stats.speed = speed;
    7864          43 :         stats.max_available_speed = max_available_speed;
    7865          43 :         stats.disp_width = group->hint_visible_width;
    7866          43 :         stats.disp_height = group->hint_visible_height;
    7867          43 :         stats.active_quality_idx = group->active_rep_index;
    7868          43 :         stats.buffer_min_ms = group->buffer_min_ms;
    7869          43 :         stats.buffer_max_ms = group->buffer_max_ms;
    7870          43 :         stats.buffer_occupancy_ms = group->buffer_occupancy_ms;
    7871          43 :         stats.quality_degradation_hint = group->quality_degradation_hint;
    7872          43 :         stats.total_rate = dash->total_rate;
    7873             : 
    7874          43 :         return dash->rate_adaptation_algo_custom(dash->udta_custom_algo, g_idx, b_idx, force_lower_complexity, &stats);
    7875             : 
    7876             : }
    7877             : 
    7878          10 : static s32 dash_do_rate_monitor_custom(GF_DashClient *dash, GF_DASH_Group *group, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur)
    7879             : {
    7880          10 :         u32 g_idx = gf_list_find(dash->groups, group);
    7881          10 :         return dash->rate_adaptation_download_monitor_custom(dash->udta_custom_algo, g_idx, bits_per_sec, total_bytes, bytes_done, us_since_start, buffer_dur_ms, current_seg_dur);
    7882             : }
    7883             : 
    7884             : GF_EXPORT
    7885           3 : void gf_dash_set_algo_custom(GF_DashClient *dash, void *udta,
    7886             :                 gf_dash_rate_adaptation algo_custom,
    7887             :                 gf_dash_download_monitor download_monitor_custom)
    7888             : {
    7889           3 :         dash->adaptation_algorithm = GF_DASH_ALGO_CUSTOM;
    7890           3 :         dash->rate_adaptation_algo = dash_do_rate_adaptation_custom;
    7891           3 :         dash->rate_adaptation_download_monitor = dash_do_rate_monitor_custom;
    7892             : 
    7893           3 :         dash->udta_custom_algo = udta;
    7894           3 :         dash->rate_adaptation_algo_custom = algo_custom;
    7895           3 :         dash->rate_adaptation_download_monitor_custom = download_monitor_custom;
    7896             : 
    7897           3 : }
    7898             : 
    7899             : GF_EXPORT
    7900         118 : GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, u32 max_cache_duration, u32 auto_switch_count, Bool keep_files, Bool disable_switching, GF_DASHInitialSelectionMode first_select_mode, u32 initial_time_shift_percent)
    7901             : {
    7902             :         GF_DashClient *dash;
    7903         118 :         if (!dash_io) {
    7904           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot create client withou sync IO for HTTP\n"));
    7905             :                 return NULL;
    7906             :         }
    7907             : 
    7908         118 :         GF_SAFEALLOC(dash, GF_DashClient);
    7909         118 :         if (!dash) return NULL;
    7910         118 :         dash->dash_io = dash_io;
    7911         118 :         dash->speed = 1.0;
    7912         118 :         dash->is_rt_speed = GF_TRUE;
    7913         118 :         dash->low_latency_mode = GF_DASH_LL_STRICT;
    7914             : 
    7915             :         //wait one segment to validate we have enough bandwidth
    7916         118 :         dash->probe_times_before_switch = 1;
    7917             :         //FIXME: mime type for segments MUST be mp2t, webvtt or a Packed Audio file (like AAC)
    7918         118 :         dash->mimeTypeForM3U8Segments = gf_strdup( "video/mp2t" );
    7919             : 
    7920         118 :         dash->max_cache_duration = max_cache_duration;
    7921         118 :         dash->initial_time_shift_value = initial_time_shift_percent;
    7922             : 
    7923         118 :         dash->auto_switch_count = auto_switch_count;
    7924         118 :         dash->keep_files = keep_files;
    7925         118 :         dash->disable_switching = disable_switching;
    7926         118 :         dash->first_select_mode = first_select_mode;
    7927         118 :         dash->min_timeout_between_404 = 500;
    7928         118 :         dash->segment_lost_after_ms = 100;
    7929         118 :         dash->dbg_grps_index = NULL;
    7930         118 :         dash->tile_rate_decrease = 100;
    7931         118 :         dash->route_ast_shift = 1000;
    7932         118 :         dash->initial_period_tunein = GF_TRUE;
    7933         118 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Client created\n"));
    7934             : 
    7935             : #ifdef GPAC_ENABLE_COVERAGE
    7936         118 :         if (gf_sys_is_cov_mode()) {
    7937         114 :                 on_group_download_error(NULL, NULL, NULL, GF_OK, NULL, NULL, NULL, GF_FALSE);
    7938         114 :                 gf_dash_is_running(dash);
    7939             :         }
    7940             : #endif
    7941             : 
    7942             :         return dash;
    7943             : }
    7944             : 
    7945             : GF_EXPORT
    7946         118 : void gf_dash_del(GF_DashClient *dash)
    7947             : {
    7948             :         //force group cleanup
    7949         118 :         dash->dash_state = GF_DASH_STATE_STOPPED;
    7950         118 :         gf_dash_close(dash);
    7951             : 
    7952         118 :         if (dash->mimeTypeForM3U8Segments) gf_free(dash->mimeTypeForM3U8Segments);
    7953         118 :         if (dash->base_url) gf_free(dash->base_url);
    7954             : 
    7955         118 :         gf_free(dash);
    7956         118 : }
    7957             : 
    7958             : GF_EXPORT
    7959         118 : void gf_dash_enable_utc_drift_compensation(GF_DashClient *dash, Bool estimate_utc_drift)
    7960             : {
    7961         118 :         dash->estimate_utc_drift = estimate_utc_drift;
    7962         118 : }
    7963             : 
    7964             : GF_EXPORT
    7965         118 : void gf_dash_set_switching_probe_count(GF_DashClient *dash, u32 switch_probe_count)
    7966             : {
    7967         118 :         dash->probe_times_before_switch = switch_probe_count;
    7968         118 : }
    7969             : 
    7970             : GF_EXPORT
    7971         118 : void gf_dash_enable_single_range_llhls(GF_DashClient *dash, Bool enable)
    7972             : {
    7973         118 :         dash->llhls_single_range = enable;
    7974         118 : }
    7975             : 
    7976             : GF_EXPORT
    7977         118 : void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool agressive_switch)
    7978             : {
    7979         118 :         dash->agressive_switching = agressive_switch;
    7980         118 : }
    7981             : 
    7982             : 
    7983             : GF_EXPORT
    7984       39249 : u32 gf_dash_get_group_count(GF_DashClient *dash)
    7985             : {
    7986       39249 :         return gf_list_count(dash->groups);
    7987             : }
    7988             : 
    7989             : 
    7990             : GF_EXPORT
    7991      127500 : void *gf_dash_get_group_udta(GF_DashClient *dash, u32 idx)
    7992             : {
    7993      127500 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    7994      127500 :         if (!group) return NULL;
    7995      127297 :         return group->udta;
    7996             : }
    7997             : 
    7998             : GF_EXPORT
    7999         394 : GF_Err gf_dash_set_group_udta(GF_DashClient *dash, u32 idx, void *udta)
    8000             : {
    8001         394 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8002         394 :         if (!group) return GF_BAD_PARAM;
    8003         394 :         group->udta = udta;
    8004         394 :         return GF_OK;
    8005             : }
    8006             : 
    8007             : GF_EXPORT
    8008         118 : Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 idx)
    8009             : {
    8010         118 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8011         118 :         if (!group) return 0;
    8012         118 :         return (group->selection == GF_DASH_GROUP_SELECTED) ? 1 : 0;
    8013             : }
    8014             : 
    8015             : GF_EXPORT
    8016         260 : Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 idx)
    8017             : {
    8018         260 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8019         260 :         if (!group) return 0;
    8020         260 :         return (group->selection == GF_DASH_GROUP_NOT_SELECTABLE) ? 0 : 1;
    8021             : }
    8022             : 
    8023             : GF_EXPORT
    8024         553 : void gf_dash_get_info(GF_DashClient *dash, const char **title, const char **source)
    8025             : {
    8026         553 :         GF_MPD_ProgramInfo *info = dash ? gf_list_get(dash->mpd->program_infos, 0) : NULL;
    8027         553 :         if (title) *title = info ? info->title : NULL;
    8028         553 :         if (source) *source = info ? info->source : NULL;
    8029         553 : }
    8030             : 
    8031             : 
    8032             : GF_EXPORT
    8033         120 : void gf_dash_switch_quality(GF_DashClient *dash, Bool switch_up, Bool immediate_switch)
    8034             : {
    8035             :         u32 i;
    8036         360 :         for (i=0; i<gf_list_count(dash->groups); i++) {
    8037             :                 u32 switch_to_rep_idx = 0;
    8038             :                 u32 bandwidth, quality, k;
    8039             :                 GF_MPD_Representation *rep, *active_rep;
    8040         240 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    8041         240 :                 u32 current_idx = group->active_rep_index;
    8042         240 :                 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    8043             : 
    8044         239 :                 if (group->base_rep_index_plus_one) current_idx = group->max_complementary_rep_index;
    8045         239 :                 if (group->force_representation_idx_plus_one) current_idx = group->force_representation_idx_plus_one - 1;
    8046             : 
    8047         239 :                 active_rep = gf_list_get(group->adaptation_set->representations, current_idx);
    8048         239 :                 if (!active_rep) continue;
    8049         239 :                 bandwidth = switch_up ? (u32) -1 : 0;
    8050             :                 quality = switch_up ? (u32) -1 : 0;
    8051             : 
    8052         357 :                 for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
    8053         357 :                         rep = gf_list_get(group->adaptation_set->representations, k);
    8054         357 :                         if (switch_up) {
    8055         357 :                                 if ((rep->quality_ranking>active_rep->quality_ranking) || (rep->bandwidth>active_rep->bandwidth)) {
    8056           7 :                                         if ((rep->quality_ranking < quality) || (rep->bandwidth < bandwidth)) {
    8057           1 :                                                 bandwidth = rep->bandwidth;
    8058             :                                                 quality = rep->quality_ranking;
    8059           1 :                                                 switch_to_rep_idx = k+1;
    8060             :                                         }
    8061             :                                 }
    8062             :                         } else {
    8063           0 :                                 if ((rep->quality_ranking < active_rep->quality_ranking) || (rep->bandwidth < active_rep->bandwidth)) {
    8064           0 :                                         if ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth)) {
    8065           0 :                                                 bandwidth = rep->bandwidth;
    8066             :                                                 quality = rep->quality_ranking;
    8067           0 :                                                 switch_to_rep_idx = k+1;
    8068             :                                         }
    8069             :                                 }
    8070             :                         }
    8071             :                 }
    8072         239 :                 if (switch_to_rep_idx && (switch_to_rep_idx-1 != current_idx) ) {
    8073           1 :                         u32 nb_cached_seg_per_rep = group->max_cached_segments / gf_dash_group_count_rep_needed(group);
    8074             : 
    8075           1 :                         group->force_switch_bandwidth = 1;
    8076           1 :                         if (!group->base_rep_index_plus_one)
    8077           1 :                                 group->force_representation_idx_plus_one = switch_to_rep_idx;
    8078             :                         else
    8079           0 :                                 group->max_complementary_rep_index = switch_to_rep_idx-1;
    8080             : 
    8081             : 
    8082           1 :                         if (group->local_files || immediate_switch) {
    8083             :                                 u32 keep_seg_index = 0;
    8084             :                                 //keep all scalable enhancements of the first segment
    8085           1 :                                 rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
    8086           1 :                                 if (rep->playback.enhancement_rep_index_plus_one) {
    8087             :                                         u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
    8088           0 :                                         while (keep_seg_index + 1 < group->nb_cached_segments) {
    8089           0 :                                                 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
    8090           0 :                                                 if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
    8091             :                                                         keep_seg_index ++;
    8092           0 :                                                         rep_idx = rep->playback.enhancement_rep_index_plus_one;
    8093             :                                                 }
    8094             :                                                 else
    8095             :                                                         break;
    8096             :                                         }
    8097             :                                 }
    8098             : 
    8099           1 :                                 if (!group->base_rep_index_plus_one) {
    8100             :                                         /*in local playback just switch at the end of the current segment
    8101             :                                         for remote, we should let the user decide*/
    8102           1 :                                         while (group->nb_cached_segments > keep_seg_index + 1) {
    8103           0 :                                                 group->nb_cached_segments--;
    8104           0 :                                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[group->nb_cached_segments].url));
    8105             : 
    8106           0 :                                                 gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
    8107             : 
    8108           0 :                                                 group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
    8109           0 :                                                 if (group->download_segment_index>1)
    8110           0 :                                                         group->download_segment_index--;
    8111             :                                         }
    8112             :                                 } else {
    8113           0 :                                         if (switch_up) {
    8114             :                                                 //first, we keep the second segment and remove all segments from the third one
    8115           0 :                                                 keep_seg_index++;
    8116           0 :                                                 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index].representation_index);
    8117           0 :                                                 if (rep->playback.enhancement_rep_index_plus_one) {
    8118             :                                                         u32 rep_idx = rep->playback.enhancement_rep_index_plus_one;
    8119           0 :                                                         while (keep_seg_index + 1 < group->nb_cached_segments) {
    8120           0 :                                                                 rep = gf_list_get(group->adaptation_set->representations, group->cached[keep_seg_index+1].representation_index);
    8121           0 :                                                                 if (rep_idx == group->cached[keep_seg_index+1].representation_index+1) {
    8122             :                                                                         keep_seg_index ++;
    8123           0 :                                                                         rep_idx = rep->playback.enhancement_rep_index_plus_one;
    8124             :                                                                 }
    8125             :                                                                 else
    8126             :                                                                         break;
    8127             :                                                         }
    8128             :                                                 }
    8129           0 :                                                 while (group->nb_cached_segments > keep_seg_index + 1) {
    8130           0 :                                                         Bool decrease_download_segment_index = (group->cached[group->nb_cached_segments-1].representation_index == current_idx) ? GF_TRUE : GF_FALSE;
    8131           0 :                                                         group->nb_cached_segments--;
    8132           0 :                                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[group->nb_cached_segments].url));
    8133             : 
    8134           0 :                                                         gf_dash_group_reset_cache_entry(&group->cached[group->nb_cached_segments]);
    8135             : 
    8136           0 :                                                         group->cached[group->nb_cached_segments].duration = (u32) group->current_downloaded_segment_duration;
    8137             : 
    8138           0 :                                                         if (decrease_download_segment_index && group->download_segment_index>1)
    8139           0 :                                                                 group->download_segment_index--;
    8140             :                                                 }
    8141             :                                                 /*force to download scalable enhancement of the second segment*/
    8142           0 :                                                 group->force_representation_idx_plus_one = switch_to_rep_idx;
    8143           0 :                                                 group->active_rep_index = switch_to_rep_idx - 1;
    8144           0 :                                                 group->download_segment_index--;
    8145             :                                         }
    8146           0 :                                         else if (group->nb_cached_segments) {
    8147             :                                                 /* we remove highest scalable enhancements of the dowloaded segments, and keep another segments*/
    8148           0 :                                                 for (k = group->nb_cached_segments - 1; k > keep_seg_index; k--) {
    8149           0 :                                                         if (group->cached[k].representation_index != current_idx)
    8150           0 :                                                                 continue;
    8151           0 :                                                         group->nb_cached_segments--;
    8152           0 :                                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group %d switching quality - delete cached segment: %s\n", i, group->cached[k].url));
    8153           0 :                                                         if (k != group->nb_cached_segments) {
    8154           0 :                                                                 memmove(&group->cached[k], &group->cached[k+1], (group->nb_cached_segments-k)*sizeof(segment_cache_entry));
    8155             :                                                         }
    8156           0 :                                                         memset(&group->cached[group->nb_cached_segments], 0, sizeof(segment_cache_entry));
    8157             :                                                 }
    8158             :                                         }
    8159             :                                 }
    8160             :                         }
    8161             :                         /*resize max cached segment*/
    8162           1 :                         group->max_cached_segments = nb_cached_seg_per_rep * gf_dash_group_count_rep_needed(group);
    8163             : 
    8164           1 :                         if (group->srd_desc)
    8165           0 :                                 gf_dash_set_tiles_quality(dash, group->srd_desc, GF_TRUE);
    8166             :                 }
    8167             :         }
    8168         120 : }
    8169             : 
    8170             : GF_EXPORT
    8171         553 : Double gf_dash_get_duration(GF_DashClient *dash)
    8172             : {
    8173         553 :         return gf_mpd_get_duration(dash->mpd);
    8174             : }
    8175             : 
    8176             : GF_EXPORT
    8177         553 : u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 idx)
    8178             : {
    8179         553 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8180         553 :         if (!group) return 0;
    8181         553 :         return group->time_shift_buffer_depth;
    8182             : }
    8183             : 
    8184             : GF_EXPORT
    8185         315 : const char *gf_dash_get_url(GF_DashClient *dash)
    8186             : {
    8187         315 :         return dash->base_url;
    8188             : }
    8189             : 
    8190             : GF_EXPORT
    8191         379 : Bool gf_dash_is_m3u8(GF_DashClient *dash) {
    8192         379 :         return dash->is_m3u8;
    8193             : }
    8194             : GF_EXPORT
    8195          97 : Bool gf_dash_is_smooth_streaming(GF_DashClient *dash) {
    8196          97 :         return dash->is_smooth;
    8197             : }
    8198             : 
    8199             : GF_EXPORT
    8200         197 : const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 idx)
    8201             : {
    8202             :         GF_MPD_Representation *rep;
    8203         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8204         197 :         if (!group) return NULL;
    8205             : 
    8206         197 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8207         197 :         return gf_dash_get_mime_type(NULL, rep, group->adaptation_set);
    8208             : }
    8209             : 
    8210             : 
    8211             : 
    8212             : GF_EXPORT
    8213         197 : const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range)
    8214             : {
    8215             :         GF_MPD_Representation *rep;
    8216         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8217         197 :         if (!group) return NULL;
    8218             : 
    8219             :         /*solve dependencies if any - we first test highest: if this is a complementary rep, keep the highest for init
    8220             :         otherwise use the selected one
    8221             :         this is need for scalable because we only init once the demuxer*/
    8222             : 
    8223         197 :         rep = gf_list_last(group->adaptation_set->representations);
    8224         197 :         if (!rep->dependency_id)
    8225         197 :                 rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8226             : 
    8227         197 :         if (group->bs_switching_init_segment_url) {
    8228          44 :                 if (start_range) *start_range = group->bs_switching_init_segment_url_start_range;
    8229          44 :                 if (end_range) *end_range = group->bs_switching_init_segment_url_end_range;
    8230          44 :                 return group->bs_switching_init_segment_url;
    8231             :         }
    8232             : 
    8233             :         //no rep found or no init for the rep, check all reps
    8234             :         //this may happen when the adaptation rate algo changed the rep between the init (TS) and the next segment
    8235         153 :         if (!rep || !rep->playback.cached_init_segment_url) {
    8236             :                 u32 i, count;
    8237           0 :                 count = gf_list_count(group->adaptation_set->representations);
    8238           0 :                 for (i=0; i<count; i++) {
    8239           0 :                         rep = gf_list_get(group->adaptation_set->representations, i);
    8240           0 :                         if (rep->playback.cached_init_segment_url) break;
    8241             :                         rep = NULL;
    8242             :                 }
    8243             :         }
    8244         153 :         if (!rep) return NULL;
    8245         153 :         if (start_range) *start_range = rep->playback.init_start_range;
    8246         153 :         if (end_range) *end_range = rep->playback.init_end_range;
    8247         153 :         return rep->playback.cached_init_segment_url;
    8248             : }
    8249             : 
    8250             : GF_EXPORT
    8251         197 : const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 idx, u32 *crypt_type, bin128 *key_IV)
    8252             : {
    8253             :         GF_MPD_Representation *rep;
    8254         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8255         197 :         if (!group) return NULL;
    8256             : 
    8257         197 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8258         197 :         if (!rep) return NULL;
    8259             : 
    8260         197 :         if (crypt_type) {
    8261         197 :                 *crypt_type = rep->crypto_type;
    8262             :         }
    8263         197 :         if (key_IV) memcpy(*key_IV, rep->playback.key_IV, sizeof(bin128));
    8264         197 :         return rep->playback.key_url;
    8265             : }
    8266             : 
    8267             : GF_EXPORT
    8268         672 : void gf_dash_group_select(GF_DashClient *dash, u32 idx, Bool select)
    8269             : {
    8270             :         Bool needs_resetup = GF_FALSE;
    8271         672 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8272         672 :         if (!group) return;
    8273         672 :         if (group->selection == GF_DASH_GROUP_NOT_SELECTABLE)
    8274             :                 return;
    8275             : 
    8276             :         if ((group->selection==GF_DASH_GROUP_NOT_SELECTED) && select) needs_resetup = 1;
    8277             : 
    8278         672 :         group->selection = select ? GF_DASH_GROUP_SELECTED : GF_DASH_GROUP_NOT_SELECTED;
    8279             :         /*this set is part of a group, make sure no all other sets from the indicated group are unselected*/
    8280         672 :         if (select && (group->adaptation_set->group>=0)) {
    8281             :                 u32 i;
    8282           0 :                 for (i=0; i<gf_dash_get_group_count(dash); i++) {
    8283           0 :                         GF_DASH_Group *agroup = gf_list_get(dash->groups, i);
    8284           0 :                         if (agroup==group) continue;
    8285             : 
    8286             :                         /*either one Representation from group 0, if present,*/
    8287           0 :                         if ((group->adaptation_set->group==0)
    8288             :                                 /*or the combination of at most one Representation from each non-zero group*/
    8289           0 :                                 || (group->adaptation_set->group==agroup->adaptation_set->group)
    8290             :                            ) {
    8291           0 :                                 agroup->selection = GF_DASH_GROUP_NOT_SELECTED;
    8292             :                         }
    8293             :                 }
    8294             :         }
    8295             :         //TODO: recompute group download index based on current playback ...
    8296             :         if (needs_resetup) {
    8297             : 
    8298             :         }
    8299             : }
    8300             : 
    8301             : GF_EXPORT
    8302         119 : void gf_dash_groups_set_language(GF_DashClient *dash, const char *lang_code_rfc_5646)
    8303             : {
    8304             :         u32 i, len;
    8305             :         s32 lang_idx;
    8306             :         GF_List *groups_selected;
    8307         119 :         if (!lang_code_rfc_5646) lang_code_rfc_5646 = "und";
    8308             : 
    8309         119 :         groups_selected = gf_list_new();
    8310             : 
    8311             :         //first pass, check exact match
    8312         358 :         for (i=0; i<gf_list_count(dash->groups); i++) {
    8313         239 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    8314         239 :                 if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
    8315             : 
    8316             :                 //select groups with no language info or undetermined or matching our code
    8317         239 :                 if (!group->adaptation_set->lang
    8318         158 :                         || !stricmp(group->adaptation_set->lang, lang_code_rfc_5646)
    8319           1 :                         || !strnicmp(group->adaptation_set->lang, "und", 3)
    8320           1 :                         || !strnicmp(group->adaptation_set->lang, "unkn", 4)
    8321             :                 ) {
    8322         238 :                         gf_dash_group_select(dash, i, 1);
    8323         238 :                         gf_list_add(groups_selected, group);
    8324             :                 }
    8325             :         }
    8326             : 
    8327         119 :         lang_idx = gf_lang_find(lang_code_rfc_5646);
    8328         119 :         if (lang_idx>=0) {
    8329         119 :                 const char *n2cc = gf_lang_get_2cc(lang_idx);
    8330         119 :                 const char *n3cc = gf_lang_get_3cc(lang_idx);
    8331             : 
    8332         358 :                 for (i=0; i<gf_list_count(dash->groups); i++) {
    8333             :                         char *sep;
    8334         239 :                         GF_DASH_Group *group = gf_list_get(dash->groups, i);
    8335         239 :                         if (group->selection==GF_DASH_GROUP_NOT_SELECTABLE) continue;
    8336         239 :                         if (!group->adaptation_set->lang) continue;
    8337         158 :                         if (gf_list_find(groups_selected, group) >= 0) continue;
    8338             : 
    8339             :                         //check we didn't select one AS in this group in the previous pass or in this pass
    8340           1 :                         if (group->adaptation_set->group>=0) {
    8341             :                                 u32 k;
    8342             :                                 Bool found = GF_FALSE;
    8343           0 :                                 for (k=0; k<gf_list_count(groups_selected); k++) {
    8344           0 :                                         GF_DASH_Group *ag = gf_list_get(groups_selected, k);
    8345             : 
    8346           0 :                                         if (ag->adaptation_set->group == group->adaptation_set->group) {
    8347             :                                                 found = 1;
    8348             :                                                 break;
    8349             :                                         }
    8350             :                                 }
    8351           0 :                                 if (found) continue;
    8352             :                         }
    8353             :                         //get the 2 or 3 land code
    8354           1 :                         sep = strchr(group->adaptation_set->lang, '-');
    8355           1 :                         if (sep) {
    8356           0 :                                 sep[0] = 0;
    8357             :                         }
    8358           1 :                         len = (u32) strlen(group->adaptation_set->lang);
    8359             :                         //compare with what we found
    8360           1 :                         if ( ((len==3) && !stricmp(group->adaptation_set->lang, n3cc))
    8361           1 :                                 || ((len==2) && !stricmp(group->adaptation_set->lang, n2cc))
    8362             :                            ) {
    8363           0 :                                 gf_dash_group_select(dash, i, 1);
    8364           0 :                                 gf_list_add(groups_selected, group);
    8365             :                         } else {
    8366           1 :                                 gf_dash_group_select(dash, i, 0);
    8367             :                         }
    8368             : 
    8369           1 :                         if (sep) sep[0] = '-';
    8370             :                 }
    8371             :         }
    8372             : 
    8373         119 :         gf_list_del(groups_selected);
    8374         119 : }
    8375             : 
    8376             : GF_EXPORT
    8377         114 : Bool gf_dash_is_running(GF_DashClient *dash)
    8378             : {
    8379         114 :         return (dash->dash_state==GF_DASH_STATE_STOPPED) ? GF_FALSE : GF_TRUE;
    8380             : }
    8381             : 
    8382             : GF_EXPORT
    8383       31651 : Bool gf_dash_is_in_setup(GF_DashClient *dash)
    8384             : {
    8385       31651 :         if (dash->dash_state==GF_DASH_STATE_SETUP) return GF_TRUE;
    8386       31647 :         if (dash->dash_state==GF_DASH_STATE_CONNECTING) return GF_TRUE;
    8387       31647 :         if (dash->request_period_switch) return GF_TRUE;
    8388       31643 :         return GF_FALSE;
    8389             : }
    8390             : 
    8391             : GF_EXPORT
    8392           7 : u32 gf_dash_get_period_switch_status(GF_DashClient *dash)
    8393             : {
    8394           7 :         return dash->request_period_switch;
    8395             : }
    8396             : GF_EXPORT
    8397           4 : void gf_dash_request_period_switch(GF_DashClient *dash)
    8398             : {
    8399           4 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Period switch has been requested\n"));
    8400           4 :         dash->request_period_switch = 1;
    8401           4 : }
    8402             : GF_EXPORT
    8403        6518 : Bool gf_dash_in_last_period(GF_DashClient *dash, Bool check_eos)
    8404             : {
    8405             :         Bool res;
    8406             : 
    8407        6518 :         switch (dash->dash_state) {
    8408             :         case GF_DASH_STATE_SETUP:
    8409             :         case GF_DASH_STATE_CONNECTING:
    8410             :                 return GF_FALSE;
    8411             :         default:
    8412             :                 break;
    8413             :         }
    8414             :         
    8415        6512 :         res = (dash->active_period_index+1 < gf_list_count(dash->mpd->periods)) ? 0 : 1;
    8416             :         //this code seems buggy, commented for now
    8417             : #if 0
    8418             :         if (res && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
    8419             :                 GF_MPD_Period*period = gf_list_last(dash->mpd->periods);
    8420             :                 //consider we are
    8421             :                 if (!period->duration || dash->mpd->media_presentation_duration) res = GF_FALSE;
    8422             :         }
    8423             : #endif
    8424        6512 :         return res;
    8425             : }
    8426             : GF_EXPORT
    8427         261 : Bool gf_dash_in_period_setup(GF_DashClient *dash)
    8428             : {
    8429         261 :         return dash->in_period_setup;
    8430             : }
    8431             : 
    8432             : GF_EXPORT
    8433         282 : void gf_dash_set_speed(GF_DashClient *dash, Double speed)
    8434             : {
    8435             :         u32 i;
    8436         282 :         if (!dash) return;
    8437         282 :         if (!speed) speed = 1.0;
    8438         282 :         if (dash->speed == speed) return;
    8439             : 
    8440           0 :         for (i=0; i<gf_list_count(dash->groups); i++) {
    8441           0 :                 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
    8442             :                 GF_MPD_Representation *active_rep;
    8443             :                 Double max_available_speed;
    8444           0 :                 if (!group || (group->selection != GF_DASH_GROUP_SELECTED)) continue;
    8445           0 :                 active_rep = (GF_MPD_Representation *)gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8446           0 :                 if (speed < 0)
    8447           0 :                         group->decode_only_rap = GF_TRUE;
    8448             : 
    8449           0 :                 max_available_speed = gf_dash_get_max_available_speed(dash, group, active_rep);
    8450             : 
    8451             :                 /*verify if this representation support this speed*/
    8452           0 :                 if (!max_available_speed || (ABS(speed) <= max_available_speed)) {
    8453             :                         //nothing to do
    8454             :                 } else {
    8455             :                         /*if the representation does not support this speed, search for another which support it*/
    8456             :                         u32 switch_to_rep_idx = 0;
    8457             :                         u32 bandwidth = 0, quality = 0, k;
    8458             :                         GF_MPD_Representation *rep;
    8459           0 :                         for (k=0; k<gf_list_count(group->adaptation_set->representations); k++) {
    8460           0 :                                 rep = gf_list_get(group->adaptation_set->representations, k);
    8461           0 :                                 if ((ABS(speed) <= rep->max_playout_rate) && ((rep->quality_ranking > quality) || (rep->bandwidth > bandwidth))) {
    8462           0 :                                         bandwidth = rep->bandwidth;
    8463             :                                         quality = rep->quality_ranking;
    8464           0 :                                         switch_to_rep_idx = k+1;
    8465             :                                 }
    8466             :                         }
    8467           0 :                         if (switch_to_rep_idx) {
    8468           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Switching representation for adapting playing speed\n"));
    8469           0 :                                 group->force_switch_bandwidth = 1;
    8470           0 :                                 group->force_representation_idx_plus_one = switch_to_rep_idx;
    8471             :                         }
    8472             :                 }
    8473           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Playing at %f speed \n", speed));
    8474           0 :                 dash->speed = speed;
    8475           0 :                 dash->is_rt_speed = (ABS(speed - 1.0)<0.1) ? GF_TRUE : GF_FALSE;
    8476             : 
    8477             :         }
    8478             : }
    8479             : 
    8480             : GF_EXPORT
    8481          78 : GF_Err gf_dash_group_get_segment_duration(GF_DashClient *dash, u32 idx, u32 *dur, u32 *timescale)
    8482             : {
    8483             :         GF_MPD_Representation *rep;
    8484          78 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8485          78 :         if (!group) return GF_BAD_PARAM;
    8486          78 :         if (!group->adaptation_set) return GF_BAD_PARAM;
    8487          78 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8488          78 :         if (!rep) return GF_BAD_PARAM;
    8489             : 
    8490          78 :         *dur = 0;
    8491          78 :         *timescale = 0;
    8492          78 :         if (rep->segment_template) { *dur = (u32) rep->segment_template->duration; (*timescale) = rep->segment_template->timescale; }
    8493          26 :         else if (rep->segment_list) { *dur = (u32) rep->segment_list->duration; *timescale = rep->segment_list->timescale; }
    8494             : 
    8495          78 :         if (group->adaptation_set->segment_template && ! *dur) *dur = (u32) group->adaptation_set->segment_template->duration;
    8496          67 :         else if (group->adaptation_set->segment_list && ! *dur) *dur = (u32) group->adaptation_set->segment_list->duration;
    8497             : 
    8498          78 :         if (group->adaptation_set->segment_template && ! *timescale) *timescale = group->adaptation_set->segment_template->timescale;
    8499          67 :         else if (group->adaptation_set->segment_list && ! *timescale) *timescale = group->adaptation_set->segment_list->timescale;
    8500             : 
    8501          78 :         if (group->period->segment_template && ! *dur) *dur = (u32) group->period->segment_template->timescale;
    8502          78 :         else if (group->period->segment_list && ! *dur) *dur = (u32) group->period->segment_list->timescale;
    8503             : 
    8504          78 :         if (group->period->segment_template && ! *timescale) *timescale = group->period->segment_template->timescale;
    8505          78 :         else if (group->period->segment_list && ! *timescale) *timescale = group->period->segment_list->timescale;
    8506             :         return GF_OK;
    8507             : }
    8508             : 
    8509             : GF_EXPORT
    8510         132 : GF_Err gf_dash_group_next_seg_info(GF_DashClient *dash, u32 group_idx, u32 dependent_representation_index, const char **seg_name, u32 *seg_number, GF_Fraction64 *seg_time, u32 *seg_dur_ms, const char **init_segment)
    8511             : {
    8512             :         GF_Err res = GF_OK;
    8513         132 :         GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    8514         132 :         if (!group) return GF_BAD_PARAM;
    8515             : 
    8516         132 :         if (init_segment) {
    8517          52 :                 if (group->bs_switching_init_segment_url) {
    8518          18 :                         *init_segment = group->bs_switching_init_segment_url_name_start;
    8519             :                 } else {
    8520          34 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8521          34 :                         *init_segment = rep ? rep->playback.init_seg_name_start : NULL;
    8522             :                 }
    8523             :         } else {
    8524             :                 u32 rep_idx = dependent_representation_index;
    8525          80 :                 if (group->nb_cached_segments <= rep_idx) {
    8526             :                         res = GF_BUFFER_TOO_SMALL;
    8527             :                 } else {
    8528          80 :                         if (seg_name) *seg_name = group->cached[rep_idx].seg_name_start;
    8529          80 :                         if (seg_number) *seg_number = group->cached[rep_idx].seg_number;
    8530          80 :                         if (seg_time) *seg_time = group->cached[rep_idx].time;
    8531          80 :                         if (seg_dur_ms) *seg_dur_ms = group->cached[rep_idx].duration;
    8532             :                 }
    8533             :         }
    8534             :         return res;
    8535             : }
    8536             : 
    8537             : GF_EXPORT
    8538          45 : const char *gf_dash_group_get_representation_id(GF_DashClient *dash, u32 idx)
    8539             : {
    8540             :         GF_MPD_Representation *rep;
    8541          45 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8542          45 :         if (!group) return NULL;
    8543          45 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8544          45 :         return rep->id;
    8545             : }
    8546             : 
    8547             : 
    8548             : GF_EXPORT
    8549        1697 : void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx)
    8550             : {
    8551             :         GF_DASH_Group *group;
    8552             :         Bool delete_next;
    8553             : 
    8554        1697 :         group = gf_list_get(dash->groups, idx);
    8555             : 
    8556        1431 : discard_segment:
    8557        3128 :         if (!group->nb_cached_segments) {
    8558             :                 return;
    8559             :         }
    8560        3128 :         delete_next = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
    8561             :         assert(group->cached[0].url);
    8562             : 
    8563        3128 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] removing segment %s from list\n", group->cached[0].url));
    8564             : 
    8565             :         //remember the representation index of the last segment
    8566        3128 :         group->prev_active_rep_index = group->cached[0].representation_index;
    8567             : 
    8568        3128 :         gf_dash_group_reset_cache_entry(&group->cached[0]);
    8569             : 
    8570        3128 :         memmove(&group->cached[0], &group->cached[1], sizeof(segment_cache_entry)*(group->nb_cached_segments-1));
    8571        3128 :         memset(&(group->cached[group->nb_cached_segments-1]), 0, sizeof(segment_cache_entry));
    8572        3128 :         group->nb_cached_segments--;
    8573             : 
    8574        3128 :         if (delete_next) {
    8575             :                 goto discard_segment;
    8576             :         }
    8577             : 
    8578             :         /*if we have dependency representations, we need also discard them*/
    8579        1697 :         if (group->base_rep_index_plus_one) {
    8580           0 :                 if (group->cached[0].url && (group->cached[0].representation_index != group->base_rep_index_plus_one-1))
    8581             :                         goto discard_segment;
    8582             :         }
    8583             : }
    8584             : 
    8585             : GF_EXPORT
    8586         364 : void gf_dash_set_group_done(GF_DashClient *dash, u32 idx, Bool done)
    8587             : {
    8588         364 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8589         364 :         if (group) group->done = done;
    8590         364 : }
    8591             : 
    8592             : GF_EXPORT
    8593       16915 : Bool gf_dash_get_group_done(GF_DashClient *dash, u32 idx)
    8594             : {
    8595       16915 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8596       16915 :         if (group) return group->done;
    8597             :         return GF_FALSE;
    8598             : }
    8599             : 
    8600             : GF_EXPORT
    8601         349 : GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 idx, u64 *presentation_time_offset, u32 *timescale)
    8602             : {
    8603         349 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8604         349 :         if (group) {
    8605             :                 u64 duration;
    8606         349 :                 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    8607         349 :                 gf_mpd_resolve_segment_duration(rep, group->adaptation_set, group->period, &duration, timescale, presentation_time_offset, NULL);
    8608             :                 return GF_OK;
    8609             :         }
    8610             :         return GF_BAD_PARAM;
    8611             : }
    8612             : 
    8613             : GF_EXPORT
    8614        4326 : GF_Err gf_dash_group_get_next_segment_location(GF_DashClient *dash, u32 idx, u32 dependent_representation_index, const char **url, u64 *start_range, u64 *end_range, s32 *switching_index, const char **switching_url, u64 *switching_start_range, u64 *switching_end_range, const char **original_url, Bool *has_next_segment, const char **key_url, bin128 *key_IV)
    8615             : {
    8616             :         GF_DASH_Group *group;
    8617             :         u32 index;
    8618             :         Bool has_dep_following;
    8619        4326 :         *url = NULL;
    8620        4326 :         if (switching_url) *switching_url = NULL;
    8621        4326 :         if (start_range) *start_range = 0;
    8622        4326 :         if (end_range) *end_range = 0;
    8623        4326 :         if (switching_start_range) *switching_start_range = 0;
    8624        4326 :         if (switching_end_range) *switching_end_range = 0;
    8625        4326 :         if (original_url) *original_url = NULL;
    8626        4326 :         if (switching_index) *switching_index = -1;
    8627        4326 :         if (has_next_segment) *has_next_segment = GF_FALSE;
    8628             : 
    8629        4326 :         group = gf_list_get(dash->groups, idx);
    8630             : 
    8631        4326 :         if (!group) {
    8632             :                 return GF_BAD_PARAM;
    8633             :         }
    8634             : 
    8635        4326 :         if (!group->nb_cached_segments) {
    8636        1245 :                 if (group->done) return GF_EOS;
    8637        1044 :                 if ((dash->low_latency_mode==GF_DASH_LL_EARLY_FETCH)
    8638        1044 :                         && group->is_low_latency
    8639          96 :                         && dash->is_rt_speed
    8640          96 :                         && !dash->time_in_tsb
    8641             :                 ) {
    8642          96 :                         group->force_early_fetch = GF_TRUE;
    8643          96 :                         dash->min_wait_ms_before_next_request = 1;
    8644             :                 }
    8645             :                 return GF_BUFFER_TOO_SMALL;
    8646             :         }
    8647             : 
    8648             :         /*check the dependent rep is in the cache and does not target next segment (next in time)*/
    8649        3081 :         has_dep_following = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
    8650             :         index = 0;
    8651       13383 :         while (dependent_representation_index) {
    8652             :                 GF_Err err = GF_OK;
    8653             : 
    8654        7221 :                 if (has_dep_following) {
    8655        7221 :                         if (index+1 >= group->nb_cached_segments)
    8656             :                                 err = GF_BUFFER_TOO_SMALL;
    8657        7221 :                         else if (! (group->cached[index].flags & SEG_FLAG_DEP_FOLLOWING) )
    8658             :                                 err = GF_BAD_PARAM;
    8659             :                 } else {
    8660           0 :                         GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
    8661             : 
    8662           0 :                         if (index+1 >= group->nb_cached_segments) err = GF_BUFFER_TOO_SMALL;
    8663           0 :                         else if (!rep->playback.enhancement_rep_index_plus_one) err = GF_BAD_PARAM;
    8664           0 :                         else if (rep->playback.enhancement_rep_index_plus_one != group->cached[index+1].representation_index + 1) err = GF_BAD_PARAM;
    8665             :                 }
    8666             : 
    8667             :                 if (err) {
    8668             :                         return err;
    8669             :                 }
    8670        7221 :                 index ++;
    8671        7221 :                 dependent_representation_index--;
    8672             :         }
    8673             : 
    8674        3081 :         *url = group->cached[index].url;
    8675        3081 :         if (start_range)
    8676        3081 :                 *start_range = group->cached[index].start_range;
    8677        3081 :         if (end_range)
    8678        3081 :                 *end_range = group->cached[index].end_range;
    8679        3081 :         if (original_url) *original_url = group->cached[index].url;
    8680        3081 :         if (key_url) *key_url = group->cached[index].key_url;
    8681        3081 :         if (key_IV) memcpy((*key_IV), group->cached[index].key_IV, sizeof(bin128));
    8682             : 
    8683        3081 :         if (!group->base_rep_index_plus_one && (group->cached[index].representation_index != group->prev_active_rep_index)) {
    8684         247 :                 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
    8685         247 :                 if (switching_index)
    8686           0 :                         *switching_index = group->cached[0].representation_index;
    8687         247 :                 if (switching_start_range)
    8688         247 :                         *switching_start_range = rep->playback.init_start_range;
    8689         247 :                 if (switching_end_range)
    8690         247 :                         *switching_end_range = rep->playback.init_end_range;
    8691         247 :                 if (switching_url)
    8692         247 :                         *switching_url = rep->playback.cached_init_segment_url;
    8693             :         }
    8694        3081 :         group->force_segment_switch = 0;
    8695             : 
    8696        3081 :         if (has_next_segment) {
    8697        3081 :                 if (group->cached[index].flags & SEG_FLAG_DEP_FOLLOWING) {
    8698        1447 :                         *has_next_segment = GF_TRUE;
    8699        1634 :                 } else if ((index+1<group->max_cached_segments) && group->cached[index+1].url  && group->adaptation_set) {
    8700             :                         GF_MPD_Representation *rep;
    8701             : 
    8702           0 :                         rep = gf_list_get(group->adaptation_set->representations, group->cached[index].representation_index);
    8703           0 :                         if (rep && (rep->playback.enhancement_rep_index_plus_one == group->cached[index+1].representation_index+1) ) {
    8704           0 :                                 *has_next_segment = GF_TRUE;
    8705             :                         }
    8706        1634 :                 } else if (group->has_pending_enhancement) {
    8707           0 :                         *has_next_segment = GF_TRUE;
    8708             :                 }
    8709             :         }
    8710        3081 :         if (group->cached[index].flags & SEG_FLAG_DISABLED)
    8711             :                 return GF_URL_REMOVED;
    8712        3081 :         return GF_OK;
    8713             : }
    8714             : 
    8715             : 
    8716             : GF_EXPORT
    8717           3 : void gf_dash_seek(GF_DashClient *dash, Double start_range)
    8718             : {
    8719             :         Bool is_dynamic = GF_FALSE;
    8720             : 
    8721           3 :         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Seek request - playing from %g\n", start_range));
    8722             : 
    8723             :         //are we live ? if so adjust start range
    8724           3 :         if (dash->mpd->type==GF_MPD_TYPE_DYNAMIC) {
    8725             :                 u64 now, availabilityStartTime;
    8726           0 :                 availabilityStartTime = dash->mpd->availabilityStartTime + dash->utc_shift;
    8727           0 :                 availabilityStartTime += dash->utc_drift_estimate;
    8728             : 
    8729           0 :                 now = dash->mpd_fetch_time + (gf_sys_clock() - dash->last_update_time) - availabilityStartTime;
    8730             : 
    8731           0 :                 if (dash->initial_time_shift_value<=100) {
    8732           0 :                         now -= dash->mpd->time_shift_buffer_depth * dash->initial_time_shift_value / 100;
    8733             :                 } else {
    8734           0 :                         now -= dash->initial_time_shift_value;
    8735             :                 }
    8736             : //              now += (u64) (start_range*1000);
    8737           0 :                 start_range = (Double) now;
    8738           0 :                 start_range /= 1000;
    8739             : 
    8740             :                 is_dynamic = 1;
    8741           0 :                 dash->initial_period_tunein = GF_TRUE;
    8742             :         }
    8743             : 
    8744             :         /*first check if we seek to another period*/
    8745           3 :         if (! gf_dash_seek_periods(dash, start_range)) {
    8746             :                 /*if no, seek in group*/
    8747           1 :                 gf_dash_seek_groups(dash, start_range, is_dynamic);
    8748             :         }
    8749           3 : }
    8750             : 
    8751             : GF_EXPORT
    8752         259 : Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 idx)
    8753             : {
    8754         259 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8755         259 :         return group->force_segment_switch;
    8756             : }
    8757             : 
    8758             : GF_EXPORT
    8759         118 : void gf_dash_set_utc_shift(GF_DashClient *dash, s32 shift_utc_sec)
    8760             : {
    8761         118 :         if (dash) dash->utc_shift = shift_utc_sec;
    8762         118 : }
    8763             : 
    8764             : GF_EXPORT
    8765         197 : GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
    8766             : {
    8767         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8768         197 :         if (!group || !max_width || !max_height) return GF_BAD_PARAM;
    8769             : 
    8770         197 :         *max_width = group->adaptation_set->max_width;
    8771         197 :         *max_height = group->adaptation_set->max_height;
    8772         197 :         return GF_OK;
    8773             : }
    8774             : 
    8775             : GF_EXPORT
    8776         197 : Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height)
    8777             : {
    8778         197 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8779         197 :         if (!group || !group->srd_desc || !max_width || !max_height) return GF_FALSE;
    8780             : 
    8781           7 :         *max_width = group->srd_desc->width;
    8782           7 :         *max_height = group->srd_desc->height;
    8783           7 :         return GF_TRUE;
    8784             : }
    8785             : 
    8786             : GF_EXPORT
    8787         118 : GF_Err gf_dash_set_min_timeout_between_404(GF_DashClient *dash, u32 min_timeout_between_404)
    8788             : {
    8789         118 :         if (!dash) return GF_BAD_PARAM;
    8790         118 :         dash->min_timeout_between_404 = min_timeout_between_404;
    8791         118 :         return GF_OK;
    8792             : }
    8793             : 
    8794             : GF_EXPORT
    8795         118 : GF_Err gf_dash_set_segment_expiration_threshold(GF_DashClient *dash, u32 expire_after_ms)
    8796             : {
    8797         118 :         if (!dash) return GF_BAD_PARAM;
    8798         118 :         dash->segment_lost_after_ms = expire_after_ms;
    8799         118 :         return GF_OK;
    8800             : }
    8801             : 
    8802             : GF_EXPORT
    8803         118 : Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 idx)
    8804             : {
    8805         118 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8806         118 :         if (!group || !group->nb_cached_segments)
    8807             :                 return GF_FALSE;
    8808         118 :         return (group->cached[0].flags & SEG_FLAG_LOOP_DETECTED) ? GF_TRUE : GF_FALSE;
    8809             : }
    8810             : 
    8811             : GF_EXPORT
    8812         222 : Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 idx)
    8813             : {
    8814         222 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    8815         222 :         if (!group) return 0.0;
    8816         222 :         return group->start_playback_range;
    8817             : }
    8818             : 
    8819             : 
    8820             : GF_EXPORT
    8821         380 : Bool gf_dash_is_dynamic_mpd(GF_DashClient *dash)
    8822             : {
    8823         380 :         return (dash && dash->mpd->type==GF_MPD_TYPE_DYNAMIC) ? 1 : 0;
    8824             : }
    8825             : 
    8826             : GF_EXPORT
    8827           5 : u32 gf_dash_get_min_buffer_time(GF_DashClient *dash)
    8828             : {
    8829           5 :         return dash ? dash->mpd->min_buffer_time : 0;
    8830             : }
    8831             : 
    8832             : #if 0 //unused
    8833             : GF_Err gf_dash_resync_to_segment(GF_DashClient *dash, const char *latest_segment_name, const char *earliest_segment_name)
    8834             : {
    8835             :         Bool found = GF_FALSE;
    8836             :         u32 i, j, latest_segment_number, earliest_segment_number, start_number;
    8837             :         /*Double latest_segment_time, earliest_segment_time;*/ //FIX : set but not used
    8838             :         u64 start_range, end_range, current_dur;
    8839             :         char *seg_url, *seg_name, *seg_sep;
    8840             :         GF_MPD_Representation *rep;
    8841             :         GF_DASH_Group *group = NULL;
    8842             :         if (!latest_segment_name) return GF_BAD_PARAM;
    8843             : 
    8844             :         seg_url = NULL;
    8845             :         for (i=0; i<gf_list_count(dash->groups); i++) {
    8846             :                 group = gf_list_get(dash->groups, i);
    8847             :                 for (j=0; j<gf_list_count(group->adaptation_set->representations); j++) {
    8848             :                         GF_Err e;
    8849             :                         rep = gf_list_get(group->adaptation_set->representations, j);
    8850             :                         e = gf_dash_resolve_url(dash->mpd, rep, group, dash->base_url, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE, i, &seg_url, &start_range, &end_range, &current_dur, NULL, NULL, NULL, NULL, NULL);
    8851             :                         if (e)
    8852             :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Unable to resolve media template URL: %s\n", gf_error_to_string(e)));
    8853             : 
    8854             :                         if (!seg_url) continue;
    8855             : 
    8856             :                         seg_sep = NULL;
    8857             :                         seg_name = strstr(seg_url, "://");
    8858             :                         if (seg_name) seg_name = strchr(seg_name+4, '/');
    8859             :                         if (!seg_name) {
    8860             :                                 gf_free(seg_url);
    8861             :                                 continue;
    8862             :                         }
    8863             :                         seg_sep = strchr(seg_name+1, '$');
    8864             :                         if (seg_sep) seg_sep[0] = 0;
    8865             : 
    8866             :                         if (!strncmp(seg_name, latest_segment_name, strlen(seg_name)))
    8867             :                                 found = GF_TRUE;
    8868             : 
    8869             :                         if (found) break;
    8870             : 
    8871             :                         if (seg_sep) seg_sep[0] = '$';
    8872             :                         gf_free(seg_url);
    8873             :                         continue;
    8874             :                 }
    8875             :                 if (found) break;
    8876             :         }
    8877             : 
    8878             :         if (!found) {
    8879             :                 if (seg_url) gf_free(seg_url);
    8880             :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] No representation found matching the resync segment name %s\n", latest_segment_name));
    8881             :                 return GF_BAD_PARAM;
    8882             :         }
    8883             : 
    8884             :         start_number = gf_dash_get_start_number(group, rep);
    8885             : 
    8886             :         if (seg_sep) {
    8887             :                 char *sep_template, *sep_name, c;
    8888             :                 char *latest_template = (char *) (latest_segment_name + strlen(seg_name));
    8889             :                 char *earliest_template = earliest_segment_name ? (char *) (earliest_segment_name + strlen(seg_name)) : NULL;
    8890             : 
    8891             :                 latest_segment_number = earliest_segment_number = 0;
    8892             :                 /*latest_segment_time = earliest_segment_time = 0;*/
    8893             : 
    8894             :                 seg_sep[0] = '$';
    8895             :                 while (seg_sep) {
    8896             :                         sep_template = strchr(seg_sep+1, '$');
    8897             :                         if (!sep_template) break;
    8898             :                         c = sep_template[1];
    8899             :                         sep_template[1] = 0;
    8900             :                         //solve template for latest
    8901             :                         sep_name = strchr(latest_template, c);
    8902             :                         if (!sep_name) break;
    8903             : 
    8904             :                         sep_name[0] = 0;
    8905             :                         if (!strcmp(seg_sep, "$Number$")) {
    8906             :                                 latest_segment_number = atoi(latest_template);
    8907             :                         }
    8908             :                         /*else if (!strcmp(seg_sep, "$Time$")) {
    8909             :                                 latest_segment_time = atof(latest_template);
    8910             :                         }*/
    8911             :                         sep_name[0] = c;
    8912             :                         latest_template = sep_name;
    8913             : 
    8914             :                         //solve template for earliest
    8915             :                         if (earliest_template) {
    8916             :                                 sep_name = strchr(earliest_template, c);
    8917             :                                 if (!sep_name) break;
    8918             : 
    8919             :                                 sep_name[0] = 0;
    8920             :                                 if (!strcmp(seg_sep, "$Number$")) {
    8921             :                                         earliest_segment_number = atoi(earliest_template);
    8922             :                                 }
    8923             :                                 /*else if (!strcmp(seg_sep, "$Time$")) {
    8924             :                                         earliest_segment_time = atof(earliest_template);
    8925             :                                 }*/
    8926             :                                 sep_name[0] = c;
    8927             :                                 earliest_template = sep_name;
    8928             :                         }
    8929             : 
    8930             :                         sep_template[1] = c;
    8931             :                         seg_sep = sep_template+1;
    8932             :                         //find next $ - if any, move the segment name of the same amount of chars that what found in the template
    8933             :                         sep_template = strchr(seg_sep, '$');
    8934             :                         if (!sep_template) break;
    8935             :                         sep_template[0]=0;
    8936             :                         latest_template += strlen(sep_template);
    8937             :                         sep_template[0]='$';
    8938             :                 }
    8939             :                 if (earliest_segment_number && !latest_segment_number) {
    8940             :                         latest_segment_number = earliest_segment_number;
    8941             :                 }
    8942             : 
    8943             :                 gf_free(seg_url);
    8944             : 
    8945             :                 //todo - recompute an AST offset so that the AST of the new segment equals UTC(now) + offset
    8946             :                 if (latest_segment_number) {
    8947             :                         Bool loop_detected = GF_FALSE;
    8948             :                         s32 nb_seg_diff = 0;
    8949             :                         s32 range_in = 0;
    8950             :                         //how many segment ahead are we ?
    8951             :                         nb_seg_diff = start_number + group->download_segment_index;
    8952             :                         nb_seg_diff -= latest_segment_number;
    8953             : 
    8954             :                         //we are just too early for this request, do request later
    8955             :                         if (nb_seg_diff == 1 ) {
    8956             :                                 //set to false, eg don't increment seg index
    8957             :                                 group->segment_in_valid_range = GF_FALSE;
    8958             :                                 return GF_OK;
    8959             :                         }
    8960             : 
    8961             :                         //if earliest is not given, allow 5 segments
    8962             :                         if (!earliest_segment_number) range_in = 4;
    8963             :                         else range_in = latest_segment_number - earliest_segment_number;
    8964             : 
    8965             :                         //loop
    8966             :                         if (latest_segment_number <= start_number ) {
    8967             :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Loop in segment start numbers detected - old start %d new seg %d\n", start_number , latest_segment_number));
    8968             :                                 loop_detected = GF_TRUE;
    8969             :                         }
    8970             :                         //we are behind live
    8971             :                         else if (nb_seg_diff<0) {
    8972             :                                 //we fall in the buffer of the sender, we liklely have a loss
    8973             :                                 if (nb_seg_diff + range_in >= 0) {
    8974             :                                         group->segment_in_valid_range = GF_TRUE;
    8975             :                                         return GF_OK;
    8976             :                                 }
    8977             :                                 //we are late (something wrong happen locally maybe) - If not too late (5 segs) jump to newest
    8978             :                                 else if (earliest_segment_number && (start_number + group->download_segment_index + 5 >= earliest_segment_number)) {
    8979             :                                         group->download_segment_index = latest_segment_number - start_number;
    8980             :                                         group->segment_in_valid_range = GF_FALSE;
    8981             :                                         return GF_OK;
    8982             :                                 }
    8983             :                                 //we are too late resync...
    8984             :                         }
    8985             : 
    8986             :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Sync to live was lost - reloading MPD (loop detected %d)\n", loop_detected));
    8987             :                         for (i=0; i< gf_list_count(dash->groups); i++) {
    8988             :                                 group = gf_list_get(dash->groups, i);
    8989             :                                 //force reinit of timeline for this group
    8990             :                                 group->start_number_at_last_ast = 0;
    8991             :                                 if (loop_detected)
    8992             :                                         group->loop_detected = GF_TRUE;
    8993             :                         }
    8994             :                         dash->force_mpd_update = GF_TRUE;
    8995             :                 }
    8996             :                 return GF_OK;
    8997             :         }
    8998             : 
    8999             :         //TODO segment list addressing:
    9000             :         return GF_OK;
    9001             : }
    9002             : #endif
    9003             : 
    9004             : 
    9005             : GF_EXPORT
    9006         109 : GF_Err gf_dash_set_max_resolution(GF_DashClient *dash, u32 width, u32 height, u8 max_display_bpp)
    9007             : {
    9008         109 :         if (dash) {
    9009         109 :                 dash->max_width = width;
    9010         109 :                 dash->max_height = height;
    9011         109 :                 dash->max_bit_per_pixel = max_display_bpp;
    9012         109 :                 return GF_OK;
    9013             :         }
    9014             :         return GF_BAD_PARAM;
    9015             : }
    9016             : 
    9017             : GF_EXPORT
    9018         118 : void gf_dash_debug_groups(GF_DashClient *dash, const u32 *groups_idx, u32 nb_groups)
    9019             : {
    9020         118 :         dash->dbg_grps_index = groups_idx;
    9021         118 :         dash->nb_dbg_grps = nb_groups;
    9022         118 : }
    9023             : 
    9024             : GF_EXPORT
    9025          10 : void gf_dash_split_adaptation_sets(GF_DashClient *dash)
    9026             : {
    9027          10 :         dash->split_adaptation_set = GF_TRUE;
    9028          10 : }
    9029             : 
    9030             : GF_EXPORT
    9031         118 : void gf_dash_set_low_latency_mode(GF_DashClient *dash, GF_DASHLowLatencyMode low_lat_mode)
    9032             : {
    9033         118 :         dash->low_latency_mode = low_lat_mode;
    9034         118 : }
    9035             : 
    9036             : 
    9037             : GF_EXPORT
    9038         259 : void gf_dash_set_user_buffer(GF_DashClient *dash, u32 buffer_time_ms)
    9039             : {
    9040         259 :         if (dash) dash->user_buffer_ms = buffer_time_ms;
    9041         259 : }
    9042             : 
    9043             : /*returns active period start in ms*/
    9044             : GF_EXPORT
    9045         172 : u64 gf_dash_get_period_start(GF_DashClient *dash)
    9046             : {
    9047             :         u64 start;
    9048             :         u32 i;
    9049             :         GF_MPD_Period *period;
    9050         172 :         if (!dash || !dash->mpd) return 0;
    9051             : 
    9052             :         start = 0;
    9053         183 :         for (i=0; i<=dash->active_period_index; i++) {
    9054         183 :                 period = gf_list_get(dash->mpd->periods, i);
    9055         183 :                 if (period->start) start = period->start;
    9056             : 
    9057         183 :                 if (i<dash->active_period_index) start += period->duration;
    9058             :         }
    9059             :         return start;
    9060             : }
    9061             : 
    9062             : 
    9063             : /*returns active period duration in ms*/
    9064             : GF_EXPORT
    9065         132 : u64 gf_dash_get_period_duration(GF_DashClient *dash)
    9066             : {
    9067             :         u64 start;
    9068             :         u32 i;
    9069             :         GF_MPD_Period *period = NULL;
    9070         132 :         if (!dash || !dash->mpd) return 0;
    9071             : 
    9072             :         start = 0;
    9073         143 :         for (i=0; i<=dash->active_period_index; i++) {
    9074         143 :                 period = gf_list_get(dash->mpd->periods, i);
    9075         143 :                 if (period->start) start = period->start;
    9076         143 :                 if (i<dash->active_period_index) start += period->duration;
    9077             :         }
    9078         132 :         if (!period) return 0;
    9079         132 :         if (period->duration) return period->duration;
    9080          20 :         period = gf_list_get(dash->mpd->periods, dash->active_period_index+1);
    9081             : 
    9082          20 :         if (!period) {
    9083             :                 //infered from MPD duration
    9084          20 :                 if (dash->mpd->media_presentation_duration) return dash->mpd->media_presentation_duration - start;
    9085             :                 //duration is not known (live)
    9086          20 :                 if (dash->mpd->type==GF_MPD_TYPE_STATIC) {
    9087           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period duration is not computable: last period without duration and no MPD duration !\n"));
    9088             :                 }
    9089             :                 return 0;
    9090             :         }
    9091           0 :         if (!period->start) {
    9092           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Period duration is not computable, paeriod has no duration and next period has no start !\n"));
    9093             :                 return 0;
    9094             :         }
    9095           0 :         return period->start - start;
    9096             : }
    9097             : 
    9098             : GF_EXPORT
    9099         118 : const char *gf_dash_group_get_language(GF_DashClient *dash, u32 idx)
    9100             : {
    9101         118 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9102         118 :         if (!group) return NULL;
    9103         118 :         return group->adaptation_set->lang;
    9104             : }
    9105             : 
    9106             : GF_EXPORT
    9107         915 : u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 idx)
    9108             : {
    9109             :         GF_MPD_Descriptor *mpd_desc;
    9110         915 :         u32 i=0;
    9111         915 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9112         915 :         if (!group) return 0;
    9113             : 
    9114         915 :         while ((mpd_desc=gf_list_enum(group->adaptation_set->audio_channels, &i))) {
    9115          28 :                 if (!strcmp(mpd_desc->scheme_id_uri, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011")) {
    9116          56 :                         return atoi(mpd_desc->value);
    9117             :                 }
    9118             :         }
    9119             :         return 0;
    9120             : }
    9121             : 
    9122             : GF_EXPORT
    9123         558 : u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 idx)
    9124             : {
    9125         558 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9126         558 :         if (!group) return 0;
    9127         558 :         return gf_list_count(group->adaptation_set->representations);
    9128             : }
    9129             : 
    9130             : GF_EXPORT
    9131         118 : u32 gf_dash_group_get_num_components(GF_DashClient *dash, u32 idx)
    9132             : {
    9133         118 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9134         118 :         if (!group) return 0;
    9135         118 :         return gf_list_count(group->adaptation_set->content_component);
    9136             : }
    9137             : 
    9138             : GF_EXPORT
    9139          78 : char *gf_dash_group_get_template(GF_DashClient *dash, u32 idx)
    9140             : {
    9141          78 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9142             :         GF_MPD_Representation *rep;
    9143             :         const char *tpl;
    9144             :         char *solved_template;
    9145          78 :         if (!group) return NULL;
    9146          78 :         rep = gf_list_get(group->adaptation_set->representations, group->active_rep_index);
    9147          78 :         if (!rep)
    9148           0 :                 rep = gf_list_get(group->adaptation_set->representations, 0);
    9149             : 
    9150             :         tpl = NULL;
    9151          78 :         if (rep && rep->segment_template) tpl = rep->segment_template->media;
    9152          52 :         if (!tpl && group->adaptation_set && group->adaptation_set->segment_template) tpl = group->adaptation_set->segment_template->media;
    9153          78 :         if (!tpl && group->period && group->period->segment_template) tpl = group->period->segment_template->media;
    9154             : 
    9155          78 :         if (tpl) {
    9156             :                 u64 range_start, range_end, segment_duration_in_ms;
    9157          63 :                 gf_mpd_resolve_url(dash->mpd, rep, group->adaptation_set, group->period, "", 0, GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, 0, 0, &solved_template, &range_start, &range_end, &segment_duration_in_ms, NULL, NULL, NULL, NULL);
    9158          63 :                 return solved_template;
    9159             :         }
    9160          15 :         if (dash->is_m3u8 && rep) {
    9161             :                 char *ext;
    9162             :                 u32 i, len, last_num, last_non_num;
    9163          15 :                 GF_MPD_SegmentURL *first_seg = gf_list_get(rep->segment_list->segment_URLs, 0);
    9164             :                 //GF_MPD_SegmentURL *last_seg = gf_list_last(rep->segment_list->segment_URLs);
    9165          15 :                 if (!first_seg) return NULL;
    9166          15 :                 if (!first_seg->media) return NULL;
    9167          15 :                 if (first_seg->media_range) return NULL;
    9168             : 
    9169          15 :                 solved_template = NULL;
    9170          15 :                 ext = strrchr(first_seg->media, '.');
    9171          15 :                 if (ext) ext[0] = 0;
    9172          15 :                 gf_dynstrcat(&solved_template, first_seg->media, NULL);
    9173          15 :                 if (ext) ext[0] = '.';
    9174          15 :                 len = (u32) strlen(solved_template);
    9175             :                 last_num = last_non_num = 0;
    9176          45 :                 for (i=len; i>0; i--) {
    9177          30 :                         if (isdigit(solved_template[i-1])) {
    9178          15 :                                 if (!last_num) last_num = i-1;
    9179             :                         } else {
    9180          15 :                                 if (last_num) {
    9181             :                                         last_non_num = i-1;
    9182             :                                         break;
    9183             :                                 }
    9184             :                         }
    9185             :                 }
    9186          15 :                 if (!last_non_num || (last_num>=last_non_num+1)) {
    9187             :                         u32 num;
    9188             :                         char szVal[100];
    9189          15 :                         solved_template[last_num] = 0;
    9190          30 :                         num = atoi(solved_template+last_non_num+1);
    9191             :                         snprintf(szVal, 100, "%u", num);
    9192          15 :                         len = (u32) strlen(szVal);
    9193          15 :                         if (len < last_num - last_non_num) {
    9194           0 :                                 u32 pad = last_num - last_non_num - len;
    9195             :                                 snprintf(szVal, 100, "$Number%%0%dd$", pad);
    9196           0 :                                 gf_dynstrcat(&solved_template, szVal, NULL);
    9197             :                         } else {
    9198          15 :                                 gf_dynstrcat(&solved_template, "$Number$", NULL);
    9199             :                         }
    9200          15 :                         gf_dynstrcat(&solved_template, first_seg->media + last_num + 1, NULL);
    9201          15 :                         return solved_template;
    9202             :                 }
    9203             : 
    9204             :         }
    9205             : 
    9206             :         return NULL;
    9207             : }
    9208             : 
    9209             : 
    9210             : GF_EXPORT
    9211         915 : GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 idx, u32 quality_idx, GF_DASHQualityInfo *quality)
    9212             : {
    9213             :         GF_MPD_Fractional *sar;
    9214             :         u32 timescale = 0;
    9215         915 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9216             :         GF_MPD_Representation *rep;
    9217         915 :         if (!group || !quality) return GF_BAD_PARAM;
    9218         915 :         rep = gf_list_get(group->adaptation_set->representations, quality_idx);
    9219         915 :         if (!rep) return GF_BAD_PARAM;
    9220             : 
    9221             :         memset(quality, 0, sizeof(GF_DASHQualityInfo));
    9222         915 :         quality->mime = rep->mime_type ? rep->mime_type : group->adaptation_set->mime_type;
    9223         915 :         quality->codec = rep->codecs ? rep->codecs : group->adaptation_set->codecs;
    9224         915 :         quality->disabled = rep->playback.disabled;
    9225         915 :         sar = rep->framerate ? rep->framerate : group->adaptation_set->framerate;
    9226         915 :         if (sar) {
    9227         596 :                 quality->fps_den = sar->den;
    9228         596 :                 quality->fps_num = sar->num;
    9229             :         }
    9230         915 :         quality->height = rep->height ? rep->height : group->adaptation_set->height;
    9231         915 :         quality->width = rep->width ? rep->width : group->adaptation_set->width;
    9232         915 :         quality->nb_channels = gf_dash_group_get_audio_channels(dash, idx);
    9233         915 :         sar = rep->sar ? rep->sar : group->adaptation_set->sar;
    9234         915 :         if (sar) {
    9235         590 :                 quality->par_num = sar->num;
    9236         590 :                 quality->par_den = sar->den;
    9237             :         }
    9238         915 :         quality->sample_rate = rep->samplerate ? rep->samplerate : group->adaptation_set->samplerate;
    9239         915 :         quality->bandwidth = rep->bandwidth;
    9240         915 :         quality->ID = rep->id;
    9241         915 :         quality->interlaced = (rep->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : ( (group->adaptation_set->scan_type == GF_MPD_SCANTYPE_INTERLACED) ? 1 : 0);
    9242             : 
    9243         915 :         if (group->was_segment_base && rep->segment_list)
    9244          34 :                 quality->seg_urls = rep->segment_list->segment_URLs;
    9245             : 
    9246             :         //scalable rep, selected quality is max_complementary_rep_index
    9247         915 :         if (group->base_rep_index_plus_one) {
    9248           0 :                 quality->is_selected = (quality_idx==group->max_complementary_rep_index) ? 1 : 0;
    9249             :         } else {
    9250         915 :                 quality->is_selected = (quality_idx==group->active_rep_index) ? 1 : 0;
    9251             :         }
    9252             : 
    9253         915 :         if (rep->segment_template) {
    9254         240 :                 if (!quality->ast_offset) quality->ast_offset = rep->segment_template->availability_time_offset;
    9255         240 :                 if (!timescale) timescale = rep->segment_template->timescale;
    9256         240 :                 if (!quality->average_duration) quality->average_duration = (Double) rep->segment_template->duration;
    9257             :         }
    9258         915 :         if (group->adaptation_set->segment_template) {
    9259         524 :                 if (!quality->ast_offset) quality->ast_offset = group->adaptation_set->segment_template->availability_time_offset;
    9260         524 :                 if (!timescale) timescale = group->adaptation_set->segment_template->timescale;
    9261         524 :                 if (!quality->average_duration) quality->average_duration =  (Double) group->adaptation_set->segment_template->duration;
    9262             :         }
    9263         915 :         if (group->period->segment_template) {
    9264           0 :                 if (!quality->ast_offset) quality->ast_offset = group->period->segment_template->availability_time_offset;
    9265           0 :                 if (!timescale) timescale = group->period->segment_template->timescale;
    9266           0 :                 if (!quality->average_duration) quality->average_duration =  (Double) group->period->segment_template->duration;
    9267             :         }
    9268             : 
    9269         915 :         if (timescale) {
    9270         686 :                 quality->average_duration /= timescale;
    9271             :         } else {
    9272         229 :                 quality->average_duration = 0;
    9273             :         }
    9274             :         return GF_OK;
    9275             : }
    9276             : 
    9277             : 
    9278        1928 : static Bool gf_dash_group_enum_descriptor_list(GF_DashClient *dash, u32 idx, GF_List *descs, const char **desc_id, const char **desc_scheme, const char **desc_value)
    9279             : {
    9280             :         GF_MPD_Descriptor *mpd_desc;
    9281        1928 :         if (idx>=gf_list_count(descs)) return 0;
    9282          10 :         mpd_desc = gf_list_get(descs, idx);
    9283          10 :         if (desc_value) *desc_value = mpd_desc->value;
    9284          10 :         if (desc_scheme) *desc_scheme = mpd_desc->scheme_id_uri;
    9285          10 :         if (desc_id) *desc_id = mpd_desc->id;
    9286             :         return 1;
    9287             : }
    9288             : 
    9289             : GF_EXPORT
    9290        1928 : Bool gf_dash_group_enum_descriptor(GF_DashClient *dash, u32 group_idx, GF_DashDescriptorType  desc_type, u32 desc_idx, const char **desc_id, const char **desc_scheme, const char **desc_value)
    9291             : {
    9292             :         GF_List *descs = NULL;
    9293        1928 :         GF_DASH_Group *group = gf_list_get(dash->groups, group_idx);
    9294        1928 :         if (!group) return 0;
    9295        1928 :         switch (desc_type) {
    9296         553 :         case GF_MPD_DESC_ACCESSIBILITY:
    9297         553 :                 descs = group->adaptation_set->accessibility;
    9298         553 :                 break;
    9299           0 :         case GF_MPD_DESC_AUDIOCONFIG:
    9300           0 :                 descs = group->adaptation_set->audio_channels;
    9301           0 :                 break;
    9302           0 :         case GF_MPD_DESC_CONTENT_PROTECTION:
    9303           0 :                 descs = group->adaptation_set->content_protection;
    9304           0 :                 break;
    9305         267 :         case GF_MPD_DESC_ESSENTIAL_PROPERTIES:
    9306         267 :                 descs = group->adaptation_set->essential_properties;
    9307         267 :                 break;
    9308           2 :         case GF_MPD_DESC_SUPPLEMENTAL_PROPERTIES:
    9309           2 :                 descs = group->adaptation_set->supplemental_properties;
    9310           2 :                 break;
    9311           0 :         case GF_MPD_DESC_FRAME_PACKING:
    9312           0 :                 descs = group->adaptation_set->frame_packing;
    9313           0 :                 break;
    9314         553 :         case GF_MPD_DESC_ROLE:
    9315         553 :                 descs = group->adaptation_set->role;
    9316         553 :                 break;
    9317         553 :         case GF_MPD_DESC_RATING:
    9318         553 :                 descs = group->adaptation_set->rating;
    9319         553 :                 break;
    9320           0 :         case GF_MPD_DESC_VIEWPOINT:
    9321           0 :                 descs = group->adaptation_set->viewpoint;
    9322           0 :                 break;
    9323             :         default:
    9324             :                 return 0;
    9325             :         }
    9326        1928 :         return gf_dash_group_enum_descriptor_list(dash, desc_idx, descs, desc_id, desc_scheme, desc_value);
    9327             : }
    9328             : 
    9329             : GF_EXPORT
    9330         411 : Bool gf_dash_get_automatic_switching(GF_DashClient *dash)
    9331             : {
    9332         411 :         return (dash && dash->disable_switching) ? GF_FALSE : GF_TRUE;
    9333             : }
    9334             : 
    9335             : GF_EXPORT
    9336           8 : GF_Err gf_dash_set_automatic_switching(GF_DashClient *dash, Bool enable_switching)
    9337             : {
    9338           8 :         if (!dash) return GF_BAD_PARAM;
    9339           8 :         dash->disable_switching = !enable_switching;
    9340           8 :         return GF_OK;
    9341             : }
    9342             : 
    9343             : GF_EXPORT
    9344         119 : GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 idx, const char *ID, u32 q_idx)
    9345             : {
    9346             :         u32 i, count;
    9347         119 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9348         119 :         if (!group) return GF_BAD_PARAM;
    9349             : 
    9350           1 :         if (!ID) {
    9351           1 :                 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, q_idx);
    9352           1 :                 if (!rep) return GF_BAD_PARAM;
    9353           1 :                 group->force_representation_idx_plus_one = q_idx+1;
    9354           1 :                 group->force_switch_bandwidth = 1;
    9355           1 :                 return GF_OK;
    9356             :         }
    9357             : 
    9358           0 :         count = gf_list_count(group->adaptation_set->representations);
    9359           0 :         for (i=0; i<count; i++) {
    9360           0 :                 GF_MPD_Representation *rep = gf_list_get(group->adaptation_set->representations, i);
    9361           0 :                 if (rep->id && !strcmp(rep->id, ID)) {
    9362           0 :                         group->force_representation_idx_plus_one = i+1;
    9363           0 :                         group->force_switch_bandwidth = 1;
    9364           0 :                         return GF_OK;
    9365             :                 }
    9366             :         }
    9367             :         return GF_BAD_PARAM;
    9368             : }
    9369             : 
    9370             : GF_EXPORT
    9371         561 : s32 gf_dash_group_get_active_quality(GF_DashClient *dash, u32 idx)
    9372             : {
    9373         561 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9374         561 :         if (!group) return -1;
    9375         561 :         return group->active_rep_index;
    9376             : }
    9377             : 
    9378             : GF_EXPORT
    9379           3 : GF_Err gf_dash_set_timeshift(GF_DashClient *dash, u32 ms_in_timeshift)
    9380             : {
    9381           3 :         if (!dash) return GF_BAD_PARAM;
    9382           3 :         dash->initial_time_shift_value = ms_in_timeshift;
    9383           3 :         return GF_OK;
    9384             : }
    9385             : 
    9386             : GF_EXPORT
    9387         192 : Double gf_dash_get_timeshift_buffer_pos(GF_DashClient *dash)
    9388             : {
    9389         192 :         return dash ? dash->prev_time_in_tsb / 1000.0 : 0.0;
    9390             : }
    9391             : 
    9392             : GF_EXPORT
    9393         135 : void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 idx, u32 avg_dec_time, u32 max_dec_time, u32 irap_avg_dec_time, u32 irap_max_dec_time, Bool codec_reset, Bool decode_only_rap)
    9394             : {
    9395         135 :         GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
    9396         135 :         if (!group) return;
    9397         135 :         group->avg_dec_time = avg_dec_time;
    9398         135 :         group->max_dec_time = max_dec_time;
    9399         135 :         group->irap_avg_dec_time = irap_avg_dec_time;
    9400         135 :         group->irap_max_dec_time = irap_max_dec_time;
    9401         135 :         group->codec_reset = codec_reset;
    9402         135 :         group->decode_only_rap = decode_only_rap;
    9403             : }
    9404             : 
    9405             : GF_EXPORT
    9406        1145 : void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms)
    9407             : {
    9408        1145 :         GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, idx);
    9409        1145 :         if (!group) return;
    9410        1145 :         group->buffer_min_ms = buffer_min_ms;
    9411        1145 :         group->buffer_max_ms = buffer_max_ms;
    9412        1145 :         if (group->max_buffer_playout_ms > buffer_max_ms) {
    9413           0 :                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Max buffer %d less than max playout buffer %d, overwriting max playout buffer\n", buffer_max_ms, group->max_buffer_playout_ms));
    9414           0 :                 group->max_buffer_playout_ms = buffer_max_ms;
    9415             :         }
    9416        1145 :         group->buffer_occupancy_ms = buffer_occupancy_ms;
    9417             : }
    9418             : 
    9419             : 
    9420             : GF_EXPORT
    9421         118 : void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable)
    9422             : {
    9423         118 :         dash->disable_speed_adaptation = disable;
    9424         118 : }
    9425             : 
    9426             : GF_EXPORT
    9427         118 : void gf_dash_override_ntp(GF_DashClient *dash, u64 server_ntp)
    9428             : {
    9429         118 :         if (server_ntp) {
    9430           0 :                 dash->utc_drift_estimate = gf_net_get_ntp_diff_ms(server_ntp);
    9431           0 :                 dash->ntp_forced = 1;
    9432           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Overwriting local NTP "LLU" to given one "LLU"\n", gf_net_get_ntp_ts(), server_ntp));
    9433             :         } else {
    9434         118 :                 dash->utc_drift_estimate = 0;
    9435         118 :                 dash->ntp_forced = 0;
    9436             :         }
    9437         118 : }
    9438             : 
    9439             : GF_EXPORT
    9440         163 : s32 gf_dash_get_utc_drift_estimate(GF_DashClient *dash) {
    9441         163 :         return (s32) dash->utc_drift_estimate;
    9442             : }
    9443             : 
    9444             : GF_EXPORT
    9445         411 : GF_DASHTileAdaptationMode gf_dash_get_tile_adaptation_mode(GF_DashClient *dash)
    9446             : {
    9447         411 :         return dash->tile_adapt_mode;
    9448             : }
    9449             : 
    9450             : GF_EXPORT
    9451         118 : void gf_dash_set_tile_adaptation_mode(GF_DashClient *dash, GF_DASHTileAdaptationMode mode, u32 tile_rate_decrease)
    9452             : {
    9453             :         u32 i;
    9454         118 :         dash->tile_adapt_mode = mode;
    9455         118 :         dash->tile_rate_decrease = (tile_rate_decrease<100) ? tile_rate_decrease : 100;
    9456         118 :         for (i=0; i<gf_list_count(dash->groups); i++) {
    9457           0 :                 GF_DASH_Group *group = (GF_DASH_Group *)gf_list_get(dash->groups, i);
    9458           0 :                 if (group->srd_desc) gf_dash_set_tiles_quality(dash, group->srd_desc, GF_TRUE);
    9459             :         }
    9460         118 : }
    9461             : 
    9462             : GF_EXPORT
    9463         118 : void gf_dash_disable_low_quality_tiles(GF_DashClient *dash, Bool disable_tiles)
    9464             : {
    9465         118 :         dash->disable_low_quality_tiles = disable_tiles;
    9466         118 : }
    9467             : 
    9468             : GF_EXPORT
    9469         558 : Bool gf_dash_group_get_srd_info(GF_DashClient *dash, u32 idx, u32 *srd_id, u32 *srd_x, u32 *srd_y, u32 *srd_w, u32 *srd_h, u32 *srd_width, u32 *srd_height)
    9470             : {
    9471         558 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9472         558 :         if (!group || !group->srd_desc) return GF_FALSE;
    9473             : 
    9474             :         if (group->srd_desc) {
    9475         140 :                 if (srd_id) (*srd_id) = group->srd_desc->id;
    9476         140 :                 if (srd_width) (*srd_width) = group->srd_desc->srd_fw;
    9477         140 :                 if (srd_height) (*srd_height) = group->srd_desc->srd_fh;
    9478             :         }
    9479             : 
    9480         140 :         if (srd_x) (*srd_x) = group->srd_x;
    9481         140 :         if (srd_y) (*srd_y) = group->srd_y;
    9482         140 :         if (srd_w) (*srd_w) = group->srd_w;
    9483         140 :         if (srd_h) (*srd_h) = group->srd_h;
    9484             : 
    9485             : 
    9486             :         return GF_TRUE;
    9487             : }
    9488             : 
    9489             : GF_EXPORT
    9490         118 : void gf_dash_ignore_xlink(GF_DashClient *dash, Bool ignore_xlink)
    9491             : {
    9492         118 :         dash->ignore_xlink = ignore_xlink;
    9493         118 : }
    9494             : 
    9495             : GF_EXPORT
    9496         118 : void gf_dash_set_route_ast_shift(GF_DashClient *dash, u32 ast_shift)
    9497             : {
    9498         118 :         dash->route_ast_shift = ast_shift;
    9499         118 : }
    9500             : 
    9501             : GF_EXPORT
    9502         259 : GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 idx, u32 max_buffer_playout_ms)
    9503             : {
    9504         259 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9505         259 :         if (!group) return GF_BAD_PARAM;
    9506         259 :         group->max_buffer_playout_ms = max_buffer_playout_ms;
    9507         259 :         return GF_OK;
    9508             : }
    9509             : 
    9510             : GF_EXPORT
    9511         119 : GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 idx, u32 quality_degradation_hint)
    9512             : {
    9513         119 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9514         119 :         if (!group) return GF_BAD_PARAM;
    9515             : 
    9516         119 :         group->quality_degradation_hint = quality_degradation_hint;
    9517         119 :         if (group->quality_degradation_hint > 100) group->quality_degradation_hint=100;
    9518             :         return GF_OK;
    9519             : }
    9520             : 
    9521             : 
    9522             : GF_EXPORT
    9523          40 : GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y, Bool is_gaze)
    9524             : {
    9525             :         u32 i, count;
    9526          40 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9527          40 :         if (!group) return GF_BAD_PARAM;
    9528             : 
    9529          40 :         if (!min_x && !max_x && !min_y && !max_y) {
    9530           0 :                 group->quality_degradation_hint = 0;
    9531             :         }
    9532             : 
    9533             :         //for both regular or tiled, store visible width/height
    9534          40 :         group->hint_visible_width = max_x - min_x;
    9535          40 :         group->hint_visible_height = max_y - min_y;
    9536             : 
    9537          40 :         if (!group->groups_depending_on) return GF_OK;
    9538             : 
    9539          40 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group Visible rect %d,%d,%d,%d \n", min_x, max_x, min_y, max_y));
    9540          40 :         count = gf_list_count(group->groups_depending_on);
    9541         400 :         for (i=0; i<count; i++) {
    9542             :                 Bool is_visible = GF_TRUE;
    9543         360 :                 GF_DASH_Group *a_group = gf_list_get(group->groups_depending_on, i);
    9544             :                 u32 old_hint;
    9545         360 :                 if (!a_group->srd_w || !a_group->srd_h) continue;
    9546             : 
    9547         360 :                 old_hint = a_group->quality_degradation_hint;
    9548         360 :                 if (is_gaze) {
    9549             : 
    9550           0 :                         if (min_x < a_group->srd_x)
    9551             :                                 is_visible = GF_FALSE;
    9552           0 :                         else if (min_x > a_group->srd_x + a_group->srd_w)
    9553             :                                 is_visible = GF_FALSE;
    9554           0 :                         else if (min_y < a_group->srd_y)
    9555             :                                 is_visible = GF_FALSE;
    9556           0 :                         else if (min_y > a_group->srd_y + a_group->srd_h)
    9557             :                                 is_visible = GF_FALSE;
    9558             : 
    9559             :                 } else {
    9560             : 
    9561             :                         //single rectangle case
    9562         360 :                         if (min_x<max_x) {
    9563         360 :                                 if (a_group->srd_x+a_group->srd_w <min_x) is_visible = GF_FALSE;
    9564         240 :                                 else if (a_group->srd_x>max_x) is_visible = GF_FALSE;
    9565             :                         } else {
    9566           0 :                                 if ( (a_group->srd_x>max_x) && (a_group->srd_x+a_group->srd_w<min_x)) is_visible = GF_FALSE;
    9567             :                         }
    9568             : 
    9569         360 :                         if (a_group->srd_y>max_y) is_visible = GF_FALSE;
    9570         300 :                         else if (a_group->srd_y+a_group->srd_h < min_y) is_visible = GF_FALSE;
    9571             : 
    9572             :                 }
    9573         240 :                 a_group->quality_degradation_hint = is_visible ? 0 : 100;
    9574         360 :                 if (old_hint != a_group->quality_degradation_hint) {
    9575          26 :                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Group SRD %d,%d,%d,%d is %s\n", a_group->srd_x, a_group->srd_w, a_group->srd_y, a_group->srd_h, is_visible ? "visible" : "hidden"));
    9576             : 
    9577             :                         //remember to update tile quality for non-custom algo
    9578          26 :                         a_group->update_tile_qualities = GF_TRUE;
    9579          26 :                         group->update_tile_qualities = GF_TRUE;
    9580             :                 }
    9581             :         }
    9582             :         return GF_OK;
    9583             : }
    9584             : 
    9585           9 : void gf_dash_set_group_download_state(GF_DashClient *dash, u32 idx, u32 cur_dep_idx, GF_Err err)
    9586             : {
    9587             :         GF_MPD_Representation *rep;
    9588             :         Bool has_dep_following;
    9589             :         char *key_url, *url;
    9590             :         GF_DASH_Group *base_group;
    9591           9 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9592           9 :         if (!group) return;
    9593             : 
    9594             :         //we forced early fetch because demux was empty, consider all errors as 404
    9595           9 :         if (group->force_early_fetch && err) {
    9596             :                 err = GF_URL_ERROR;
    9597             :         }
    9598             : 
    9599           8 :         if (!err) {
    9600           0 :                 group->force_early_fetch = GF_FALSE;
    9601           0 :                 return;
    9602             :         }
    9603           9 :         if (!group->nb_cached_segments) return;
    9604           9 :         rep = gf_list_get(group->adaptation_set->representations, group->cached[0].representation_index);
    9605             : 
    9606          18 :         while (group->cached[0].representation_index < cur_dep_idx) {
    9607           0 :                 gf_free(group->cached[0].key_url);
    9608           0 :                 gf_free(group->cached[0].url);
    9609           0 :                 group->nb_cached_segments--;
    9610           0 :                 memmove(&group->cached[0], &group->cached[1], sizeof(segment_cache_entry) * group->nb_cached_segments);
    9611           0 :                 if (!group->nb_cached_segments)
    9612             :                         break;
    9613             :         }
    9614           9 :         has_dep_following = (group->cached[0].flags & SEG_FLAG_DEP_FOLLOWING) ? GF_TRUE : GF_FALSE;
    9615             :         //detach URL and key URL, they will be freed in on_group_download_error below
    9616           9 :         key_url = group->cached[0].key_url;
    9617           9 :         url = group->cached[0].url;
    9618           9 :         group->nb_cached_segments--;
    9619             :         assert(!group->nb_cached_segments);
    9620             : 
    9621             :         base_group = group;
    9622           9 :         while (base_group->depend_on_group) {
    9623             :                 base_group = base_group->depend_on_group;
    9624             :         }
    9625           9 :         on_group_download_error(dash, group, base_group, err, rep, url, key_url, has_dep_following);
    9626             : 
    9627           9 :         if (dash->speed>=0) {
    9628           9 :                 group->download_segment_index--;
    9629             :         } else {
    9630           0 :                 group->download_segment_index++;
    9631             :         }
    9632             : }
    9633             : 
    9634        2981 : void gf_dash_group_store_stats(GF_DashClient *dash, u32 idx, u32 dep_rep_idx, u32 bytes_per_sec, u64 file_size, Bool is_broadcast, u64 us_since_start)
    9635             : {
    9636        2981 :         GF_DASH_Group *group = gf_list_get(dash->groups, idx);
    9637        2981 :         if (!group) return;
    9638        2981 :         if (!group->nb_cached_segments) return;
    9639             : 
    9640        2981 :         if (group->groups_depending_on) {
    9641        1607 :                 Bool is_last = (dep_rep_idx == gf_list_count(group->groups_depending_on)) ? GF_TRUE : GF_FALSE;
    9642        1607 :                 if (dep_rep_idx) {
    9643        1446 :                         group = gf_list_get(group->groups_depending_on, dep_rep_idx-1);
    9644        1446 :                         if (!group)
    9645             :                                 return;
    9646             :                 }
    9647        1607 :                 dash_store_stats(dash, group, bytes_per_sec, (u32) file_size, is_broadcast, 1+dep_rep_idx, us_since_start);
    9648             : 
    9649        1607 :                 if (is_last)
    9650         160 :                         dash_global_rate_adaptation(dash, GF_FALSE);
    9651             :         } else {
    9652        1374 :                 dash_store_stats(dash, group, bytes_per_sec, (u32) file_size, is_broadcast, 1+dep_rep_idx, us_since_start);
    9653             : 
    9654        1374 :                 dash_global_rate_adaptation(dash, GF_FALSE);
    9655             :         }
    9656             : }
    9657             : 
    9658       37636 : u32 gf_dash_get_min_wait_ms(GF_DashClient *dash)
    9659             : {
    9660       37636 :         if (dash && dash->min_wait_ms_before_next_request) {
    9661         105 :                 u32 ellapsed = gf_sys_clock() - dash->min_wait_sys_clock;
    9662         105 :                 if (ellapsed < dash->min_wait_ms_before_next_request) dash->min_wait_ms_before_next_request -= ellapsed;
    9663          26 :                 else dash->min_wait_ms_before_next_request = 0;
    9664         105 :                 return dash->min_wait_ms_before_next_request;
    9665             :         }
    9666             :         return 0;
    9667             : }
    9668             : 
    9669             : GF_EXPORT
    9670           6 : Bool gf_dash_all_groups_done(GF_DashClient *dash)
    9671             : {
    9672           6 :         u32 i, count = gf_list_count(dash->groups);
    9673           6 :         for (i=0; i<count; i++) {
    9674           0 :                 GF_DASH_Group *group = gf_list_get(dash->groups, i);
    9675           0 :                 if (group->selection != GF_DASH_GROUP_SELECTED) continue;
    9676           0 :                 if (!group->done) return GF_FALSE;
    9677           0 :                 if (group->nb_cached_segments) return GF_FALSE;
    9678             :         }
    9679             :         return GF_TRUE;
    9680             : }
    9681             : 
    9682             : GF_EXPORT
    9683         118 : void gf_dash_set_period_xlink_query_string(GF_DashClient *dash, const char *query_string)
    9684             : {
    9685         118 :         if (dash) dash->query_string = query_string;
    9686         118 : }
    9687             : 
    9688             : GF_EXPORT
    9689         413 : s32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx)
    9690             : {
    9691             :         GF_DASH_Group *group;
    9692         413 :         if (!dash) return 0;
    9693         413 :         group = gf_list_get(dash->groups, group_idx);
    9694         413 :         if (!group) return 0;
    9695         413 :         return group->adaptation_set->id;
    9696             : }
    9697             : 
    9698             : 
    9699           0 : GF_Err gf_dash_group_push_tfrf(GF_DashClient *dash, u32 group_idx, void *_tfrf, u32 timescale)
    9700             : {
    9701             :         GF_MSSTimeRefBox *tfrf = (GF_MSSTimeRefBox *)_tfrf;
    9702             :         u32 i;
    9703             :         GF_MPD_SegmentTemplate *stpl;
    9704             :         GF_DASH_Group *group;
    9705           0 :         if (!dash) return GF_BAD_PARAM;
    9706           0 :         if (!dash->is_smooth) return GF_OK;
    9707           0 :         if (dash->mpd->type != GF_MPD_TYPE_DYNAMIC) return GF_OK;
    9708             : 
    9709           0 :         group = gf_list_get(dash->groups, group_idx);
    9710           0 :         if (!group) return GF_BAD_PARAM;
    9711           0 :         if (!group->adaptation_set || !group->adaptation_set->segment_template || !group->adaptation_set->segment_template->segment_timeline)
    9712             :                 return GF_BAD_PARAM;
    9713             : 
    9714             :         stpl = group->adaptation_set->segment_template;
    9715             : 
    9716           0 :         for (i=0; i<tfrf->frags_count; i++) {
    9717             :                 u32 k, nb_segs;
    9718             :                 Bool exists = GF_FALSE;
    9719             :                 GF_MPD_SegmentTimelineEntry *se = NULL;
    9720             :                 u64 start = 0;
    9721           0 :                 u64 frag_time = tfrf->frags[i].absolute_time_in_track_timescale;
    9722           0 :                 u64 frag_dur = tfrf->frags[i].fragment_duration_in_track_timescale;
    9723           0 :                 if (timescale != stpl->timescale) {
    9724           0 :                         frag_time *= stpl->timescale;
    9725           0 :                         frag_time /= timescale;
    9726           0 :                         frag_dur *= stpl->timescale;
    9727           0 :                         frag_dur /= timescale;
    9728             :                 }
    9729           0 :                 nb_segs = gf_list_count(stpl->segment_timeline->entries);
    9730           0 :                 for (k=0; k<nb_segs; k++) {
    9731             :                         u64 se_dur;
    9732           0 :                         se = gf_list_get(stpl->segment_timeline->entries, k);
    9733           0 :                         if (se->start_time)
    9734             :                                 start = se->start_time;
    9735           0 :                         se_dur = se->duration * (1+se->repeat_count);
    9736           0 :                         if (start + se_dur <= frag_time) {
    9737             :                                 start += se_dur;
    9738           0 :                                 continue;
    9739             :                         }
    9740             :                         exists = GF_TRUE;
    9741             :                         break;
    9742             :                 }
    9743           0 :                 if (!exists) {
    9744           0 :                         if (se && se->duration==frag_dur) {
    9745           0 :                                 se->repeat_count++;
    9746             :                         } else {
    9747           0 :                                 GF_SAFEALLOC(se, GF_MPD_SegmentTimelineEntry);
    9748           0 :                                 if (frag_time != start)
    9749           0 :                                         se->start_time = frag_time;
    9750           0 :                                 se->duration = (u32) frag_dur;
    9751           0 :                                 gf_list_add(stpl->segment_timeline->entries, se);
    9752             :                         }
    9753           0 :                         GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Smooth push new fragment start "LLU" dur "LLU" (inserted at start_time "LLU")\n", frag_time, frag_dur, start));
    9754           0 :                         group->nb_segments_in_rep++;
    9755             :                 }
    9756             :         }
    9757             :         return GF_OK;
    9758             : }
    9759             : 
    9760             : #endif //GPAC_DISABLE_DASH_CLIENT
    9761             : 

Generated by: LCOV version 1.13