]> git.sesse.net Git - vlc/blobdiff - modules/access/bluray.c
Fix Metacube header handling with multiple header blocks.
[vlc] / modules / access / bluray.c
index 5d469cea79801311a302b3fb55507b3d26a08a3b..238cf6b661d9c69629f4c320175111dfcbe63e37 100644 (file)
@@ -26,7 +26,6 @@
 #endif
 
 #include <assert.h>
-#include <limits.h>                         /* PATH_MAX */
 
 #if defined (HAVE_MNTENT_H) && defined(HAVE_SYS_STAT_H)
 # include <mntent.h>
@@ -125,10 +124,12 @@ struct  demux_sys_t
     /* Titles */
     unsigned int        i_title;
     unsigned int        i_longest_title;
-    int                 i_playlist;          /* -1 = no playlist playing */
-    unsigned int        i_current_clip;
     input_title_t       **pp_title;
 
+    vlc_mutex_t             pl_info_lock;
+    BLURAY_TITLE_INFO      *p_pl_info;
+    const BLURAY_CLIP_INFO *p_clip_info;
+
     /* Meta information */
     const META_DL       *p_meta;
 
@@ -136,6 +137,9 @@ struct  demux_sys_t
     bluray_overlay_t    *p_overlays[MAX_OVERLAY];
     int                 current_overlay; // -1 if no current overlay;
     bool                b_menu;
+    bool                b_menu_open;
+    bool                b_popup_available;
+    mtime_t             i_still_end_time;
 
     /* */
     input_thread_t      *p_input;
@@ -148,6 +152,7 @@ struct  demux_sys_t
     int                 i_spu_stream;   /* Selected subtitle stream. -1 if default */
     int                 i_video_stream;
     stream_t            *p_parser;
+    bool                b_flushed;
 
     /* Used to store bluray disc path */
     char                *psz_bd_path;
