]> git.sesse.net Git - vlc/blobdiff - modules/access/bluray.c
Fix Metacube header handling with multiple header blocks.
[vlc] / modules / access / bluray.c
index 583bcfcd7c13d33afa68ac9eb822fac84f5f995e..238cf6b661d9c69629f4c320175111dfcbe63e37 100644 (file)
 #endif
 
 #include <assert.h>
-#include <limits.h>                         /* PATH_MAX */
+
 #if defined (HAVE_MNTENT_H) && defined(HAVE_SYS_STAT_H)
-#include <mntent.h>
-#include <sys/stat.h>
+# include <mntent.h>
+# include <sys/stat.h>
 #endif
 
 #ifdef __APPLE__
-#include <sys/stat.h>
-#include <sys/param.h>
-#include <sys/ucred.h>
-#include <sys/mount.h>
+# include <sys/stat.h>
+# include <sys/param.h>
+# include <sys/ucred.h>
+# include <sys/mount.h>
 #endif
 
 #include <vlc_common.h>
 #include <vlc_dialog.h>                     /* BD+/AACS warnings */
 #include <vlc_vout.h>                       /* vout_PutSubpicture / subpicture_t */
 #include <vlc_url.h>                        /* vlc_path2uri */
+#include <vlc_iso_lang.h>
+
+/* FIXME we should find a better way than including that */
+#include "../../src/text/iso-639_def.h"
+
 
 #include <libbluray/bluray.h>
+#include <libbluray/bluray-version.h>
 #include <libbluray/keys.h>
 #include <libbluray/meta_data.h>
 #include <libbluray/overlay.h>
  * Module descriptor
  *****************************************************************************/
 
-#define BD_MENU_TEXT        N_( "Blu-ray menus" )
-#define BD_MENU_LONGTEXT    N_( "Use Blu-ray menus. If disabled, "\
-                                "the movie will start directly" )
-#define BD_REGION_TEXT      N_( "Region code" )
-#define BD_REGION_LONGTEXT  N_( "Blu-Ray player region code. "\
+#define BD_MENU_TEXT        N_("Blu-ray menus")
+#define BD_MENU_LONGTEXT    N_("Use Blu-ray menus. If disabled, "\
+                                "the movie will start directly")
+#define BD_REGION_TEXT      N_("Region code")
+#define BD_REGION_LONGTEXT  N_("Blu-Ray player region code. "\
                                 "Some discs can be played only with a correct region code.")
 
 static const char *const ppsz_region_code[] = {
@@ -70,25 +76,26 @@ static const char *const ppsz_region_code_text[] = {
     "Region A", "Region B", "Region C" };
 
 #define REGION_DEFAULT   1   /* Index to region list. Actual region code is (1<<REGION_DEFAULT) */
+#define LANGUAGE_DEFAULT ("eng")
 
 /* Callbacks */
-static int  blurayOpen ( vlc_object_t * );
-static void blurayClose( vlc_object_t * );
+static int  blurayOpen (vlc_object_t *);
+static void blurayClose(vlc_object_t *);
 
 vlc_module_begin ()
-    set_shortname( N_("Blu-ray") )
-    set_description( N_("Blu-ray Disc support (libbluray)") )
+    set_shortname(N_("Blu-ray"))
+    set_description(N_("Blu-ray Disc support (libbluray)"))
 
-    set_category( CAT_INPUT )
-    set_subcategory( SUBCAT_INPUT_ACCESS )
-    set_capability( "access_demux", 200)
-    add_bool( "bluray-menu", false, BD_MENU_TEXT, BD_MENU_LONGTEXT, false )
-    add_string( "bluray-region", ppsz_region_code[REGION_DEFAULT], BD_REGION_TEXT, BD_REGION_LONGTEXT, false)
-        change_string_list( ppsz_region_code, ppsz_region_code_text )
+    set_category(CAT_INPUT)
+    set_subcategory(SUBCAT_INPUT_ACCESS)
+    set_capability("access_demux", 200)
+    add_bool("bluray-menu", false, BD_MENU_TEXT, BD_MENU_LONGTEXT, false)
+    add_string("bluray-region", ppsz_region_code[REGION_DEFAULT], BD_REGION_TEXT, BD_REGION_LONGTEXT, false)
+        change_string_list(ppsz_region_code, ppsz_region_code_text)
 
-    add_shortcut( "bluray", "file" )
+    add_shortcut("bluray", "file")
 
-    set_callbacks( blurayOpen, blurayClose )
+    set_callbacks(blurayOpen, blurayClose)
 vlc_module_end ()
 
 /* libbluray's overlay.h defines 2 types of overlay (bd_overlay_plane_e). */
@@ -117,16 +124,22 @@ struct  demux_sys_t
     /* Titles */
     unsigned int        i_title;
     unsigned int        i_longest_title;
-    unsigned int        i_current_clip;
     input_title_t       **pp_title;
 
-    /* Meta informations */
+    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;
 
     /* Menus */
     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;
@@ -136,8 +149,10 @@ struct  demux_sys_t
     es_out_t            *p_out;
     vlc_array_t         es;
     int                 i_audio_stream; /* Selected audio stream. -1 if default */
+    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;
@@ -148,15 +163,52 @@ struct subpicture_updater_sys_t
     bluray_overlay_t    *p_overlay;
 };
 
