diff --git a/libavformat/isom.h b/libavformat/isom.h
index bbe395ee4b..7362c876be 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -169,11 +169,14 @@ typedef struct MOVStreamContext {
     int64_t *chunk_offsets;
     unsigned int stts_count;
     MOVStts *stts_data;
+    uint32_t min_sample_duration;
     unsigned int sdtp_count;
     uint8_t *sdtp_data;
     unsigned int ctts_count;
     unsigned int ctts_allocated_size;
     MOVCtts *ctts_data;
+    int32_t *sample_offsets;
+    int sample_offsets_count;
     unsigned int stsc_count;
     MOVStsc *stsc_data;
     unsigned int stsc_index;
@@ -222,6 +225,8 @@ typedef struct MOVStreamContext {
     MOVSbgp *sync_group;
     uint8_t *sgpd_sync;
     uint32_t sgpd_sync_count;
+    int *open_key_samples;
+    int open_key_samples_count;
 
     int nb_frames_for_fps;
     int64_t duration_for_fps;
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 6b22d8c5f8..919dd940c0 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -50,6 +50,7 @@
 #include "libavutil/dovi_meta.h"
 #include "libavcodec/ac3tab.h"
 #include "libavcodec/flac.h"
+#include "libavcodec/hevc.h"
 #include "libavcodec/mpegaudiodecheader.h"
 #include "libavcodec/mlp_parse.h"
 #include "avformat.h"
@@ -2948,6 +2949,7 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     if (entries >= INT_MAX / sizeof(*sc->stts_data))
         return AVERROR(ENOMEM);
 
+    sc->min_sample_duration = INT_MAX;
     for (i = 0; i < entries && !pb->eof_reached; i++) {
         unsigned int sample_duration;
         unsigned int sample_count;
@@ -2967,6 +2969,7 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
         sc->stts_data[i].count= sample_count;
         sc->stts_data[i].duration= sample_duration;
+        sc->min_sample_duration = FFMIN(sc->min_sample_duration, sample_duration);
 
         av_log(c->fc, AV_LOG_TRACE, "sample_count=%u, sample_duration=%u\n",
                 sample_count, sample_duration);
@@ -3069,6 +3072,7 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     AVStream *st;
     MOVStreamContext *sc;
     unsigned int i, entries, ctts_count = 0;
+    int32_t *sample_offsets;
 
     if (c->fc->nb_streams < 1)
         return 0;
@@ -3090,6 +3094,7 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     if (!sc->ctts_data)
         return AVERROR(ENOMEM);
 
+    sc->sample_offsets_count = 0;
     for (i = 0; i < entries && !pb->eof_reached; i++) {
         int count    = avio_rb32(pb);
         int duration = avio_rb32(pb);
@@ -3101,8 +3106,9 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
             continue;
         }
 
-        add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size,
-                       count, duration);
+        if (add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size,
+                           count, duration) >= 0)
+            sc->sample_offsets_count += count;
 
         av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n",
                 count, duration);
@@ -3127,6 +3133,15 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
     av_log(c->fc, AV_LOG_TRACE, "dts shift %d\n", sc->dts_shift);
 
+    av_freep(&sc->sample_offsets);
+    sc->sample_offsets = av_calloc(sc->sample_offsets_count, sizeof(*sc->sample_offsets));
+    if (!sc->sample_offsets)
+        return AVERROR(ENOMEM);
+    sample_offsets = sc->sample_offsets;
+    for (int i = 0; i < sc->ctts_count; i++)
+        for (int j = 0; j < sc->ctts_data[i].count; j++)
+             *sample_offsets++ = sc->ctts_data[i].duration;
+
     return 0;
 }
 
@@ -3882,6 +3897,52 @@ static void mov_fix_index(MOVContext *mov, AVStream *st)
     msc->current_index = msc->index_ranges[0].start;
 }
 
