From 68aeb29c491a5807e2a02a804016fca03976eaf2 Mon Sep 17 00:00:00 2001
From: Ole Andre Birkedal <o.birkedal@sportradar.com>
Date: Tue, 22 Oct 2019 12:30:58 +0200
Subject: [PATCH] Added support for zulu timezone format in HLS playlists

---
 doc/muxers.texi           | 3 +++
 libavformat/dashenc.c     | 2 +-
 libavformat/hlsenc.c      | 7 +++++--
 libavformat/hlsplaylist.c | 8 +++++++-
 libavformat/hlsplaylist.h | 2 +-
 5 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 4c88b5daec..e0cacd5c40 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -874,6 +874,9 @@ seeking. This flag should be used with the @code{hls_time} option.
 @item program_date_time
 Generate @code{EXT-X-PROGRAM-DATE-TIME} tags.
 
+@item zulu_timezone
+Add @code{Z} behind timestamps in @code{EXT-X-PROGRAM-DATE-TIME} that have a zero offset from UTC.
+
 @item second_level_segment_index
 Makes it possible to use segment indexes as %%d in hls_segment_filename expression
 besides date/time values when strftime is on.
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index a462876c13..b3f5e7c174 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -525,7 +525,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
                                 (double) seg->duration / timescale, 0,
                                 seg->range_length, seg->start_pos, NULL,
                                 c->single_file ? os->initfile : seg->file,
-                                &prog_date_time, 0, 0, 0);
+                                &prog_date_time, 0, 0, 0, 0);
         if (ret < 0) {
             av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
         }
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 7b1d54e23e..d2cc5e37ff 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -102,6 +102,7 @@ typedef enum HLSFlags {
     HLS_PERIODIC_REKEY = (1 << 12),
     HLS_INDEPENDENT_SEGMENTS = (1 << 13),
     HLS_I_FRAMES_ONLY = (1 << 14),
+    HLS_ZULU_TIMEZONE = (1 << 15),
 } HLSFlags;
 
 typedef enum {
@@ -1433,6 +1434,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
     AVDictionary *options = NULL;
     double prog_date_time = vs->initial_prog_date_time;
     double *prog_date_time_p = (hls->flags & HLS_PROGRAM_DATE_TIME) ? &prog_date_time : NULL;
+    int zulu_time = hls->flags & HLS_ZULU_TIMEZONE;
     int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
 
     hls->version = 3;
@@ -1495,7 +1497,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         ret = ff_hls_write_file_entry(byterange_mode ? hls->m3u8_out : vs->out, en->discont, byterange_mode,
                                       en->duration, hls->flags & HLS_ROUND_DURATIONS,
                                       en->size, en->pos, vs->baseurl,
-                                      en->filename, prog_date_time_p, en->keyframe_size, en->keyframe_pos, hls->flags & HLS_I_FRAMES_ONLY);
+                                      en->filename, prog_date_time_p, zulu_time, en->keyframe_size, en->keyframe_pos, hls->flags & HLS_I_FRAMES_ONLY);
         if (ret < 0) {
             av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
         }
@@ -1516,7 +1518,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs)
         for (en = vs->segments; en; en = en->next) {
             ret = ff_hls_write_file_entry(hls->sub_m3u8_out, 0, byterange_mode,
                                           en->duration, 0, en->size, en->pos,
-                                          vs->baseurl, en->sub_filename, NULL, 0, 0, 0);
+                                          vs->baseurl, en->sub_filename, NULL, zulu_time, 0, 0, 0);
             if (ret < 0) {
                 av_log(s, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
             }
@@ -3020,6 +3022,7 @@ static const AVOption options[] = {
     {"split_by_time", "split the hls segment by time which user set by hls_time", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SPLIT_BY_TIME }, 0, UINT_MAX,   E, "flags"},
     {"append_list", "append the new segments into old hls segment list", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_APPEND_LIST }, 0, UINT_MAX,   E, "flags"},
     {"program_date_time", "add EXT-X-PROGRAM-DATE-TIME", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_PROGRAM_DATE_TIME }, 0, UINT_MAX,   E, "flags"},
+    {"zulu_timezone", "Add a Z to the end of times that have no offset from UTC", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_ZULU_TIMEZONE }, 0, UINT_MAX, E, "flags"},
     {"second_level_segment_index", "include segment index in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_INDEX }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_duration", "include segment duration in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_DURATION }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_size", "include segment size in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_SIZE }, 0, UINT_MAX,   E, "flags"},
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 9cbd02353f..4304a51a0f 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -111,7 +111,7 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time,
+                             char *filename, double *prog_date_time, int zulu_time,
                              int64_t video_keyframe_size, int64_t video_keyframe_pos, int iframe_mode) {
     if (!out || !filename)
         return AVERROR(EINVAL);
@@ -151,6 +151,12 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                      tz_min / 60,
                      tz_min % 60);
         }
+
+        if (!strcmp(buf1, "+0000") && zulu_time) {
+          memset(buf1, '\0', sizeof(buf1));
+          buf1[0] = 'Z';
+        }
+
         avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
         *prog_date_time += duration;
     }
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index a8d29d62d3..7de2b3f616 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -52,7 +52,7 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
                              double duration, int round_duration,
                              int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set
                              char *baseurl, //Ignored if NULL
-                             char *filename, double *prog_date_time,
+                             char *filename, double *prog_date_time, int zulu_time,
                              int64_t video_keyframe_size, int64_t video_keyframe_pos, int iframe_mode);
 void ff_hls_write_end_list (AVIOContext *out);
 
-- 
2.19.1