+/* Get a 3 char code
+ * FIXME: partiallyy duplicated from src/input/es_out.c
+ */
+static const char *DemuxGetLanguageCode( demux_t *p_demux, const char *psz_var )
+{
+    const iso639_lang_t *pl;
+    char *psz_lang;
+    char *p;
+
+    psz_lang = var_CreateGetString( p_demux, psz_var );
+    if( !psz_lang )
+        return LANGUAGE_DEFAULT;
+
+    /* XXX: we will use only the first value
+     * (and ignore other ones in case of a list) */
+    if( ( p = strchr( psz_lang, ',' ) ) )
+        *p = '\0';
+
+    for( pl = p_languages; pl->psz_eng_name != NULL; pl++ )
+    {
+        if( *psz_lang == '\0' )
+            continue;
+        if( !strcasecmp( pl->psz_eng_name, psz_lang ) ||
+            !strcasecmp( pl->psz_iso639_1, psz_lang ) ||
+            !strcasecmp( pl->psz_iso639_2T, psz_lang ) ||
+            !strcasecmp( pl->psz_iso639_2B, psz_lang ) )
+            break;
+    }
+
+    free( psz_lang );
+
+    if( pl->psz_eng_name != NULL )
+        return pl->psz_iso639_2T;
+
+    return LANGUAGE_DEFAULT;
+}
+
 /*****************************************************************************
  * Local prototypes
  *****************************************************************************/
-static es_out_t *esOutNew( demux_t *p_demux );
+static es_out_t *esOutNew(demux_t *p_demux);
 
 static int   blurayControl(demux_t *, int, va_list);
 static int   blurayDemux(demux_t *);
 
-static void  blurayInitTitles(demux_t *p_demux );
+static void  blurayInitTitles(demux_t *p_demux, int menu_titles);
 static int   bluraySetTitle(demux_t *p_demux, int i_title);
 
 static void  blurayOverlayProc(void *ptr, const BD_OVERLAY * const overlay);
