diff --git a/doc/muxers.texi b/doc/muxers.texi index 351cd8c5c4..26a8f2dbc3 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -416,9 +416,34 @@ This option is useful to avoid to fill the disk with many segment files, and limits the maximum number of segment files written to disk to @var{wrap}. + +@item hls_start_number_source +Start the playlist sequence number (@code{#EXT-X-MEDIA-SEQUENCE}) according to the specified source. +Unless @code{hls_flags single_file} is set, it also specifies source of starting sequence numbers of +segment and subtitle filenames. In any case, if @code{hls_flags append_list} +is set and read playlist sequence number is greater than the specified start sequence number, +then that value will be used as start value. + +It accepts the following values: + +@table @option + +@item generic (default) +Set the starting sequence numbers according to @var{start_number} option value. + +@item epoch +The start number will be the seconds since epoch (1970-01-01 00:00:00) + +@item datetime +The start number will be based on the current date/time as YYYYmmddHHMMSS. e.g. 20161231235759. + +@end table + @item start_number @var{number} -Start the playlist sequence number from @var{number}. Default value is -0. +Start the playlist sequence number (@code{#EXT-X-MEDIA-SEQUENCE}) from the specified @var{number} +when @var{hls_start_number_source} value is @var{generic}. (This is the default case.) +Unless @code{hls_flags single_file} is set, it also specifies starting sequence numbers of segment and subtitle filenames. +Default value is 0. @item hls_allow_cache @var{allowcache} Explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments. diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index a2c606cb6e..3bb15d3f92 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -39,6 +39,12 @@ #include "internal.h" #include "os_support.h" +typedef enum { + HLS_START_SEQUENCE_AS_START_NUMBER = 0, + HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH = 1, + HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2, // YYYYMMDDhhmmss +} StartSequenceSourceType; + #define KEYSIZE 16 #define LINE_BUFFER_SIZE 1024 @@ -83,6 +89,7 @@ typedef struct HLSContext { unsigned number; int64_t sequence; int64_t start_sequence; + uint32_t start_sequence_source_type; // enum StartSequenceSourceType AVOutputFormat *oformat; AVOutputFormat *vtt_oformat; @@ -594,7 +601,16 @@ static int parse_playlist(AVFormatContext *s, const char *url) while (!avio_feof(in)) { read_chomp_line(in, line, sizeof(line)); if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { - hls->sequence = atoi(ptr); + int64_t tmp_sequence = strtoll(ptr, NULL, 10); + if (tmp_sequence < hls->sequence) + av_log(hls, AV_LOG_VERBOSE, + "Found playlist sequence number was smaller """ + "than specified start sequence number: %"PRId64" < %"PRId64", " + "omitting\n", tmp_sequence, hls->start_sequence); + else { + av_log(hls, AV_LOG_DEBUG, "Found playlist sequence number: %"PRId64"\n", tmp_sequence); + hls->sequence = tmp_sequence; + } } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) { is_segment = 1; hls->discontinuity = 1; @@ -805,9 +821,8 @@ static int hls_start(AVFormatContext *s) av_strlcpy(vtt_oc->filename, c->vtt_basename, sizeof(vtt_oc->filename)); } else if (c->max_seg_size > 0) { - if (av_get_frame_filename2(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), + c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename); return AVERROR(EINVAL); } @@ -882,16 +897,14 @@ static int hls_start(AVFormatContext *s) } av_free(fn_copy); } - } else if (av_get_frame_filename2(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + } else if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), + c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try to use -use_localtime 1 with it\n", c->basename); return AVERROR(EINVAL); } if( c->vtt_basename) { - if (av_get_frame_filename2(vtt_oc->filename, sizeof(vtt_oc->filename), - c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + if (replace_int_data_in_filename(vtt_oc->filename, sizeof(vtt_oc->filename), + c->vtt_basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename); return AVERROR(EINVAL); } @@ -979,6 +992,22 @@ static int hls_write_header(AVFormatContext *s) int basename_size; int vtt_basename_size; + if (hls->start_sequence_source_type == HLS_START_SEQUNCE_AS_SECONDS_SINCE_EPOCH || hls->start_sequence_source_type == HLS_START_SEQUNCE_AS_FORMATTED_DATETIME) { + time_t t = time(NULL); // we will need it in either case + if (hls->start_sequence_source_type == HLS_START_SEQUNCE_AS_SECONDS_SINCE_EPOCH) { + hls->start_sequence = (int64_t)t; + } else if (hls->start_sequence_source_type == HLS_START_SEQUNCE_AS_FORMATTED_DATETIME) { + char b[15]; + struct tm *p, tmbuf; + if (!(p = localtime_r(&t, &tmbuf))) + return AVERROR(ENOMEM); + if (!strftime(b, sizeof(b), "%Y%m%d%H%M%S", p)) + return AVERROR(ENOMEM); + hls->start_sequence = strtoll(b, NULL, 10); + } + av_log(hls, AV_LOG_DEBUG, "start_number evaluated to %"PRId64"\n", hls->start_sequence); + } + hls->sequence = hls->start_sequence; hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE; hls->start_pts = AV_NOPTS_VALUE; @@ -1366,7 +1395,10 @@ static const AVOption options[] = { {"event", "EVENT playlist", 0, AV_OPT_TYPE_CONST, {.i64 = PLAYLIST_TYPE_EVENT }, INT_MIN, INT_MAX, E, "pl_type" }, {"vod", "VOD playlist", 0, AV_OPT_TYPE_CONST, {.i64 = PLAYLIST_TYPE_VOD }, INT_MIN, INT_MAX, E, "pl_type" }, {"method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, - + {"hls_start_number_source", "set source of first number in sequence", OFFSET(start_sequence_source_type), AV_OPT_TYPE_INT, {.i64 = HLS_START_SEQUNCE_AS_START_NUMBER }, 0, HLS_START_SEQUNCE_AS_FORMATTED_DATETIME, E, "start_sequence_source_type" }, + {"generic", "start_number value (default)", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUNCE_AS_START_NUMBER }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, + {"epoch", "seconds since epoch", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUNCE_AS_SECONDS_SINCE_EPOCH }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, + {"datetime", "current datetime as YYYYMMDDhhmmss", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUNCE_AS_FORMATTED_DATETIME }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, { NULL }, };