text: #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <stdint.h>
  #include <assert.h>
  #include <pthread.h>
  #include <time.h>
  
  #ifndef _MSC_VER
  #if !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__CYGWIN__)
  #include <sys/prctl.h>
  #endif
  #include <unistd.h>
  #else
  #include <Windows.h>
  #define sleep Sleep
  #endif
  
  #include <inttypes.h>
  
  #include "curl.h"
  #include "hls.h"
  #include "msg.h"
  #include "misc.h"
  #include "aes.h"
  #include "mpegts.h"
  
  static uint64_t get_duration_ms(const char *ptr)
  {
      uint64_t v1 = 0;
      uint64_t v2 = 0;
      uint32_t n = 0;
      bool hasDot = false;
  
      while (*ptr == ' ' || *ptr == '\t' ) ++ptr;
  
      while (*ptr != '\0') {
          if (*ptr >= '0' && *ptr <= '9') {
              uint32_t digit = (uint32_t)((*ptr) - '0');
              if (!hasDot)
                  v1 = v1 * 10 + digit;
              else if (n < 3) {
                  ++n;
                  v2 = v2 * 10 + digit;
              }
              else
                  break;
          }
          else if (*ptr == '.' && !hasDot) {
              hasDot = true;
          }
          else
              break;
          ++ptr;
      }
  
      if (v2 > 0)
      while (n < 3) {
          ++n;
          v2 *= 10;
      }
  
      return v1 * 1000 + v2;
  }
  
  static void set_hls_http_header(void *session)
  {
      if (hls_args.user_agent) {
          set_user_agent_http_session(session, hls_args.user_agent);
      }
  
      if (hls_args.proxy_uri) {
          set_proxy_uri_http_session(session, hls_args.proxy_uri);
      }
  
      if (hls_args.cookie_file) {
          set_cookie_file_session(session, hls_args.cookie_file, hls_args.cookie_file_mutex);
      }
  
      for (int i=0; i<HLSDL_MAX_NUM_OF_CUSTOM_HEADERS; ++i) {
          if (hls_args.custom_headers[i]) {
              add_custom_header_http_session(session, hls_args.custom_headers[i]);
          }
          else {
              break;
          }
      }
  }
  
  static void * init_hls_session(void)
  {
      void *session = init_http_session();
      assert(session);
      set_hls_http_header(session);
      return session;
  }
  
  long get_hls_data_from_url(char *url, char **out, size_t *size, int type, char **new_url)
  {
      void *session = init_hls_session();
      long http_code = get_data_from_url_with_session(&session, url, out, size, type, new_url, -1, -1);
      clean_http_session(session);
      return http_code;
  }
  
  int is_playlist_FPS(char* source)
  {
      return strstr(source, "KEYFORMAT=\"com.apple.streamingkeydelivery\"");
  }
  
  int get_playlist_type(char *source)
  {
      if (strncmp("#EXTM3U", source, 7) != 0) {
          MSG_WARNING("Not a valid M3U8 file. Exiting.\n");
          return -1;
      }
  
      if (strstr(source, "#EXT-X-STREAM-INF")) {
          return MASTER_PLAYLIST;
      }
  
      if (!hls_args.force_ignoredrm && is_playlist_FPS(source)) {
          MSG_WARNING("HLS stream is DRM protected. Exiting\n");
          return -1;
      }
  
      return MEDIA_PLAYLIST;
  }
  
  /*
   * In response to
   * http://stackoverflow.com/questions/1634359/is-there-a-reverse-fn-for-strstr
   *
   * Basically, strstr but return last occurence, not first.
   *
   * This file contains several implementations and a harness to test and
   * benchmark them.
   *
   * Some of the implementations of the actual function are copied from
   * elsewhere; they are commented with the location. The rest of the coe
   * was written by Lars Wirzenius (liw@liw.fi) and is hereby released into
   * the public domain. No warranty. If it turns out to be broken, you get
   * to keep the pieces.
   */
  
   /* By liw. */
  static char* last_strstr(const char* haystack, const char* needle)
  {
      if (*needle == '\0')
          return (char*)haystack;
  
      char* result = NULL;
      for (;;) {
          char* p = strstr(haystack, needle);
          if (p == NULL)
              break;
          result = p;
          haystack = p + 1;
      }
  
      return result;
  }
  static int extend_url(char **url, const char *baseurl)
  {
      static const char proxy_marker[] = "englandproxy.co.uk"; // ugly workaround to be fixed
      static const char proxy_url[] = "http://www.englandproxy.co.uk/";
      size_t max_length = strlen(*url) + strlen(baseurl) + 10;
  
      if (!strncmp(*url, "http://", 7) || !strncmp(*url, "https://", 8)) {
          if (strstr(baseurl, proxy_marker) && !strstr(*url, proxy_marker)) {
              max_length = strlen(*url) + strlen(proxy_url);
              char *buffer = malloc(max_length);
              snprintf(buffer, max_length, "%s%s", proxy_url, strstr(*url, "://") + 3);
              *url = realloc(*url, strlen(buffer) + 1);
              strcpy(*url, buffer);
              free(buffer);
          }
          return 0;
      }
      else if (**url == '/') {
          char *domain = malloc(max_length);
          strcpy(domain, baseurl);
          char proto[6];
          if( 2 == sscanf(baseurl, "%5[^:]://%[^/]", proto, domain))
          {
              char *buffer = malloc(max_length);
              if ( (*url)[1] == '/') // url start with "//"
              {
                  snprintf(buffer, max_length, "%s:%s", proto, *url);
              }
              else
              {
                  snprintf(buffer, max_length, "%s://%s%s", proto, domain, *url);
              }
              *url = realloc(*url, strlen(buffer) + 1);
              strcpy(*url, buffer);
              free(buffer);
          }
          free(domain);
          return 0;
      }
      else if (strstr(baseurl, "://")) {
          // URLs can have '?'. To make /../ work, remove it.
          char *domain = strdup(baseurl);
          char *find_questionmark = strchr(domain, '?');
          if (find_questionmark) {
              *find_questionmark = '\0';
          }
  
          char *buffer = malloc(max_length);
          snprintf(buffer, max_length, "%s/../%s", domain, *url);
          *url = realloc(*url, strlen(buffer) + 1);
          strcpy(*url, buffer);
          free(buffer);
          free(domain);
          return 0;
      }
      else {
          // Assume local URL
          char* folder = strdup(baseurl);
          char* separator = "/";
          char* find_folder = last_strstr(folder, separator);
          if (find_folder) {
              *find_folder = '\0';
          } else {
              separator = "\\";
              find_folder = last_strstr(folder, separator);
              if (find_folder)
                  *find_folder = '\0';
          }
          if (find_folder) {
              char* buffer = malloc(max_length);
              snprintf(buffer, max_length, "%s%s%s", folder, separator, *url);
              *url = realloc(*url, strlen(buffer) + 1);
              strcpy(*url, buffer);
              free(buffer);
          }
          free(folder);
          return 0;
      }
  }
  
  static int parse_tag(hls_media_playlist_t *me, struct hls_media_segment *ms, char *tag, int64_t *seg_offset, int64_t *seg_size)
  {
      int enc_type;
  
      if (!strncmp(tag, "#EXT-X-KEY:METHOD=AES-128", 25)) {
          enc_type = ENC_AES128;
          me->enc_aes.iv_is_static = false;
      } else if (!strncmp(tag, "#EXT-X-KEY:METHOD=SAMPLE-AES", 28)) {
          enc_type = ENC_AES_SAMPLE;
          me->enc_aes.iv_is_static = is_playlist_FPS(me->source);
      } else  {
          if (!strncmp(tag, "#EXTINF:", 8)){
              ms->duration_ms = get_duration_ms(tag+8);
              return 0;
          } else if (!strncmp(tag, "#EXT-X-ENDLIST", 14)){
              me->is_endlist = true;
              return 0;
          } else if (!strncmp(tag, "#EXT-X-MEDIA-SEQUENCE:", 22)){
              if(sscanf(tag+22, "%d",  &(me->first_media_sequence)) == 1){
                  return 0;
              }
          } else if (!strncmp(tag, "#EXT-X-TARGETDURATION:", 22)){
              me->target_duration_ms = get_duration_ms(tag+22);
              return 0;
          } else if (!strncmp(tag, "#EXT-X-BYTERANGE:", 17)) {
              *seg_size = strtoll(tag+17, NULL, 10);
              tag = strchr(tag+17, '@');
              if (tag) {
                  *seg_offset = strtoll(tag+1, NULL, 10);
              }
              return 0;
          }
          return 1;
      }
  
      me->encryption = true;
      me->encryptiontype = enc_type;
  
      char *link_to_key = malloc(strlen(tag) + strlen(me->url) + 10);
      char iv_str[STRLEN_BTS(KEYLEN)] = "\0";
      char sep = '\0';
      if ((sscanf(tag, "#EXT-X-KEY:METHOD=AES-128,URI=\"%[^\"]\",IV=0%c%32[0-9a-f]", link_to_key, &sep, iv_str) > 0 ||
           sscanf(tag, "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=\"%[^\"]\",IV=0%c%32[0-9a-f]", link_to_key, &sep, iv_str) > 0))
      {
          if (sep == 'x' || sep == 'X')
          {
              uint8_t *iv_bin = malloc(KEYLEN);
              str_to_bin(iv_bin, iv_str, KEYLEN);
              memcpy(me->enc_aes.iv_value, iv_bin, KEYLEN);
              me->enc_aes.iv_is_static = true;
              free(iv_bin);
          }
  
          extend_url(&link_to_key, me->url);
  
          free(me->enc_aes.key_url);
          me->enc_aes.key_url = strdup(link_to_key);
      }
      free(link_to_key);
      return 0;
  }
  
  static int media_playlist_get_links(hls_media_playlist_t *me)
  {
      struct hls_media_segment *ms = NULL;
      struct hls_media_segment *curr_ms = NULL;
      char *src = me->source;
      int64_t seg_offset = 0;
      int64_t seg_size = -1;
  
      MSG_PRINT("> START media_playlist_get_links\n");
  
      int i = 0;
      while(src != NULL){
          if (ms == NULL)
          {
              ms = malloc(sizeof(struct hls_media_segment));
              memset(ms, 0x00, sizeof(struct hls_media_segment));
          }
  
          while ((src = (strchr(src, '\n')))) {
              src++;
              if (*src == '\n') {
                  continue;
              }
              if (*src == '\r') {
                  continue;
              }
              if (*src == '#') {
                  parse_tag(me, ms, src, &seg_offset, &seg_size);
                  continue;
              }
              if (*src == '\0') {
                  goto finish;
              }
  
              char *end_ptr = strchr(src, '\n');
              char *end_ptr2 = strchr(src, '\r');
              if (end_ptr2 && end_ptr2 < end_ptr)
                  end_ptr = end_ptr2;
              if (end_ptr != NULL) {
                  int url_size = (int)(end_ptr - src) + 1;
                  ms->url = malloc(url_size);
                  strncpy(ms->url, src, url_size-1);
                  ms->url[url_size-1] = '\0';
                  ms->sequence_number = i + me->first_media_sequence;
                  if (me->encryptiontype == ENC_AES128 || me->encryptiontype == ENC_AES_SAMPLE) {
                      memcpy(ms->enc_aes.key_value, me->enc_aes.key_value, KEYLEN);
                      memcpy(ms->enc_aes.iv_value, me->enc_aes.iv_value, KEYLEN);
                      ms->enc_aes.key_url = strdup(me->enc_aes.key_url);
                      if (me->enc_aes.iv_is_static == false) {
                          char iv_str[STRLEN_BTS(KEYLEN)];
                          snprintf(iv_str, STRLEN_BTS(KEYLEN), "%032x\n", ms->sequence_number);
                          uint8_t *iv_bin = malloc(KEYLEN);
                          str_to_bin(iv_bin, iv_str, KEYLEN);
                          memcpy(ms->enc_aes.iv_value, iv_bin, KEYLEN);
                          free(iv_bin);
                      }
                  }
  
                  /* Get full url */
                  extend_url(&(ms->url), me->url);
  
                  ms->size = seg_size;
                  if (seg_size >= 0) {
                      ms->offset = seg_offset;
                      seg_offset += seg_size;
                      seg_size = -1;
                  } else {
                      ms->offset = 0;
                      seg_offset = 0;
                  }
  
                  /* Add new segment to segment list */
                  if (me->first_media_segment == NULL)
                  {
                      me->first_media_segment = ms;
                      curr_ms = ms;
                  }
                  else
                  {
                      curr_ms->next = ms;
                      ms->prev = curr_ms;
                      curr_ms = ms;
                  }
                  ms = NULL;
                  i += 1;
                  break;
              }
          }
      }
  
  finish:
      me->last_media_segment = curr_ms;
  
      if (i > 0) {
          me->last_media_sequence = me->first_media_sequence + i - 1;
      }
  
      media_segment_cleanup(ms);
  
      MSG_PRINT("> END media_playlist_get_links\n");
  
      return 0;
  }
  
  static uint64_t get_duration_hls_media_playlist(hls_media_playlist_t *me)
  {
      uint64_t duration_ms = 0;
      struct hls_media_segment *ms = me->first_media_segment;
      while(ms) {
          duration_ms += ms->duration_ms;
          ms = ms->next;
      }
      return duration_ms;
  }
  
  int handle_hls_media_playlist(hls_media_playlist_t *me)
  {
      me->encryption = false;
      me->encryptiontype = ENC_NONE;
  
      if (!me->source) {
          size_t size = 0;
          long http_code = 0;
          int tries = hls_args.open_max_retries;
  
          while (tries) {
              http_code = get_hls_data_from_url(me->orig_url, &me->source, &size, STRING, &me->url);
              if (200 != http_code || size == 0) {
                  MSG_ERROR("%s %d tries[%d]\n", me->orig_url, (int)http_code, (int)tries);
                  --tries;
                  sleep(1);
                  continue;
              }
              break;
          }
      }
  
      me->first_media_segment = NULL;
      me->last_media_segment = NULL;
      me->target_duration_ms = 0;
      me->is_endlist = false;
      me->last_media_sequence = 0;
  
      if (media_playlist_get_links(me)) {
          MSG_ERROR("Could not parse links. Exiting.\n");
          return 1;
      }
      me->total_duration_ms = get_duration_hls_media_playlist(me);
      return 0;
  }
  
  static bool get_next_attrib(char **source, char **tag, char **val)
  {
      bool ret = false;
      char *ptr = NULL;
      char *token = NULL;
      char *value = NULL;
      char end_val_marker = '\0';
      char *src = *source;
      while (*src != '\0' && strchr(", \t\n\r", *src)) {
          ++src;
          continue;
      }
  
      ptr = src;
      while (*ptr != '=' && *ptr != '\0' && !strchr("\n\r", *ptr)) ++ptr;
      if (*ptr != '\0') {
          token = src;
          *ptr = '\0';
  
          ptr += 1;
          if (*ptr == '"') {
              ++ptr;
              end_val_marker = '"';
          } else {
              end_val_marker = ',';
          }
  
          value = ptr;
          while (*ptr != end_val_marker && *ptr != '\0' && !strchr("\n\r", *ptr)) ++ptr;
          src = ptr;
          if (*ptr) {
              ++src;
              *ptr = '\0';
          }
  
          if (*value) {
              *val = value;
              *tag = token;
              ret = true;
          }
          *source = src;
      } else {
          *source = ptr;
      }
  
      return ret;
  }
  
  int handle_hls_master_playlist(struct hls_master_playlist *ma)
  {
      bool url_expected = false;
      unsigned int bitrate = 0;
      char *res = NULL;
      char *codecs = NULL;
      char *audio_grp = NULL;
  
      char *token = NULL;
      char *value = NULL;
  
      char *src = ma->source;
      while(*src != '\0'){
          char *end_ptr = strchr(src, '\n');
          if (!end_ptr) {
              goto finish;
          }
          *end_ptr = '\0';
          if (*src == '#') {
              url_expected = false;
              bitrate = 0;
              res = NULL;
              codecs = NULL;
              audio_grp = NULL;
  
              if (!strncmp(src, "#EXT-X-STREAM-INF:", 18)) {
                  src += 18;
                  while (get_next_attrib(&src, &token, &value)) {
                      if (!strncmp(token, "BANDWIDTH", 9)) {
                          sscanf(value, "%u", &bitrate);
                      } else if (!strncmp(token, "AUDIO", 5)) {
                          audio_grp = value;
                      } else if (!strncmp(token, "RESOLUTION", 10)) {
                          res = value;
                      } else if (!strncmp(token, "CODECS", 6)) {
                          codecs = value;
                      }
                  }
                  url_expected = true;
              } else if (!strncmp(src, "#EXT-X-MEDIA:TYPE=AUDIO,", 24)) {
                  src += 24;
                  char *grp_id = NULL;
                  char *name = NULL;
                  char *lang = NULL;
                  char *url = NULL;
                  bool is_default = false;
  
                  while (get_next_attrib(&src, &token, &value)) {
                      if (!strncmp(token, "GROUP-ID", 8)) {
                          grp_id = value;
                      } else if (!strncmp(token, "NAME", 4)) {
                          name = value;
                      } else if (!strncmp(token, "LANGUAGE", 8)) {
                          lang = value;
                      } else if (!strncmp(token, "URI", 3)) {
                          url = value;
                      } else if (!strncmp(token, "DEFAULT", 7)) {
                          if (!strncmp(value, "YES", 3)) {
                              is_default = true;
                          }
                      }
                  }
  
                  if (grp_id && name && url) {
                      size_t len = strlen(url);
  
                      hls_audio_t *audio = malloc(sizeof(hls_audio_t));
                      memset(audio, 0x00, sizeof(hls_audio_t));
                      audio->url = malloc(len + 1);
                      memcpy(audio->url, url, len);
                      audio->url[len] = '\0';
                      extend_url(&(audio->url), ma->url);
  
                      audio->grp_id = strdup(grp_id);
                      audio->name = strdup(name);
                      audio->lang = lang ? strdup(lang) : NULL;
                      audio->is_default = is_default;
  
                      if (ma->audio) {
                          audio->next = ma->audio;
                      }
                      ma->audio = audio;
                  }
              }
          } else if (url_expected) {
              size_t len = strlen(src);
  
              // here we will fill new playlist
              hls_media_playlist_t *me = malloc(sizeof(hls_media_playlist_t));
              memset(me, 0x00, sizeof(hls_media_playlist_t));
  
              me->url = malloc(len + 1);
              memcpy(me->url, src, len);
              me->url[len] = '\0';
              extend_url(&(me->url), ma->url);
              me->bitrate = bitrate;
              me->audio_grp = audio_grp ? strdup(audio_grp) : NULL;
              me->resolution = res ? strdup(res) : strdup("unknown");
              me->codecs = codecs ? strdup(codecs) : strdup("unknown");
  
              if (ma->media_playlist) {
                  me->next = ma->media_playlist;
              }
              ma->media_playlist = me;
  
              url_expected = false;
          }
  
          src = end_ptr + 1;
      }
  
  finish:
      return 0;
  }
  
  static int sample_aes_append_av_data(ByteBuffer_t *out, ByteBuffer_t *in, const uint8_t *pcr, uint16_t pid, uint8_t *cont_count)
  {
      uint8_t *av_data = in->data;
      uint32_t av_size = in->pos;
  
      uint8_t ts_header[4] = {TS_SYNC_BYTE, 0x40, 0x00, 0x10};
      ts_header[1] = ((pid >> 8) & 0x1F) | 0x40; // 0x40 - set payload_unit_start_indicator
      ts_header[2] = pid & 0xFF;
  
      uint8_t adapt_header[8] = {0x00};
      uint8_t adapt_header_size = 0;
      uint32_t payload_size = TS_PACKET_LENGTH - sizeof(ts_header);
      if (pcr[0] & 0x10) {
          adapt_header_size = 8;
          adapt_header[1] = pcr[0] & 0xF0; // set previus flags: discontinuity_indicator, random_access_indicator, elementary_stream_priority_indicator, PCR_flag
          memcpy(adapt_header + 2, pcr + 1, 6);
      } else if (pcr[0] & 0x20) {
          adapt_header_size = 2;
          adapt_header[1] = pcr[0] & 0xF0; // restore flags as described above
      } else if (av_size < payload_size) {
          adapt_header_size = payload_size - av_size == 1 ? 1 : 2;
      }
  
      payload_size -= adapt_header_size;
  
      if (adapt_header_size) {
          adapt_header[0] = adapt_header_size - 1; // size without field size
          if (av_size < payload_size) {
              adapt_header[0] += payload_size - av_size;
          }
  
          if (adapt_header[0]) {
              ts_header[3] = (ts_header[3] & 0xcf) | 0x30; // set addaptation filed flag
          }
      }
  
      // ts header
      ts_header[3] = (ts_header[3] & 0xf0) | (*cont_count);
      *cont_count = (*cont_count + 1) % 16;
      memcpy(&out->data[out->pos], ts_header, sizeof(ts_header));
      out->pos += sizeof(ts_header);
  
      // adaptation field
      if (adapt_header_size) {
          memcpy(&out->data[out->pos], adapt_header, adapt_header_size);
          out->pos += adapt_header_size;
      }
  
      if (av_size < payload_size) {
          uint32_t s;
          for(s=0; s < payload_size - av_size; ++s) {
              out->data[out->pos + s] = 0xff;
          }
          out->pos += payload_size - av_size;
          payload_size = av_size;
      }
  
      memcpy(&out->data[out->pos], av_data, payload_size);
      out->pos += payload_size;
      av_data += payload_size;
      av_size -= payload_size;
  
      if (av_size > 0) {
          uint32_t packets_num = av_size / (TS_PACKET_LENGTH - 4);
          uint32_t p;
          ts_header[1] &= 0xBF; // unset payload_unit_start_indicator
          ts_header[3] = (ts_header[3] & 0xcf) | 0x10; // unset addaptation filed flag
          for (p=0; p < packets_num; ++p) {
             ts_header[3] = (ts_header[3] & 0xf0) | (*cont_count);
             *cont_count = (*cont_count + 1) % 16;
             memcpy(&out->data[out->pos], ts_header, sizeof(ts_header));
             memcpy(&out->data[out->pos+4], av_data, TS_PACKET_LENGTH - sizeof(ts_header));
             out->pos += TS_PACKET_LENGTH;
             av_data += TS_PACKET_LENGTH - sizeof(ts_header);
             av_size -= TS_PACKET_LENGTH - sizeof(ts_header);
          }
  
          ts_header[3] = (ts_header[3] & 0xcf) | 0x30; // set addaptation filed flag to add aligment
          adapt_header[1] = 0; // none flags set, only for alignment
          if (av_size > 0) {
              ts_header[3] = (ts_header[3] & 0xf0) | (*cont_count);
              *cont_count = (*cont_count + 1) % 16;
              // add ts_header
              memcpy(&out->data[out->pos], ts_header, sizeof(ts_header));
  
              // add adapt header
              adapt_header_size = TS_PACKET_LENGTH - 4 - av_size == 1 ? 1 : 2;
              adapt_header[0] = adapt_header_size - 1; // size without field size
              if (adapt_header[0]) {
                  adapt_header[0] +=  TS_PACKET_LENGTH - 4 - 2 - av_size;
              }
  
              memcpy(&out->data[out->pos+4], adapt_header, adapt_header_size);
              out->pos += 4 + adapt_header_size;
  
              // add alignment
              if (adapt_header[0]) {
                  int32_t s;
                  for(s=0; s < adapt_header[0] - 1; ++s) {
                      out->data[out->pos + s] = 0xff;
                  }
                  out->pos += adapt_header[0] -1;
              }
  
              // add payload
              memcpy(out->data + out->pos, av_data, av_size);
              out->pos += av_size;
              av_data += av_size;
              av_size -= av_size;
          }
      }
  
      return 0;
  }
  
  
  static uint8_t * remove_emulation_prev(const uint8_t  *src,
                                         const uint8_t  *src_end,
                                               uint8_t  *dst,
                                               uint8_t  *dst_end)
  {
      while (src + 2 < src_end)
          if (!*src && !*(src + 1) && *(src + 2) == 3) {
              *dst++ = *src++;
              *dst++ = *src++;
              src++; // remove emulation_prevention_three_byte
          } else
              *dst++ = *src++;
  
      while (src < src_end)
          *dst++ = *src++;
  
      return dst;
  }
  
  static int insert_emulation_prev(   const uint8_t   *src,
                                      const uint8_t   *src_end,
                                            uint8_t   *dst,
                                            uint8_t   *dst_end)
  {
      int bytes_inserted = 0;
      while (src + 2 < src_end && dst + 3 < dst_end)
          if (!*src && !*(src + 1) && *(src + 2) < 3) {
              *dst++ = *src++;
              *dst++ = *src++;
              *dst++ = 3; // insert emulation_prevention_three_byte
              *dst++ = *src++;
              bytes_inserted++;
          }
          else {
              *dst++ = *src++;
          }
  
      while (src < src_end)
          *dst++ = *src++;
  
      return bytes_inserted;
  }
  
  static uint8_t *ff_avc_find_startcode_internal(uint8_t *p, uint8_t *end)
  {
      uint8_t *a = p + 4 - ((intptr_t)p & 3);
  
      for (end -= 3; p < a && p < end; p++) {
          if (p[0] == 0 && p[1] == 0 && p[2] == 1)
              return p;
      }
  
      for (end -= 3; p < end; p += 4) {
          uint32_t x = *(uint32_t*)p;
          if ((x - 0x01010101) & (~x) & 0x80808080) { // generic
              if (p[1] == 0) {
                  if (p[0] == 0 && p[2] == 1)
                      return p;
                  if (p[2] == 0 && p[3] == 1)
                      return p+1;
              }
              if (p[3] == 0) {
                  if (p[2] == 0 && p[4] == 1)
                      return p+2;
                  if (p[4] == 0 && p[5] == 1)
                      return p+3;
              }
          }
      }
  
      for (end += 3; p < end; p++) {
          if (p[0] == 0 && p[1] == 0 && p[2] == 1)
              return p;
      }
  
      return end + 3;
  }
  
  static uint8_t *ff_avc_find_startcode(uint8_t *p, uint8_t *end){
      uint8_t *out= ff_avc_find_startcode_internal(p, end);
      if(p<out && out<end && !out[-1]) out--;
      return out;
  }
  
  static int sample_aes_decrypt_nal_units(hls_media_segment_t *s, uint8_t *buf_in, int size)
  {
      uint8_t *end = buf_in + size;
      uint8_t *nal_start;
      uint8_t *nal_end;
      uint8_t* nal_new_start = NULL;
      int      nal_new_allocated = 0;
      end = remove_emulation_prev(buf_in, end, buf_in, end);
  
      nal_start = ff_avc_find_startcode(buf_in, end);
      for (;;) {
          while (nal_start < end && !*(nal_start++));
          if (nal_start == end)
              break;
  
          nal_end = ff_avc_find_startcode(nal_start, end);
          int nal_unit_type = *nal_start & 0x1F;
          int nal_size = nal_end - nal_start;
          // NAL unit with length of 48 bytes or fewer is completely unencrypted.
          if ((nal_unit_type == 1 || nal_unit_type == 5) && nal_size > 48) {
              uint8_t* nal_start_bkup = nal_start;
              nal_start += 32;
              void *ctx = AES128_CBC_CTX_new();
              AES128_CBC_DecryptInit(ctx, s->enc_aes.key_value, s->enc_aes.iv_value, false);
              while (nal_start + 16 < nal_end) {
                  AES128_CBC_DecryptUpdate(ctx, nal_start, nal_start, 16);
                  nal_start += 16 * 10; // Each 16-byte block of encrypted data is followed by up to nine 16-byte blocks of unencrypted data.
              }
              AES128_CBC_free(ctx);
              nal_start = nal_start_bkup;
          }
          int bytes_inserted = 0;
          if (nal_size) {
              int nal_new_maxsize = nal_size * 4 / 3;
              nal_new_start = realloc(nal_new_start, nal_new_maxsize);
              nal_new_allocated = MAX(nal_new_allocated, nal_new_maxsize);
              bytes_inserted = insert_emulation_prev(nal_start, nal_end, nal_new_start, nal_new_start + nal_new_maxsize);
          }
          if (bytes_inserted) {
              memmove(nal_end + bytes_inserted, nal_end, end - nal_end);
              memcpy(nal_start, nal_new_start, nal_size + bytes_inserted);
              nal_start = nal_end + bytes_inserted;
              end += bytes_inserted;
          }
          else
              nal_start = nal_end;
      }
      free(nal_new_start);
      return (int)(end - buf_in);
  }
  
  static int sample_aes_decrypt_audio_data(hls_media_segment_t *s, uint8_t *ptr, uint32_t size, audiotype_t audio_codec)
  {
      bool (* get_next_frame)(const uint8_t **, const uint8_t *, uint32_t *);
      switch (audio_codec)
      {
      case AUDIO_ADTS:
          get_next_frame = adts_get_next_frame;
          break;
      case AUDIO_AC3:
          get_next_frame = ac3_get_next_frame;
          break;
      case AUDIO_EC3:
          get_next_frame = ec3_get_next_frame;
          break;
      case AUDIO_UNKNOWN:
      default:
          MSG_ERROR("Wrong audio_codec! Should never happen here > EXIT!\n");
          exit(1);
      }
  
      uint8_t *audio_frame = ptr;
      uint8_t *end_ptr = ptr + size;
      uint32_t frame_length = 0;
      while (get_next_frame((const uint8_t **)&audio_frame, end_ptr, &frame_length)) {
          // The IV must be reset at the beginning of every packet.
          uint8_t leaderSize = 0;
  
          if (audio_codec == AUDIO_ADTS) {
              // ADTS headers can contain CRC checks.
              // If the CRC check bit is 0, CRC exists.
              //
              // Header (7 or 9 byte) + unencrypted leader (16 bytes)
              leaderSize = (audio_frame[1] & 0x01) ? 23 : 25;
          } else { // AUDIO_AC3, AUDIO_EC3
              // AC3 Audio is untested. Sample streams welcome.
              //
              // unencrypted leader
              leaderSize = 16;
          }
  
          int tmp_size = frame_length > leaderSize ? (frame_length - leaderSize) & 0xFFFFFFF0  : 0;
          if (tmp_size) {
              void *ctx = AES128_CBC_CTX_new();
              AES128_CBC_DecryptInit(ctx, s->enc_aes.key_value, s->enc_aes.iv_value, false);
              AES128_CBC_DecryptUpdate(ctx, audio_frame + leaderSize, audio_frame + leaderSize, tmp_size);
              AES128_CBC_free(ctx);
          }
  
          audio_frame += frame_length;
      }
  
      return 0;
  }
  
  
  static int sample_aes_handle_pes_data(hls_media_segment_t *s, ByteBuffer_t *out, ByteBuffer_t *in, uint8_t *pcr, uint16_t pid, audiotype_t audio_codec, uint8_t *counter)
  {
      uint16_t pes_header_size = 0;
      // we need to skip PES header it is not part of NAL unit
      if (in->pos <= PES_HEADER_SIZE || in->data[0] != 0x00 || in->data[1] != 0x00 || in->data[1] == 0x01) {
          MSG_ERROR("Wrong or missing PES header!\n");
          return -1;
      }
  
      pes_header_size = in->data[8] + 9;
      if (pes_header_size >= in->pos) {
          MSG_ERROR("Wrong PES header size %hu!\n", &pes_header_size);
          return -1;
      }
  
      if (AUDIO_UNKNOWN == audio_codec) {
          // handle video data in NAL units
          int size = sample_aes_decrypt_nal_units(s, in->data + pes_header_size, in->pos - pes_header_size) + pes_header_size;
  
          // to check if I did not any mistake in offset calculation
          if (size > in->pos) {
              MSG_ERROR("NAL size after decryption is grater then before - before: %d, after: %d - should never happen!\n", size);
              exit(-1);
          }
  
          // output size could be less then input because the start code emulation prevention could be removed if available
          if (size < in->pos) {
              // we need to update size in the PES header if it was set
              int32_t payload_size = ((uint16_t)(in->data[4]) << 8) | in->data[5];
              if (payload_size > 0) {
                  payload_size -=  in->pos - size;
                  in->data[4] = (payload_size >> 8) & 0xff;
                  in->data[5] = payload_size & 0xff;
              }
              in->pos = size;
          }
      } else {
          sample_aes_decrypt_audio_data(s, in->data + pes_header_size, in->pos - pes_header_size, audio_codec);
      }
  
      return sample_aes_append_av_data(out, in, pcr, pid, counter);
  }
  
  static int decrypt_sample_aes(hls_media_segment_t *s, ByteBuffer_t *buf)
  {
      int ret = 0;
      fill_key_value(&(s->enc_aes));
      if (buf->len > TS_PACKET_LENGTH && buf->data[0] == TS_SYNC_BYTE) {
          pmt_data_t pmt = {0};
          if (find_pmt(buf->data, buf->len, &pmt)) {
              bool write_pmt = true;
              uint16_t audio_PID = PID_UNSPEC;
              uint16_t video_PID = PID_UNSPEC;
              audiotype_t audio_codec = AUDIO_UNKNOWN;
              uint32_t i;
              // https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/TransportStreamSignaling/TransportStreamSignaling.html
              for (i=0; i < pmt.component_num; ++i) {
                  uint8_t stream_type = pmt.components[i].stream_type;
                  switch (stream_type) {
                      case 0xdb:
                          video_PID = pmt.components[i].elementary_PID;
                          stream_type = 0x1B; // AVC video stream as defined in ITU-T Rec. H.264 | ISO/IEC 14496-10 Video, or AVC base layer of an HEVC video stream as defined in ITU-T H.265 | ISO/IEC 23008-2
                          break;
                      case 0xcf:
                          audio_codec = AUDIO_ADTS;
                          audio_PID = pmt.components[i].elementary_PID;
                          stream_type = 0x0F; // ISO/IEC 13818-7 Audio with ADTS transport syntax
                          break;
                      case 0xc1:
                          audio_codec = AUDIO_AC3;
                          audio_PID = pmt.components[i].elementary_PID;
                          stream_type = 0x81; // User Private / AC-3 (ATSC)
                          break;
                      case 0xc2:
                          audio_codec = AUDIO_EC3;
                          audio_PID = pmt.components[i].elementary_PID;
                          stream_type = 0x87; // User Private / E-AC-3 (ATSC)
                          break;
                      default:
                          MSG_DBG("Unknown component type: 0x%02hhx, pid: 0x%03hx\n", pmt.components[i].stream_type, pmt.components[i].elementary_PID);
                          break;
                  }
  
                  if (stream_type != pmt.components[i].stream_type) {
                      // we update stream type to reflect unencrypted data
                      pmt.components[i].stream_type = stream_type;
                      pmt.data[pmt.components[i].offset] = stream_type;
                  }
              }
  
              if (audio_PID != PID_UNSPEC || video_PID != PID_UNSPEC) {
                  uint8_t audio_counter = 0;
                  uint8_t video_counter = 0;
                  uint8_t audio_pcr[7]; // first byte is adaptation filed flags
                  uint8_t video_pcr[7]; // - || -
                  ByteBuffer_t outBuffer = {NULL};
                  outBuffer.data = malloc(buf->len * 4 / 3);
                  outBuffer.len = buf->len;
  
                  ByteBuffer_t audioBuffer = {NULL};
                  ByteBuffer_t videoBuffer = {NULL};
  
                  if (audio_PID != PID_UNSPEC) {
                      audioBuffer.data = malloc(buf->len);
                      audioBuffer.len = buf->len;
                  }
  
                  if (video_PID != PID_UNSPEC) {
                      videoBuffer.data = malloc(buf->len * 4 / 3);    // reserve space for emulation_prevention_three_byte
                      videoBuffer.len = buf->len;
                  }
  
                  // collect all audio and video data
                  uint32_t packet_id = 0;
                  uint8_t *ptr = buf->data;
                  uint8_t *end = ptr + buf->len;
                  while (ptr + TS_PACKET_LENGTH <= end) {
                      if (*ptr != TS_SYNC_BYTE) {
                          MSG_WARNING("Expected sync byte but got 0x%02hhx!\n", *ptr);
                          ptr += 1;
                          continue;
                      }
                      ts_packet_t packed = {0};
                      parse_ts_packet(ptr, &packed);
  
                      if (packed.pid == pmt.pid) {
                          if (write_pmt) {
                              write_pmt = false;
                              pmt_update_crc(&pmt);
                              memcpy(&outBuffer.data[outBuffer.pos], pmt.data, TS_PACKET_LENGTH);
                              outBuffer.pos += TS_PACKET_LENGTH;
                          }
                      } else if (packed.pid == audio_PID || packed.pid == video_PID) {
                          ByteBuffer_t *pCurrBuffer = packed.pid == audio_PID ? &audioBuffer : &videoBuffer;
                          uint8_t *pcr = packed.pid == audio_PID ? audio_pcr : video_pcr;
                          uint8_t *counter = packed.pid == audio_PID ? &audio_counter : &video_counter;
  
                          if (packed.unitstart) {
                              // consume previous data if any
                              if (pCurrBuffer->pos) {
                                  sample_aes_handle_pes_data(s, &outBuffer, pCurrBuffer, pcr, packed.pid, packed.pid == audio_PID ? audio_codec : AUDIO_UNKNOWN, counter);
                              }
  
                              if ((packed.afc & 2) && (ptr[5] & 0x10)) { // remember PCR if available
                                  memcpy(pcr, ptr + 4 + 1, 7);
                              } else if ((packed.afc & 2) && (ptr[5] & 0x20)) { // remember discontinuity_indicator if set
                                  pcr[0] = ptr[5];
                              } else {
                                  pcr[0] = 0;
                              }
                              pCurrBuffer->pos = 0;
                          }
  
                          if (packed.payload_offset < TS_PACKET_LENGTH) {
                              memcpy(&(pCurrBuffer->data[pCurrBuffer->pos]), ptr + packed.payload_offset, TS_PACKET_LENGTH - packed.payload_offset);
                              pCurrBuffer->pos += TS_PACKET_LENGTH - packed.payload_offset;
                          }
                      } else {
                          memcpy(&outBuffer.data[outBuffer.pos], ptr, TS_PACKET_LENGTH);
                          outBuffer.pos += TS_PACKET_LENGTH;
                      }
  
                      ptr += TS_PACKET_LENGTH;
                      packet_id += 1;
                  }
  
                  if (audioBuffer.pos) {
                      sample_aes_handle_pes_data(s, &outBuffer, &audioBuffer, audio_pcr, audio_PID, audio_codec, &audio_counter);
                  }
  
                  if (videoBuffer.pos) {
                      sample_aes_handle_pes_data(s, &outBuffer, &videoBuffer, video_pcr, video_PID, AUDIO_UNKNOWN, &video_counter);
                  }
  
                  if (outBuffer.pos > buf->len ) {
                      MSG_ERROR("decrypt_sample_aes - buffer overflow detected!\n");
                      exit(-1);
                  }
  
                  free(videoBuffer.data);
                  free(audioBuffer.data);
  
                  // replace encrypted data with decrypted one
                  free(buf->data);
                  buf->data = outBuffer.data;
                  buf->len = outBuffer.pos;
              } else {
                  MSG_WARNING("None audio nor video component found!\n");
                  ret = -3;
              }
          } else {
              MSG_WARNING("PMT could not be found!\n");
              ret = -2;
          }
      } else {
          MSG_WARNING("Unknown segment type!\n");
          ret = -1;
      }
  
      return ret;
  }
  
  static int decrypt_aes128(hls_media_segment_t *s, ByteBuffer_t *buf)
  {
      // The AES128 method encrypts whole segments.
      // Simply decrypting them is enough.
      fill_key_value(&(s->enc_aes));
  
      void *ctx = AES128_CBC_CTX_new();
      /* some AES-128 encrypted segments could be not correctly padded
       * and decryption with padding will fail - example stream with such problem is welcome
       * From other hand dump correctly padded segment will contain trashes, which will cause many
       * errors during processing such TS, for example by DVBInspector,
       * if padding will be not removed.
       */
  #if 1
      int out_size = 0;
      AES128_CBC_DecryptInit(ctx, s->enc_aes.key_value, s->enc_aes.iv_value, true);
      AES128_CBC_DecryptPadded(ctx, buf->data, buf->data, buf->len, &out_size);
      // decoded data size could be less then input because of the padding
      buf->len = out_size;
  #else
      AES128_CBC_DecryptInit(ctx, s->enc_aes.key_value, s->enc_aes.iv_value, false);
      AES128_CBC_DecryptUpdate(ctx, buf->data, buf->data, buf->len);
  #endif
      AES128_CBC_free(ctx);
      return 0;
  }
  
  static void *hls_playlist_update_thread(void *arg)
  {
  #ifndef _MSC_VER
      char threadname[50];
      strncpy(threadname, __func__, sizeof(threadname));
      threadname[49] = '\0';
  #if !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__CYGWIN__)
      prctl(PR_SET_NAME, (unsigned long)&threadname);
  #endif
  #endif
  
      hls_playlist_updater_params *updater_params = arg;
  
      hls_media_playlist_t *me = updater_params->media_playlist;
      pthread_mutex_t *media_playlist_mtx         = (pthread_mutex_t *)(updater_params->media_playlist_mtx);
  
      pthread_cond_t  *media_playlist_refresh_cond = (pthread_cond_t *)(updater_params->media_playlist_refresh_cond);
      pthread_cond_t  *media_playlist_empty_cond   = (pthread_cond_t *)(updater_params->media_playlist_empty_cond);
  
      void *session = init_hls_session();
      set_timeout_session(session, 2L, 3L);
      bool is_endlist = false;
      //char *url = NULL;
      int refresh_delay_s = 0;
  
      // no lock is needed here because download_live_hls not change this fields
      //pthread_mutex_lock(media_playlist_mtx);
      is_endlist = me->is_endlist;
      if (hls_args.refresh_delay_sec < 0) {
          refresh_delay_s = (int)(me->target_duration_ms / 1000);
          //pthread_mutex_unlock(media_playlist_mtx);
  
          if (refresh_delay_s > HLSDL_MAX_REFRESH_DELAY_SEC) {
              refresh_delay_s = HLSDL_MAX_REFRESH_DELAY_SEC;
          } else if (refresh_delay_s < HLSDL_MIN_REFRESH_DELAY_SEC) {
              refresh_delay_s = HLSDL_MIN_REFRESH_DELAY_SEC;
          }
      } else {
          refresh_delay_s = hls_args.refresh_delay_sec;
      }
  
      struct timespec ts;
      memset(&ts, 0x00, sizeof(ts));
      MSG_VERBOSE("Update thread started\n");
      while (!is_endlist) {
          // download live hls can interrupt waiting
          ts.tv_sec =  time(NULL) + refresh_delay_s;
          pthread_mutex_lock(media_playlist_mtx);
          pthread_cond_timedwait(media_playlist_refresh_cond, media_playlist_mtx, &ts);
          pthread_mutex_unlock(media_playlist_mtx);
  
          // update playlist
          hls_media_playlist_t new_me;
          memset(&new_me, 0x00, sizeof(new_me));
  
          size_t size = 0;
          MSG_PRINT("> START DOWNLOAD LIST url[%s]\n", me->url);
          long http_code = get_data_from_url_with_session(&session, me->url, &new_me.source, &size, STRING, &(new_me.url), -1, -1);
          MSG_PRINT("> END DOWNLOAD LIST\n");
          if (200 == http_code && 0 == media_playlist_get_links(&new_me)) {
              // no mutex is needed here because download_live_hls not change this fields
              if (new_me.is_endlist ||
                  new_me.first_media_sequence != me->first_media_sequence ||
                  new_me.last_media_sequence != me->last_media_sequence)
              {
                  bool list_extended = false;
                  // we need to update list
                  pthread_mutex_lock(media_playlist_mtx);
                  me->is_endlist = new_me.is_endlist;
                  is_endlist = new_me.is_endlist;
                  me->first_media_sequence = new_me.first_media_sequence;
  
                  if (new_me.last_media_sequence > me->last_media_sequence)
                  {
                      // add new segments
                      struct hls_media_segment *ms = new_me.first_media_segment;
                      while (ms) {
                          if (ms->sequence_number > me->last_media_sequence) {
                              if (ms->prev) {
                                  ms->prev->next = NULL;
                              }
                              ms->prev = NULL;
  
                              if (me->last_media_segment) {
                                  me->last_media_segment->next = ms;
                              } else {
                                  assert(me->first_media_segment == NULL);
                                  me->first_media_segment = ms;
                              }
  
                              me->last_media_segment = new_me.last_media_segment;
                              me->last_media_sequence = new_me.last_media_sequence;
  
                              if (ms == new_me.first_media_segment) {
                                  // all segments are new
                                  new_me.first_media_segment = NULL;
                                  new_me.last_media_segment = NULL;
                              }
  
                              while (ms) {
                                  me->total_duration_ms += ms->duration_ms;
                                  ms = ms->next;
                              }
  
                              list_extended = true;
                              break;
                          }
  
                          ms = ms->next;
                      }
                  }
                  if (list_extended) {
                      pthread_cond_signal(media_playlist_empty_cond);
                  }
                  pthread_mutex_unlock(media_playlist_mtx);
              }
          } else {
              MSG_WARNING("Fail to update playlist \"%s\". http_code[%d].\n", me->url, (int)http_code);
              clean_http_session(session);
              sleep(1);
              session = init_hls_session();
              if (session) {
                  set_timeout_session(session, 2L, 15L);
                  set_fresh_connect_http_session(session, 1);
              }
          }
          media_playlist_cleanup(&new_me);
      }
  
      clean_http_session(session);
      pthread_exit(NULL);
      return NULL;
  }
  
  int download_live_hls(write_ctx_t *out_ctx, hls_media_playlist_t *me)
  {
      MSG_API("{\"d_t\":\"live\"}\n");
  
      hls_playlist_updater_params updater_params;
  
      /* declaration synchronization prymitives */
      pthread_mutex_t media_playlist_mtx;
      pthread_mutex_t cookie_file_mtx;
  
      pthread_cond_t  media_playlist_refresh_cond;
      pthread_cond_t  media_playlist_empty_cond;
  
      /* init synchronization prymitives */
      pthread_mutex_init(&media_playlist_mtx, NULL);
      pthread_mutex_init(&cookie_file_mtx, NULL);
  
      pthread_cond_init(&media_playlist_refresh_cond, NULL);
      pthread_cond_init(&media_playlist_empty_cond, NULL);
  
      memset(&updater_params, 0x00, sizeof(updater_params));
      updater_params.media_playlist = me;
      updater_params.media_playlist_mtx = (void *)&media_playlist_mtx;
      updater_params.media_playlist_refresh_cond = (void *)&media_playlist_refresh_cond;
      updater_params.media_playlist_empty_cond   = (void *)&media_playlist_empty_cond;
  
      hls_args.cookie_file_mutex = (void *)&cookie_file_mtx;
  
      // skip first segments
      if (me->first_media_segment != me->last_media_segment) {
          struct hls_media_segment *ms = me->last_media_segment;
          uint64_t duration_ms = 0;
          uint64_t duration_offset_ms = hls_args.live_start_offset_sec * 1000;
          while (ms) {
              duration_ms += ms->duration_ms;
              if (duration_ms >= duration_offset_ms) {
                  break;
              }
              ms = ms->prev;
          }
  
          if (ms && ms != me->first_media_segment){
              // remove segments
              while (me->first_media_segment != ms) {
                  struct hls_media_segment *tmp_ms = me->first_media_segment;
                  me->first_media_segment = me->first_media_segment->next;
                  media_segment_cleanup(tmp_ms);
              }
              ms->prev = NULL;
              me->first_media_segment = ms;
          }
  
          me->total_duration_ms = get_duration_hls_media_playlist(me);
      }
  
      // start update thread
      pthread_t thread;
      void *ret;
  
      pthread_create(&thread, NULL, hls_playlist_update_thread, &updater_params);
  
      void *session = init_hls_session();
      set_timeout_session(session, 2L, 3L);
      uint64_t downloaded_duration_ms = 0;
      int64_t download_size = 0;
      time_t repTime = 0;
      bool download = true;
      while(download) {
  
          pthread_mutex_lock(&media_playlist_mtx);
          struct hls_media_segment *ms = me->first_media_segment;
          if (ms != NULL) {
              me->first_media_segment = ms->next;
              if (me->first_media_segment) {
                  me->first_media_segment->prev = NULL;
              }
          }
          else {
              me->last_media_segment = NULL;
              download = !me->is_endlist;
          }
          if (ms == NULL) {
              if (download) {
                  pthread_cond_signal(&media_playlist_refresh_cond);
                  pthread_cond_wait(&media_playlist_empty_cond, &media_playlist_mtx);
              }
          }
          pthread_mutex_unlock(&media_playlist_mtx);
          if (ms == NULL) {
              continue;
          }
  
          MSG_PRINT("Downloading part %d\n", ms->sequence_number);
          int retries = 0;
          do {
              struct ByteBuffer seg;
              memset(&seg, 0x00, sizeof(seg));
              size_t size = 0;
              long http_code = get_data_from_url_with_session(&session, ms->url, (char **)&(seg.data), &size, BINARY, NULL, ms->offset, ms->size);
              seg.len = (int)size;
              if (!(http_code == 200 || (http_code == 206 && (ms->size > -1 || hls_args.accept_partial_content)))) {
                  int first_media_sequence = 0;
                  if (seg.data) {
                      free(seg.data);
                      seg.data  = NULL;
                  }
  
                  pthread_mutex_lock(&media_playlist_mtx);
                  first_media_sequence = me->first_media_sequence;
                  pthread_mutex_unlock(&media_playlist_mtx);
  
                  if(http_code != 403 && http_code != 401 &&  http_code != 410 && retries <= hls_args.segment_download_retries && ms->sequence_number > first_media_sequence) {
                      clean_http_session(session);
                      sleep(1);
                      session = init_hls_session();
                      if (session) {
                          set_timeout_session(session, 2L, 5L);
                          set_fresh_connect_http_session(session, 1);
                          MSG_WARNING("Live retry segment %d download, due to previous error. http_code[%d].\n", ms->sequence_number, (int)http_code);
                          retries += 1;
                          continue;
                      }
                  }
                  else
                  {
                      MSG_WARNING("Live mode skipping segment %d. http_code[%d].\n", ms->sequence_number, (int)http_code);
                      break;
                  }
              }
  
              downloaded_duration_ms += ms->duration_ms;
  
              if (me->encryption == true && me->encryptiontype == ENC_AES128) {
                  decrypt_aes128(ms, &seg);
              } else if (me->encryption == true && me->encryptiontype == ENC_AES_SAMPLE) {
                  decrypt_sample_aes(ms, &seg);
              }
              download_size += out_ctx->write(seg.data, seg.len, out_ctx->opaque);
              free(seg.data);
  
              set_fresh_connect_http_session(session, 0);
  
              time_t curRepTime = time(NULL);
              if ((curRepTime - repTime) >= 1) {
                  MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size);
                  repTime = curRepTime;
              }
  
              break;
          } while(true);
  
          media_segment_cleanup(ms);
      }
  
      pthread_join(thread, &ret);
      pthread_mutex_destroy(&media_playlist_mtx);
  
      pthread_cond_destroy(&media_playlist_refresh_cond);
      pthread_cond_destroy(&media_playlist_empty_cond);
  
      pthread_mutex_destroy(&cookie_file_mtx);
      hls_args.cookie_file_mutex = NULL;
  
      MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size);
      if (session)
      {
          clean_http_session(session);
      }
  
      return 0;
  }
  
  static int vod_download_segment(void **psession, hls_media_playlist_t *me, struct hls_media_segment *ms, struct ByteBuffer *seg)
  {
      int retries = 0;
      int ret = 0;
      while (true) {
          MSG_PRINT("Downloading part %d\n", ms->sequence_number);
  
          memset(seg, 0x00, sizeof(*seg));
          size_t size = 0;
          long http_code = get_data_from_url_with_session(psession, ms->url, (char **)&(seg->data), &size, BINARY, NULL, ms->offset, ms->size);
          seg->len = (int)size;
          if (!(http_code == 200 || (http_code == 206 && (ms->size > -1 || hls_args.accept_partial_content)))) {
              if (seg->data) {
                  free(seg->data);
                  seg->data = NULL;
              }
              if (http_code != 403 && http_code != 401 && http_code != 410 && retries <= hls_args.segment_download_retries) {
                  clean_http_session(*psession);
                  sleep(1);
                  *psession = init_hls_session();
                  set_timeout_session(*psession, 2L, 30L);
                  if (*psession) {
                      set_fresh_connect_http_session(*psession, 1);
                      MSG_WARNING("VOD retry segment %d download, due to previous error. http_code[%d].\n", ms->sequence_number, (int)http_code);
                      retries += 1;
                      continue;
                  }
              }
              ret = 1;
              MSG_API("{\"error_code\":%d, \"error_msg\":\"http\"}\n", (int)http_code);
              break;
          }
          break;
      }
  
      if (ret == 0) {
          if (me->encryption == true && me->encryptiontype == ENC_AES128) {
              decrypt_aes128(ms, seg);
          } else if (me->encryption == true && me->encryptiontype == ENC_AES_SAMPLE) {
              decrypt_sample_aes(ms, seg);
          }
      }
  
      /* normally we want to reuse sessions,
       * so restore it in case when fresh session
       * was requested do to re-try
       */
      if (retries) {
          set_fresh_connect_http_session(*psession, 0);
      }
  
      return ret;
  }
  
  bool consecutive_sync_byte(uint8_t *buf, size_t len, uint8_t n) {
      if (len < n * TS_PACKET_LENGTH) {
          return false;
      }
  
      for (uint8_t i = 1; i < n; ++i) {
          if (buf[i * TS_PACKET_LENGTH] != TS_SYNC_BYTE) {
              return false;
          }
      }
  
      return true;
  }
  
  uint8_t * find_first_ts_packet(ByteBuffer_t *buf) {
      uint8_t *cursor = buf->data;
      size_t len = buf->len;
  
      while (TS_PACKET_LENGTH <= len) {
          uint8_t *next = memchr(cursor, TS_SYNC_BYTE, len);
          if (next == NULL) {
              return NULL;
          }
          len -= (next - cursor);
          cursor = next;
          if (consecutive_sync_byte(cursor, len, 3)) {
              return cursor;
          }
          ++cursor;
          --len;
      }
  
      return NULL;
  }
  
  int download_hls(write_ctx_t *out_ctx, hls_media_playlist_t *me, hls_media_playlist_t *me_audio)
  {
      MSG_VERBOSE("Downloading segments.\n");
      MSG_API("{\"d_t\":\"vod\"}\n"); // d_t - download type
      MSG_API("{\"t_d\":%u,\"d_d\":0, \"d_s\":0}\n", (uint32_t)(me->total_duration_ms / 1000)); // t_d - total duration, d_d  - download duration, d_s - download size
  
      int ret = 0;
      void *session = init_hls_session();
      set_timeout_session(session, 2L, 3L);
      assert(session);
      time_t repTime = 0;
  
      uint64_t downloaded_duration_ms = 0;
      int64_t download_size = 0;
      struct ByteBuffer seg;
      struct ByteBuffer seg_audio;
  
      struct hls_media_segment *ms = me->first_media_segment;
      struct hls_media_segment *ms_audio = NULL;
      merge_context_t merge_context;
  
      if (me_audio) {
          ms_audio = me_audio->first_media_segment;
          memset(&merge_context, 0x00, sizeof(merge_context));
          merge_context.out = out_ctx;
      }
  
      while(ms) {
          if (0 != vod_download_segment(&session, me, ms, &seg)) {
              break;
          }
  
          uint8_t *first_video_packet = find_first_ts_packet(&seg);
          uint8_t *first_audio_packet = NULL;
          if (ms_audio) {
              if ( 0 != vod_download_segment(&session, me_audio, ms_audio, &seg_audio)) {
                  break;
              }
              first_audio_packet = find_first_ts_packet(&seg_audio);
          }
  
          // first segment should be TS for success merge
          if (first_video_packet && first_audio_packet) {
              size_t video_len = seg.len - (first_video_packet - seg.data);
              size_t audio_len = seg_audio.len - (first_audio_packet - seg_audio.data);
  
              download_size += merge_packets(
                  &merge_context,
                  first_video_packet,
                  video_len,
                  first_audio_packet,
                  audio_len
              );
          } else {
              download_size += out_ctx->write(seg.data, seg.len, out_ctx->opaque);
          }
  
          if (ms_audio) {
              free(seg_audio.data);
              ms_audio = ms_audio->next;
          }
  
          free(seg.data);
  
          downloaded_duration_ms += ms->duration_ms;
  
          time_t curRepTime = time(NULL);
          if ((curRepTime - repTime) >= 1) {
              MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size);
              repTime = curRepTime;
          }
  
          ms = ms->next;
      }
  
      MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size);
  
      if (session) {
          clean_http_session(session);
      }
  
      return ret;
  }
  
  int print_enc_keys(hls_media_playlist_t *me)
  {
      struct hls_media_segment *ms = me->first_media_segment;
      while(ms) {
          if (me->encryption == true) {
              fill_key_value(&(ms->enc_aes));
              MSG_PRINT("[AES-128]KEY: 0x");
              for(size_t count = 0; count < KEYLEN; count++) {
                  MSG_PRINT("%02x", ms->enc_aes.key_value[count]);
              }
              MSG_PRINT(" IV: 0x");
              for(size_t count = 0; count < KEYLEN; count++) {
                  MSG_PRINT("%02x", ms->enc_aes.iv_value[count]);
              }
              MSG_PRINT("\n");
          }
          ms = ms->next;
      }
      return 0;
  }
  
  void media_segment_cleanup(struct hls_media_segment *ms)
  {
      if (ms)
      {
          free(ms->url);
          free(ms->enc_aes.key_url);
          free(ms);
      }
  }
  
  void media_playlist_cleanup(hls_media_playlist_t *me)
  {
      struct hls_media_segment *ms = me->first_media_segment;
      free(me->source);
      free(me->orig_url);
      free(me->url);
      free(me->audio_grp);
      free(me->resolution);
      free(me->codecs);
      free(me->enc_aes.key_url);
  
      while(ms){
          me->first_media_segment = ms->next;
          media_segment_cleanup(ms);
          ms = me->first_media_segment;
      }
      assert(me->first_media_segment == NULL);
      me->last_media_segment = NULL;
  }
  
  static void audio_cleanup(hls_audio_t *audio)
  {
      free(audio->url);
      free(audio->grp_id);
      free(audio->lang);
      free(audio->name);
  }
  
  void master_playlist_cleanup(struct hls_master_playlist *ma)
  {
      hls_media_playlist_t *me = ma->media_playlist;
      while (me) {
          hls_media_playlist_t *ptr = me;
          me = me->next;
          media_playlist_cleanup(ptr);
          free(ptr);
      }
  
      hls_audio_t *audio = ma->audio;
      while (audio) {
          hls_audio_t *ptr = audio;
          audio = audio->next;
          audio_cleanup(ptr);
          free(ptr);
      }
  
      free(ma->source);
      free(ma->orig_url);
      free(ma->url);
  }
  
  int fill_key_value(struct enc_aes128 *es)
  {
      /* temporary we will create cache with keys url here
       * this will make this function thread unsafe but at now
       * it is not problem because it is used only from one thread
       *
       * last allocation of cache_key_url will not be free
       * but this is not big problem since this code is run
       * as standalone process
       *(system will free all memory allocated by process at it exit).
       *
       * But this must be fixed for clear valgrind memory leak detection.
       */
      static char cache_key_value[KEYLEN] = "";
      static char *cache_key_url = NULL;
  
      if (es && es->key_url)
      {
          if (cache_key_url && 0 == strcmp(cache_key_url, es->key_url))
          {
              memcpy(es->key_value, cache_key_value, KEYLEN);
          }
          else if (hls_args.key_value)
          {
              memcpy(es->key_value, hls_args.key_value, KEYLEN);
              memcpy(cache_key_value, es->key_value, KEYLEN);
              free(cache_key_url);
              cache_key_url = strdup(es->key_url);
          } else
          {
              char *key_url = NULL;
              char *key_value = NULL;
              size_t size = 0;
              long http_code = 0;
  
              if (NULL != hls_args.key_uri_replace_old && \
                  NULL != hls_args.key_uri_replace_new && \
                  '\0' != hls_args.key_uri_replace_old[0]) {
                  key_url = repl_str(es->key_url, hls_args.key_uri_replace_old, hls_args.key_uri_replace_new);
              } else {
                  key_url = es->key_url;
              }
  
              http_code = get_hls_data_from_url(key_url, &key_value, &size, BINKEY, NULL);
              if (es->key_url != key_url) {
                  free(key_url);
              }
  
              if (http_code != 200 || size == 0) {
                  MSG_ERROR("Getting key-file [%s] failed http_code[%d].\n", es->key_url, http_code);
                  return 1;
              }
  
              memcpy(es->key_value, key_value, KEYLEN);
              free(key_value);
  
              free(cache_key_url);
              cache_key_url = strdup(es->key_url);
              memcpy(cache_key_value, es->key_value, KEYLEN);
          }
  
          free(es->key_url);
          es->key_url = NULL;
      }
  
      return 0;
  }
  