diff --git a/player/audio.c b/player/audio.c
index 5fcc2cbd22..2491051a54 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -92,6 +92,13 @@ int reinit_audio_filters(struct MPContext *mpctx)
     return 1;
 }
 
+void reset_audio_state(struct MPContext *mpctx)
+{
+    if (mpctx->d_audio)
+        audio_reset_decoding(mpctx->d_audio);
+    mpctx->audio_status = mpctx->d_audio ? STATUS_SYNCING : STATUS_EOF;
+}
+
 void reinit_audio_chain(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
@@ -117,6 +124,7 @@ void reinit_audio_chain(struct MPContext *mpctx)
         mpctx->d_audio->replaygain_data = sh->audio->replaygain_data;
         if (!audio_init_best_codec(mpctx->d_audio, opts->audio_decoders))
             goto init_error;
+        reset_audio_state(mpctx);
     }
     assert(mpctx->d_audio);
 
diff --git a/player/core.h b/player/core.h
index b49412164e..c24a6c1f94 100644
--- a/player/core.h
+++ b/player/core.h
@@ -374,6 +374,7 @@ typedef struct MPContext {
 } MPContext;
 
 // audio.c
+void reset_audio_state(struct MPContext *mpctx);
 void reinit_audio_chain(struct MPContext *mpctx);
 int reinit_audio_filters(struct MPContext *mpctx);
 double playing_audio_pts(struct MPContext *mpctx);
@@ -450,6 +451,7 @@ void set_osd_function(struct MPContext *mpctx, int osd_function);
 void set_osd_subtitle(struct MPContext *mpctx, const char *text);
 
 // playloop.c
+void reset_playback_state(struct MPContext *mpctx);
 void pause_player(struct MPContext *mpctx);
 void unpause_player(struct MPContext *mpctx);
 void add_step_frame(struct MPContext *mpctx, int dir);
@@ -481,6 +483,7 @@ struct mp_scripting {
 void mp_load_scripts(struct MPContext *mpctx);
 
 // sub.c
+void reset_subtitle_state(struct MPContext *mpctx);
 void reset_subtitles(struct MPContext *mpctx, int order);
 void uninit_subs(struct demuxer *demuxer);
 void reinit_subs(struct MPContext *mpctx, int order);
@@ -495,6 +498,7 @@ void build_mpv_edl_timeline(struct MPContext *mpctx);
 void build_cue_timeline(struct MPContext *mpctx);
 
 // video.c
+void reset_video_state(struct MPContext *mpctx);
 int reinit_video_chain(struct MPContext *mpctx);
 int reinit_video_filters(struct MPContext *mpctx);
 int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok,
diff --git a/player/loadfile.c b/player/loadfile.c
index e26968000b..bcb9e900de 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -1251,8 +1251,6 @@ goto_reopen_demuxer: ;
 
     MP_VERBOSE(mpctx, "Starting playback...\n");
 
-    mpctx->drop_frame_cnt = 0;
-    mpctx->dropped_frames = 0;
     mpctx->max_frames = opts->play_frames;
 
     if (mpctx->max_frames == 0) {
@@ -1260,27 +1258,18 @@ goto_reopen_demuxer: ;
         goto terminate_playback;
     }
 
-    mpctx->time_frame = 0;
-    mpctx->drop_message_shown = 0;
-    mpctx->video_pts = 0;
+    reset_playback_state(mpctx);
+
     mpctx->last_vo_pts = MP_NOPTS_VALUE;
-    mpctx->last_frame_duration = 0;
-    mpctx->last_seek_pts = 0;
-    mpctx->playback_pts = MP_NOPTS_VALUE;
-    mpctx->hrseek_active = false;
-    mpctx->hrseek_framedrop = false;
-    mpctx->step_frames = 0;
-    mpctx->backstep_active = false;
-    mpctx->total_avsync_change = 0;
     mpctx->last_chapter_seek = -2;
-    mpctx->playing_msg_shown = false;
+    mpctx->last_chapter_pts = MP_NOPTS_VALUE;
+    mpctx->last_chapter = -2;
     mpctx->paused = false;
     mpctx->paused_for_cache = false;
-    mpctx->last_chapter = -2;
+    mpctx->playing_msg_shown = false;
+    mpctx->step_frames = 0;
+    mpctx->backstep_active = false;
     mpctx->seek = (struct seek_params){ 0 };
-    mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
-    mpctx->audio_status = mpctx->d_audio ? STATUS_SYNCING : STATUS_EOF;
-    mpctx->restart_complete = false;
 
     // If there's a timeline force an absolute seek to initialize state
     double startpos = rel_time_to_abs(mpctx, opts->play_start);
diff --git a/player/playloop.c b/player/playloop.c
index cfce7aea19..60caf39389 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -146,43 +146,22 @@ void add_step_frame(struct MPContext *mpctx, int dir)
     }
 }
 
