[vlc-commits] [Git][videolan/vlc][master] 7 commits: dvdnav: let play get past endless stills with no buttons

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Fri Jul 17 23:59:02 UTC 2026



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
daf86755 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: let play get past endless stills with no buttons

- - - - -
0b4d647b by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: forget the still before jumping elsewhere

- - - - -
e0c3e532 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: stop waiting forever for the output to empty

- - - - -
60c78a65 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: don't drain the output again before new data

- - - - -
7b812405 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: step through slideshow stills with the arrow keys

- - - - -
0d02c486 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: hold the progress bar full through a still show

- - - - -
f42ec8a1 by Saifelden Mohamed Ismail at 2026-07-18T01:44:16+02:00
dvdnav: space steps through a still show instead of pausing

- - - - -


1 changed file:

- modules/access/dvdnav.c


Changes:

=====================================
modules/access/dvdnav.c
=====================================
@@ -118,6 +118,9 @@ vlc_module_end ()
 
 #define BLOCK_FLAG_CELL_DISCONTINUITY (BLOCK_FLAG_PRIVATE_SHIFT << 1)
 
+/* how long to wait for the output to empty before moving on anyway */
+#define WAIT_EMPTY_TIMEOUT VLC_TICK_FROM_SEC(2)
+
 /*****************************************************************************
  * Local prototypes
  *****************************************************************************/
@@ -130,11 +133,20 @@ typedef struct
     bool        b_reset_pcr;
     bool        b_readahead;
     bool        b_wait_empty;
+    vlc_tick_t  i_wait_start;
+    bool        b_es_drained;
+    /* still show navigation */
+    bool        b_still_show;
+    int         i_show_title; /* title known to hold a still show */
+    bool        b_show_done; /* user stepped past the last slide */
+    int         i_step_part; /* part a step is jumping to */
+    vlc_tick_t  i_cell_length;
 
     struct
     {
         bool         b_created;
         bool         b_enabled;
+        bool         b_infinite;
         vlc_mutex_t  lock;
         vlc_timer_t  timer;
     } still;
@@ -199,6 +211,33 @@ static char *DemuxGetLanguageCode( demux_t *p_demux, const char *psz_var );
 static int ControlInternal( demux_t *, int, ... );
 
 static void StillTimer( void * );
+static bool StillActive( demux_sys_t * );
+static void StillReset( demux_sys_t * );
+static void StillEnd( demux_sys_t * );
+static bool StillSkipIfNoButtons( demux_t * );
+static bool StillStep( demux_t *, int );
+
+/* some transitions never report an empty es out and would spin forever */
+static bool WaitEmptyTimeout( demux_sys_t *p_sys )
+{
+    if( p_sys->i_wait_start == VLC_TICK_INVALID )
+        p_sys->i_wait_start = vlc_tick_now();
+    return vlc_tick_now() - p_sys->i_wait_start > WAIT_EMPTY_TIMEOUT;
+}
+
+/* a second drain before new data would restart it and never complete */
+static int EsOutDrainOnce( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if( p_sys->b_es_drained )
+        return VLC_SUCCESS;
+
+    int ret = es_out_Control( p_sys->p_tf_out, ES_OUT_DRAIN );
+    if( ret == VLC_SUCCESS )
+        p_sys->b_es_drained = true;
+    return ret;
+}
 
 static void EventMouse( const vlc_mouse_t *mouse, void *p_data );
 
