stream_dvdnav: add support for still frames

DVDNAV_STILL_FRAME means the VM is holding a frame indefinitely (menu
pages, some logos) or for a timed duration. Report the indefinite case
via nav state so the player holds playback at EOF instead of ending
the file, and skip timed stills, which would otherwise stall playback.
This commit is contained in:
Kacper Michajłow committed 2026-08-26 01:47:41 +02:00
1 parent fbcced5bf0
commit 3897b3a579
5 files changed
+83 -11

No files matched your search

+2
View File
@@ -447,6 +447,8 @@ typedef struct MPContext {
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct disc_nav_state *disc_nav;
// Demuxer parked on an indefinite still frame (DVD/BD menu).
bool disc_nav_still_frame;
struct encode_lavc_context *encode_lavc_ctx;
struct mp_option_callback *option_callbacks;
+16
View File
@@ -46,6 +46,10 @@ struct disc_nav_state {
uint32_t bd_last_change_id;
struct mp_osd_res bd_last_vo_res;
// Last menu-overlay change_id we forced an OSD redraw for.
uint32_t last_overlay_change_id;
bool overlay_change_seen;
// Last observed disc-nav discontinuity counter (bumped by the stream
// backend on user nav actions and on libbluray/libdvdnav-internal
// playlist/title/cell transitions).
@@ -441,6 +445,11 @@ void disc_nav_update(struct MPContext *mpctx)
struct disc_nav_state *st = get_state(mpctx);
struct stream_nav_state nav = {0};
bool have = s && stream_control(s, STREAM_CTRL_GET_NAV_STATE, &nav) >= 1;
bool still = have && nav.still_active;
if (s && still != mpctx->disc_nav_still_frame)
MP_VERBOSE(s, "still_frame %d->%d\n",
mpctx->disc_nav_still_frame, still);
mpctx->disc_nav_still_frame = still;
if (!have) {
if (st->overlay_visible) {
osd_set_bitmaps(mpctx->osd, OSDTYPE_DISC_MENU, NULL);
@@ -450,6 +459,7 @@ void disc_nav_update(struct MPContext *mpctx)
st->bd_last_change_id = 0;
st->bd_last_vo_res = (struct mp_osd_res){0};
st->menu_selected_track = NULL;
st->overlay_change_seen = false;
if (st->menu_active_seen && st->last_menu_active) {
st->last_menu_active = false;
mp_notify_property(mpctx, "disc-menu-active");
@@ -477,6 +487,12 @@ void disc_nav_update(struct MPContext *mpctx)
ensure_menu_sub_selection(mpctx, nav.menu_active);
}
if (!st->overlay_change_seen || nav.change_id != st->last_overlay_change_id) {
st->overlay_change_seen = true;
st->last_overlay_change_id = nav.change_id;
osd_changed(mpctx->osd);
}
if (visible != st->overlay_visible) {
MP_VERBOSE(s, "overlay %s\n", visible ? "on" : "off");
if (visible) {
+4
View File
@@ -1238,6 +1238,10 @@ static void handle_eof(struct MPContext *mpctx)
bool prevent_eof =
mpctx->paused && mpctx->video_out && vo_has_frame(mpctx->video_out) &&
!mpctx->vo_chain->is_coverart;
/* A disc menu parked on an infinite still frame reports EOF so the decoder
* drains and the menu frame is shown. Hold it until the user navigates,
* rather than ending the file. */
prevent_eof |= mpctx->disc_nav_still_frame;
/* It's possible for the user to simultaneously switch both audio
* and video streams to "disabled" at runtime. Handle this by waiting
* rather than immediately stopping playback due to EOF.
+1
View File
@@ -147,6 +147,7 @@ struct stream_nav_state {
bool nav_active; // interactive disc navigation is enabled
bool menu_active; // a selectable menu/highlight is currently visible
bool has_popup; // disc supports a popup menu (BD only)
bool still_active; // holding an indefinite still frame
int src_w, src_h; // dimensions of the coordinate space mouse uses
// Highlight rectangle of the currently focused button (in src coords).
int hl_x, hl_y, hl_w, hl_h;
+60 -11
View File
@@ -63,6 +63,7 @@ struct priv {
char *filename; // path
unsigned int duration; // in milliseconds
int title;
bool still_active; // fill_buffer() is holding a still
uint32_t spu_clut[16];
bool spu_clut_valid;
bool had_initial_vts;
@@ -391,7 +392,28 @@ static void update_highlight(struct priv *priv)
}
}
static void handle_nav_cmd(stream_t *stream, struct stream_nav_cmd *cmd)
// Move the highlight to the spec-defined neighbour of the current button. We
// resolve the neighbour ourselves and use dvdnav_button_select() (rather than
// dvdnav_{upper,lower,left,right}_button_select()) so that auto-action buttons
// are activated only through update_highlight(), where we can observe it.
static void select_neighbour_button(struct priv *priv, pci_t *pci,
enum stream_nav_action action)
{
int32_t cur = 0;
dvdnav_get_current_highlight(priv->dvdnav, &cur);
if (cur <= 0 || cur > pci->hli.hl_gi.btn_ns)
return;
btni_t b;
memcpy(&b, &pci->hli.btnit[cur - 1], sizeof(b));
int target = action == STREAM_NAV_UP ? b.up :
action == STREAM_NAV_DOWN ? b.down :
action == STREAM_NAV_LEFT ? b.left :
action == STREAM_NAV_RIGHT ? b.right : 0;
if (target > 0)
dvdnav_button_select(priv->dvdnav, pci, target);
}
static void do_nav_cmd(stream_t *stream, struct stream_nav_cmd *cmd)
{
struct priv *priv = stream->priv;
@@ -425,16 +447,10 @@ static void handle_nav_cmd(stream_t *stream, struct stream_nav_cmd *cmd)
switch (cmd->action) {
case STREAM_NAV_UP:
dvdnav_upper_button_select(priv->dvdnav, pci);
break;
case STREAM_NAV_DOWN:
dvdnav_lower_button_select(priv->dvdnav, pci);
break;
case STREAM_NAV_LEFT:
dvdnav_left_button_select(priv->dvdnav, pci);
break;
case STREAM_NAV_RIGHT:
dvdnav_right_button_select(priv->dvdnav, pci);
select_neighbour_button(priv, pci, cmd->action);
break;
case STREAM_NAV_MOUSE_MOVE:
dvdnav_mouse_select(priv->dvdnav, pci, cmd->x, cmd->y);
@@ -452,6 +468,18 @@ static void handle_nav_cmd(stream_t *stream, struct stream_nav_cmd *cmd)
update_highlight(priv);
}
static void handle_nav_cmd(stream_t *stream, struct stream_nav_cmd *cmd)
{
struct priv *priv = stream->priv;
int prev_auto = priv->auto_actioned_button;
do_nav_cmd(stream, cmd);
bool activated = stream_nav_action_activates(cmd->action) ||
priv->auto_actioned_button != prev_auto;
if (priv->still_active && activated)
priv->discontinuity_id++;
}
/**
* \brief mp_dvdnav_lang_from_aid() returns the language corresponding to audio id 'aid'
* \param stream: - stream pointer
@@ -545,8 +573,12 @@ static int fill_buffer(stream_t *s, void *buf, int max_len)
}
switch (event) {
case DVDNAV_BLOCK_OK:
// Real data is flowing again: we are no longer holding a still.
priv->still_active = false;
return len;
case DVDNAV_STOP:
// End of disc: a real EOF, not a held still.
priv->still_active = false;
return 0;
case DVDNAV_NAV_PACKET: {
pci_t *pnavpci = dvdnav_get_current_nav_pci(dvdnav);
@@ -557,9 +589,25 @@ static int fill_buffer(stream_t *s, void *buf, int max_len)
update_highlight(priv);
break;
}
case DVDNAV_STILL_FRAME:
dvdnav_still_skip(dvdnav);
break;
case DVDNAV_STILL_FRAME: {
dvdnav_still_event_t *still = buf;
// We only honor indefinite (0xff) stills. Finite stills (studio
// logos / warnings shown for a few seconds before the menu) are
// not hold on screen. This avoids complexities with correctly
// timing the still frames, and there is little use-case for holding
// 10+ seconds on single still frame.
if (still->length != 0xFF) {
MP_VERBOSE(s, "skipping finite still (%d s)\n",
still->length);
dvdnav_still_skip(dvdnav);
break;
}
// Indefinite still: report EOF to the demuxer so the video decoder
// is drained and the last frame is actually pushed to screen.
MP_VERBOSE(s, "indefinite still -> EOF, hold last frame\n");
priv->still_active = true;
return 0;
}
case DVDNAV_WAIT:
dvdnav_wait_skip(dvdnav);
break;
@@ -844,6 +892,7 @@ static int control(stream_t *stream, int cmd, void *arg)
.no_audio = no_audio,
.menu_active = priv->in_menu,
.has_popup = false,
.still_active = priv->still_active,
.src_w = priv->src_w,
.src_h = priv->src_h,
.hl_x = priv->btn_rect[0],