-static void seek_reset(struct MPContext *mpctx, bool reset_ao)
+// Clear some playback-related fields on file loading or after seeks.
+void reset_playback_state(struct MPContext *mpctx)
 {
-    if (mpctx->d_video) {
-        video_reset_decoding(mpctx->d_video);
-        vo_seek_reset(mpctx->video_out);
-    }
+    reset_video_state(mpctx);
+    reset_audio_state(mpctx);
+    reset_subtitle_state(mpctx);
 
-    if (mpctx->d_audio) {
-        audio_reset_decoding(mpctx->d_audio);
-        if (reset_ao)
-            clear_audio_output_buffers(mpctx);
-    }
-
-    reset_subtitles(mpctx, 0);
-    reset_subtitles(mpctx, 1);
-
-    mpctx->video_pts = MP_NOPTS_VALUE;
-    mpctx->video_next_pts = MP_NOPTS_VALUE;
-    mpctx->playing_last_frame = false;
-    mpctx->last_frame_duration = 0;
-    mpctx->delay = 0;
-    mpctx->time_frame = 0;
     mpctx->hrseek_active = false;
     mpctx->hrseek_framedrop = false;
-    mpctx->total_avsync_change = 0;
-    mpctx->drop_frame_cnt = 0;
-    mpctx->dropped_frames = 0;
     mpctx->playback_pts = MP_NOPTS_VALUE;
-    mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
-    mpctx->audio_status = mpctx->d_audio ? STATUS_SYNCING : STATUS_EOF;
+    mpctx->last_seek_pts = MP_NOPTS_VALUE;
     mpctx->restart_complete = false;
 
 #if HAVE_ENCODING
     encode_lavc_discontinuity(mpctx->encode_lavc_ctx);
 #endif
-
-    mp_notify(mpctx, MPV_EVENT_SEEK, NULL);
 }
 
 // return -1 if seek failed (non-seekable stream?), 0 otherwise
@@ -290,7 +269,10 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
         }
     }
 
-    seek_reset(mpctx, !timeline_fallthrough);
+    if (!timeline_fallthrough)
+        clear_audio_output_buffers(mpctx);
+
+    reset_playback_state(mpctx);
 
     if (timeline_fallthrough) {
         // Important if video reinit happens.
@@ -305,8 +287,7 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
     if (seek.type == MPSEEK_ABSOLUTE) {
         mpctx->video_pts = seek.amount;
         mpctx->last_seek_pts = seek.amount;
-    } else
-        mpctx->last_seek_pts = MP_NOPTS_VALUE;
+    }
 
     // The hr_seek==false case is for skipping frames with PTS before the
     // current timeline chapter start. It's not really known where the demuxer
@@ -324,6 +305,7 @@ static int mp_seek(MPContext *mpctx, struct seek_params seek,
     mpctx->start_timestamp = mp_time_sec();
     mpctx->sleeptime = 0;
 
+    mp_notify(mpctx, MPV_EVENT_SEEK, NULL);
     mp_notify(mpctx, MPV_EVENT_TICK, NULL);
 
     return 0;
diff --git a/player/sub.c b/player/sub.c
index 2ee928794c..1a9fdcafe7 100644
--- a/player/sub.c
+++ b/player/sub.c
@@ -76,6 +76,12 @@ void reset_subtitles(struct MPContext *mpctx, int order)
     osd_set_text(mpctx->osd, obj, NULL);
 }
 
+void reset_subtitle_state(struct MPContext *mpctx)
+{
+    reset_subtitles(mpctx, 0);
+    reset_subtitles(mpctx, 1);
+}
+
 static void update_subtitle(struct MPContext *mpctx, int order)
 {
     struct MPOpts *opts = mpctx->opts;
diff --git a/player/video.c b/player/video.c
index dacf1200c5..713849798b 100644
--- a/player/video.c
+++ b/player/video.c
@@ -168,6 +168,26 @@ int reinit_video_filters(struct MPContext *mpctx)
     return d_video->vfilter->initialized;
 }
 
+void reset_video_state(struct MPContext *mpctx)
+{
+    if (mpctx->d_video)
+        video_reset_decoding(mpctx->d_video);
+    if (mpctx->video_out)
+        vo_seek_reset(mpctx->video_out);
+
+    mpctx->delay = 0;
+    mpctx->time_frame = 0;
+    mpctx->video_next_pts = MP_NOPTS_VALUE;
+    mpctx->playing_last_frame = false;
+    mpctx->last_frame_duration = 0;
+    mpctx->total_avsync_change = 0;
+    mpctx->drop_frame_cnt = 0;
+    mpctx->dropped_frames = 0;
+    mpctx->drop_message_shown = 0;
+
+    mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
+}
+
 int reinit_video_chain(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
@@ -223,21 +243,15 @@ int reinit_video_chain(struct MPContext *mpctx)
     vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE
                                                : VOCTRL_RESUME, NULL);
 
-    mpctx->video_status = STATUS_SYNCING;
     mpctx->sync_audio_to_video = !sh->attached_picture;
-    mpctx->delay = 0;
-    mpctx->video_next_pts = MP_NOPTS_VALUE;
-    mpctx->playing_last_frame = false;
-    mpctx->last_frame_duration = 0;
     mpctx->vo_pts_history_seek_ts++;
 
     // If we switch on video again, ensure audio position matches up.
     if (mpctx->d_audio)
         mpctx->audio_status = STATUS_SYNCING;
 
-    vo_seek_reset(mpctx->video_out);
-    reset_subtitles(mpctx, 0);
-    reset_subtitles(mpctx, 1);
+    reset_video_state(mpctx);
+    reset_subtitle_state(mpctx);
 
     if (opts->force_fps) {
         d_video->fps = opts->force_fps;