@@ -176,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)) {
@@ -194,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)) {
@@ -217,10 +271,31 @@ 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
  *****************************************************************************/
-static int blurayOpen( vlc_object_t *object )
+static int blurayOpen(vlc_object_t *object)
 {
     demux_t *p_demux = (demux_t*)object;
     demux_sys_t *p_sys;
@@ -240,14 +315,16 @@ static int blurayOpen( vlc_object_t *object )
 
     p_sys->current_overlay = -1;
     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;
     p_demux->info.i_title     = 0;
     p_demux->info.i_seekpoint = 0;
 
-    TAB_INIT( p_sys->i_title, p_sys->pp_title );
+    TAB_INIT(p_sys->i_title, p_sys->pp_title);
 
     /* store current bd path */
     if (p_demux->psz_file)
@@ -263,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);
 
@@ -278,11 +357,11 @@ static int blurayOpen( vlc_object_t *object )
 
     /* AACS */
     if (disc_info->aacs_detected) {
+        msg_Dbg(p_demux, "Disc is using AACS");
         if (!disc_info->libaacs_detected)
             BLURAY_ERROR(_("This Blu-ray Disc needs a library for AACS decoding"
                       ", and your system does not have it."));
         if (!disc_info->aacs_handled) {
-#ifdef BD_AACS_CORRUPTED_DISC
             if (disc_info->aacs_error_code) {
                 switch (disc_info->aacs_error_code) {
                 case BD_AACS_CORRUPTED_DISC:
@@ -299,16 +378,12 @@ static int blurayOpen( vlc_object_t *object )
                     BLURAY_ERROR(_("AACS MMC failed."));
                 }
             }
-#else
-            /* libbluray < 0.2.3 */
-            BLURAY_ERROR(_("Your system AACS decoding library does not work. "
-                      "Missing keys?"));
-#endif /* BD_AACS_CORRUPTED_DISC */
         }
     }
 
     /* BD+ */
     if (disc_info->bdplus_detected) {
+        msg_Dbg(p_demux, "Disc is using BD+");
         if (!disc_info->libbdplus_detected)
             BLURAY_ERROR(_("This Blu-ray Disc needs a library for BD+ decoding"
                       ", and your system does not have it."));
@@ -323,25 +398,37 @@ static int blurayOpen( vlc_object_t *object )
     free(psz_region);
     bd_set_player_setting(p_sys->bluray, BLURAY_PLAYER_SETTING_REGION_CODE, 1<<region);
 
+    /* set preferred languages */
+    const char *psz_code = DemuxGetLanguageCode( p_demux, "audio-language" );
+    bd_set_player_setting_str(p_sys->bluray, BLURAY_PLAYER_SETTING_AUDIO_LANG, psz_code);
+    psz_code = DemuxGetLanguageCode( p_demux, "sub-language" );
+    bd_set_player_setting_str(p_sys->bluray, BLURAY_PLAYER_SETTING_PG_LANG,    psz_code);
+    psz_code = DemuxGetLanguageCode( p_demux, "menu-language" );
+    bd_set_player_setting_str(p_sys->bluray, BLURAY_PLAYER_SETTING_MENU_LANG,  psz_code);
+
     /* Get titles and chapters */
     p_sys->p_meta = bd_get_meta(p_sys->bluray);
     if (!p_sys->p_meta)
-        msg_Warn(p_demux, "Failed to get meta info." );
+        msg_Warn(p_demux, "Failed to get meta info.");
+
+    p_sys->b_menu = var_InheritBool(p_demux, "bluray-menu");
 
-    blurayInitTitles(p_demux);
+    blurayInitTitles(p_demux, disc_info->num_hdmv_titles + disc_info->num_bdj_titles + 1/*Top Menu*/ + 1/*First Play*/);
 
     /*
      * Initialize the event queue, so we can receive events in blurayDemux(Menu).
      */
     bd_get_event(p_sys->bluray, NULL);
 
-    p_sys->b_menu = var_InheritBool(p_demux, "bluray-menu");
+    /* 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)
@@ -351,22 +438,20 @@ static int blurayOpen( vlc_object_t *object )
         if (bd_play(p_sys->bluray) == 0)
             BLURAY_ERROR(_("Failed to start bluray playback. Please try without menu support."));
 
-        /* Registering overlay event handler */
-        bd_register_overlay_proc(p_sys->bluray, p_demux, blurayOverlayProc);
     } else {
         /* set start title number */
         if (bluraySetTitle(p_demux, p_sys->i_longest_title) != VLC_SUCCESS) {
-            msg_Err( p_demux, "Could not set the title %d", p_sys->i_longest_title );
+            msg_Err(p_demux, "Could not set the title %d", p_sys->i_longest_title);
             goto error;
         }
     }
 
     vlc_array_init(&p_sys->es);
-    p_sys->p_out = esOutNew( p_demux );
+    p_sys->p_out = esOutNew(p_demux);
     if (unlikely(p_sys->p_out == NULL))
         goto error;
 
-    blurayResetParser( p_demux );
+    blurayResetParser(p_demux);
     if (!p_sys->p_parser) {
         msg_Err(p_demux, "Failed to create TS demuxer");
         goto error;
@@ -389,15 +474,17 @@ error:
 /*****************************************************************************
  * blurayClose: module destroy function
  *****************************************************************************/
-static void blurayClose( vlc_object_t *object )
+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
-     * bd_close( NULL ) can crash
+     * bd_close(NULL) can crash
      */
     assert(p_sys->bluray);
     bd_close(p_sys->bluray);
@@ -413,13 +500,15 @@ static void blurayClose( vlc_object_t *object )
         stream_Delete(p_sys->p_parser);
     if (p_sys->p_out != NULL)
         es_out_Delete(p_sys->p_out);
-    assert( vlc_array_count(&p_sys->es) == 0 );
-    vlc_array_clear( &p_sys->es );
+    assert(vlc_array_count(&p_sys->es) == 0);
+    vlc_array_clear(&p_sys->es);
 
     /* Titles */
     for (unsigned int i = 0; i < p_sys->i_title; i++)
         vlc_input_title_Delete(p_sys->pp_title[i]);
-    TAB_CLEAN( p_sys->i_title, p_sys->pp_title );
+    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);
@@ -438,54 +527,76 @@ typedef struct  fmt_es_pair {
     es_out_id_t *p_es;
 }               fmt_es_pair_t;
 
-static int  findEsPairIndex( demux_sys_t *p_sys, int i_id )
+static int  findEsPairIndex(demux_sys_t *p_sys, int i_id)
 {
-    for ( int i = 0; i < vlc_array_count(&p_sys->es); ++i ) {
-        if ( ((fmt_es_pair_t*)vlc_array_item_at_index(&p_sys->es, i))->i_id == i_id )
+    for (int i = 0; i < vlc_array_count(&p_sys->es); ++i)
+        if (((fmt_es_pair_t*)vlc_array_item_at_index(&p_sys->es, i))->i_id == i_id)
             return i;
-    }
+
     return -1;
 }
 
-static int  findEsPairIndexByEs( demux_sys_t *p_sys, es_out_id_t *p_es )
+static int  findEsPairIndexByEs(demux_sys_t *p_sys, es_out_id_t *p_es)
 {
-    for ( int i = 0; i < vlc_array_count(&p_sys->es); ++i ) {
-        if ( ((fmt_es_pair_t*)vlc_array_item_at_index(&p_sys->es, i))->p_es == p_es )
+    for (int i = 0; i < vlc_array_count(&p_sys->es); ++i)
+        if (((fmt_es_pair_t*)vlc_array_item_at_index(&p_sys->es, i))->p_es == p_es)
             return i;
-    }
+
     return -1;
 }
 
-static es_out_id_t *esOutAdd( es_out_t *p_out, const es_format_t *p_fmt )
+static void setStreamLang(es_format_t *p_fmt,
+                          const BLURAY_STREAM_INFO *p_streams, int i_stream_count)
+{
+    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((const char *)p_streams[i].lang, 3);
+            return;
+        }
+    }
+}
+
+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;
     es_format_t fmt;
 
     es_format_Copy(&fmt, p_fmt);
-    switch (fmt.i_cat)
-    {
+
+    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 )
-            fmt.i_priority = -2;
+        if (p_sys->i_video_stream != -1 && p_sys->i_video_stream != p_fmt->i_id)
+            fmt.i_priority = ES_PRIORITY_NOT_SELECTABLE;
         break ;
     case AUDIO_ES:
-        if ( p_sys->i_audio_stream != -1 && p_sys->i_audio_stream != p_fmt->i_id )
-            fmt.i_priority = -2;
+        if (p_sys->i_audio_stream != -1 && p_sys->i_audio_stream != p_fmt->i_id)
+            fmt.i_priority = ES_PRIORITY_NOT_SELECTABLE;
+        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 (p_sys->p_clip_info)
+            setStreamLang(&fmt, p_sys->p_clip_info->pg_streams, p_sys->p_clip_info->pg_stream_count);
         break ;
     }
 
