diff --git a/demux/demux.c b/demux/demux.c
index 63d4136cb0..87366de06d 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -197,9 +197,6 @@ struct demux_internal {
 
     double ts_offset;           // timestamp offset to apply to everything
 
-    void (*run_fn)(void *);     // if non-NULL, function queued to be run on
-    void *run_fn_arg;           // the thread as run_fn(run_fn_arg)
-
     // (sorted by least recent use: index 0 is least recently used)
     struct demux_cached_range **ranges;
     int num_ranges;
@@ -1768,12 +1765,6 @@ static void execute_seek(struct demux_internal *in)
 // Make demuxing progress. Return whether progress was made.
 static bool thread_work(struct demux_internal *in)
 {
-    if (in->run_fn) {
-        in->run_fn(in->run_fn_arg);
-        in->run_fn = NULL;
-        pthread_cond_signal(&in->wakeup);
-        return true;
-    }
     if (in->tracks_switched) {
         execute_trackswitch(in);
         return true;
@@ -3116,133 +3107,73 @@ static void update_cache(struct demux_internal *in)
     pthread_mutex_unlock(&in->lock);
 }
 
-// must be called locked
-static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
-{
-    switch (cmd) {
-    case DEMUXER_CTRL_GET_BITRATE_STATS: {
-        double *rates = arg;
-        for (int n = 0; n < STREAM_TYPE_COUNT; n++)
-            rates[n] = -1;
-        for (int n = 0; n < in->num_streams; n++) {
-            struct demux_stream *ds = in->streams[n]->ds;
-            if (ds->selected && ds->bitrate >= 0)
-                rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
-        }
-        return CONTROL_OK;
-    }
-    case DEMUXER_CTRL_GET_READER_STATE: {
-        struct demux_ctrl_reader_state *r = arg;
-        *r = (struct demux_ctrl_reader_state){
-            .eof = in->last_eof,
-            .ts_reader = MP_NOPTS_VALUE,
-            .ts_end = MP_NOPTS_VALUE,
-            .ts_duration = -1,
-            .total_bytes = in->total_bytes,
-            .fw_bytes = in->fw_bytes,
-            .seeking = in->seeking_in_progress,
-            .low_level_seeks = in->low_level_seeks,
-            .ts_last = in->demux_ts,
-            .bytes_per_second = in->bytes_per_second,
-        };
-        bool any_packets = false;
-        for (int n = 0; n < in->num_streams; n++) {
-            struct demux_stream *ds = in->streams[n]->ds;
-            if (ds->eager && !(!ds->queue->head && ds->eof) && !ds->ignore_eof)
-            {
-                r->underrun |= !ds->reader_head && !ds->eof && !ds->still_image;
-                r->ts_reader = MP_PTS_MAX(r->ts_reader, ds->base_ts);
-                r->ts_end = MP_PTS_MAX(r->ts_end, ds->queue->last_ts);
-                any_packets |= !!ds->reader_head;
-            }
-        }
-        r->idle = (in->idle && !r->underrun) || r->eof;
-        r->underrun &= !r->idle;
-        r->ts_reader = MP_ADD_PTS(r->ts_reader, in->ts_offset);
-        r->ts_end = MP_ADD_PTS(r->ts_end, in->ts_offset);
-        if (r->ts_reader != MP_NOPTS_VALUE && r->ts_reader <= r->ts_end)
-            r->ts_duration = r->ts_end - r->ts_reader;
-        if (in->seeking || !any_packets)
-            r->ts_duration = 0;
-        for (int n = 0; n < MPMIN(in->num_ranges, MAX_SEEK_RANGES); n++) {
-            struct demux_cached_range *range = in->ranges[n];
-            if (range->seek_start != MP_NOPTS_VALUE) {
-                r->seek_ranges[r->num_seek_ranges++] =
-                    (struct demux_seek_range){
-                        .start = MP_ADD_PTS(range->seek_start, in->ts_offset),
-                        .end = MP_ADD_PTS(range->seek_end, in->ts_offset),
-                    };
-            }
-        }
-        return CONTROL_OK;
-    }
-    }
-    return CONTROL_UNKNOWN;
-}
-
-struct demux_control_args {
-    struct demuxer *demuxer;
-    int cmd;
-    void *arg;
-    int *r;
-};
-
-static void thread_demux_control(void *p)
-{
-    struct demux_control_args *args = p;
-    struct demuxer *demuxer = args->demuxer;
-    int cmd = args->cmd;
-    void *arg = args->arg;
-    struct demux_internal *in = demuxer->in;
-    int r = CONTROL_UNKNOWN;
-
-    pthread_mutex_unlock(&in->lock);
-
-    if (r != CONTROL_OK) {
-        if (in->threading)
-            MP_VERBOSE(demuxer, "blocking for DEMUXER_CTRL %d\n", cmd);
-        if (demuxer->desc->control)
-            r = demuxer->desc->control(demuxer->in->d_thread, cmd, arg);
-    }
-
-    pthread_mutex_lock(&in->lock);
-
-    *args->r = r;
-}
-
-int demux_control(demuxer_t *demuxer, int cmd, void *arg)
+void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates)
 {
     struct demux_internal *in = demuxer->in;
     assert(demuxer == in->d_user);
 
-    if (in->threading) {
-        pthread_mutex_lock(&in->lock);
-        int cr = cached_demux_control(in, cmd, arg);
-        pthread_mutex_unlock(&in->lock);
-        if (cr != CONTROL_UNKNOWN)
-            return cr;
+    pthread_mutex_lock(&in->lock);
+
+    for (int n = 0; n < STREAM_TYPE_COUNT; n++)
+        rates[n] = -1;
+    for (int n = 0; n < in->num_streams; n++) {
+        struct demux_stream *ds = in->streams[n]->ds;
+        if (ds->selected && ds->bitrate >= 0)
+            rates[ds->type] = MPMAX(0, rates[ds->type]) + ds->bitrate;
     }
 
-    int r = 0;
-    struct demux_control_args args = {demuxer, cmd, arg, &r};
-    if (in->threading) {
-        MP_VERBOSE(in, "blocking on demuxer thread\n");
-        pthread_mutex_lock(&in->lock);
-        while (in->run_fn)
-            pthread_cond_wait(&in->wakeup, &in->lock);
-        in->run_fn = thread_demux_control;
-        in->run_fn_arg = &args;
-        pthread_cond_signal(&in->wakeup);
-        while (in->run_fn)
-            pthread_cond_wait(&in->wakeup, &in->lock);
-        pthread_mutex_unlock(&in->lock);
-    } else {
-        pthread_mutex_lock(&in->lock);
-        thread_demux_control(&args);
-        pthread_mutex_unlock(&in->lock);
+    pthread_mutex_unlock(&in->lock);
+}
+
+void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r)
+{
+    struct demux_internal *in = demuxer->in;
+    assert(demuxer == in->d_user);
+
+    pthread_mutex_lock(&in->lock);
+
+    *r = (struct demux_reader_state){
+        .eof = in->last_eof,
+        .ts_reader = MP_NOPTS_VALUE,
+        .ts_end = MP_NOPTS_VALUE,
+        .ts_duration = -1,
+        .total_bytes = in->total_bytes,
+        .fw_bytes = in->fw_bytes,
+        .seeking = in->seeking_in_progress,
+        .low_level_seeks = in->low_level_seeks,
+        .ts_last = in->demux_ts,
+        .bytes_per_second = in->bytes_per_second,
+    };
+    bool any_packets = false;
+    for (int n = 0; n < in->num_streams; n++) {
+        struct demux_stream *ds = in->streams[n]->ds;
+        if (ds->eager && !(!ds->queue->head && ds->eof) && !ds->ignore_eof) {
+            r->underrun |= !ds->reader_head && !ds->eof && !ds->still_image;
+            r->ts_reader = MP_PTS_MAX(r->ts_reader, ds->base_ts);
+            r->ts_end = MP_PTS_MAX(r->ts_end, ds->queue->last_ts);
+            any_packets |= !!ds->reader_head;
+        }
+    }
+    r->idle = (in->idle && !r->underrun) || r->eof;
+    r->underrun &= !r->idle;
+    r->ts_reader = MP_ADD_PTS(r->ts_reader, in->ts_offset);
+    r->ts_end = MP_ADD_PTS(r->ts_end, in->ts_offset);
+    if (r->ts_reader != MP_NOPTS_VALUE && r->ts_reader <= r->ts_end)
+        r->ts_duration = r->ts_end - r->ts_reader;
+    if (in->seeking || !any_packets)
+        r->ts_duration = 0;
+    for (int n = 0; n < MPMIN(in->num_ranges, MAX_SEEK_RANGES); n++) {
+        struct demux_cached_range *range = in->ranges[n];
+        if (range->seek_start != MP_NOPTS_VALUE) {
+            r->seek_ranges[r->num_seek_ranges++] =
+                (struct demux_seek_range){
+                    .start = MP_ADD_PTS(range->seek_start, in->ts_offset),
+                    .end = MP_ADD_PTS(range->seek_end, in->ts_offset),
+                };
+        }
     }
 
-    return r;
+    pthread_mutex_unlock(&in->lock);
 }
 
 bool demux_cancel_test(struct demuxer *demuxer)
diff --git a/demux/demux.h b/demux/demux.h
index 27df83556e..540aeff5bb 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -32,8 +32,6 @@
 
 enum demux_ctrl {
     DEMUXER_CTRL_SWITCHED_TRACKS = 1,
-    DEMUXER_CTRL_GET_READER_STATE,
-    DEMUXER_CTRL_GET_BITRATE_STATS, // double[STREAM_TYPE_COUNT]
     DEMUXER_CTRL_REPLACE_STREAM,
 };
 
@@ -43,7 +41,7 @@ struct demux_seek_range {
     double start, end;
 };
 
-struct demux_ctrl_reader_state {
+struct demux_reader_state {
     bool eof, underrun, idle;
     double ts_duration;
     double ts_reader; // approx. timerstamp of decoder position
@@ -60,12 +58,6 @@ struct demux_ctrl_reader_state {
     struct demux_seek_range seek_ranges[MAX_SEEK_RANGES];
 };
 
-struct demux_ctrl_stream_ctrl {
-    int ctrl;
-    void *arg;
-    int res;
-};
-
 #define SEEK_FACTOR   (1 << 1)      // argument is in range [0,1]
 #define SEEK_FORWARD  (1 << 2)      // prefer later time if not exact
                                     // (if unset, prefer earlier time)
@@ -291,7 +283,8 @@ void demux_flush(struct demuxer *demuxer);
 int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
 void demux_set_ts_offset(struct demuxer *demuxer, double offset);
 
-int demux_control(struct demuxer *demuxer, int cmd, void *arg);
+void demux_get_bitrate_stats(struct demuxer *demuxer, double *rates);
+void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *r);
 
 void demux_block_reading(struct demuxer *demuxer, bool block);
 
diff --git a/player/command.c b/player/command.c
index 7efbb9ca40..f816380b46 100644
--- a/player/command.c
+++ b/player/command.c
@@ -1423,9 +1423,8 @@ static int mp_property_cache_speed(void *ctx, struct m_property *prop,
     if (!mpctx->demuxer)
         return M_PROPERTY_UNAVAILABLE;
 
-    struct demux_ctrl_reader_state s;
-    if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     uint64_t val = s.bytes_per_second;
 
@@ -1443,9 +1442,8 @@ static int mp_property_demuxer_cache_duration(void *ctx, struct m_property *prop
     if (!mpctx->demuxer)
         return M_PROPERTY_UNAVAILABLE;
 
-    struct demux_ctrl_reader_state s;
-    if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     if (s.ts_duration < 0)
         return M_PROPERTY_UNAVAILABLE;
@@ -1460,9 +1458,8 @@ static int mp_property_demuxer_cache_time(void *ctx, struct m_property *prop,
     if (!mpctx->demuxer)
         return M_PROPERTY_UNAVAILABLE;
 
-    struct demux_ctrl_reader_state s;
-    if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     if (s.ts_end == MP_NOPTS_VALUE)
         return M_PROPERTY_UNAVAILABLE;
@@ -1477,9 +1474,8 @@ static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop,
     if (!mpctx->demuxer)
         return M_PROPERTY_UNAVAILABLE;
 
-    struct demux_ctrl_reader_state s;
-    if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     return m_property_flag_ro(action, arg, s.idle);
 }
@@ -1498,9 +1494,8 @@ static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop,
     if (action != M_PROPERTY_GET)
         return M_PROPERTY_NOT_IMPLEMENTED;
 
-    struct demux_ctrl_reader_state s;
-    if (demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     struct mpv_node *r = (struct mpv_node *)arg;
     node_init(r, MPV_FORMAT_NODE_MAP, NULL);
@@ -3030,8 +3025,7 @@ static int mp_property_packet_bitrate(void *ctx, struct m_property *prop,
         return M_PROPERTY_UNAVAILABLE;
 
     double r[STREAM_TYPE_COUNT];
-    if (demux_control(demuxer, DEMUXER_CTRL_GET_BITRATE_STATS, &r) < 1)
-        return M_PROPERTY_UNAVAILABLE;
+    demux_get_bitrate_stats(demuxer, r);
     if (r[type] < 0)
         return M_PROPERTY_UNAVAILABLE;
 
diff --git a/player/osd.c b/player/osd.c
index 7d24c01619..00bef58e6b 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -232,8 +232,8 @@ static char *get_term_status_msg(struct MPContext *mpctx)
     if (mpctx->demuxer && demux_is_network_cached(mpctx->demuxer)) {
         saddf(&line, " Cache: ");
 
-        struct demux_ctrl_reader_state s = {.ts_duration = -1};
-        demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s);
+        struct demux_reader_state s;
+        demux_get_reader_state(mpctx->demuxer, &s);
 
         if (s.ts_duration < 0) {
             saddf(&line, "???");
diff --git a/player/playloop.c b/player/playloop.c
index ef075804ae..a2aabe5d84 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -625,8 +625,8 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx)
 
     double now = mp_time_sec();
 
-    struct demux_ctrl_reader_state s = {.idle = true, .ts_duration = -1};
-    demux_control(mpctx->demuxer, DEMUXER_CTRL_GET_READER_STATE, &s);
+    struct demux_reader_state s;
+    demux_get_reader_state(mpctx->demuxer, &s);
 
     int cache_buffer = 100;
     bool use_pause_on_low_cache = demux_is_network_cached(mpctx->demuxer) &&