@@ -223,16 +228,17 @@ static void FindMountPoint(char **file)
 {
     char *device = *file;
 #if defined (HAVE_MNTENT_H) && defined (HAVE_SYS_STAT_H)
+    /* bd path may be a symlink (e.g. /dev/dvd -> /dev/sr0), so make sure
+     * we look up the real device */
+    char *bd_device = realpath(device, NULL);
+    if (bd_device == NULL)
+        return;
+
     struct stat st;
-    if (!stat (device, &st) && S_ISBLK (st.st_mode)) {
+    if (lstat (bd_device, &st) == 0 && S_ISBLK (st.st_mode)) {
         FILE *mtab = setmntent ("/proc/self/mounts", "r");
         struct mntent *m, mbuf;
         char buf [8192];
-        /* bd path may be a symlink (e.g. /dev/dvd -> /dev/sr0), so make
-         * sure we look up the real device */
-        char *bd_device = realpath(device, NULL);
-        if (!bd_device)
-            bd_device = strdup(device);
 
         while ((m = getmntent_r (mtab, &mbuf, buf, sizeof(buf))) != NULL) {
             if (!strcmp (m->mnt_fsname, bd_device)) {
@@ -241,9 +247,10 @@ static void FindMountPoint(char **file)
                 break;
             }
         }
-        free(bd_device);
         endmntent (mtab);
     }
+    free(bd_device);
+
 #elif defined(__APPLE__)
     struct stat st;
     if (!stat (device, &st) && S_ISBLK (st.st_mode)) {
@@ -264,6 +271,27 @@ static void FindMountPoint(char **file)
 #endif
 }
 
+/*****************************************************************************
+ * cache current playlist (title) information
+ *****************************************************************************/
+
+static void setTitleInfo(demux_sys_t *p_sys, BLURAY_TITLE_INFO *info)
+{
+    vlc_mutex_lock(&p_sys->pl_info_lock);
+
+    if (p_sys->p_pl_info) {
+        bd_free_title_info(p_sys->p_pl_info);
+    }
+    p_sys->p_pl_info   = info;
+    p_sys->p_clip_info = NULL;
+
+    if (p_sys->p_pl_info && p_sys->p_pl_info->clip_count) {
+        p_sys->p_clip_info = &p_sys->p_pl_info->clips[0];
+    }
+
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
+}
+
 /*****************************************************************************
  * blurayOpen: module init function
  *****************************************************************************/
@@ -289,6 +317,7 @@ static int blurayOpen(vlc_object_t *object)
     p_sys->i_audio_stream = -1;
     p_sys->i_spu_stream = -1;
     p_sys->i_video_stream = -1;
+    p_sys->i_still_end_time = 0;
 
     /* init demux info fields */
     p_demux->info.i_update    = 0;
@@ -311,6 +340,8 @@ static int blurayOpen(vlc_object_t *object)
         return VLC_EGENERIC;
     }
 
+    vlc_mutex_init(&p_sys->pl_info_lock);
+
     /* Warning the user about AACS/BD+ */
     const BLURAY_DISC_INFO *disc_info = bd_get_disc_info(p_sys->bluray);
 
@@ -391,13 +422,13 @@ static int blurayOpen(vlc_object_t *object)
 
     /* Registering overlay event handler */
     bd_register_overlay_proc(p_sys->bluray, p_demux, blurayOverlayProc);
+    p_sys->p_input = demux_GetParentInput(p_demux);
+    if (unlikely(!p_sys->p_input)) {
+        msg_Err(p_demux, "Could not get parent input");
+        goto error;
+    }
 
     if (p_sys->b_menu) {
-        p_sys->p_input = demux_GetParentInput(p_demux);
-        if (unlikely(!p_sys->p_input)) {
-            msg_Err(p_demux, "Could not get parent input");
-            goto error;
-        }
 
         /* Register ARGB overlay handler for BD-J */
         if (disc_info->num_bdj_titles)
@@ -448,6 +479,8 @@ static void blurayClose(vlc_object_t *object)
     demux_t *p_demux = (demux_t*)object;
     demux_sys_t *p_sys = p_demux->p_sys;
 
+    setTitleInfo(p_sys, NULL);
+
     /*
      * Close libbluray first.
      * This will close all the overlays before we release p_vout
@@ -475,6 +508,8 @@ static void blurayClose(vlc_object_t *object)
         vlc_input_title_Delete(p_sys->pp_title[i]);
     TAB_CLEAN(p_sys->i_title, p_sys->pp_title);
 
+    vlc_mutex_destroy(&p_sys->pl_info_lock);
+
     free(p_sys->psz_bd_path);
     free(p_sys);
 }
@@ -516,7 +551,7 @@ static void setStreamLang(es_format_t *p_fmt,
     for (int i = 0; i < i_stream_count; i++) {
         if (p_fmt->i_id == p_streams[i].pid) {
             free(p_fmt->psz_language);
-            p_fmt->psz_language = strndup(p_streams[i].lang, 3);
+            p_fmt->psz_language = strndup((const char *)p_streams[i].lang, 3);
             return;
         }
     }
@@ -525,15 +560,12 @@ static void setStreamLang(es_format_t *p_fmt,
 static es_out_id_t *esOutAdd(es_out_t *p_out, const es_format_t *p_fmt)
 {
     demux_sys_t *p_sys = p_out->p_sys->p_demux->p_sys;
-    BLURAY_TITLE_INFO *title_info = bd_get_playlist_info(p_sys->bluray, p_sys->i_playlist, 0);
-    BLURAY_CLIP_INFO *clip_info = NULL;
     es_format_t fmt;
 
-    if (title_info && p_sys->i_current_clip < title_info->clip_count) {
-        clip_info = &title_info->clips[p_sys->i_current_clip];
-    }
-
     es_format_Copy(&fmt, p_fmt);
+
+    vlc_mutex_lock(&p_sys->pl_info_lock);
+
     switch (fmt.i_cat) {
     case VIDEO_ES:
         if (p_sys->i_video_stream != -1 && p_sys->i_video_stream != p_fmt->i_id)
@@ -542,19 +574,18 @@ static es_out_id_t *esOutAdd(es_out_t *p_out, const es_format_t *p_fmt)
     case AUDIO_ES:
         if (p_sys->i_audio_stream != -1 && p_sys->i_audio_stream != p_fmt->i_id)
             fmt.i_priority = ES_PRIORITY_NOT_SELECTABLE;
-        if (clip_info)
-            setStreamLang(&fmt, clip_info->audio_streams, clip_info->audio_stream_count);
+        if (p_sys->p_clip_info)
+            setStreamLang(&fmt, p_sys->p_clip_info->audio_streams, p_sys->p_clip_info->audio_stream_count);
         break ;
     case SPU_ES:
         if (p_sys->i_spu_stream != -1 && p_sys->i_spu_stream != p_fmt->i_id)
             fmt.i_priority = ES_PRIORITY_NOT_SELECTABLE;
-        if (clip_info)
-            setStreamLang(&fmt, clip_info->pg_streams, clip_info->pg_stream_count);
+        if (p_sys->p_clip_info)
+            setStreamLang(&fmt, p_sys->p_clip_info->pg_streams, p_sys->p_clip_info->pg_stream_count);
         break ;
     }
 
-    if (title_info)
-        bd_free_title_info(title_info);
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
 
     es_out_id_t *p_es = es_out_Add(p_out->p_sys->p_demux->out, &fmt);
     if (p_fmt->i_id >= 0) {
@@ -741,7 +772,7 @@ static int onMouseEvent(vlc_object_t *p_vout, const char *psz_var, vlc_value_t o
         bd_mouse_select(p_sys->bluray, now, val.coords.x, val.coords.y);
         bd_user_input(p_sys->bluray, now, BD_VK_MOUSE_ACTIVATE);
     } else {
-        assert(0);
+        vlc_assert_unreachable();
     }
     return VLC_SUCCESS;
 }
@@ -791,10 +822,12 @@ static void blurayCloseOverlay(demux_t *p_demux, int plane)
             return;
 
     /* All overlays have been closed */
-    var_DelCallback(p_sys->p_vout, "mouse-moved", onMouseEvent, p_demux);
-    var_DelCallback(p_sys->p_vout, "mouse-clicked", onMouseEvent, p_demux);
-    vlc_object_release(p_sys->p_vout);
-    p_sys->p_vout = NULL;
+    if (p_sys->p_vout != NULL) {
+        var_DelCallback(p_sys->p_vout, "mouse-moved", onMouseEvent, p_demux);
+        var_DelCallback(p_sys->p_vout, "mouse-clicked", onMouseEvent, p_demux);
+        vlc_object_release(p_sys->p_vout);
+        p_sys->p_vout = NULL;
+    }
 }
 
 /*
@@ -836,20 +869,17 @@ static void blurayInitOverlay(demux_t *p_demux, int plane, int width, int height
 
     assert(p_sys->p_overlays[plane] == NULL);
 
-    p_sys->p_overlays[plane] = calloc(1, sizeof(**p_sys->p_overlays));
-    if (unlikely(!p_sys->p_overlays[plane]))
+    bluray_overlay_t *ov = calloc(1, sizeof(*ov));
+    if (unlikely(ov == NULL))
         return;
 
-    bluray_overlay_t *ov = p_sys->p_overlays[plane];
-
     subpicture_updater_sys_t *p_upd_sys = malloc(sizeof(*p_upd_sys));
-    if (unlikely(!p_upd_sys)) {
+    if (unlikely(p_upd_sys == NULL)) {
         free(ov);
-        p_sys->p_overlays[plane] = NULL;
         return;
     }
     /* two references: vout + demux */
-    ov->released_once = ATOMIC_FLAG_INIT;
+    atomic_flag_clear(&ov->released_once);
 
     p_upd_sys->p_overlay = ov;
     subpicture_updater_t updater = {
@@ -858,12 +888,22 @@ static void blurayInitOverlay(demux_t *p_demux, int plane, int width, int height
         .pf_destroy  = subpictureUpdaterDestroy,
         .p_sys       = p_upd_sys,
     };
-    vlc_mutex_init(&ov->lock);
+
     ov->p_pic = subpicture_New(&updater);
+    if (ov->p_pic == NULL) {
+        free(p_upd_sys);
+        free(ov);
+        return;
+    }
+
     ov->p_pic->i_original_picture_width = width;
     ov->p_pic->i_original_picture_height = height;
     ov->p_pic->b_ephemer = true;
     ov->p_pic->b_absolute = true;
+
+    vlc_mutex_init(&ov->lock);
+
+    p_sys->p_overlays[plane] = ov;
 }
 
 /**
@@ -1103,19 +1143,8 @@ static void bluraySendOverlayToVout(demux_t *p_demux)
     p_sys->p_overlays[p_sys->current_overlay]->status = Outdated;
 }
 
-static void blurayUpdateTitleInfo(demux_t *p_demux, input_title_t *t, int i_title_idx, int i_playlist)
+static void blurayUpdateTitleInfo(input_title_t *t, BLURAY_TITLE_INFO *title_info)
 {
-    demux_sys_t *p_sys = p_demux->p_sys;
-    BLURAY_TITLE_INFO *title_info = NULL;
-
-    if (i_playlist >= 0)
-        title_info = bd_get_playlist_info(p_sys->bluray, i_playlist, 0);
-    else if (i_title_idx >= 0)
-        title_info = bd_get_title_info(p_sys->bluray, i_title_idx, 0);
-    if (!title_info) {
-        return;
-    }
-
     t->i_length = FROM_TICKS(title_info->duration);
 
     if (!t->i_seekpoint) {
@@ -1129,20 +1158,19 @@ static void blurayUpdateTitleInfo(demux_t *p_demux, input_title_t *t, int i_titl
             TAB_APPEND(t->i_seekpoint, t->seekpoint, s);
         }
     }
-
-    bd_free_title_info(title_info);
 }
 
 static void blurayInitTitles(demux_t *p_demux, int menu_titles)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
-    int64_t duration = 0;
 
     /* get and set the titles */
     unsigned i_title = menu_titles;
 
-    if (!p_sys->b_menu)
+    if (!p_sys->b_menu) {
         i_title = bd_get_titles(p_sys->bluray, TITLES_RELEVANT, 60);
+        p_sys->i_longest_title = bd_get_main_title(p_sys->bluray);
+    }
 
     for (unsigned int i = 0; i < i_title; i++) {
         input_title_t *t = vlc_input_title_New();
@@ -1150,12 +1178,10 @@ static void blurayInitTitles(demux_t *p_demux, int menu_titles)
             break;
 
         if (!p_sys->b_menu) {
-            blurayUpdateTitleInfo(p_demux, t, i, -1);
+            BLURAY_TITLE_INFO *title_info = bd_get_title_info(p_sys->bluray, i, 0);
+            blurayUpdateTitleInfo(t, title_info);
+            bd_free_title_info(title_info);
 
-            if (t->i_length > duration) {
-                duration = t->i_length;
-                p_sys->i_longest_title = i;
-            }
         } else if (i == 0) {
             t->psz_name = strdup(_("Top Menu"));
         } else if (i == i_title - 1) {
@@ -1183,34 +1209,6 @@ static void blurayResetParser(demux_t *p_demux)
         msg_Err(p_demux, "Failed to create TS demuxer");
 }
 
-static void blurayUpdateTitle(demux_t *p_demux, unsigned i_title)
-{
-    blurayResetParser(p_demux);
-    if (i_title >= p_demux->p_sys->i_title)
-        return;
-
-    /* read title info and init some values */
-    p_demux->info.i_title = i_title;
-    p_demux->info.i_seekpoint = 0;
-    p_demux->info.i_update |= INPUT_UPDATE_TITLE | INPUT_UPDATE_SEEKPOINT;
-}
-
-static void blurayUpdatePlaylist(demux_t *p_demux, unsigned i_playlist)
-{
-    blurayResetParser(p_demux);
-
-    p_demux->p_sys->i_playlist = i_playlist;
-    p_demux->p_sys->i_current_clip = 0;
-
-    /* read title info and init some values */
-    if (!p_demux->p_sys->b_menu)
-        p_demux->info.i_title = bd_get_current_title(p_demux->p_sys->bluray);
-    p_demux->info.i_seekpoint = 0;
-    p_demux->info.i_update |= INPUT_UPDATE_TITLE | INPUT_UPDATE_SEEKPOINT;
-
-    blurayUpdateTitleInfo(p_demux, p_demux->p_sys->pp_title[p_demux->info.i_title], -1, i_playlist);
-}
-
 /*****************************************************************************
  * bluraySetTitle: select new BD title
  *****************************************************************************/
@@ -1228,7 +1226,7 @@ static int bluraySetTitle(demux_t *p_demux, int i_title)
             msg_Dbg(p_demux, "Playing Title %i", i_title);
         }
 
-        if (bd_play_title(p_demux->p_sys->bluray, i_title) == 0) {
+        if (bd_play_title(p_sys->bluray, i_title) == 0) {
             msg_Err(p_demux, "cannot play bd title '%d'", i_title);
             return VLC_EGENERIC;
         }
@@ -1244,11 +1242,12 @@ static int bluraySetTitle(demux_t *p_demux, int i_title)
 
     msg_Dbg(p_demux, "Selecting Title %i", i_title);
 
-    if (bd_select_title(p_demux->p_sys->bluray, i_title) == 0) {
+    if (bd_select_title(p_sys->bluray, i_title) == 0) {
         msg_Err(p_demux, "cannot select bd title '%d'", i_title);
         return VLC_EGENERIC;
     }
-    blurayUpdateTitle(p_demux, i_title);
+
+    blurayResetParser(p_demux);
 
     return VLC_SUCCESS;
 }
@@ -1383,6 +1382,9 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
     }
 
     case DEMUX_NAV_ACTIVATE:
+        if (p_sys->b_popup_available && !p_sys->b_menu_open) {
+            return sendKeyEvent(p_sys, BD_VK_POPUP);
+        }
         return sendKeyEvent(p_sys, BD_VK_ENTER);
     case DEMUX_NAV_UP:
         return sendKeyEvent(p_sys, BD_VK_UP);
@@ -1406,36 +1408,130 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
     return VLC_SUCCESS;
 }
 