-    es_out_id_t *p_es = es_out_Add( p_out->p_sys->p_demux->out, &fmt );
-    if ( p_fmt->i_id >= 0 ) {
+    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) {
         /* Ensure we are not overriding anything */
         int idx = findEsPairIndex(p_sys, p_fmt->i_id);
-        if ( idx == -1 ) {
-            fmt_es_pair_t *p_pair = malloc( sizeof(*p_pair) );
-            if ( likely(p_pair != NULL) ) {
+        if (idx == -1) {
+            fmt_es_pair_t *p_pair = malloc(sizeof(*p_pair));
+            if (likely(p_pair != NULL)) {
                 p_pair->i_id = p_fmt->i_id;
                 p_pair->p_es = p_es;
-                msg_Info( p_out->p_sys->p_demux, "Adding ES %d", p_fmt->i_id );
+                msg_Info(p_out->p_sys->p_demux, "Adding ES %d", p_fmt->i_id);
                 vlc_array_append(&p_sys->es, p_pair);
             }
         }
@@ -494,41 +605,40 @@ static es_out_id_t *esOutAdd( es_out_t *p_out, const es_format_t *p_fmt )
     return p_es;
 }
 
-static int esOutSend( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
+static int esOutSend(es_out_t *p_out, es_out_id_t *p_es, block_t *p_block)
 {
-    return es_out_Send( p_out->p_sys->p_demux->out, p_es, p_block );
+    return es_out_Send(p_out->p_sys->p_demux->out, p_es, p_block);
 }
 
-static void esOutDel( es_out_t *p_out, es_out_id_t *p_es )
+static void esOutDel(es_out_t *p_out, es_out_id_t *p_es)
 {
-    int idx = findEsPairIndexByEs( p_out->p_sys->p_demux->p_sys, p_es );
-    if (idx >= 0)
-    {
-        free( vlc_array_item_at_index( &p_out->p_sys->p_demux->p_sys->es, idx) );
+    int idx = findEsPairIndexByEs(p_out->p_sys->p_demux->p_sys, p_es);
+    if (idx >= 0) {
+        free(vlc_array_item_at_index(&p_out->p_sys->p_demux->p_sys->es, idx));
         vlc_array_remove(&p_out->p_sys->p_demux->p_sys->es, idx);
     }
-    es_out_Del( p_out->p_sys->p_demux->out, p_es );
+    es_out_Del(p_out->p_sys->p_demux->out, p_es);
 }
 
-static int esOutControl( es_out_t *p_out, int i_query, va_list args )
+static int esOutControl(es_out_t *p_out, int i_query, va_list args)
 {
-    return es_out_vaControl( p_out->p_sys->p_demux->out, i_query, args );
+    return es_out_vaControl(p_out->p_sys->p_demux->out, i_query, args);
 }
 
-static void esOutDestroy( es_out_t *p_out )
+static void esOutDestroy(es_out_t *p_out)
 {
-    for ( int i = 0; i < vlc_array_count(&p_out->p_sys->p_demux->p_sys->es); ++i )
-        free( vlc_array_item_at_index(&p_out->p_sys->p_demux->p_sys->es, i) );
+    for (int i = 0; i < vlc_array_count(&p_out->p_sys->p_demux->p_sys->es); ++i)
+        free(vlc_array_item_at_index(&p_out->p_sys->p_demux->p_sys->es, i));
     vlc_array_clear(&p_out->p_sys->p_demux->p_sys->es);
-    free( p_out->p_sys );
-    free( p_out );
+    free(p_out->p_sys);
+    free(p_out);
 }
 
-static es_out_t *esOutNew( demux_t *p_demux )
+static es_out_t *esOutNew(demux_t *p_demux)
 {
-    assert( vlc_array_count(&p_demux->p_sys->es) == 0 );
-    es_out_t    *p_out = malloc( sizeof(*p_out) );
-    if ( unlikely(p_out == NULL) )
+    assert(vlc_array_count(&p_demux->p_sys->es) == 0);
+    es_out_t    *p_out = malloc(sizeof(*p_out));
+    if (unlikely(p_out == NULL))
         return NULL;
 
     p_out->pf_add       = esOutAdd;
@@ -537,9 +647,9 @@ static es_out_t *esOutNew( demux_t *p_demux )
     p_out->pf_destroy   = esOutDestroy;
     p_out->pf_send      = esOutSend;
 
-    p_out->p_sys = malloc( sizeof(*p_out->p_sys) );
-    if ( unlikely( p_out->p_sys == NULL ) ) {
-        free( p_out );
+    p_out->p_sys = malloc(sizeof(*p_out->p_sys));
+    if (unlikely(p_out->p_sys == NULL)) {
+        free(p_out);
         return NULL;
     }
     p_out->p_sys->p_demux = p_demux;
@@ -549,16 +659,16 @@ static es_out_t *esOutNew( demux_t *p_demux )
 /*****************************************************************************
  * subpicture_updater_t functions:
  *****************************************************************************/
-static int subpictureUpdaterValidate( subpicture_t *p_subpic,
+static int subpictureUpdaterValidate(subpicture_t *p_subpic,
                                       bool b_fmt_src, const video_format_t *p_fmt_src,
                                       bool b_fmt_dst, const video_format_t *p_fmt_dst,
-                                      mtime_t i_ts )
+                                      mtime_t i_ts)
 {
-    VLC_UNUSED( b_fmt_src );
-    VLC_UNUSED( b_fmt_dst );
-    VLC_UNUSED( p_fmt_src );
-    VLC_UNUSED( p_fmt_dst );
-    VLC_UNUSED( i_ts );
+    VLC_UNUSED(b_fmt_src);
+    VLC_UNUSED(b_fmt_dst);
+    VLC_UNUSED(p_fmt_src);
+    VLC_UNUSED(p_fmt_dst);
+    VLC_UNUSED(i_ts);
 
     subpicture_updater_sys_t *p_upd_sys = p_subpic->updater.p_sys;
     bluray_overlay_t         *p_overlay = p_upd_sys->p_overlay;
@@ -617,8 +727,7 @@ static void subpictureUpdaterUpdate(subpicture_t *p_subpic,
     vlc_mutex_lock(&p_overlay->lock);
 
     subpicture_region_t *p_src = p_overlay->p_regions;
-    if (!p_src)
-    {
+    if (!p_src) {
         vlc_mutex_unlock(&p_overlay->lock);
         return;
     }
@@ -642,6 +751,7 @@ static void blurayCleanOverlayStruct(bluray_overlay_t *);
 static void subpictureUpdaterDestroy(subpicture_t *p_subpic)
 {
     blurayCleanOverlayStruct(p_subpic->updater.p_sys->p_overlay);
+    free(p_subpic->updater.p_sys);
 }
 
 /*****************************************************************************
@@ -662,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;
 }
@@ -670,9 +780,9 @@ static int onMouseEvent(vlc_object_t *p_vout, const char *psz_var, vlc_value_t o
 static int sendKeyEvent(demux_sys_t *p_sys, unsigned int key)
 {
     mtime_t now = mdate();
-    if (bd_user_input(p_sys->bluray, now, key) < 0) {
+    if (bd_user_input(p_sys->bluray, now, key) < 0)
         return VLC_EGENERIC;
-    }
+
     return VLC_SUCCESS;
 }
 
@@ -688,30 +798,36 @@ static void blurayCleanOverlayStruct(bluray_overlay_t *p_overlay)
      * Don't delete it again from here!
      */
     vlc_mutex_destroy(&p_overlay->lock);
-    subpicture_region_Delete(p_overlay->p_regions);
+    subpicture_region_ChainDelete(p_overlay->p_regions);
     free(p_overlay);
 }
 
-static void blurayCloseAllOverlays(demux_t *p_demux)
+static void blurayCloseOverlay(demux_t *p_demux, int plane)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
+    bluray_overlay_t *ov = p_sys->p_overlays[plane];
 
-    p_demux->p_sys->current_overlay = -1;
-    if (!p_sys->p_vout)
-        return;
+    if (ov != NULL) {
+        if (p_sys->p_vout)
+            vout_FlushSubpictureChannel(p_sys->p_vout, ov->p_pic->i_channel);
+        blurayCleanOverlayStruct(ov);
+        if (p_sys->current_overlay == plane)
+            p_sys->current_overlay = -1;
 
-    for (int i = 0; i < 0; i++) {
-        if (p_sys->p_overlays[i] != NULL) {
-            vout_FlushSubpictureChannel(p_sys->p_vout,
-                                        p_sys->p_overlays[i]->p_pic->i_channel);
-            blurayCleanOverlayStruct(p_sys->p_overlays[i]);
-            p_sys->p_overlays[i] = NULL;
-        }
+        p_sys->p_overlays[plane] = NULL;
+    }
+
+    for (int i = 0; i < MAX_OVERLAY; i++)
+        if (p_sys->p_overlays[i])
+            return;
+
+    /* All overlays have been closed */
+    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;
     }
-    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;
 }
 
 /*
@@ -753,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 = {
@@ -775,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;
 }
 
 /**
@@ -837,7 +960,7 @@ static void blurayDrawOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
     if (!p_reg) {
         video_format_t fmt;
         video_format_Init(&fmt, 0);
-        video_format_Setup(&fmt, VLC_CODEC_YUVP, ov->w, ov->h, 1, 1);
+        video_format_Setup(&fmt, VLC_CODEC_YUVP, ov->w, ov->h, ov->w, ov->h, 1, 1);
 
         p_reg = subpicture_region_New(&fmt);
         p_reg->i_x = ov->x;
@@ -878,10 +1001,14 @@ static void blurayDrawOverlay(demux_t *p_demux, const BD_OVERLAY* const ov)
 static void blurayOverlayProc(void *ptr, const BD_OVERLAY *const overlay)
 {
     demux_t *p_demux = (demux_t*)ptr;
+    demux_sys_t *p_sys = p_demux->p_sys;
 
     if (!overlay) {
-        msg_Info(p_demux, "Closing overlay.");
-        blurayCloseAllOverlays(p_demux);
+        msg_Info(p_demux, "Closing overlays.");
+        p_sys->current_overlay = -1;
+        if (p_sys->p_vout)
+            for (int i = 0; i < MAX_OVERLAY; i++)
+                blurayCloseOverlay(p_demux, i);
         return;
     }
 
@@ -890,6 +1017,10 @@ static void blurayOverlayProc(void *ptr, const BD_OVERLAY *const overlay)
         msg_Info(p_demux, "Initializing overlay");
         blurayInitOverlay(p_demux, overlay->plane, overlay->w, overlay->h);
         break;
+    case BD_OVERLAY_CLOSE:
+        blurayClearOverlay(p_demux, overlay->plane);
+        blurayCloseOverlay(p_demux, overlay->plane);
+        break;
     case BD_OVERLAY_CLEAR:
         blurayClearOverlay(p_demux, overlay->plane);
         break;
@@ -917,7 +1048,7 @@ static void blurayInitArgbOverlay(demux_t *p_demux, int plane, int width, int he
     if (!p_sys->p_overlays[plane]->p_regions) {
         video_format_t fmt;
         video_format_Init(&fmt, 0);
-        video_format_Setup(&fmt, VLC_CODEC_RGBA, width, height, 1, 1);
+        video_format_Setup(&fmt, VLC_CODEC_RGBA, width, height, width, height, 1, 1);
 
         p_sys->p_overlays[plane]->p_regions = subpicture_region_New(&fmt);
     }
@@ -972,7 +1103,7 @@ static void blurayArgbOverlayProc(void *ptr, const BD_ARGB_OVERLAY *const overla
         break;
     case BD_ARGB_OVERLAY_CLOSE:
         blurayClearOverlay(p_demux, overlay->plane);
-        // TODO: blurayCloseOverlay(p_demux, overlay->plane);
+        blurayCloseOverlay(p_demux, overlay->plane);
         break;
     case BD_ARGB_OVERLAY_FLUSH:
         blurayActivateOverlay(p_demux, overlay->plane);
@@ -1012,50 +1143,56 @@ static void bluraySendOverlayToVout(demux_t *p_demux)
     p_sys->p_overlays[p_sys->current_overlay]->status = Outdated;
 }
 
-static void blurayInitTitles(demux_t *p_demux )
+static void blurayUpdateTitleInfo(input_title_t *t, BLURAY_TITLE_INFO *title_info)
+{
+    t->i_length = FROM_TICKS(title_info->duration);
+
+    if (!t->i_seekpoint) {
+        for (unsigned int j = 0; j < title_info->chapter_count; j++) {
+            seekpoint_t *s = vlc_seekpoint_New();
+            if (!s) {
+                break;
+            }
+            s->i_time_offset = title_info->chapters[j].offset;
+
+            TAB_APPEND(t->i_seekpoint, t->seekpoint, s);
+        }
+    }
+}
+
+static void blurayInitTitles(demux_t *p_demux, int menu_titles)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
     /* get and set the titles */
-    unsigned i_title = bd_get_titles(p_sys->bluray, TITLES_RELEVANT, 60);
-    int64_t duration = 0;
+    unsigned i_title = menu_titles;
+
+    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();
         if (!t)
             break;
 
-        BLURAY_TITLE_INFO *title_info = bd_get_title_info(p_sys->bluray, i, 0);
-        if (!title_info) {
-            vlc_input_title_Delete(t);
-            break;
-        }
-
-        t->i_length = FROM_TICKS(title_info->duration);
+        if (!p_sys->b_menu) {
+            BLURAY_TITLE_INFO *title_info = bd_get_title_info(p_sys->bluray, i, 0);
+            blurayUpdateTitleInfo(t, title_info);
+            bd_free_title_info(title_info);
 
-        for ( unsigned int j = 0; j < title_info->chapter_count; j++) {
-            seekpoint_t *s = vlc_seekpoint_New();
-            if (!s) {
-                bd_free_title_info(title_info);
-                vlc_input_title_Delete(t);
-                break;
-            }
-            s->i_time_offset = title_info->chapters[j].offset;
-
-            TAB_APPEND( t->i_seekpoint, t->seekpoint, s );
+        } else if (i == 0) {
+            t->psz_name = strdup(_("Top Menu"));
+        } else if (i == i_title - 1) {
+            t->psz_name = strdup(_("First Play"));
         }
 
-        if (t->i_length > duration) {
-            duration = t->i_length;
-            p_sys->i_longest_title = i;
-        }
-
-        TAB_APPEND( p_sys->i_title, p_sys->pp_title, t );
-        bd_free_title_info(title_info);
+        TAB_APPEND(p_sys->i_title, p_sys->pp_title, t);
     }
 }
 
-static void blurayResetParser( demux_t *p_demux )
+static void blurayResetParser(demux_t *p_demux)
 {
     /*
      * This is a hack and will have to be removed.
@@ -1072,18 +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;
-}
-
 /*****************************************************************************
  * bluraySetTitle: select new BD title
  *****************************************************************************/
@@ -1091,19 +1216,38 @@ static int bluraySetTitle(demux_t *p_demux, int i_title)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
+    if (p_sys->b_menu) {
+        if (i_title <= 0) {
+            msg_Dbg(p_demux, "Playing TopMenu Title");
+        } else if (i_title >= (int)p_sys->i_title - 1) {
+            msg_Dbg(p_demux, "Playing FirstPlay Title");
+            i_title = BLURAY_TITLE_FIRST_PLAY;
+        } else {
+            msg_Dbg(p_demux, "Playing Title %i", i_title);
+        }
+
+        if (bd_play_title(p_sys->bluray, i_title) == 0) {
+            msg_Err(p_demux, "cannot play bd title '%d'", i_title);
+            return VLC_EGENERIC;
+        }
+
+        return VLC_SUCCESS;
+    }
+
     /* Looking for the main title, ie the longest duration */
     if (i_title < 0)
         i_title = p_sys->i_longest_title;
     else if ((unsigned)i_title > p_sys->i_title)
         return VLC_EGENERIC;
 
-    msg_Dbg( p_demux, "Selecting Title %i", i_title);
+    msg_Dbg(p_demux, "Selecting Title %i", i_title);
 
-    if (bd_select_title(p_demux->p_sys->bluray, i_title) == 0 ) {
-        msg_Err(p_demux, "cannot select bd title '%d'", p_demux->info.i_title);
+    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;
 }
@@ -1121,12 +1265,12 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
     case DEMUX_CAN_SEEK:
     case DEMUX_CAN_PAUSE:
     case DEMUX_CAN_CONTROL_PACE:
-         pb_bool = (bool*)va_arg( args, bool * );
+         pb_bool = (bool*)va_arg(args, bool *);
          *pb_bool = true;
          break;
 
     case DEMUX_GET_PTS_DELAY:
-        pi_64 = (int64_t*)va_arg( args, int64_t * );
+        pi_64 = (int64_t*)va_arg(args, int64_t *);
         *pi_64 = INT64_C(1000) * var_InheritInteger(p_demux, "disc-caching");
         break;
 
@@ -1136,25 +1280,25 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
 
     case DEMUX_SET_TITLE:
     {
-        int i_title = (int)va_arg( args, int );
+        int i_title = (int)va_arg(args, int);
         if (bluraySetTitle(p_demux, i_title) != VLC_SUCCESS)
             return VLC_EGENERIC;
         break;
     }
     case DEMUX_SET_SEEKPOINT:
     {
-        int i_chapter = (int)va_arg( args, int );
-        bd_seek_chapter( p_sys->bluray, i_chapter );
+        int i_chapter = (int)va_arg(args, int);
+        bd_seek_chapter(p_sys->bluray, i_chapter);
         p_demux->info.i_update = INPUT_UPDATE_SEEKPOINT;
         break;
     }
 
     case DEMUX_GET_TITLE_INFO:
     {
-        input_title_t ***ppp_title = (input_title_t***)va_arg( args, input_title_t*** );
-        int *pi_int             = (int*)va_arg( args, int* );
-        int *pi_title_offset    = (int*)va_arg( args, int* );
-        int *pi_chapter_offset  = (int*)va_arg( args, int* );
+        input_title_t ***ppp_title = (input_title_t***)va_arg(args, input_title_t***);
+        int *pi_int             = (int*)va_arg(args, int*);
+        int *pi_title_offset    = (int*)va_arg(args, int*);
+        int *pi_chapter_offset  = (int*)va_arg(args, int*);
 
         /* */
         *pi_title_offset   = 0;
@@ -1162,7 +1306,7 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
 
         /* Duplicate local title infos */
         *pi_int = p_sys->i_title;
-        *ppp_title = malloc(p_sys->i_title * sizeof(input_title_t **));
+        *ppp_title = malloc(p_sys->i_title * sizeof(input_title_t *));
         for (unsigned int i = 0; i < p_sys->i_title; i++)
             (*ppp_title)[i] = vlc_input_title_Duplicate(p_sys->pp_title[i]);
 
@@ -1190,9 +1334,9 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
 
     case DEMUX_GET_POSITION:
     {
-        double *pf_position = (double*)va_arg( args, double * );
-        *pf_position = p_demux->info.i_title < (int)p_sys->i_title ?
-                    (double)FROM_TICKS(bd_tell_time(p_sys->bluray))/CUR_LENGTH : 0.0;
+        double *pf_position = (double*)va_arg(args, double *);
+        *pf_position = p_demux->info.i_title < (int)p_sys->i_title && CUR_LENGTH > 0 ?
+                      (double)FROM_TICKS(bd_tell_time(p_sys->bluray))/CUR_LENGTH : 0.0;
         return VLC_SUCCESS;
     }
     case DEMUX_SET_POSITION:
@@ -1218,28 +1362,29 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
         // if (meta->di_set_number > 0) vlc_meta_SetTrackNum(p_meta, meta->di_set_number);
         // if (meta->di_num_sets > 0) vlc_meta_AddExtra(p_meta, "Discs numbers in Set", meta->di_num_sets);
 
-        if (meta->thumb_count > 0 && meta->thumbnails)
-        {
+        if (meta->thumb_count > 0 && meta->thumbnails) {
             char *psz_thumbpath;
-            if( asprintf( &psz_thumbpath, "%s" DIR_SEP "BDMV" DIR_SEP "META" DIR_SEP "DL" DIR_SEP "%s",
-                          p_sys->psz_bd_path, meta->thumbnails[0].path ) > 0 )
-            {
-                char *psz_thumburl = vlc_path2uri( psz_thumbpath, "file" );
-                if( unlikely(psz_thumburl == NULL) ) {
-                    free( psz_thumbpath );
+            if (asprintf(&psz_thumbpath, "%s" DIR_SEP "BDMV" DIR_SEP "META" DIR_SEP "DL" DIR_SEP "%s",
+                          p_sys->psz_bd_path, meta->thumbnails[0].path) > 0) {
+                char *psz_thumburl = vlc_path2uri(psz_thumbpath, "file");
+                if (unlikely(psz_thumburl == NULL)) {
+                    free(psz_thumbpath);
                     return VLC_ENOMEM;
                 }
 
-                vlc_meta_SetArtURL( p_meta, psz_thumburl );
-                free( psz_thumburl );
+                vlc_meta_SetArtURL(p_meta, psz_thumburl);
+                free(psz_thumburl);
             }
-            free( psz_thumbpath );
+            free(psz_thumbpath);
         }
 
         return VLC_SUCCESS;
     }
 
     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);
@@ -1257,66 +1402,254 @@ static int blurayControl(demux_t *p_demux, int query, va_list args)
     case DEMUX_GET_ATTACHMENTS:
         return VLC_EGENERIC;
     default:
-        msg_Warn( p_demux, "unimplemented query (%d) in control", query );
+        msg_Warn(p_demux, "unimplemented query (%d) in control", query);
         return VLC_EGENERIC;
     }
     return VLC_SUCCESS;
 }
 
-static void blurayUpdateCurrentClip( demux_t *p_demux, uint32_t clip )
+/*****************************************************************************
+ * libbluray event handling
+ *****************************************************************************/
+
+static void streamFlush( demux_sys_t *p_sys )
+{
+    /*
+     * 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->b_flushed)
+        return;
+
+    block_t *p_block = block_Alloc(192);
+    if (!p_block)
+        return;
+
+    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 );
+    }
+
+    /* 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 < 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 < 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;
+            }
+        }
+    }
+
+    vlc_mutex_unlock(&p_sys->pl_info_lock);
+
+    if (i_pid > 0) {
+        int i_idx = findEsPairIndex(p_sys, i_pid);
+        if (i_idx >= 0) {
+            es_out_id_t *p_es = vlc_array_item_at_index(&p_sys->es, i_idx);
+            es_out_Control(p_demux->out, ES_OUT_SET_ES, p_es);
+        }
+    }
+}
+
+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_title_info(p_sys->bluray,
-                                        bd_get_current_title(p_sys->bluray), 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 )
+static void blurayHandleEvent(demux_t *p_demux, const BD_EVENT *e)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
 
-    switch (e->event)
-    {
+    switch (e->event) {
     case BD_EVENT_TITLE:
-        blurayUpdateTitle(p_demux, e->param);
+        if (e->param == BLURAY_TITLE_FIRST_PLAY)
+            p_demux->info.i_title = p_sys->i_title - 1;
+        else
+            p_demux->info.i_title = e->param;
+        /* this is feature title, we don't know yet which playlist it will play (if any) */
+        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;
+    case BD_EVENT_PLAYLIST:
+        /* Start of playlist playback (?????.mpls) */
+        blurayUpdatePlaylist(p_demux, e->param);
         break;
     case BD_EVENT_PLAYITEM:
         blurayUpdateCurrentClip(p_demux, e->param);
         break;
-    case BD_EVENT_AUDIO_STREAM:
-        if ( e->param == 0xFF )
-            break ;
-        BLURAY_TITLE_INFO *info = bd_get_title_info(p_sys->bluray,
-                                   bd_get_current_title(p_sys->bluray), 0);
-        if (info == NULL)
-            break ;
-        /* The param we get is the real stream id, not an index, ie. it starts from 1 */
-        int pid = info->clips[p_sys->i_current_clip].audio_streams[e->param - 1].pid;
-        bd_free_title_info( info );
-        int idx = findEsPairIndex(p_sys, pid);
-        if (idx >= 0) {
-            es_out_id_t *p_es = vlc_array_item_at_index(&p_sys->es, idx);
-            es_out_Control( p_demux->out, ES_OUT_SET_ES, p_es );
-        }
-        p_sys->i_audio_stream = pid;
-        break ;
     case BD_EVENT_CHAPTER:
         p_demux->info.i_update |= INPUT_UPDATE_SEEKPOINT;
         p_demux->info.i_seekpoint = e->param;
         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
+     */
+    case BD_EVENT_AUDIO_STREAM:
+    case BD_EVENT_PG_TEXTST_STREAM:
+        blurayStreamSelect(p_demux, e->event, e->param);
+        break;
     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);
+        break;
+    case BD_EVENT_IDLE:
+        /* nothing to do (ex. BD-J is preparing menus, waiting user input or running animation) */
+        /* avoid busy loop (bd_read() returns no data) */
+        msleep( 40000 );
+        break;
+
     default:
         msg_Warn(p_demux, "event: %d param: %d", e->event, e->param);
         break;
@@ -1338,52 +1671,48 @@ 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,
                         NB_TS_PACKETS * BD_TS_PACKET_SIZE);
-        if (nread < 0) {
-            block_Release(p_block);
-            return nread;
-        }
     } else {
         nread = bd_read_ext(p_sys->bluray, p_block->p_buffer,
                             NB_TS_PACKETS * BD_TS_PACKET_SIZE, &e);
-        if (nread < 0)
-        {
-            block_Release(p_block);
-            return -1;
-        }
-        if (nread == 0) {
-            if (e.event == BD_EVENT_NONE)
-                msg_Info(p_demux, "We reached the end of a title");
-            else
-                blurayHandleEvent(p_demux, &e);
-            block_Release(p_block);
-            return 1;
+        while (e.event != BD_EVENT_NONE) {
+            blurayHandleEvent(p_demux, &e);
+            bd_get_event(p_sys->bluray, &e);
         }
+    }
 
-        if (p_sys->current_overlay != -1) {
-            bluray_overlay_t *ov = p_sys->p_overlays[p_sys->current_overlay];
-            vlc_mutex_lock(&ov->lock);
-            bool display = ov->status == ToDisplay;
-            vlc_mutex_unlock(&ov->lock);
-            if (display) {
-                if (p_sys->p_vout == NULL)
-                    p_sys->p_vout = input_GetVout(p_sys->p_input);
-                if (p_sys->p_vout != NULL) {
-                    var_AddCallback(p_sys->p_vout, "mouse-moved", onMouseEvent, p_demux);
-                    var_AddCallback(p_sys->p_vout, "mouse-clicked", onMouseEvent, p_demux);
-                    bluraySendOverlayToVout(p_demux);
-                }
+    if (p_sys->current_overlay != -1) {
+        bluray_overlay_t *ov = p_sys->p_overlays[p_sys->current_overlay];
+        vlc_mutex_lock(&ov->lock);
+        bool display = ov->status == ToDisplay;
+        vlc_mutex_unlock(&ov->lock);
+        if (display) {
+            if (p_sys->p_vout == NULL)
+                p_sys->p_vout = input_GetVout(p_sys->p_input);
+            if (p_sys->p_vout != NULL) {
+                var_AddCallback(p_sys->p_vout, "mouse-moved", onMouseEvent, p_demux);
+                var_AddCallback(p_sys->p_vout, "mouse-clicked", onMouseEvent, p_demux);
+                bluraySendOverlayToVout(p_demux);
             }
         }
     }
 
+    if (nread <= 0) {
+        block_Release(p_block);
+        if (nread < 0)
+            return -1;
+        return 1;
+    }
+
     p_block->i_buffer = nread;
 
     stream_DemuxSend(p_sys->p_parser, p_block);
 
+    p_sys->b_flushed = false;
+
     return 1;
 }