+static uint32_t get_sgpd_sync_index(const MOVStreamContext *sc, int nal_unit_type)
+{
+    for (uint32_t i = 0; i < sc->sgpd_sync_count; i++)
+        if (sc->sgpd_sync[i] == HEVC_NAL_CRA_NUT)
+            return i + 1;
+    return 0;
+}
+
+static int build_open_gop_key_points(AVStream *st)
+{
+    int k = 0;
+    int sample_id = 0;
+    int open_sample_count = 0;
+    uint32_t cra_index;
+    MOVStreamContext *sc = st->priv_data;
+
+    if (st->codecpar->codec_id != AV_CODEC_ID_HEVC || !sc->sync_group_count)
+        return 0;
+
+    /* The following HEVC NAL type reveal the use of open GOP sync points
+     * (TODO: BLA types may also be concerned) */
+    cra_index = get_sgpd_sync_index(sc, HEVC_NAL_CRA_NUT); /* Clean Random Access */
+    if (!cra_index)
+        return 0;
+
+    for (uint32_t i = 0; i < sc->sync_group_count; i++)
+        if (sc->sync_group[i].index == cra_index)
+            open_sample_count += sc->sync_group[i].count;
+
+    av_freep(&sc->open_key_samples);
+    sc->open_key_samples_count = open_sample_count;
+    sc->open_key_samples = av_calloc(open_sample_count, sizeof(*sc->open_key_samples));
+    if (!sc->open_key_samples)
+        return AVERROR(ENOMEM);
+
+    for (uint32_t i = 0; i < sc->sync_group_count; i++) {
+        const MOVSbgp *sg = &sc->sync_group[i];
+        if (sg->index == cra_index)
+            for (uint32_t j = 0; j < sg->count; j++)
+                sc->open_key_samples[k++] = sample_id;
+        sample_id += sg->count;
+    }
+
+    return 0;
+}
+
 static void mov_build_index(MOVContext *mov, AVStream *st)
 {
     MOVStreamContext *sc = st->priv_data;
@@ -3897,6 +3958,10 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
     MOVCtts *ctts_data_old = sc->ctts_data;
     unsigned int ctts_count_old = sc->ctts_count;
 
+    int ret = build_open_gop_key_points(st);
+    if (ret < 0)
+        return;
+
     if (sc->elst_count) {
         int i, edit_start_index = 0, multiple_edits = 0;
         int64_t empty_duration = 0; // empty duration of the first edit list entry
@@ -7772,6 +7837,8 @@ static int mov_read_close(AVFormatContext *s)
         av_freep(&sc->rap_group);
         av_freep(&sc->sync_group);
         av_freep(&sc->sgpd_sync);
+        av_freep(&sc->open_key_samples);
+        av_freep(&sc->sample_offsets);
         av_freep(&sc->display_matrix);
         av_freep(&sc->index_ranges);
 
@@ -8444,6 +8511,49 @@ static int mov_seek_fragment(AVFormatContext *s, AVStream *st, int64_t timestamp
     return 0;
 }
 
+static int is_open_key_sample(const MOVStreamContext *sc, int sample)
+{
+    // TODO: a bisect search would scale much better
+    for (int i = 0; i < sc->open_key_samples_count; i++) {
+        const int oks = sc->open_key_samples[i];
+        if (oks == sample)
+            return 1;
+        if (oks > sample) /* list is monotically increasing so we can stop early */
+            break;
+    }
+    return 0;
+}
+
+/*
+ * Some key sample may be key frames but not IDR frames, so a random access to
+ * them may not be allowed.
+ */
+static int can_seek_to_key_sample(AVStream *st, int sample, int64_t requested_pts)
+{
+    MOVStreamContext *sc = st->priv_data;
+    FFStream *const sti = ffstream(st);
+    int64_t key_sample_dts, key_sample_pts;
+
+    if (st->codecpar->codec_id != AV_CODEC_ID_HEVC)
+        return 1;
+
+    if (sample >= sc->sample_offsets_count)
+        return 1;
+
+    key_sample_dts = sti->index_entries[sample].timestamp;
+    key_sample_pts = key_sample_dts + sc->sample_offsets[sample] + sc->dts_shift;
+
+    /*
+     * If the sample needs to be presented before an open key sample, they may
+     * not be decodable properly, even though they come after in decoding
+     * order.
+     */
+    if (is_open_key_sample(sc, sample) && key_sample_pts > requested_pts)
+        return 0;
+
+    return 1;
+}
+
 static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp, int flags)
 {
     MOVStreamContext *sc = st->priv_data;
@@ -8459,12 +8569,19 @@ static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp,
     if (ret < 0)
         return ret;
 
+    for (;;) {
     sample = av_index_search_timestamp(st, timestamp, flags);
     av_log(s, AV_LOG_TRACE, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample);
     if (sample < 0 && sti->nb_index_entries && timestamp < sti->index_entries[0].timestamp)
         sample = 0;
     if (sample < 0) /* not sure what to do */
         return AVERROR_INVALIDDATA;
+
+        if (!sample || can_seek_to_key_sample(st, sample, timestamp))
+            break;
+        timestamp -= FFMAX(sc->min_sample_duration, 1);
+    }
+
     mov_current_sample_set(sc, sample);
     av_log(s, AV_LOG_TRACE, "stream %d, found sample %d\n", st->index, sc->current_sample);
     /* adjust ctts index */