-static void blurayStreamSelect(demux_t *p_demux, uint32_t i_type, uint32_t i_id)
+/*****************************************************************************
+ * libbluray event handling
+ *****************************************************************************/
+
+static void streamFlush( demux_sys_t *p_sys )
 {
-    demux_sys_t *p_sys = p_demux->p_sys;
-    int i_pid = -1;
+    /*
+     * MPEG-TS demuxer does not flush last video frame if size of PES packet is unknown.
+     * Packet is flushed only when TS packet with PUSI flag set is received.
+     *
+     * Fix this by emitting (video) ts packet with PUSI flag set.
+     * Add video sequence end code to payload so that also video decoder is flushed.
+     * Set PES packet size in the payload so that it will be sent to decoder immediately.
+     */
 
-    if (p_sys->i_playlist < 0)
+    if (p_sys->b_flushed)
+        return;
+
+    block_t *p_block = block_Alloc(192);
+    if (!p_block)
         return;
 
-    BLURAY_TITLE_INFO *title_info = bd_get_playlist_info(p_sys->bluray, p_sys->i_playlist, 0);
-    if (title_info == NULL)
+    static const uint8_t seq_end_pes[] = {
+        0x00, 0x00, 0x01, 0xe0, 0x00, 0x07, 0x80, 0x00, 0x00,  /* PES header */
+        0x00, 0x00, 0x01, 0xb7,                                /* PES payload: sequence end */
+    };
+    static const uint8_t vid_pusi_ts[] = {
+        0x00, 0x00, 0x00, 0x00,                /* TP extra header (ATC) */
+        0x47, 0x50, 0x11, 0x30,                /* TP header */
+        (192 - (4 + 5) - sizeof(seq_end_pes)), /* adaptation field length */
+        0x80,                                  /* adaptation field: discontinuity indicator */
+    };
+
+    memset(p_block->p_buffer, 0, 192);
+    memcpy(p_block->p_buffer, vid_pusi_ts, sizeof(vid_pusi_ts));
+    memcpy(p_block->p_buffer + 192 - sizeof(seq_end_pes), seq_end_pes, sizeof(seq_end_pes));
+    p_block->i_buffer = 192;
+
+    /* set correct sequence end code */
+    vlc_mutex_lock(&p_sys->pl_info_lock);
+    if (p_sys->p_clip_info != NULL) {
+        if (p_sys->p_clip_info->video_streams[0].coding_type > 2) {
+            /* VC1 / H.264 sequence end */
+            p_block->p_buffer[191] = 0x0a;
+        }
+    }
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
+
+    stream_DemuxSend(p_sys->p_parser, p_block);
+    p_sys->b_flushed = true;
+}
+
+static void blurayResetStillImage( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if (p_sys->i_still_end_time) {
+        p_sys->i_still_end_time = 0;
+
+        blurayResetParser(p_demux);
+        es_out_Control( p_demux->out, ES_OUT_RESET_PCR );
+    }
+}
+
+static void blurayStillImage( demux_t *p_demux, unsigned i_timeout )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    /* time period elapsed ? */
+    if (p_sys->i_still_end_time > 0 && p_sys->i_still_end_time <= mdate()) {
+        msg_Dbg(p_demux, "Still image end");
+        bd_read_skip_still(p_sys->bluray);
+
+        blurayResetStillImage(p_demux);
         return;
+    }
+
+    /* show last frame as still image */
+    if (!p_sys->i_still_end_time) {
+        if (i_timeout) {
+            msg_Dbg(p_demux, "Still image (%d seconds)", i_timeout);
+            p_sys->i_still_end_time = mdate() + i_timeout * CLOCK_FREQ;
+        } else {
+            msg_Dbg(p_demux, "Still image (infinite)");
+            p_sys->i_still_end_time = -1;
+        }
+
+        /* flush demuxer and decoder (there won't be next video packet starting with ts PUSI) */
+        streamFlush(p_sys);
+
+        /* stop buffering */
+        bool b_empty;
+        es_out_Control( p_demux->out, ES_OUT_GET_EMPTY, &b_empty );
+    }
 