@@ -372,6 +411,11 @@ static int CommonOpen( vlc_object_t *p_this,
     if( i_angle <= 0 ) i_angle = 1;
 
     p_sys->still.b_enabled = false;
+    p_sys->b_still_show = false;
+    p_sys->i_show_title = -1;
+    p_sys->b_show_done = false;
+    p_sys->i_step_part = 0;
+    p_sys->i_wait_start = VLC_TICK_INVALID;
     vlc_mutex_init( &p_sys->still.lock );
     vlc_mutex_init( &p_sys->event_lock );
     if( !vlc_timer_create( &p_sys->still.timer, StillTimer, p_sys ) )
@@ -633,12 +677,24 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             switch( i_query )
             {
             case DEMUX_GET_POSITION:
-                *va_arg( args, double* ) = (double)pos / (double)len;
+            {
+                double f;
+                if( p_sys->b_still_show )
+                    f = 1.0;
+                else
+                    f = (double)pos / (double)len;
+                *va_arg( args, double* ) = f;
                 return VLC_SUCCESS;
+            }
 
             case DEMUX_SET_POSITION:
             {
                 const double f = va_arg( args, double );
+                /* the bar of a still show is not a timeline there is
+                   nowhere to scrub to */
+                if( p_sys->b_still_show )
+                    return VLC_SUCCESS;
+                StillReset( p_sys );
                 if( p_sys->i_pgc_length > 0 &&
                     dvdnav_jump_to_sector_by_time( p_sys->dvdnav,
                         TO_SCALE_NZ((vlc_tick_t)( f * p_sys->i_pgc_length )),
@@ -655,6 +711,12 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             }
 
             case DEMUX_GET_LENGTH:
+            {
+                if( p_sys->b_still_show && p_sys->i_cell_length > 0 )
+                {
+                    *va_arg( args, vlc_tick_t * ) = p_sys->i_cell_length;
+                    return VLC_SUCCESS;
+                }
                 if( p_sys->i_pgc_length > 0 )
                 {
                     *va_arg( args, vlc_tick_t * ) = p_sys->i_pgc_length;
@@ -662,10 +724,20 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                 }
                 break;
             }
+            }
             return VLC_EGENERIC;
         }
 
         case DEMUX_GET_TIME:
+        {
+            /* the bar stays full through a still show a full unmoving
+               bar is the only thing the interface cannot keep animating
+               on its own */
+            if( p_sys->b_still_show && p_sys->i_cell_length > 0 )
+            {
+                *va_arg( args, vlc_tick_t * ) = p_sys->i_cell_length;
+                return VLC_SUCCESS;
+            }
             if( p_sys->i_pgc_length > 0 )
             {
                 *va_arg( args, vlc_tick_t * ) =
@@ -673,10 +745,14 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                 return VLC_SUCCESS;
             }
             break;
+        }
 
         case DEMUX_SET_TIME:
         {
             vlc_tick_t i_time = va_arg( args, vlc_tick_t );
+            if( p_sys->b_still_show )
+                return VLC_SUCCESS;
+            StillReset( p_sys );
             if( dvdnav_jump_to_sector_by_time( p_sys->dvdnav,
                                                TO_SCALE_NZ(i_time),
                                                SEEK_SET ) == DVDNAV_STATUS_OK )
@@ -694,7 +770,18 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             return VLC_SUCCESS;
 
         case DEMUX_SET_PAUSE_STATE:
+        {
+            bool b_pause = (bool)va_arg( args, int );
+            /* nothing in a still show is worth pausing space means next
+               slide whether something moves or not */
+            if( b_pause && p_sys->b_still_show && StillStep( p_demux, 1 ) )
+                return VLC_EGENERIC;
+            /* play on a clip end still means continue like on a hardware
+               player refusing the pause keeps the next program playing */
+            if( StillSkipIfNoButtons( p_demux ) && b_pause )
+                return VLC_EGENERIC;
             return VLC_SUCCESS;
+        }
 
         case DEMUX_GET_TITLE_INFO:
             ppp_title = va_arg( args, input_title_t*** );
@@ -730,7 +817,7 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
 
             if( i != 0 )
             {
-                dvdnav_still_skip( p_sys->dvdnav );
+                StillReset( p_sys );
                 if( dvdnav_title_play( p_sys->dvdnav, i ) != DVDNAV_STATUS_OK )
                 {
                     msg_Warn( p_demux, "cannot set title/chapter" );
@@ -756,11 +843,15 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                     dvdnav_menu_call(p_sys->dvdnav, menuid) != DVDNAV_STATUS_OK )
                     return VLC_EGENERIC;
             }
-            else if( dvdnav_part_play( p_sys->dvdnav, p_sys->cur_title,
-                                       i + 1 ) != DVDNAV_STATUS_OK )
+            else
             {
-                msg_Warn( p_demux, "cannot set title/chapter" );
-                return VLC_EGENERIC;
+                StillReset( p_sys );
+                if( dvdnav_part_play( p_sys->dvdnav, p_sys->cur_title,
+                                      i + 1 ) != DVDNAV_STATUS_OK )
+                {
+                    msg_Warn( p_demux, "cannot set title/chapter" );
+                    return VLC_EGENERIC;
+                }
             }
             p_sys->updates |= INPUT_UPDATE_SEEKPOINT;
             p_sys->cur_seekpoint = i;
@@ -806,6 +897,11 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         {
             pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
 
+            if( StillStep( p_demux, 1 ) || StillSkipIfNoButtons( p_demux ) )
+                break;
+            if( pci == NULL )
+                return VLC_EGENERIC;
+
             if( dvdnav_button_activate( p_sys->dvdnav, pci ) != DVDNAV_STATUS_OK )
                 return VLC_EGENERIC;
             vlc_mutex_lock( &p_sys->event_lock );
@@ -836,7 +932,10 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         {
             pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
 
-            if( dvdnav_left_button_select( p_sys->dvdnav, pci ) != DVDNAV_STATUS_OK )
+            if( StillStep( p_demux, -1 ) )
+                break;
+            if( pci == NULL ||
+                dvdnav_left_button_select( p_sys->dvdnav, pci ) != DVDNAV_STATUS_OK )
                 return VLC_EGENERIC;
             break;
         }
@@ -845,7 +944,10 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         {
             pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
 
-            if( dvdnav_right_button_select( p_sys->dvdnav, pci ) != DVDNAV_STATUS_OK )
+            if( StillStep( p_demux, 1 ) )
+                break;
+            if( pci == NULL ||
+                dvdnav_right_button_select( p_sys->dvdnav, pci ) != DVDNAV_STATUS_OK )
                 return VLC_EGENERIC;
             break;
         }
@@ -904,11 +1006,14 @@ static int Demux( demux_t *p_demux )
     {
         bool b_empty;
         es_out_Control( p_sys->p_tf_out, ES_OUT_IS_EMPTY, &b_empty );
-        if( !b_empty )
+        if( !b_empty && !WaitEmptyTimeout( p_sys ) )
         {
             vlc_tick_sleep( VLC_TICK_FROM_MS(40) );
             return VLC_DEMUXER_SUCCESS;
         }
+        if( !b_empty )
+            msg_Warn( p_demux, "es_out not drained, resuming demux" );
+        p_sys->i_wait_start = VLC_TICK_INVALID;
         p_sys->b_wait_empty = false;
         /* Don't call RESET_PCR directly as we night need to handle NAV events */
         p_sys->b_reset_pcr = true;
@@ -941,10 +1046,7 @@ static int Demux( demux_t *p_demux )
     switch( i_event )
     {
     case DVDNAV_BLOCK_OK:   /* mpeg block */
-        vlc_mutex_lock( &p_sys->still.lock );
-        vlc_timer_disarm( p_sys->still.timer );
-        p_sys->still.b_enabled = false;
-        vlc_mutex_unlock( &p_sys->still.lock );
+        StillReset( p_sys );
         if( p_sys->b_reset_pcr )
         {
             es_out_Control( p_sys->p_tf_out, ES_OUT_RESET_PCR );
@@ -973,6 +1075,19 @@ static int Demux( demux_t *p_demux )
         {
             msg_Dbg( p_demux, "DVDNAV_STILL_FRAME" );
             msg_Dbg( p_demux, "     - length=0x%x", event->length );
+            pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
+            msg_Dbg( p_demux, "     - buttons=%d",
+                     pci ? pci->hli.hl_gi.btn_ns : -1 );
+            p_sys->still.b_infinite = event->length == 0xff;
+            /* a title cell ending on an endless still with nothing to
+               press only happens in still shows */
+            if( p_sys->still.b_infinite && p_sys->cur_title > 0 &&
+                ( pci == NULL || pci->hli.hl_gi.btn_ns == 0 ) )
+            {
+                p_sys->b_still_show = true;
+                p_sys->i_show_title = p_sys->cur_title;
+                p_sys->b_show_done = false;
+            }
             p_sys->still.b_enabled = true;
 
             if( event->length != 0xff && p_sys->still.b_created )
@@ -987,6 +1102,10 @@ static int Demux( demux_t *p_demux )
 
         if( b_still_init )
             DemuxForceStill( p_demux );
+        else
+            /* the wait above may have given up so pace the repeats of
+               this event instead of spinning on them */
+            vlc_tick_sleep( VLC_TICK_FROM_MS(40) );
         break;
     }
 
@@ -1058,6 +1177,10 @@ static int Demux( demux_t *p_demux )
         msg_Dbg( p_demux, "     - vtsN=%d", event->new_vtsN );
         msg_Dbg( p_demux, "     - domain=%d", event->new_domain );
 
+        p_sys->b_still_show = false;
+        p_sys->b_show_done = false;
+        p_sys->i_step_part = 0;
+
         for( int i = 0; i < PS_TK_COUNT; i++ )
         {
             ps_track_t *tk = &p_sys->tk[i];
@@ -1132,8 +1255,9 @@ static int Demux( demux_t *p_demux )
         msg_Dbg( p_demux, "     - cell_start=%"PRId64, event->cell_start );
         msg_Dbg( p_demux, "     - pg_start=%"PRId64, event->pg_start );
 
-        /* Store the length in time of the current PGC */
+        /* Store the length in time of the current PGC and cell */
         p_sys->i_pgc_length = FROM_SCALE_NZ(event->pgc_length);
+        p_sys->i_cell_length = FROM_SCALE_NZ(event->cell_length);
         p_sys->i_vobu_index = 0;
         p_sys->i_vobu_flush = 0;
         p_sys->cell_ts.dvd = VLC_TICK_INVALID;
@@ -1156,9 +1280,20 @@ static int Demux( demux_t *p_demux )
                     i_nav_part <= p_sys->title[i_nav_title]->i_seekpoint )
                     p_sys->cur_seekpoint = i_nav_part - 1;
                 else p_sys->cur_seekpoint = 0;
+                /* a title that held an endless still once is a still
+                   show for the whole session so stepping works from its
+                   first cell on any revisit until the user runs past
+                   its last slide */
+                if( p_sys->cur_title == p_sys->i_show_title &&
+                    !p_sys->b_show_done )
+                    p_sys->b_still_show = true;
+                p_sys->i_step_part = 0;
             }
             else if( i_nav_title == 0 ) /* in menus, i_part == menu id */
             {
+                p_sys->b_still_show = false;
+                p_sys->b_show_done = false;
+                p_sys->i_step_part = 0;
                 if( MenuIDToSeekpoint( i_nav_part, &p_sys->cur_seekpoint ) )
                     p_sys->cur_seekpoint = 0; /* non standard menu number, can't map back */
                 else
@@ -1240,6 +1375,9 @@ static int Demux( demux_t *p_demux )
         msg_Dbg( p_demux, "DVDNAV_HOP_CHANNEL" );
         p_sys->i_vobu_index = 0;
         p_sys->i_vobu_flush = 0;
+        /* the hop ended any still and any pending drain wait */
+        StillReset( p_sys );
+        p_sys->i_wait_start = VLC_TICK_INVALID;
         es_out_Control( p_sys->p_tf_out, ES_OUT_RESET_PCR );
         break;
 
@@ -1247,15 +1385,17 @@ static int Demux( demux_t *p_demux )
         msg_Dbg( p_demux, "DVDNAV_WAIT" );
 
         bool b_empty = true;
-        int ret = es_out_Control( p_sys->p_tf_out, ES_OUT_DRAIN );
-        if( ret == VLC_SUCCESS )
+        if( EsOutDrainOnce( p_demux ) == VLC_SUCCESS )
             es_out_Control( p_sys->p_tf_out, ES_OUT_IS_EMPTY, &b_empty );
-        if( !b_empty )
+        if( !b_empty && !WaitEmptyTimeout( p_sys ) )
         {
             vlc_tick_sleep( VLC_TICK_FROM_MS(40) );
         }
         else
         {
+            if( !b_empty )
+                msg_Warn( p_demux, "es_out not drained, skipping wait" );
+            p_sys->i_wait_start = VLC_TICK_INVALID;
             dvdnav_wait_skip( p_sys->dvdnav );
             p_sys->b_reset_pcr = true;
         }
@@ -1501,6 +1641,8 @@ static int DemuxBlock( demux_t *p_demux, const uint8_t *p, int32_t len )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
+    p_sys->b_es_drained = false;
+
     while( len > 0 )
     {
         int i_size = ps_pkt_size( p, len );
@@ -1623,8 +1765,7 @@ static void DemuxForceStill( demux_t *p_demux )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
-    int ret = es_out_Control( p_sys->p_tf_out, ES_OUT_DRAIN );
-    if (ret == VLC_SUCCESS)
+    if( EsOutDrainOnce( p_demux ) == VLC_SUCCESS )
         p_sys->b_wait_empty = true;
 }
 
@@ -1775,6 +1916,129 @@ static void StillTimer( void *p_data )
     vlc_mutex_unlock( &p_sys->still.lock );
 }
 
+static bool StillActive( demux_sys_t *p_sys )
+{
+    vlc_mutex_lock( &p_sys->still.lock );
+    bool b_active = p_sys->still.b_enabled;
+    vlc_mutex_unlock( &p_sys->still.lock );
+    return b_active;
+}
+
+/* forget the still without dvdnav_still_skip since a jump cancels it
+   anyway and a pending skip would eat the next still */
+static void StillReset( demux_sys_t *p_sys )
+{
+    vlc_mutex_lock( &p_sys->still.lock );
+    vlc_timer_disarm( p_sys->still.timer );
+    p_sys->still.b_enabled = false;
+    /* stop waiting for the last picture the jump replaces it */
+    p_sys->b_wait_empty = false;
+    p_sys->i_wait_start = VLC_TICK_INVALID;
+    vlc_mutex_unlock( &p_sys->still.lock );
+}
+
+/* end the still and let playback continue */
+static void StillEnd( demux_sys_t *p_sys )
+{
+    vlc_mutex_lock( &p_sys->still.lock );
+    if( p_sys->still.b_enabled )
+    {
+        vlc_timer_disarm( p_sys->still.timer );
+        p_sys->still.b_enabled = false;
+        dvdnav_still_skip( p_sys->dvdnav );
+        /* read what the disc does next right away instead of finishing
+           the wait for the picture the user is leaving */
+        p_sys->b_wait_empty = false;
+        p_sys->i_wait_start = VLC_TICK_INVALID;
+    }
+    vlc_mutex_unlock( &p_sys->still.lock );
+}
+
+/* some discs end a clip on an endless still with nothing to activate
+   expecting the user to press play to continue */
+static bool StillSkipIfNoButtons( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
+
+    vlc_mutex_lock( &p_sys->still.lock );
+    bool b_skip = p_sys->still.b_enabled && p_sys->still.b_infinite &&
+                  ( pci == NULL || pci->hli.hl_gi.btn_ns == 0 );
+    if( b_skip )
+    {
+        msg_Dbg( p_demux, "skipping button-less still" );
+        vlc_timer_disarm( p_sys->still.timer );
+        p_sys->still.b_enabled = false;
+        dvdnav_still_skip( p_sys->dvdnav );
+        /* read what the disc does next right away instead of finishing
+           the wait for the picture the user is leaving */
+        p_sys->b_wait_empty = false;
+        p_sys->i_wait_start = VLC_TICK_INVALID;
+    }
+    vlc_mutex_unlock( &p_sys->still.lock );
+    return b_skip;
+}
+
+/* step between the stills of a still show even when pressed in the short
+   data window between two of them where no still is active yet */
+static bool StillStep( demux_t *p_demux, int i_dir )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    pci_t *pci = dvdnav_get_current_nav_pci( p_sys->dvdnav );
+
+    /* once a title is known to hold a show its parts stay steppable
+       even while the show flag is down past the last slide */
+    if( p_sys->cur_title <= 0 || p_sys->cur_title != p_sys->i_show_title )
+        return false;
+    if( pci != NULL && pci->hli.hl_gi.btn_ns > 0 )
+        return false; /* menu buttons take priority */
+
+    /* the 1 based part on display */
+    int i_shown = p_sys->cur_seekpoint + 1;
+
+    if( i_dir > 0 && StillActive( p_sys ) )
+    {
+        /* ending the still lets the disc reach the next slide on its
+           own without the rebuffering a jump would cost */
+        msg_Dbg( p_demux, "still show: step forward" );
+        StillEnd( p_sys );
+        p_sys->i_step_part = i_shown + 1;
+        return true;
+    }
+
+    /* step from the jump already in flight so quick presses add up */
+    int i_base = p_sys->i_step_part > 0 ? p_sys->i_step_part : i_shown;
+    /* going back never lands on the start of what is already showing */
+    if( i_dir < 0 && i_base > i_shown )
+        i_base = i_shown;
+    int i_part = i_base + ( i_dir > 0 ? 1 : -1 );
+    if( i_part < 1 )
+        i_part = 1;
+
+    bool b_in_still = StillActive( p_sys );
+    StillReset( p_sys );
+    msg_Dbg( p_demux, "still show: step to part %d", i_part );
+    if( dvdnav_part_play( p_sys->dvdnav, p_sys->cur_title, i_part )
+            != DVDNAV_STATUS_OK )
+    {
+        /* no part to step to swallow the press so it cannot turn into
+           a seek and with nothing frozen the show ends here until a
+           still proves it again */
+        if( !b_in_still )
+        {
+            p_sys->b_still_show = false;
+            p_sys->b_show_done = true;
+            p_sys->i_step_part = 0;
+        }
+    }
+    else
+    {
+        p_sys->b_show_done = false;
+        p_sys->i_step_part = i_part;
+    }
+    return true;
+}
+
 static void EventMouse( const vlc_mouse_t *newmouse, void *p_data )
 {
     demux_t *p_demux = p_data;



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/2be076408ac3ca71d2ddc693db73dc48f1c4d68f...f42ec8a122a76228ddd9b0e265761604986d6a6c

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/2be076408ac3ca71d2ddc693db73dc48f1c4d68f...f42ec8a122a76228ddd9b0e265761604986d6a6c
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list