-    if (p_sys->i_current_clip < title_info->clip_count) {
-        BLURAY_CLIP_INFO *clip_info = &title_info->clips[p_sys->i_current_clip];
+    /* avoid busy loops (read returns no data) */
+    msleep( 40000 );
+}
+
+static void blurayStreamSelect(demux_t *p_demux, uint32_t i_type, uint32_t i_id)
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i_pid = -1;
+
+    vlc_mutex_lock(&p_sys->pl_info_lock);
+
+    if (p_sys->p_clip_info) {
 
         /* The param we get is the real stream id, not an index, ie. it starts from 1 */
         i_id--;
         if (i_type == BD_EVENT_AUDIO_STREAM) {
-            if (i_id < clip_info->audio_stream_count) {
-                i_pid = clip_info->audio_streams[i_id].pid;
+            if (i_id < p_sys->p_clip_info->audio_stream_count) {
+                i_pid = p_sys->p_clip_info->audio_streams[i_id].pid;
                 p_sys->i_audio_stream = i_pid;
             }
         } else if (i_type == BD_EVENT_PG_TEXTST_STREAM) {
-            if (i_id < clip_info->pg_stream_count) {
-                i_pid = clip_info->pg_streams[i_id].pid;
+            if (i_id < p_sys->p_clip_info->pg_stream_count) {
+                i_pid = p_sys->p_clip_info->pg_streams[i_id].pid;
                 p_sys->i_spu_stream = i_pid;
             }
         }
     }
-    bd_free_title_info(title_info);
+
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
 
     if (i_pid > 0) {
         int i_idx = findEsPairIndex(p_sys, i_pid);
@@ -1446,22 +1542,50 @@ static void blurayStreamSelect(demux_t *p_demux, uint32_t i_type, uint32_t i_id)
     }
 }
 
+static void blurayUpdatePlaylist(demux_t *p_demux, unsigned i_playlist)
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    blurayResetParser(p_demux);
+
+    /* read title info and init some values */
+    if (!p_sys->b_menu)
+        p_demux->info.i_title = bd_get_current_title(p_sys->bluray);
+    p_demux->info.i_seekpoint = 0;
+    p_demux->info.i_update |= INPUT_UPDATE_TITLE | INPUT_UPDATE_SEEKPOINT;
+
+    BLURAY_TITLE_INFO *p_title_info = bd_get_playlist_info(p_sys->bluray, i_playlist, 0);
+    if (p_title_info) {
+        blurayUpdateTitleInfo(p_sys->pp_title[p_demux->info.i_title], p_title_info);
+    }
+    setTitleInfo(p_sys, p_title_info);
+
+    blurayResetStillImage(p_demux);
+}
+
 static void blurayUpdateCurrentClip(demux_t *p_demux, uint32_t clip)
 {
-    if (clip == 0xFF)
-        return ;
     demux_sys_t *p_sys = p_demux->p_sys;
 
-    p_sys->i_current_clip = clip;
-    BLURAY_TITLE_INFO *info = bd_get_playlist_info(p_sys->bluray, p_sys->i_playlist, 0);
-    if (info == NULL)
-        return ;
+    vlc_mutex_lock(&p_sys->pl_info_lock);
+
+    p_sys->p_clip_info = NULL;
+    p_sys->i_video_stream = -1;
+
+    if (p_sys->p_pl_info && clip < p_sys->p_pl_info->clip_count) {
+
+        p_sys->p_clip_info = &p_sys->p_pl_info->clips[clip];
+
     /* Let's assume a single video track for now.
      * This may brake later, but it's enough for now.
      */
-    assert(info->clips[p_sys->i_current_clip].video_stream_count >= 1);
-    p_sys->i_video_stream = info->clips[p_sys->i_current_clip].video_streams[0].pid;
-    bd_free_title_info(info);
+        assert(p_sys->p_clip_info->video_stream_count >= 1);
+        p_sys->i_video_stream = p_sys->p_clip_info->video_streams[0].pid;
+    }
+
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
+
+    blurayResetStillImage(p_demux);
 }
 
 static void blurayHandleEvent(demux_t *p_demux, const BD_EVENT *e)
@@ -1475,7 +1599,7 @@ static void blurayHandleEvent(demux_t *p_demux, const BD_EVENT *e)
         else
             p_demux->info.i_title = e->param;
         /* this is feature title, we don't know yet which playlist it will play (if any) */
-        p_sys->i_playlist = -1;
+        setTitleInfo(p_sys, NULL);
         /* reset title infos here ? */
         p_demux->info.i_update |= INPUT_UPDATE_TITLE | INPUT_UPDATE_SEEKPOINT; /* might be BD-J title with no video */
         break;
@@ -1492,6 +1616,13 @@ static void blurayHandleEvent(demux_t *p_demux, const BD_EVENT *e)
         break;
     case BD_EVENT_ANGLE:
         break;
+    case BD_EVENT_MENU:
+        p_sys->b_menu_open = e->param;
+        break;
+    case BD_EVENT_POPUP:
+        p_sys->b_popup_available = e->param;
+        /* TODO: show / hide pop-up menu button in gui ? */
+        break;
 
     /*
      * stream selection events
@@ -1503,6 +1634,12 @@ static void blurayHandleEvent(demux_t *p_demux, const BD_EVENT *e)
     case BD_EVENT_IG_STREAM:
         break;
 
+    /*
+     * playback control events
+     */
+    case BD_EVENT_STILL_TIME:
+        blurayStillImage(p_demux, e->param);
+        break;
     case BD_EVENT_DISCONTINUITY:
         /* reset demuxer (partially decoded PES packets must be dropped) */
         blurayResetParser(p_demux);
@@ -1534,7 +1671,7 @@ static int blurayDemux(demux_t *p_demux)
     int nread;
 
     if (p_sys->b_menu == false) {
-        while (bd_get_event(p_demux->p_sys->bluray, &e))
+        while (bd_get_event(p_sys->bluray, &e))
             blurayHandleEvent(p_demux, &e);
 
         nread = bd_read(p_sys->bluray, p_block->p_buffer,
@@ -1575,5 +1712,7 @@ static int blurayDemux(demux_t *p_demux)
 
     stream_DemuxSend(p_sys->p_parser, p_block);
 
+    p_sys->b_flushed = false;
+
     return 1;
 }