[SCM] libbluray/master: Merge branch 'upstream'

ceros-guest at users.alioth.debian.org ceros-guest at users.alioth.debian.org
Mon Jul 18 00:36:42 UTC 2011


Imported Upstream version 0.0~git20110717.3477b65
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-Git-Refname: refs/heads/master
X-Git-Reftype: branch
X-Git-Oldrev: 5fd5314c12fd74e743efefb50752eaf5f321ce33
X-Git-Newrev: 541638fdd458c41ec711afe632b48bc051d1e35c

The following commit has been merged in the master branch:
commit 399bed314566bab2e90f9fddc0f14731092a9411
Merge: 2b6e11f2819a78841ac7198261cebe16526504c1 ddd1a206f1c24c128124e6a3e2992fba855b7da3
Author: Andres Mejia <mcitadel at gmail.com>
Date:   Sun Jul 17 20:33:08 2011 -0400

    Merge branch 'upstream'

diff --combined configure
index 2cb7b2d,98d4581..7ec2c45
--- a/configure
+++ b/configure
@@@ -11585,7 -11585,7 +11585,7 @@@ f
  
  done
  
 -for ac_header in stdlib.h mntent.h linux/cdrom.h inttypes.h strings.h
 +for ac_header in stdlib.h mntent.h linux/cdrom.h inttypes.h
  do :
    as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
  ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
@@@ -12128,7 -12128,8 +12128,8 @@@ $as_echo "#define DLOPEN_CRYPTO_LIBS 1
  
    fi
  elif [ $use_dlopen_crypto_libs = "yes" ]; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+   if test "${SYS}" != "mingw32" ; then
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
  $as_echo_n "checking for dlopen in -ldl... " >&6; }
  if ${ac_cv_lib_dl_dlopen+:} false; then :
    $as_echo_n "(cached) " >&6
@@@ -12174,6 -12175,11 +12175,11 @@@ els
    as_fn_error $? "$library_not_found" "$LINENO" 5
  fi
  
+   else
+ 
+ $as_echo "#define DLOPEN_CRYPTO_LIBS 1" >>confdefs.h
+ 
+   fi
  else
    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for aacs_open in -laacs" >&5
  $as_echo_n "checking for aacs_open in -laacs... " >&6; }
diff --combined configure.ac
index 97b4a2c,316ad47..525a31d
--- a/configure.ac
+++ b/configure.ac
@@@ -97,7 -97,7 +97,7 @@@ AC_TYPE_SIGNA
  
  # required headers
  AC_CHECK_HEADERS([stdarg.h sys/types.h dirent.h errno.h libgen.h malloc.h])
 -AC_CHECK_HEADERS([stdlib.h mntent.h linux/cdrom.h inttypes.h strings.h])
 +AC_CHECK_HEADERS([stdlib.h mntent.h linux/cdrom.h inttypes.h])
  AC_CHECK_HEADERS([sys/time.h time.h])
  if test "${SYS}" != "mingw32" ; then
      AC_CHECK_HEADERS(pthread.h,, [AC_MSG_ERROR([pthread.h required])])
@@@ -129,10 -129,14 +129,14 @@@ if [[ $use_dlopen_crypto_libs = "auto" 
           AC_DEFINE([DLOPEN_CRYPTO_LIBS], [1], ["Define to 1 if dlopening crypto libs"])
    fi
  elif [[ $use_dlopen_crypto_libs = "yes" ]]; then
-   AC_CHECK_LIB([dl], [dlopen],
-     [DLOPEN_LDFLAGS="-ldl"; AC_MSG_NOTICE($using_dlopen_crypto_libs)
-      AC_DEFINE([DLOPEN_CRYPTO_LIBS], [1], ["Define to 1 if dlopening crypto libs"])],
-     [AC_MSG_ERROR($library_not_found)])
+   if test "${SYS}" != "mingw32" ; then
+       AC_CHECK_LIB([dl], [dlopen],
+         [DLOPEN_LDFLAGS="-ldl"; AC_MSG_NOTICE($using_dlopen_crypto_libs)
+          AC_DEFINE([DLOPEN_CRYPTO_LIBS], [1], ["Define to 1 if dlopening crypto libs"])],
+         [AC_MSG_ERROR($library_not_found)])
+   else
+          AC_DEFINE([DLOPEN_CRYPTO_LIBS], [1], ["Define to 1 if dlopening crypto libs"])
+   fi
  else
    AC_CHECK_LIB([aacs], [aacs_open],,
      [AC_MSG_ERROR($library_not_found)])
diff --combined player_wrappers/xine/input_bluray.c
index 4088b50,21f5009..9e13cfc
--- a/player_wrappers/xine/input_bluray.c
+++ b/player_wrappers/xine/input_bluray.c
@@@ -38,6 -38,7 +38,6 @@@
  #include <string.h>
  #include <errno.h>
  #include <dlfcn.h>
 -#include <pthread.h>
  
  #include <libbluray/bluray.h>
  #include <libbluray/keys.h>
@@@ -51,7 -52,7 +51,7 @@@
  
  #define LOGMSG(x...)  xine_log (this->stream->xine, XINE_LOG_MSG, "input_bluray: " x);
  
 -#define XINE_ENGINE_INTERNAL  // stream->demux_plugin
 +#define XINE_ENGINE_INTERNAL
  
  #ifdef HAVE_CONFIG_H
  # include "xine_internal.h"
@@@ -80,6 -81,8 +80,8 @@@
  #define PKT_SIZE          192
  #define TICKS_IN_MS       45
  
+ #define MIN_TITLE_LENGTH  180
+ 
  typedef struct {
  
    input_class_t   input_class;
@@@ -99,7 -102,7 +101,7 @@@ typedef struct 
  
    xine_stream_t        *stream;
    xine_event_queue_t   *event_queue;
-   xine_osd_t           *osd;
+   xine_osd_t           *osd[2];
  
    bluray_input_class_t *class;
  
@@@ -117,122 -120,153 +119,142 @@@
    int                num_titles;        /* navigation mode, number of titles in disc index */
    int                current_title;     /* navigation mode, title from disc index */
    BLURAY_TITLE_INFO *title_info;
-   int                current_clip;
+   pthread_mutex_t    title_info_mutex;  /* lock this when accessing title_info outside of input/demux thread */
+   unsigned int       current_clip;
+   time_t             still_end_time;
    int                error;
    int                menu_open;
+   int                stream_flushed;
    int                pg_enable;
    int                pg_stream;
+   int                mouse_inside_button;
  
    uint32_t           cap_seekable;
    uint8_t            nav_mode;
  
  } bluray_input_plugin_t;
  
- static void close_overlay(bluray_input_plugin_t *this)
+ static void send_num_buttons(bluray_input_plugin_t *this, int n)
  {
-   if (this->osd) {
-     xine_osd_free(this->osd);
-     this->osd = NULL;
+   xine_event_t   event;
+   xine_ui_data_t data;
+ 
+   event.type = XINE_EVENT_UI_NUM_BUTTONS;
+   event.data = &data;
+   event.data_length = sizeof(data);
+   data.num_buttons = n;
+ 
+   xine_event_send(this->stream, &event);
+ }
+ 
+ static void close_overlay(bluray_input_plugin_t *this, int plane)
+ {
+   if (plane < 0) {
+     close_overlay(this, 0);
+     close_overlay(this, 1);
+     return;
+   }
+ 
+   if (plane < 2 && this->osd[plane]) {
+     xine_osd_free(this->osd[plane]);
+     this->osd[plane] = NULL;
+     if (plane == 1) {
+       send_num_buttons(this, 0);
+       this->menu_open = 0;
+     }
    }
  }
  
  static void overlay_proc(void *this_gen, const BD_OVERLAY * const ov)
  {
    bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen;
 +  uint32_t color[256];
 +  uint8_t  trans[256];
    unsigned i;
  
    if (!this) {
      return;
    }
  
-   if (!ov || ov->plane == 1)
-     this->menu_open = 0;
- 
-   if (!ov || !ov->img) {
+   if (!ov) {
      /* hide OSD */
-     close_overlay(this);
+     close_overlay(this, -1);
+     return;
+   }
+ 
+   if (ov->plane > 1) {
      return;
    }
  
    /* open xine OSD */
  
-   if (!this->osd) {
-     this->osd = xine_osd_new(this->stream, 0, 0, 1920, 1080);
+   if (!this->osd[ov->plane]) {
+     this->osd[ov->plane] = xine_osd_new(this->stream, 0, 0, 1920, 1080);
    }
-   if (!this->pg_enable)
+   xine_osd_t *osd = this->osd[ov->plane];
+   if (!this->pg_enable) {
      _x_select_spu_channel(this->stream, -1);
 -  }
  
    /* convert and set palette */
 -  if (ov->palette) {
 -    uint32_t color[256];
 -    uint8_t  trans[256];
 -    for(i = 0; i < 256; i++) {
 -      trans[i] = ov->palette[i].T;
 -      color[i] = (ov->palette[i].Y << 16) | (ov->palette[i].Cr << 8) | ov->palette[i].Cb;
 -    }
  
-   for(i = 0; i < 256; i++) {
-     trans[i] = ov->palette[i].T;
-     color[i] = (ov->palette[i].Y << 16) | (ov->palette[i].Cr << 8) | ov->palette[i].Cb;
+     xine_osd_set_palette(osd, color, trans);
    }
  
 -  /* uncompress and draw bitmap */
 -  if (ov->img) {
 -    const BD_PG_RLE_ELEM *rlep = ov->img;
 -    uint8_t *img = malloc(ov->w * ov->h);
 -    unsigned pixels = ov->w * ov->h;
 -
 -    for (i = 0; i < pixels; i += rlep->len, rlep++) {
 -      memset(img + i, rlep->color, rlep->len);
 -    }
 +  xine_osd_set_palette(this->osd, color, trans);
  
-   /* uncompress and draw bitmap */
+     xine_osd_draw_bitmap(osd, img, ov->x, ov->y, ov->w, ov->h, NULL);
  
 -    free(img);
 +  const BD_PG_RLE_ELEM *rlep = ov->img;
 +  uint8_t *img = malloc(ov->w * ov->h);
 +  unsigned pixels = ov->w * ov->h;
  
 -  } else {
 +  for (i = 0; i < pixels; i += rlep->len, rlep++) {
 +    memset(img + i, rlep->color, rlep->len);
 +  }
  
-   xine_osd_draw_bitmap(this->osd, img, ov->x, ov->y, ov->w, ov->h, NULL);
+     if (ov->x == 0 && ov->y == 0 && ov->w == 1920 && ov->h == 1080) {
+       /* Nothing to display, close OSD */
+       close_overlay(this, ov->plane);
+       return;
+     }
  
-   free(img);
+     /* wipe rect */
+     xine_osd_draw_rect(osd, ov->x, ov->y, ov->x + ov->w - 1, ov->y + ov->h - 1, 0xff, 1);
+   }
  
    /* display */
  
-   xine_osd_show(this->osd, 0);
+   xine_osd_show(osd, 0);
  
-   if (ov->plane == 1)
+   if (ov->plane == 1) {
      this->menu_open = 1;
+     send_num_buttons(this, 1);
+   }
  }
  
  static void update_stream_info(bluray_input_plugin_t *this)
  {
 -  if (this->title_info) {
 -    /* set stream info */
 -    _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_COUNT,    this->title_info->angle_count);
 -    _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER,   bd_get_current_angle(this->bdh));
 -    _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_CHAPTERS,       this->title_info->chapter_count > 0);
 -    _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_COUNT,  this->title_info->chapter_count);
 -    _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER, bd_get_current_chapter(this->bdh) + 1);
 -  }
 +  /* set stream info */
 +
 +  _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_COUNT,    this->title_info->angle_count);
 +  _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER,   bd_get_current_angle(this->bdh));
 +  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_CHAPTERS,       this->title_info->chapter_count > 0);
 +  _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_COUNT,  this->title_info->chapter_count);
 +  _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER, bd_get_current_chapter(this->bdh) + 1);
  }
  
- static void update_title_info(bluray_input_plugin_t *this, int playlist_id)
+ static void update_title_name(bluray_input_plugin_t *this)
  {
-   if (this->title_info)
-     bd_free_title_info(this->title_info);
- 
-   if (playlist_id < 0)
-     this->title_info = bd_get_title_info(this->bdh, this->current_title_idx);
-   else
-     this->title_info = bd_get_playlist_info(this->bdh, playlist_id);
- 
-   if (!this->title_info) {
-     LOGMSG("bd_get_title_info(%d) failed\n", this->current_title_idx);
-     return;
-   }
- 
-   /* set title */
- 
+   char           title_name[64] = "";
    xine_ui_data_t udata;
-   xine_event_t uevent = {
-     .type = XINE_EVENT_UI_SET_TITLE,
-     .stream = this->stream,
-     .data = &udata,
+   xine_event_t   uevent = {
+     .type        = XINE_EVENT_UI_SET_TITLE,
+     .stream      = this->stream,
+     .data        = &udata,
      .data_length = sizeof(udata)
    };
  
-   char title_name[64] = "";
- 
+   /* check disc library metadata */
    if (this->meta_dl) {
      unsigned i;
      for (i = 0; i < this->meta_dl->toc_count; i++)
@@@ -242,6 -276,7 +264,7 @@@
              strncpy(title_name, this->meta_dl->toc_entries[i].title_name, sizeof(title_name));
    }
  
+   /* title name */
    if (title_name[0]) {
    } else if (this->current_title == BLURAY_TITLE_TOP_MENU) {
      strcpy(title_name, "Top Menu");
@@@ -256,6 -291,7 +279,7 @@@
               this->current_title_idx + 1, this->num_title_idx);
    }
  
+   /* disc name */
    if (this->disc_name && this->disc_name[0]) {
      udata.str_len = snprintf(udata.str, sizeof(udata.str), "%s, %s",
                               this->disc_name, title_name);
@@@ -263,10 -299,33 +287,33 @@@
      udata.str_len = snprintf(udata.str, sizeof(udata.str), "%s",
                               title_name);
    }
  
    _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, udata.str);
  
+   xine_event_send(this->stream, &uevent);
+ }
+ 
+ static void update_title_info(bluray_input_plugin_t *this, int playlist_id)
+ {
+   /* update title_info */
+ 
+   pthread_mutex_lock(&this->title_info_mutex);
+ 
+   if (this->title_info)
+     bd_free_title_info(this->title_info);
+ 
+   if (playlist_id < 0)
+     this->title_info = bd_get_title_info(this->bdh, this->current_title_idx, 0);
+   else
+     this->title_info = bd_get_playlist_info(this->bdh, playlist_id, 0);
+ 
+   pthread_mutex_unlock(&this->title_info_mutex);
+ 
+   if (!this->title_info) {
+     LOGMSG("bd_get_title_info(%d) failed\n", this->current_title_idx);
+     return;
+   }
+ 
    /* calculate and set stream rate */
  
    uint64_t rate = bd_get_title_size(this->bdh) * UINT64_C(8) // bits
@@@ -276,10 -335,18 +323,18 @@@
  
    /* set stream info */
  
-   _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_COUNT,  this->num_title_idx);
-   _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER, this->current_title_idx + 1);
+   if (this->nav_mode) {
+     _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_COUNT,  this->num_titles);
+     _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER, this->current_title);
+   } else {
+     _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_COUNT,  this->num_title_idx);
+     _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER, this->current_title_idx + 1);
+   }
  
    update_stream_info(this);
+ 
+   /* set title */
+   update_title_name(this);
  }
  
  static int open_title (bluray_input_plugin_t *this, int title_idx)
@@@ -296,6 -363,27 +351,27 @@@
    return 1;
  }
  
+ #ifndef DEMUX_OPTIONAL_DATA_FLUSH
+ #  define DEMUX_OPTIONAL_DATA_FLUSH 0x10000
+ #endif
+ 
+ static void stream_flush(bluray_input_plugin_t *this)
+ {
+   if (this->stream_flushed)
+     return;
+ 
+   lprintf("Stream flush\n");
+ 
+   this->stream_flushed = 1;
+ 
+   int tmp = 0;
+   if (DEMUX_OPTIONAL_SUCCESS !=
+       this->stream->demux_plugin->get_optional_data(this->stream->demux_plugin, &tmp, DEMUX_OPTIONAL_DATA_FLUSH)) {
+     LOGMSG("stream flush not supported by the demuxer !\n");
+     return;
+   }
+ }
+ 
  static void stream_reset(bluray_input_plugin_t *this)
  {
    if (!this || !this->stream || !this->stream->demux_plugin)
@@@ -305,7 -393,7 +381,7 @@@
  
    this->cap_seekable = 0;
  
 -  _x_set_fine_speed(this->stream, XINE_FINE_SPEED_NORMAL);
 +  xine_set_param(this->stream, XINE_PARAM_FINE_SPEED, XINE_FINE_SPEED_NORMAL);
    this->stream->demux_plugin->seek(this->stream->demux_plugin, 0, 0, 1);
    _x_demux_control_start(this->stream);
  
@@@ -314,34 -402,28 +390,28 @@@
  
  static void wait_secs(bluray_input_plugin_t *this, unsigned seconds)
  {
-   // infinite still mode ?
-   if (!seconds) {
-     xine_usec_sleep(10*1000);
-     return;
-   }
- 
-   // clip to allowed range
-   if (seconds > 300) {
-     seconds = 300;
+   stream_flush(this);
+ 
+   if (this->still_end_time) {
+     if (time(NULL) >= this->still_end_time) {
+       lprintf("pause end\n");
+       this->still_end_time = 0;
+       bd_read_skip_still(this->bdh);
+       stream_reset(this);
+       return;
+     }
    }
  
-   // pause the stream
-   int paused = _x_get_fine_speed(this->stream) == XINE_SPEED_PAUSE;
-   if (!paused) {
-     _x_set_fine_speed(this->stream, XINE_SPEED_PAUSE);
-   }
+   else if (seconds) {
+     if (seconds > 300) {
+       seconds = 300;
+     }
  
-   // wait until interrupted
-   int loops = seconds * 25; /* N * 40 ms */
-   while (!this->stream->demux_action_pending && loops-- > 0) {
-     xine_usec_sleep(40*1000);
+     lprintf("still image, pause for %d seconds\n", seconds);
+     this->still_end_time = time(NULL) + seconds;
    }
  
-   lprintf("paused for %d seconds (%d ms left)\n", seconds - loops/25, loops * 40);
- 
-   if (!paused) {
-     _x_set_fine_speed(this->stream, XINE_FINE_SPEED_NORMAL);
-   }
+   xine_usec_sleep(40*1000);
  }
  
  static void update_spu_channel(bluray_input_plugin_t *this, int channel)
@@@ -387,6 -469,7 +457,7 @@@ static void handle_libbluray_event(blur
  
        case BD_EVENT_SEEK:
          lprintf("BD_EVENT_SEEK\n");
+         this->still_end_time = 0;
          stream_reset(this);
          break;
  
@@@ -413,6 -496,11 +484,11 @@@
          _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER, ev.param);
          break;
  
+       case BD_EVENT_END_OF_TITLE:
+         lprintf("BD_EVENT_END_OF_TITLE\n");
+         stream_flush(this);
+         break;
+ 
        case BD_EVENT_TITLE:
          this->current_title = ev.param;
          break;
@@@ -427,10 -515,8 +503,8 @@@
  
        case BD_EVENT_PLAYITEM:
          lprintf("BD_EVENT_PLAYITEM %d\n", ev.param);
-         if (ev.param < this->title_info->clip_count)
-           this->current_clip = ev.param;
-         else
-           this->current_clip = 0;
+         this->current_clip = ev.param;
+         this->still_end_time = 0;
          break;
  
        case BD_EVENT_CHAPTER:
@@@ -470,6 -556,7 +544,7 @@@
          break;
  
        default:
+         LOGMSG("unhandled libbluray event %d [param %d]\n", ev.event, ev.param);
          break;
      }
  }
@@@ -484,6 -571,25 +559,25 @@@ static void handle_libbluray_events(blu
    }
  }
  
+ static void send_mouse_enter_leave_event(bluray_input_plugin_t *this, int direction)
+ {
+   if (direction != this->mouse_inside_button) {
+     xine_event_t        event;
+     xine_spu_button_t   spu_event;
+ 
+     spu_event.direction = direction;
+     spu_event.button    = 1;
+ 
+     event.type        = XINE_EVENT_SPU_BUTTON;
+     event.stream      = this->stream;
+     event.data        = &spu_event;
+     event.data_length = sizeof(spu_event);
+     xine_event_send(this->stream, &event);
+ 
+     this->mouse_inside_button = direction;
+   }
+ }
+ 
  static void handle_events(bluray_input_plugin_t *this)
  {
    if (!this->event_queue)
@@@ -515,6 -621,7 +609,7 @@@
            } else {
              bd_play_title(this->bdh, MAX(1, this->current_title - 1));
            }
+           stream_reset(this);
            break;
  
          case XINE_EVENT_INPUT_RIGHT:
@@@ -524,6 -631,7 +619,7 @@@
            } else {
              bd_play_title(this->bdh, MIN(this->num_titles, this->current_title + 1));
            }
+           stream_reset(this);
            break;
        }
      }
@@@ -536,13 -644,18 +632,18 @@@
          if (input->button == 1) {
            bd_mouse_select(this->bdh, pts, input->x, input->y);
            bd_user_input(this->bdh, pts, BD_VK_MOUSE_ACTIVATE);
+           send_mouse_enter_leave_event(this, 0);
          }
          break;
        }
  
        case XINE_EVENT_INPUT_MOUSE_MOVE: {
          xine_input_data_t *input = event->data;
-         bd_mouse_select(this->bdh, pts, input->x, input->y);
+         if (bd_mouse_select(this->bdh, pts, input->x, input->y) > 0) {
+           send_mouse_enter_leave_event(this, 1);
+         } else {
+           send_mouse_enter_leave_event(this, 0);
+         }
          break;
        }
  
@@@ -571,37 -684,42 +672,42 @@@
        case XINE_EVENT_INPUT_NUMBER_9:  bd_user_input(this->bdh, pts, BD_VK_9); break;
  
        case XINE_EVENT_INPUT_NEXT: {
-         unsigned chapter = bd_get_current_chapter(this->bdh) + 1;
- 
-         lprintf("XINE_EVENT_INPUT_NEXT: next chapter\n");
- 
-         if (chapter >= this->title_info->chapter_count) {
-           if (this->current_title_idx < this->num_title_idx - 1) {
-             open_title(this, this->current_title_idx + 1);
-             stream_reset(this);
-           }
-         } else {
-           bd_seek_chapter(this->bdh, chapter);
-           update_stream_info(this);
-           stream_reset(this);
+         cfg_entry_t* entry = this->class->xine->config->lookup_entry(this->class->xine->config,
+                                                                      "media.bluray.skip_behaviour");
+         switch (entry->num_value) {
+           case 0: /* skip by chapter */
+             bd_seek_chapter(this->bdh, bd_get_current_chapter(this->bdh) + 1);
+             update_stream_info(this);
+             break;
+           case 1: /* skip by title */
+             if (!this->nav_mode) {
+               open_title(this, MIN(this->num_title_idx - 1, this->current_title_idx + 1));
+             } else {
+               bd_play_title(this->bdh, MIN(this->num_titles, this->current_title + 1));
+             }
+             break;
          }
+         stream_reset(this);
          break;
        }
  
        case XINE_EVENT_INPUT_PREVIOUS: {
-         int chapter = bd_get_current_chapter(this->bdh) - 1;
- 
-         lprintf("XINE_EVENT_INPUT_PREVIOUS: previous chapter\n");
- 
-         if (chapter < 0 && this->current_title_idx > 0) {
-           open_title(this, this->current_title_idx - 1);
-           stream_reset(this);
-         } else {
-           chapter = MAX(0, chapter);
-           bd_seek_chapter(this->bdh, chapter);
-           update_stream_info(this);
-           stream_reset(this);
+         cfg_entry_t* entry = this->class->xine->config->lookup_entry(this->class->xine->config,
+                                                                      "media.bluray.skip_behaviour");
+         switch (entry->num_value) {
+           case 0: /* skip by chapter */
+             bd_seek_chapter(this->bdh, MAX(0, ((int)bd_get_current_chapter(this->bdh)) - 1));
+             update_stream_info(this);
+             break;
+           case 1: /* skip by title */
+             if (!this->nav_mode) {
+               open_title(this, MAX(0, this->current_title_idx - 1));
+             } else {
+               bd_play_title(this->bdh, MAX(1, this->current_title - 1));
+             }
+             break;
          }
+         stream_reset(this);
          break;
        }
  
@@@ -638,7 -756,8 +744,8 @@@ static uint32_t bluray_plugin_get_capab
    return this->cap_seekable  |
           INPUT_CAP_BLOCK     |
           INPUT_CAP_AUDIOLANG |
-          INPUT_CAP_SPULANG;
+          INPUT_CAP_SPULANG   |
+          INPUT_CAP_CHAPTERS;
  }
  
  #if XINE_VERSION_CODE >= 10190
@@@ -663,7 -782,7 +770,7 @@@ static off_t bluray_plugin_read (input_
        if (result == 0) {
          handle_events(this);
          if (ev.event == BD_EVENT_NONE) {
 -          if (_x_action_pending(this->stream)) {
 +          if (this->stream->demux_action_pending) {
              break;
            }
          }
@@@ -677,6 -796,8 +784,8 @@@
    if (result < 0)
      LOGMSG("bd_read() failed: %s (%d of %d)\n", strerror(errno), (int)result, (int)len);
  
+   this->stream_flushed = 0;
+ 
    return result;
  }
  
@@@ -713,7 -834,7 +822,7 @@@ static off_t bluray_plugin_seek (input_
  
    if (!this || !this->bdh)
      return -1;
-   if (this->current_title_idx < 0)
+   if (this->still_end_time)
      return offset;
  
    /* convert relative seeks to absolute */
@@@ -737,20 -858,33 +846,23 @@@ static off_t bluray_plugin_seek_time (i
  {
    bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen;
  
 -  if (!this || !this->bdh)
 +  if (!this || !this->bdh || !this->title_info)
      return -1;
  
+   if (this->still_end_time)
+     return bd_tell(this->bdh);
+ 
    /* convert relative seeks to absolute */
  
    if (origin == SEEK_CUR) {
      time_offset += this_gen->get_current_time(this_gen);
    }
    else if (origin == SEEK_END) {
 -
 -    pthread_mutex_lock(&this->title_info_mutex);
 -
 -    if (!this->title_info) {
 -      pthread_mutex_unlock(&this->title_info_mutex);
 -      return -1;
 -    }
 -
      int duration = this->title_info->duration / 90;
      if (time_offset < duration)
        time_offset = duration - time_offset;
      else
        time_offset = 0;
 -
 -    pthread_mutex_unlock(&this->title_info_mutex);
    }
  
    lprintf("bluray_plugin_seek_time() seeking to %d.%03ds\n", time_offset / 1000, time_offset % 1000);
@@@ -793,12 -927,9 +905,12 @@@ static const char* bluray_plugin_get_mr
    return this->mrl;
  }
  
 -static int get_optional_data_impl (bluray_input_plugin_t *this, void *data, int data_type)
 +static int bluray_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type)
  {
 -  unsigned int current_clip = this->current_clip;
 +  bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen;
 +
 +  if (!this || !this->stream || !data)
 +    return INPUT_OPTIONAL_UNSUPPORTED;
  
    switch (data_type) {
      case INPUT_OPTIONAL_DATA_DEMUXER:
@@@ -810,9 -941,9 +922,9 @@@
       * - channel number can be mpeg-ts PID (0x1100 ... 0x11ff)
       */
      case INPUT_OPTIONAL_DATA_AUDIOLANG:
 -      if (this->title_info && this->title_info->clip_count < current_clip) {
 +      if (this->title_info) {
          int               channel = *((int *)data);
 -        BLURAY_CLIP_INFO *clip    = &this->title_info->clips[current_clip];
 +        BLURAY_CLIP_INFO *clip    = &this->title_info->clips[this->current_clip];
  
          if (channel >= 0 && channel < clip->audio_stream_count) {
            memcpy(data, clip->audio_streams[channel].lang, 4);
@@@ -840,9 -971,9 +952,9 @@@
       * - channel number can be mpeg-ts PID (0x1200 ... 0x12ff)
       */
      case INPUT_OPTIONAL_DATA_SPULANG:
 -      if (this->title_info && this->title_info->clip_count < current_clip) {
 +      if (this->title_info) {
          int               channel = *((int *)data);
 -        BLURAY_CLIP_INFO *clip    = &this->title_info->clips[current_clip];
 +        BLURAY_CLIP_INFO *clip    = &this->title_info->clips[this->current_clip];
  
          if (channel >= 0 && channel < clip->pg_stream_count) {
            memcpy(data, clip->pg_streams[channel].lang, 4);
@@@ -872,6 -1003,20 +984,6 @@@
    return INPUT_OPTIONAL_UNSUPPORTED;
  }
  
 -static int bluray_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type)
 -{
 -  bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen;
 -  int r = INPUT_OPTIONAL_UNSUPPORTED;
 -
 -  if (this && this->stream && data) {
 -    pthread_mutex_lock(&this->title_info_mutex);
 -    r = get_optional_data_impl(this, data, data_type);
 -    pthread_mutex_unlock(&this->title_info_mutex);
 -  }
 -
 -  return r;
 -}
 -
  static void bluray_plugin_dispose (input_plugin_t *this_gen)
  {
    bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen;
@@@ -879,13 -1024,18 +991,13 @@@
    if (this->bdh)
      bd_register_overlay_proc(this->bdh, NULL, NULL);
  
-   close_overlay(this);
+   close_overlay(this, -1);
  
    if (this->event_queue)
      xine_event_dispose_queue(this->event_queue);
  
 -  pthread_mutex_lock(&this->title_info_mutex);
    if (this->title_info)
      bd_free_title_info(this->title_info);
 -  this->title_info = NULL;
 -  pthread_mutex_unlock(&this->title_info_mutex);
 -
 -  pthread_mutex_destroy(&this->title_info_mutex);
  
    if (this->bdh)
      bd_close(this->bdh);
@@@ -1044,7 -1194,7 +1156,7 @@@ static int bluray_plugin_open (input_pl
  
    /* load title list */
  
-   this->num_title_idx = bd_get_titles(this->bdh, TITLES_RELEVANT);
+   this->num_title_idx = bd_get_titles(this->bdh, TITLES_RELEVANT, MIN_TITLE_LENGTH);
    LOGMSG("%d titles\n", this->num_title_idx);
  
    if (this->num_title_idx < 1)
@@@ -1057,7 -1207,7 +1169,7 @@@
      uint64_t duration = 0;
      int i, playlist = 99999;
      for (i = 0; i < this->num_title_idx; i++) {
 -      BLURAY_TITLE_INFO *info = bd_get_title_info(this->bdh, i, 0);
 +      BLURAY_TITLE_INFO *info = bd_get_title_info(this->bdh, i);
        if (info->duration > duration) {
          title    = i;
          duration = info->duration;
@@@ -1077,9 -1227,8 +1189,8 @@@
    bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_MENU_LANG,    this->class->language);
    bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_COUNTRY_CODE, this->class->country);
  
-   /* init eq */
-   BD_EVENT ev;
-   bd_get_event(this->bdh, &ev);
+   /* init event queue */
+   bd_get_event(this->bdh, NULL);
  
    /* get disc name */
  
@@@ -1166,6 -1315,8 +1277,6 @@@ static input_plugin_t *bluray_class_get
  
    this->event_queue = xine_event_new_queue (this->stream);
  
 -  pthread_mutex_init(&this->title_info_mutex, NULL);
 -
    this->pg_stream = -1;
  
    return &this->input_plugin;
@@@ -1262,6 -1413,8 +1373,8 @@@ static void *bluray_init_plugin (xine_
  {
    (void)data;
  
+   static char *skip_modes[] = {"skip chapter", "skip title", NULL};
+ 
    config_values_t      *config = xine->config;
    bluray_input_class_t *this   = (bluray_input_class_t *) calloc(1, sizeof (bluray_input_class_t));
  
@@@ -1318,6 -1471,14 +1431,14 @@@
                             "control age limit is higher than this limit"),
                           0, parental_change_cb, this);
  
+   /* */
+   config->register_enum(config, "media.bluray.skip_behaviour", 0,
+                         skip_modes,
+                         _("unit for the skip action"),
+                         _("You can configure the behaviour when issuing a skip command (using the skip "
+                           "buttons for example)."),
+                         20, NULL, NULL);
+ 
    return this;
  }
  
diff --combined src/libbluray/bluray.c
index 38d1ee1,4e2fce3..8f627ee
--- a/src/libbluray/bluray.c
+++ b/src/libbluray/bluray.c
@@@ -28,6 -28,7 +28,7 @@@
  #include "util/macro.h"
  #include "util/logging.h"
  #include "util/strutl.h"
+ #include "util/mutex.h"
  #include "bdnav/navigation.h"
  #include "bdnav/index_parse.h"
  #include "bdnav/meta_parse.h"
@@@ -56,6 -57,7 +57,7 @@@ typedef void*   (*fptr_p_void)()
  
  #define MAX_EVENTS 31  /* 2^n - 1 */
  typedef struct bd_event_queue_s {
+     BD_MUTEX mutex;
      unsigned in;  /* next free slot */
      unsigned out; /* next event */
      BD_EVENT ev[MAX_EVENTS+1];
@@@ -78,6 -80,8 +80,6 @@@ typedef struct 
      /* current aligned unit */
      uint16_t       int_buf_off;
  
 -    BD_UO_MASK     uo_mask;
 -
  } BD_STREAM;
  
  typedef struct {
@@@ -168,8 -172,21 +170,21 @@@ static void _init_event_queue(BLURAY *b
  {
      if (!bd->event_queue) {
          bd->event_queue = calloc(1, sizeof(struct bd_event_queue_s));
+         bd_mutex_init(&bd->event_queue->mutex);
      } else {
-         memset(bd->event_queue, 0, sizeof(struct bd_event_queue_s));
+         bd_mutex_lock(&bd->event_queue->mutex);
+         bd->event_queue->in  = 0;
+         bd->event_queue->out = 0;
+         memset(bd->event_queue->ev, 0, sizeof(bd->event_queue->ev));
+         bd_mutex_unlock(&bd->event_queue->mutex);
+     }
+ }
+ 
+ static void _free_event_queue(BLURAY *bd)
+ {
+     if (bd->event_queue) {
+         bd_mutex_destroy(&bd->event_queue->mutex);
+         X_FREE(bd->event_queue);
      }
  }
  
@@@ -178,11 -195,18 +193,18 @@@ static int _get_event(BLURAY *bd, BD_EV
      struct bd_event_queue_s *eq = bd->event_queue;
  
      if (eq) {
+         bd_mutex_lock(&eq->mutex);
+ 
          if (eq->in != eq->out) {
+ 
              *ev = eq->ev[eq->out];
              eq->out = (eq->out + 1) & MAX_EVENTS;
+ 
+             bd_mutex_unlock(&eq->mutex);
              return 1;
          }
+ 
+         bd_mutex_unlock(&eq->mutex);
      }
  
      ev->event = BD_EVENT_NONE;
@@@ -195,14 -219,20 +217,20 @@@ static int _queue_event(BLURAY *bd, BD_
      struct bd_event_queue_s *eq = bd->event_queue;
  
      if (eq) {
+         bd_mutex_lock(&eq->mutex);
+ 
          unsigned new_in = (eq->in + 1) & MAX_EVENTS;
  
          if (new_in != eq->out) {
              eq->ev[eq->in] = ev;
              eq->in = new_in;
+ 
+             bd_mutex_unlock(&eq->mutex);
              return 1;
          }
  
+         bd_mutex_unlock(&eq->mutex);
+ 
          BD_DEBUG(DBG_BLURAY|DBG_CRIT, "_queue_event(%d, %d): queue overflow !\n", ev.event, ev.param);
      }
  
@@@ -295,6 -325,9 +323,6 @@@ static void _close_m2ts(BD_STREAM *st
          file_close(st->fp);
          st->fp = NULL;
      }
 -
 -    /* reset UO mask */
 -    memset(&st->uo_mask, 0, sizeof(st->uo_mask));
  }
  
  static int _open_m2ts(BLURAY *bd, BD_STREAM *st)
@@@ -328,6 -361,10 +356,6 @@@
              }
  
              if (st == &bd->st0) {
 -                MPLS_PL *pl = st->clip->title->pl;
 -                st->uo_mask = bd_uo_mask_combine(pl->app_info.uo_mask,
 -                                                 pl->play_item[st->clip->ref].uo_mask);
 -
                  _update_clip_psrs(bd, st->clip);
              }
  
@@@ -738,7 -775,8 +766,7 @@@ static int _libbdplus_open(BLURAY *bd, 
          return 0;
      }
  
 -    const uint8_t *aacs_vid = (const uint8_t *)_libaacs_get_vid(bd);
 -    bd->bdplus = bd->bdplus_init(bd->device_path, keyfile_path, aacs_vid ? aacs_vid : vid);
 +    bd->bdplus = bd->bdplus_init(bd->device_path, keyfile_path, _libaacs_get_vid(bd) ?: vid);
  
      if (bd->bdplus) {
          BD_DEBUG(DBG_BLURAY,"libbdplus initialized\n");
@@@ -887,7 -925,7 +915,7 @@@ void bd_close(BLURAY *bd
      indx_free(&bd->index);
      bd_registers_free(bd->regs);
  
-     X_FREE(bd->event_queue);
+     _free_event_queue(bd);
      X_FREE(bd->device_path);
  
      BD_DEBUG(DBG_BLURAY, "BLURAY destroyed! (%p)\n", bd);
@@@ -1163,7 -1201,10 +1191,7 @@@ int bd_read(BLURAY *bd, unsigned char *
                          return 0;
                      }
                      if (pi->still_mode == BLURAY_STILL_TIME) {
 -                        if (bd->event_queue) {
 -                            _queue_event(bd, (BD_EVENT){BD_EVENT_STILL_TIME, pi->still_time});
 -                            return 0;
 -                        }
 +                        _queue_event(bd, (BD_EVENT){BD_EVENT_STILL_TIME, pi->still_time});
                      }
  
                      // find next clip
@@@ -1176,14 -1217,6 +1204,14 @@@
                      if (!_open_m2ts(bd, st)) {
                          return -1;
                      }
 +
 +                    // timed still mode: allow application to process BD_EVENT_STILL_TIME.
 +                    // next bd_read() will return new data.
 +                    if (bd->event_queue) {
 +                        if (pi->still_mode == BLURAY_STILL_TIME) {
 +                            return 0;
 +                        }
 +                    }
                  }
  
                  if (_read_block(bd, st, bd->int_buf)) {
@@@ -1221,6 -1254,24 +1249,6 @@@
      return -1;
  }
  
 -int bd_read_skip_still(BLURAY *bd)
 -{
 -    BD_STREAM *st = &bd->st0;
 -
 -    if (st->clip) {
 -        MPLS_PI *pi = &st->clip->title->pl->play_item[st->clip->ref];
 -
 -        if (pi->still_mode == BLURAY_STILL_TIME) {
 -            st->clip = nav_next_clip(bd->title, st->clip);
 -            if (st->clip) {
 -                return _open_m2ts(bd, st);
 -            }
 -        }
 -    }
 -
 -    return 0;
 -}
 -
  /*
   * preloader for asynchronous sub paths
   */
@@@ -1299,11 -1350,11 +1327,11 @@@ static void _close_playlist(BLURAY *bd
      }
  }
  
 -static int _open_playlist(BLURAY *bd, const char *f_name, unsigned angle)
 +static int _open_playlist(BLURAY *bd, const char *f_name)
  {
      _close_playlist(bd);
  
 -    bd->title = nav_title_open(bd->device_path, f_name, angle);
 +    bd->title = nav_title_open(bd->device_path, f_name);
      if (bd->title == NULL) {
          BD_DEBUG(DBG_BLURAY | DBG_CRIT, "Unable to open title %s! (%p)\n",
                f_name, bd);
@@@ -1347,7 -1398,7 +1375,7 @@@ int bd_select_playlist(BLURAY *bd, uint
          }
      }
  
 -    result = _open_playlist(bd, f_name, 0);
 +    result = _open_playlist(bd, f_name);
  
      X_FREE(f_name);
      return result;
@@@ -1373,7 -1424,7 +1401,7 @@@ int bd_select_title(BLURAY *bd, uint32_
      bd->title_idx = title_idx;
      f_name = bd->title_list->title_info[title_idx].name;
  
 -    return _open_playlist(bd, f_name, 0);
 +    return _open_playlist(bd, f_name);
  }
  
  uint32_t bd_get_current_title(BLURAY *bd)
@@@ -1432,7 -1483,7 +1460,7 @@@ void bd_seamless_angle_change(BLURAY *b
   * title lists
   */
  
 -uint32_t bd_get_titles(BLURAY *bd, uint8_t flags, uint32_t min_title_length)
 +uint32_t bd_get_titles(BLURAY *bd, uint8_t flags)
  {
      if (!bd) {
          BD_DEBUG(DBG_BLURAY | DBG_CRIT, "bd_get_titles(NULL) failed (%p)\n", bd);
@@@ -1442,7 -1493,7 +1470,7 @@@
      if (bd->title_list != NULL) {
          nav_free_title_list(bd->title_list);
      }
 -    bd->title_list = nav_get_title_list(bd->device_path, flags, min_title_length);
 +    bd->title_list = nav_get_title_list(bd->device_path, flags);
  
      if (!bd->title_list) {
          BD_DEBUG(DBG_BLURAY | DBG_CRIT, "nav_get_title_list(%s) failed (%p)\n", bd->device_path, bd);
@@@ -1518,12 -1569,13 +1546,12 @@@ static BLURAY_TITLE_INFO* _fill_title_i
      return title_info;
  }
  
 -static BLURAY_TITLE_INFO *_get_title_info(BLURAY *bd, uint32_t title_idx, uint32_t playlist, const char *mpls_name,
 -                                          unsigned angle)
 +static BLURAY_TITLE_INFO *_get_title_info(BLURAY *bd, uint32_t title_idx, uint32_t playlist, const char *mpls_name)
  {
      NAV_TITLE *title;
      BLURAY_TITLE_INFO *title_info;
  
 -    title = nav_title_open(bd->device_path, mpls_name, angle);
 +    title = nav_title_open(bd->device_path, mpls_name);
      if (title == NULL) {
          BD_DEBUG(DBG_BLURAY | DBG_CRIT, "Unable to open title %s! (%p)\n",
                mpls_name, bd);
@@@ -1536,7 -1588,7 +1564,7 @@@
      return title_info;
  }
  
 -BLURAY_TITLE_INFO* bd_get_title_info(BLURAY *bd, uint32_t title_idx, unsigned angle)
 +BLURAY_TITLE_INFO* bd_get_title_info(BLURAY *bd, uint32_t title_idx)
  {
      if (bd->title_list == NULL) {
          BD_DEBUG(DBG_BLURAY, "Title list not yet read! (%p)\n", bd);
@@@ -1549,15 -1601,16 +1577,15 @@@
  
      return _get_title_info(bd,
                             title_idx, bd->title_list->title_info[title_idx].mpls_id,
 -                           bd->title_list->title_info[title_idx].name,
 -                           angle);
 +                           bd->title_list->title_info[title_idx].name);
  }
  
 -BLURAY_TITLE_INFO* bd_get_playlist_info(BLURAY *bd, uint32_t playlist, unsigned angle)
 +BLURAY_TITLE_INFO* bd_get_playlist_info(BLURAY *bd, uint32_t playlist)
  {
      char *f_name = str_printf("%05d.mpls", playlist);
      BLURAY_TITLE_INFO *title_info;
  
 -    title_info = _get_title_info(bd, 0, playlist, f_name, angle);
 +    title_info = _get_title_info(bd, 0, playlist, f_name);
  
      X_FREE(f_name);
  
@@@ -1667,24 -1720,78 +1695,78 @@@ void bd_stop_bdj(BLURAY *bd
   * Navigation mode interface
   */
  
+ static void _process_psr_restore_event(BLURAY *bd, BD_PSR_EVENT *ev)
+ {
+     /* PSR restore events are handled internally.
+      * Restore stored playback position.
+      */
+ 
+     BD_DEBUG(DBG_BLURAY, "PSR restore: psr%u = %u (%p)\n", ev->psr_idx, ev->new_val, bd);
+ 
+     switch (ev->psr_idx) {
+         case PSR_ANGLE_NUMBER:
+             /* can't set angle before playlist is opened */
+             return;
+         case PSR_TITLE_NUMBER:
+             /* pass to the application */
+             _queue_event(bd, (BD_EVENT){BD_EVENT_TITLE, ev->new_val});
+             return;
+         case PSR_CHAPTER:
+             /* will be selected automatically */
+             return;
+         case PSR_PLAYLIST:
+             bd_select_playlist(bd, ev->new_val);
+             nav_set_angle(bd->title, bd->st0.clip, bd_psr_read(bd->regs, PSR_ANGLE_NUMBER) - 1);
+             return;
+         case PSR_PLAYITEM:
+             bd_seek_playitem(bd, ev->new_val);
+             return;
+         case PSR_TIME:
+             bd_seek_time(bd, ((int64_t)ev->new_val) << 1);
+             return;
+ 
+         case PSR_SELECTED_BUTTON_ID:
+         case PSR_MENU_PAGE_ID:
+             /* handled by graphics controller */
+             return;
+ 
+         default:
+             /* others: ignore */
+             return;
+     }
+ }
+ 
  /*
   * notification events to APP
   */
- static void _process_psr_event(void *handle, BD_PSR_EVENT *ev)
- {
-     BLURAY *bd = (BLURAY*)handle;
  
-     BD_DEBUG(DBG_BLURAY, "PSR event %d %d (%p)\n", ev->psr_idx, ev->new_val, bd);
+ static void _process_psr_write_event(BLURAY *bd, BD_PSR_EVENT *ev)
+ {
+     if (ev->ev_type == BD_PSR_WRITE) {
+         BD_DEBUG(DBG_BLURAY, "PSR write: psr%u = %u (%p)\n", ev->psr_idx, ev->new_val, bd);
+     }
  
      switch (ev->psr_idx) {
  
          /* current playback position */
  
-         case PSR_ANGLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_ANGLE, ev->new_val}); break;
-         case PSR_TITLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_TITLE, ev->new_val}); break;
-         case PSR_PLAYLIST: _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYLIST, ev->new_val}); break;
-         case PSR_PLAYITEM: _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYITEM, ev->new_val}); break;
-         case PSR_CHAPTER:  _queue_event(bd, (BD_EVENT){BD_EVENT_CHAPTER,  ev->new_val}); break;
+         case PSR_ANGLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_ANGLE,    ev->new_val}); break;
+         case PSR_TITLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_TITLE,    ev->new_val}); break;
+         case PSR_PLAYLIST:     _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYLIST, ev->new_val}); break;
+         case PSR_PLAYITEM:     _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYITEM, ev->new_val}); break;
+         case PSR_CHAPTER:      _queue_event(bd, (BD_EVENT){BD_EVENT_CHAPTER,  ev->new_val}); break;
+ 
+         default:;
+     }
+ }
+ 
+ static void _process_psr_change_event(BLURAY *bd, BD_PSR_EVENT *ev)
+ {
+     BD_DEBUG(DBG_BLURAY, "PSR change: psr%u = %u (%p)\n", ev->psr_idx, ev->new_val, bd);
+ 
+     _process_psr_write_event(bd, ev);
+ 
+     switch (ev->psr_idx) {
  
          /* stream selection */
  
@@@ -1721,6 -1828,30 +1803,30 @@@
      }
  }
  
+ static void _process_psr_event(void *handle, BD_PSR_EVENT *ev)
+ {
+     BLURAY *bd = (BLURAY*)handle;
+ 
+     switch(ev->ev_type) {
+         case BD_PSR_WRITE:
+             _process_psr_write_event(bd, ev);
+             break;
+         case BD_PSR_CHANGE:
+             _process_psr_change_event(bd, ev);
+             break;
+         case BD_PSR_RESTORE:
+             _process_psr_restore_event(bd, ev);
+             break;
+ 
+         case BD_PSR_SAVE:
+             BD_DEBUG(DBG_BLURAY, "PSR save event (%p)\n", bd);
+             break;
+         default:
+             BD_DEBUG(DBG_BLURAY, "PSR event %d: psr%u = %u (%p)\n", ev->ev_type, ev->psr_idx, ev->new_val, bd);
+             break;
+     }
+ }
+ 
  static void _queue_initial_psr_events(BLURAY *bd)
  {
      const uint32_t psrs[] = {
@@@ -1738,13 -1869,13 +1844,13 @@@
  
      for (ii = 0; ii < sizeof(psrs) / sizeof(psrs[0]); ii++) {
          BD_PSR_EVENT ev = {
-             .ev_type = 0,
+             .ev_type = BD_PSR_CHANGE,
              .psr_idx = psrs[ii],
              .old_val = 0,
              .new_val = bd_psr_read(bd->regs, psrs[ii]),
          };
  
-         _process_psr_event(bd, &ev);
+         _process_psr_change_event(bd, &ev);
      }
  }
  
@@@ -1763,6 -1894,8 +1869,6 @@@ static int _play_bdj(BLURAY *bd, const 
  
  static int _play_hdmv(BLURAY *bd, unsigned id_ref)
  {
 -    int result = 1;
 -
      bd->title_type = title_hdmv;
  
  #ifdef USING_BDJAVA
@@@ -1770,16 -1903,16 +1876,16 @@@
  #endif
  
      if (!bd->hdmv_vm) {
 -        bd->hdmv_vm = hdmv_vm_init(bd->device_path, bd->regs, bd->index);
 +        bd->hdmv_vm = hdmv_vm_init(bd->device_path, bd->regs);
      }
  
      if (hdmv_vm_select_object(bd->hdmv_vm, id_ref)) {
 -        result = 0;
 +        return 0;
      }
  
      bd->hdmv_suspended = !hdmv_vm_running(bd->hdmv_vm);
  
 -    return result;
 +    return 1;
  }
  
  static int _play_title(BLURAY *bd, unsigned title)
@@@ -1862,8 -1995,10 +1968,8 @@@ int bd_play(BLURAY *bd
  
      _init_event_queue(bd);
  
 -    bd_psr_lock(bd->regs);
      bd_psr_register_cb(bd->regs, _process_psr_event, bd);
      _queue_initial_psr_events(bd);
 -    bd_psr_unlock(bd->regs);
  
      return _play_title(bd, BLURAY_TITLE_FIRST_PLAY);
  }
@@@ -1871,7 -2006,12 +1977,7 @@@
  int bd_play_title(BLURAY *bd, unsigned title)
  {
      if (bd->title_type == title_undef && title != BLURAY_TITLE_FIRST_PLAY) {
 -        BD_DEBUG(DBG_BLURAY|DBG_CRIT, "bd_play_title(): bd_play() not called\n");
 -        return 0;
 -    }
 -
 -    if (bd->st0.uo_mask.title_search) {
 -        BD_DEBUG(DBG_BLURAY|DBG_CRIT, "title search masked by stream\n");
 +        // bd_play not called
          return 0;
      }
  
@@@ -1892,7 -2032,12 +1998,7 @@@ int bd_menu_call(BLURAY *bd, int64_t pt
      }
  
      if (bd->title_type == title_undef) {
 -        BD_DEBUG(DBG_BLURAY|DBG_CRIT, "bd_menu_call(): bd_play() not called\n");
 -        return 0;
 -    }
 -
 -    if (bd->st0.uo_mask.menu_call) {
 -        BD_DEBUG(DBG_BLURAY|DBG_CRIT, "menu call masked by stream\n");
 +        // bd_play not called
          return 0;
      }
  
@@@ -1901,23 -2046,31 +2007,27 @@@
              BD_DEBUG(DBG_BLURAY|DBG_CRIT, "menu call masked by movie object\n");
              return 0;
          }
 -
 -        if (hdmv_vm_suspend_pl(bd->hdmv_vm) < 0) {
 -            BD_DEBUG(DBG_BLURAY|DBG_CRIT, "bd_menu_call(): error storing playback location\n");
 -        }
      }
  
      return _play_title(bd, BLURAY_TITLE_TOP_MENU);
  }
  
- static void _run_gc(BLURAY *bd, gc_ctrl_e msg, uint32_t param)
+ static int _run_gc(BLURAY *bd, gc_ctrl_e msg, uint32_t param)
  {
+     int result = -1;
+ 
      if (bd && bd->graphics_controller && bd->hdmv_vm) {
          GC_NAV_CMDS cmds = {-1, NULL, -1};
  
-         gc_run(bd->graphics_controller, msg, param, &cmds);
+         result = gc_run(bd->graphics_controller, msg, param, &cmds);
  
          if (cmds.num_nav_cmds > 0) {
              hdmv_vm_set_object(bd->hdmv_vm, cmds.num_nav_cmds, cmds.nav_cmds);
              bd->hdmv_suspended = !hdmv_vm_running(bd->hdmv_vm);
          }
      }
+ 
+     return result;
  }
  
  static void _process_hdmv_vm_event(BLURAY *bd, HDMV_EVENT *hev)
@@@ -1926,6 -2079,7 +2036,7 @@@
  
      switch (hev->event) {
          case HDMV_EVENT_TITLE:
+             _close_playlist(bd);
              _play_title(bd, hev->param);
              break;
  
@@@ -1946,12 -2100,8 +2057,8 @@@
              break;
  
          case HDMV_EVENT_PLAY_STOP:
-             BD_DEBUG(DBG_BLURAY|DBG_CRIT, "HDMV_EVENT_PLAY_STOP: not tested !\n");
              // stop current playlist
-             bd_seek(bd, (uint64_t)bd->title->packets * 192 - 1);
-             bd->st0.clip = NULL;
-             // resume suspended movie object
-             hdmv_vm_resume(bd->hdmv_vm);
+             _close_playlist(bd);
              break;
  
          case HDMV_EVENT_STILL:
@@@ -1975,7 -2125,6 +2082,7 @@@
              break;
  
          case HDMV_EVENT_IG_END:
 +            BD_DEBUG(DBG_BLURAY|DBG_CRIT, "HDMV_EVENT_IG_END\n");
              _run_gc(bd, GC_CTRL_IG_END, 0);
              break;
  
@@@ -2062,29 -2211,36 +2169,36 @@@ int bd_get_event(BLURAY *bd, BD_EVENT *
          _queue_initial_psr_events(bd);
      }
  
-     return _get_event(bd, event);
+     if (event) {
+         return _get_event(bd, event);
+     }
+ 
+     return 0;
  }
  
  /*
   * user interaction
   */
  
- void bd_mouse_select(BLURAY *bd, int64_t pts, uint16_t x, uint16_t y)
+ void bd_set_scr(BLURAY *bd, int64_t pts)
  {
      if (pts >= 0) {
          bd_psr_write(bd->regs, PSR_TIME, (uint32_t)(((uint64_t)pts) >> 1));
      }
+ }
+ 
+ int bd_mouse_select(BLURAY *bd, int64_t pts, uint16_t x, uint16_t y)
+ {
+     bd_set_scr(bd, pts);
  
-     _run_gc(bd, GC_CTRL_MOUSE_MOVE, (x << 16) | y);
+     return _run_gc(bd, GC_CTRL_MOUSE_MOVE, (x << 16) | y);
  }
  
- void bd_user_input(BLURAY *bd, int64_t pts, uint32_t key)
+ int bd_user_input(BLURAY *bd, int64_t pts, uint32_t key)
  {
-     if (pts >= 0) {
-         bd_psr_write(bd->regs, PSR_TIME, (uint32_t)(((uint64_t)pts) >> 1));
-     }
+     bd_set_scr(bd, pts);
  
-     _run_gc(bd, GC_CTRL_VK_KEY, key);
+     return _run_gc(bd, GC_CTRL_VK_KEY, key);
  }
  
  void bd_register_overlay_proc(BLURAY *bd, void *handle, bd_overlay_proc_f func)
@@@ -2100,6 -2256,10 +2214,10 @@@
      }
  }
  
+ /*
+  *
+  */
+ 
  struct meta_dl *bd_get_meta(BLURAY *bd)
  {
      if (!bd) {
diff --combined src/libbluray/bluray.h
index 6f5dd1f,11f838c..d2ace64
--- a/src/libbluray/bluray.h
+++ b/src/libbluray/bluray.h
@@@ -22,6 -22,10 +22,6 @@@
  #ifndef BLURAY_H_
  #define BLURAY_H_
  
 -#ifdef __cplusplus
 -extern "C" {
 -#endif
 -
  /**
   * @file libbluray/bluray.h
   * external API header
@@@ -170,9 -174,10 +170,9 @@@ typedef struct bd_title_info 
   *
   * @param bd  BLURAY object
   * @param flags  title flags
 - * @param min_title_length  filter out titles shorter than min_title_length seconds
   * @return number of titles found
   */
 -uint32_t bd_get_titles(BLURAY *bd, uint8_t flags, uint32_t min_title_length);
 +uint32_t bd_get_titles(BLURAY *bd, uint8_t flags);
  
  /**
   *
@@@ -180,9 -185,10 +180,9 @@@
   *
   * @param bd  BLURAY object
   * @param title_idx title index number
 - * @param angle angle number (chapter offsets and clip size depend on selected angle)
   * @return allocated BLURAY_TITLE_INFO object, NULL on error
   */
 -BLURAY_TITLE_INFO* bd_get_title_info(BLURAY *bd, uint32_t title_idx, unsigned angle);
 +BLURAY_TITLE_INFO* bd_get_title_info(BLURAY *bd, uint32_t title_idx);
  
  /**
   *
@@@ -190,9 -196,10 +190,9 @@@
   *
   * @param bd  BLURAY object
   * @param playlist playlist number
 - * @param angle angle number (chapter offsets and clip size depend on selected angle)
   * @return allocated BLURAY_TITLE_INFO object, NULL on error
   */
 -BLURAY_TITLE_INFO* bd_get_playlist_info(BLURAY *bd, uint32_t playlist, unsigned angle);
 +BLURAY_TITLE_INFO* bd_get_playlist_info(BLURAY *bd, uint32_t playlist);
  
  /**
   *
@@@ -244,12 -251,21 +244,12 @@@ int64_t bd_seek_time(BLURAY *bd, uint64
   * @param bd  BLURAY object
   * @param buf buffer to read data into
   * @param len size of data to be read
-  * @return size of data read, -1 if error
+  * @return size of data read, -1 if error, 0 if EOF
   */
  int bd_read(BLURAY *bd, unsigned char *buf, int len);
  
  /**
   *
 - *  Continue reading after still mode clip
 - *
 - * @param bd  BLURAY object
 - * @return 0 on error
 - */
 -int bd_read_skip_still(BLURAY *bd);
 -
 -/**
 - *
   *  Seek to a chapter. First chapter is 0
   *
   * @param bd  BLURAY object
@@@ -496,7 -512,7 +496,7 @@@ typedef struct 
   *  Get event from libbluray event queue.
   *
   * @param bd  BLURAY object
-  * @param event next BD_EVENT from event queue
+  * @param event next BD_EVENT from event queue, NULL to initialize event queue
   * @return 1 on success, 0 if no events
   */
  int  bd_get_event(BLURAY *bd, BD_EVENT *event);
@@@ -507,7 -523,7 +507,7 @@@
  
  /**
   *
-  *  Start playing disc in navigation mode.
+  *  Start playing disc in navigation mode (using on-disc menus).
   *
   *  Playback is started from "First Play" title.
   *
@@@ -583,9 -599,9 +583,9 @@@ void bd_register_overlay_proc(BLURAY *b
   * @param bd  BLURAY object
   * @param pts current playback position (1/90000s) or -1
   * @param key input key
-  * @return 1 on success, 0 if error
+  * @return <0 on error, 0 on success, >0 if selection/activation changed
   */
- void bd_user_input(BLURAY *bd, int64_t pts, uint32_t key);
+ int bd_user_input(BLURAY *bd, int64_t pts, uint32_t key);
  
  /**
   *
@@@ -595,9 -611,13 +595,13 @@@
   * @param pts current playback position (1/90000s) or -1
   * @param x mouse pointer x-position
   * @param y mouse pointer y-position
-  * @return none
+  * @return <0 on error, 0 when mouse is outside of buttons, 1 when mouse is inside button
+  */
+ int bd_mouse_select(BLURAY *bd, int64_t pts, uint16_t x, uint16_t y);
+ 
+ /*
+  *
   */
- void bd_mouse_select(BLURAY *bd, int64_t pts, uint16_t x, uint16_t y);
  
  struct meta_dl;
  /**
@@@ -629,4 -649,8 +633,4 @@@ struct clpi_cl *bd_get_clpi(BLURAY *bd
   */
  void bd_free_clpi(struct clpi_cl *cl);
  
 -#ifdef __cplusplus
 -};
 -#endif
 -
  #endif /* BLURAY_H_ */
diff --combined src/libbluray/decoders/graphics_controller.c
index a2c595d,8b33b81..d48b057
--- a/src/libbluray/decoders/graphics_controller.c
+++ b/src/libbluray/decoders/graphics_controller.c
@@@ -31,6 -31,7 +31,6 @@@
  #include "../keys.h"
  
  #include <inttypes.h>
 -#include <string.h>
  
  #define GC_ERROR(...) BD_DEBUG(DBG_GC | DBG_CRIT, __VA_ARGS__)
  #define GC_TRACE(...) BD_DEBUG(DBG_GC,            __VA_ARGS__)
@@@ -39,6 -40,12 +39,12 @@@
   *
   */
  
+ typedef struct {
+     uint16_t enabled_button;  /* enabled button id */
+     uint16_t x, y, w, h;      /* button rect on overlay plane (if drawn) */
+     int      animate_indx;    /* currently showing object index of animated button, < 0 for static buttons */
+ } BOG_DATA;
+ 
  struct graphics_controller_s {
  
      BD_REGISTERS   *regs;
@@@ -54,11 -61,12 +60,13 @@@
      unsigned        pg_drawn;
      unsigned        popup_visible;
      unsigned        valid_mouse_position;
+     BOG_DATA       *bog_data;
+     BOG_DATA       *saved_bog_data;
  
      /* data */
      PG_DISPLAY_SET *pgs;
      PG_DISPLAY_SET *igs;
 +    uint16_t       *enabled_button;
  
      /* */
      GRAPHICS_PROCESSOR *pgp;
@@@ -141,23 -149,53 +149,53 @@@ static BD_IG_PAGE *_find_page(BD_IG_INT
  enum { BTN_NORMAL, BTN_SELECTED, BTN_ACTIVATED };
  
  static BD_PG_OBJECT *_find_object_for_button(PG_DISPLAY_SET *s,
-                                              BD_IG_BUTTON *button, int state)
+                                              BD_IG_BUTTON *button, int state,
+                                              BOG_DATA *bog_data)
  {
      BD_PG_OBJECT *object   = NULL;
      unsigned object_id     = 0xffff;
+     unsigned object_id_end = 0xffff;
+     unsigned repeat        = 0;
  
      switch (state) {
          case BTN_NORMAL:
-             object_id = button->normal_start_object_id_ref;
+             object_id     = button->normal_start_object_id_ref;
+             object_id_end = button->normal_end_object_id_ref;
+             repeat        = button->normal_repeat_flag;
              break;
          case BTN_SELECTED:
-             object_id = button->selected_start_object_id_ref;
+             object_id     = button->selected_start_object_id_ref;
+             object_id_end = button->selected_end_object_id_ref;
+             repeat        = button->selected_repeat_flag;
              break;
          case BTN_ACTIVATED:
-             object_id = button->activated_start_object_id_ref;
+             object_id     = button->activated_start_object_id_ref;
+             object_id_end = button->activated_end_object_id_ref;
              break;
      }
  
+     if (bog_data) {
+         if (bog_data->animate_indx >= 0) {
+             int range = object_id_end - object_id;
+ 
+             if (range > 0 && object_id < 0xffff && object_id_end < 0xffff) {
+                 GC_TRACE("animate button #%d: animate_indx %d, range %d, repeat %d\n",
+                          button->id, bog_data->animate_indx, range, repeat);
+ 
+                 object_id += bog_data->animate_indx % (range + 1);
+                 bog_data->animate_indx++;
+                 if (!repeat && bog_data->animate_indx > range) {
+                 /* terminate animation to the last object */
+                     bog_data->animate_indx = -1;
+                 }
+ 
+             } else {
+                 /* no animation for this button */
+                 bog_data->animate_indx = -1;
+             }
+         }
+     }
+ 
      object = _find_object(s, object_id);
  
      return object;
@@@ -171,7 -209,7 +209,7 @@@ static int _is_button_enabled(GRAPHICS_
  {
      unsigned ii;
      for (ii = 0; ii < page->num_bogs; ii++) {
 -        if (gc->bog_data[ii].enabled_button == button_id) {
 +        if (gc->enabled_button[ii] == button_id) {
              return 1;
          }
      }
@@@ -207,11 -245,12 +245,11 @@@ static uint16_t _find_selected_button_i
      /* 2) fallback to current PSR10 value if it is valid */
      for (ii = 0; ii < page->num_bogs; ii++) {
          BD_IG_BOG *bog = &page->bog[ii];
 -        uint16_t   enabled_button = gc->bog_data[ii].enabled_button;
  
 -        if (button_id == enabled_button) {
 -            if (_find_button_bog(bog, enabled_button)) {
 -                GC_TRACE("_find_selected_button_id() -> PSR10 #%d\n", enabled_button);
 -                return enabled_button;
 +        if (button_id == gc->enabled_button[ii]) {
 +            if (_find_button_bog(bog, gc->enabled_button[ii])) {
 +                GC_TRACE("_find_selected_button_id() -> PSR10 #%d\n", gc->enabled_button[ii]);
 +                return gc->enabled_button[ii];
              }
          }
      }
@@@ -219,10 -258,11 +257,10 @@@
      /* 3) fallback to find first valid_button_id_ref from page */
      for (ii = 0; ii < page->num_bogs; ii++) {
          BD_IG_BOG *bog = &page->bog[ii];
 -        uint16_t   enabled_button = gc->bog_data[ii].enabled_button;
  
 -        if (_find_button_bog(bog, enabled_button)) {
 -            GC_TRACE("_find_selected_button_id() -> first valid #%d\n", enabled_button);
 -            return enabled_button;
 +        if (_find_button_bog(bog, gc->enabled_button[ii])) {
 +            GC_TRACE("_find_selected_button_id() -> first valid #%d\n", gc->enabled_button[ii]);
 +            return gc->enabled_button[ii];
          }
      }
  
@@@ -230,7 -270,54 +268,54 @@@
      return 0xffff;
  }
  
- static void _reset_enabled_button(GRAPHICS_CONTROLLER *gc)
+ static int _save_page_state(GRAPHICS_CONTROLLER *gc)
+ {
+     if (!gc->bog_data) {
+         GC_ERROR("_save_page_state(): no bog data !\n");
+         return -1;
+     }
+ 
+     PG_DISPLAY_SET *s       = gc->igs;
+     BD_IG_PAGE     *page    = NULL;
+     unsigned        page_id = bd_psr_read(gc->regs, PSR_MENU_PAGE_ID);
+     unsigned        ii;
+ 
+     page = _find_page(&s->ics->interactive_composition, page_id);
+     if (!page) {
+         GC_ERROR("_save_page_state(): unknown page #%d (have %d pages)\n",
+               page_id, s->ics->interactive_composition.num_pages);
+         return -1;
+     }
+ 
+     /* copy enabled button state, clear draw state */
+ 
+     X_FREE(gc->saved_bog_data);
+     gc->saved_bog_data = calloc(page->num_bogs, sizeof(*gc->saved_bog_data));
+ 
+     for (ii = 0; ii < page->num_bogs; ii++) {
+         gc->saved_bog_data[ii].enabled_button = gc->bog_data[ii].enabled_button;
+         gc->saved_bog_data[ii].animate_indx   = gc->bog_data[ii].animate_indx >= 0 ? 0 : -1;
+     }
+ 
+     return 1;
+ }
+ 
+ static int _restore_page_state(GRAPHICS_CONTROLLER *gc)
+ {
+     if (gc->saved_bog_data) {
+         if (gc->bog_data) {
+             GC_ERROR("_restore_page_state(): bog data already exists !\n");
+             X_FREE(gc->bog_data);
+         }
+         gc->bog_data       = gc->saved_bog_data;
+         gc->saved_bog_data = NULL;
+ 
+         return 1;
+     }
+     return -1;
+ }
+ 
+ static void _reset_page_state(GRAPHICS_CONTROLLER *gc)
  {
      PG_DISPLAY_SET *s       = gc->igs;
      BD_IG_PAGE     *page    = NULL;
@@@ -239,58 -326,82 +324,74 @@@
  
      page = _find_page(&s->ics->interactive_composition, page_id);
      if (!page) {
 -        GC_ERROR("_reset_page_state(): unknown page #%d (have %d pages)\n",
 +        GC_ERROR("_reset_enabled_button(): unknown page #%d (have %d pages)\n",
                page_id, s->ics->interactive_composition.num_pages);
          return;
      }
  
 -    size_t size = page->num_bogs * sizeof(*gc->bog_data);
 -    gc->bog_data = realloc(gc->bog_data, size);
 -
 -    memset(gc->bog_data, 0, size);
 +    gc->enabled_button = realloc(gc->enabled_button,
 +                                 page->num_bogs * sizeof(uint16_t));
  
      for (ii = 0; ii < page->num_bogs; ii++) {
-         gc->enabled_button[ii] = page->bog[ii].default_valid_button_id_ref;
+         gc->bog_data[ii].enabled_button = page->bog[ii].default_valid_button_id_ref;
+         gc->bog_data[ii].animate_indx   = 0;
      }
  }
  
 -static void _clear_osd_area(GRAPHICS_CONTROLLER *gc, int plane,
 -                            uint16_t x, uint16_t y, uint16_t w, uint16_t h)
 +static void _clear_osd(GRAPHICS_CONTROLLER *gc, int plane)
  {
      if (gc->overlay_proc) {
          /* clear plane */
          const BD_OVERLAY ov = {
              .pts     = -1,
              .plane   = plane,
 -            .x       = x,
 -            .y       = y,
 -            .w       = w,
 -            .h       = h,
 +            .x       = 0,
 +            .y       = 0,
 +            .w       = 1920,
 +            .h       = 1080,
              .palette = NULL,
              .img     = NULL,
          };
  
          gc->overlay_proc(gc->overlay_proc_handle, &ov);
      }
 -}
 -
 -static void _clear_osd(GRAPHICS_CONTROLLER *gc, int plane)
 -{
 -    _clear_osd_area(gc, plane, 0, 0, 1920, 1080);
  
-     if (plane) {
+     if (plane == BD_OVERLAY_IG) {
          gc->ig_drawn      = 0;
      } else {
          gc->pg_drawn      = 0;
      }
  }
  
+ static void _clear_bog_area(GRAPHICS_CONTROLLER *gc, BOG_DATA *bog_data)
+ {
+     if (gc->ig_drawn && bog_data->w && bog_data->h) {
+ 
+         _clear_osd_area(gc, BD_OVERLAY_IG, bog_data->x, bog_data->y, bog_data->w, bog_data->h);
+ 
+         bog_data->x = bog_data->y = bog_data->w = bog_data->h = 0;
+     }
+ }
+ 
+ static void _select_button(GRAPHICS_CONTROLLER *gc, uint32_t button_id)
+ {
+     bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);
+ }
+ 
  static void _select_page(GRAPHICS_CONTROLLER *gc, uint16_t page_id)
  {
      bd_psr_write(gc->regs, PSR_MENU_PAGE_ID, page_id);
-     _clear_osd(gc, 1);
-     _reset_enabled_button(gc);
+     _clear_osd(gc, BD_OVERLAY_IG);
+     _reset_page_state(gc);
  
      uint16_t button_id = _find_selected_button_id(gc);
-     bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);
+     _select_button(gc, button_id);
  }
  
  static void _gc_reset(GRAPHICS_CONTROLLER *gc)
  {
-     _clear_osd(gc, 0);
-     _clear_osd(gc, 1);
+     _clear_osd(gc, BD_OVERLAY_PG);
+     _clear_osd(gc, BD_OVERLAY_IG);
  
      gc->popup_visible = 0;
  
@@@ -300,10 -411,48 +401,48 @@@
      pg_display_set_free(&gc->pgs);
      pg_display_set_free(&gc->igs);
  
 -    X_FREE(gc->bog_data);
 +    X_FREE(gc->enabled_button);
  }
  
  /*
+  * register hook
+  */
+ static void _process_psr_event(void *handle, BD_PSR_EVENT *ev)
+ {
+     GRAPHICS_CONTROLLER *gc = (GRAPHICS_CONTROLLER *)handle;
+ 
+     if (ev->ev_type == BD_PSR_SAVE) {
+         BD_DEBUG(DBG_GC, "PSR SAVE event\n");
+ 
+         /* save menu page state */
+         bd_mutex_lock(&gc->mutex);
+         _save_page_state(gc);
+         bd_mutex_unlock(&gc->mutex);
+ 
+         return;
+     }
+ 
+     if (ev->ev_type == BD_PSR_RESTORE) {
+         switch (ev->psr_idx) {
+ 
+             case PSR_SELECTED_BUTTON_ID:
+               return;
+ 
+             case PSR_MENU_PAGE_ID:
+                 /* restore menus */
+                 bd_mutex_lock(&gc->mutex);
+                 _restore_page_state(gc);
+                 bd_mutex_unlock(&gc->mutex);
+                 return;
+ 
+             default:
+                 /* others: ignore */
+                 return;
+         }
+     }
+ }
+ 
+ /*
   * init / free
   */
  
@@@ -318,6 -467,8 +457,8 @@@ GRAPHICS_CONTROLLER *gc_init(BD_REGISTE
  
      bd_mutex_init(&p->mutex);
  
+     bd_psr_register_cb(regs, _process_psr_event, p);
+ 
      return p;
  }
  
@@@ -325,13 -476,17 +466,17 @@@ void gc_free(GRAPHICS_CONTROLLER **p
  {
      if (p && *p) {
  
-         _gc_reset(*p);
+         GRAPHICS_CONTROLLER *gc = *p;
+ 
+         bd_psr_unregister_cb(gc->regs, _process_psr_event, gc);
+ 
+         _gc_reset(gc);
  
-         if ((*p)->overlay_proc) {
-             (*p)->overlay_proc((*p)->overlay_proc_handle, NULL);
+         if (gc->overlay_proc) {
+             gc->overlay_proc(gc->overlay_proc_handle, NULL);
          }
  
-         bd_mutex_destroy(&(*p)->mutex);
+         bd_mutex_destroy(&gc->mutex);
  
          X_FREE(*p);
      }
@@@ -341,11 -496,11 +486,11 @@@
   * graphics stream input
   */
  
 -int gc_decode_ts(GRAPHICS_CONTROLLER *gc, uint16_t pid, uint8_t *block, unsigned num_blocks, int64_t stc)
 +void gc_decode_ts(GRAPHICS_CONTROLLER *gc, uint16_t pid, uint8_t *block, unsigned num_blocks, int64_t stc)
  {
      if (!gc) {
          GC_TRACE("gc_decode_ts(): no graphics controller\n");
 -        return -1;
 +        return;
      }
  
      if (pid >= 0x1400 && pid < 0x1500) {
@@@ -357,12 -512,17 +502,17 @@@
  
          bd_mutex_lock(&gc->mutex);
  
-         graphics_processor_decode_ts(gc->igp, &gc->igs,
-                                      pid, block, num_blocks,
-                                      stc);
+         if (!graphics_processor_decode_ts(gc->igp, &gc->igs,
+                                           pid, block, num_blocks,
+                                           stc)) {
+             /* no new complete display set */
+             bd_mutex_unlock(&gc->mutex);
+             return 0;
+         }
+ 
          if (!gc->igs || !gc->igs->complete) {
              bd_mutex_unlock(&gc->mutex);
 -            return 0;
 +            return;
          }
  
          gc->popup_visible = 0;
@@@ -370,6 -530,8 +520,6 @@@
          _select_page(gc, 0);
  
          bd_mutex_unlock(&gc->mutex);
 -
 -        return 1;
      }
  
      else if (pid >= 0x1200 && pid < 0x1300) {
@@@ -382,9 -544,13 +532,9 @@@
                                       stc);
  
          if (!gc->pgs || !gc->pgs->complete) {
 -            return 0;
 +            return;
          }
 -
 -        return 1;
      }
 -
 -    return -1;
  }
  
  /*
@@@ -392,24 -558,27 +542,24 @@@
   */
  
  static void _render_button(GRAPHICS_CONTROLLER *gc, BD_IG_BUTTON *button, BD_PG_PALETTE *palette,
 -                           int state, BOG_DATA *bog_data)
 +                           int state)
  {
      BD_PG_OBJECT *object    = NULL;
      BD_OVERLAY    ov;
  
-     object = _find_object_for_button(gc->igs, button, state);
+     object = _find_object_for_button(gc->igs, button, state, bog_data);
      if (!object) {
          GC_TRACE("_render_button(#%d): object (state %d) not found\n", button->id, state);
 -
 -        _clear_bog_area(gc, bog_data);
 -
          return;
      }
  
      ov.pts   = -1;
-     ov.plane = 1; /* IG */
+     ov.plane = BD_OVERLAY_IG;
  
 -    ov.x = bog_data->x = button->x_pos;
 -    ov.y = bog_data->y = button->y_pos;
 -    ov.w = bog_data->w = object->width;
 -    ov.h = bog_data->h = object->height;
 +    ov.x = button->x_pos;
 +    ov.y = button->y_pos;
 +    ov.w = object->width;
 +    ov.h = object->height;
  
      ov.img     = object->img;
      ov.palette = palette->entry;
@@@ -434,7 -603,7 +584,7 @@@ static void _render_page(GRAPHICS_CONTR
      if (s->ics->interactive_composition.ui_model == IG_UI_MODEL_POPUP && !gc->popup_visible) {
          GC_TRACE("_render_page(): popup menu not visible\n");
  
-         _clear_osd(gc, 1);
+         _clear_osd(gc, BD_OVERLAY_IG);
  
          return;
      }
@@@ -458,7 -627,7 +608,7 @@@
  
      for (ii = 0; ii < page->num_bogs; ii++) {
          BD_IG_BOG    *bog      = &page->bog[ii];
 -        unsigned      valid_id = gc->bog_data[ii].enabled_button;
 +        unsigned      valid_id = gc->enabled_button[ii];
          BD_IG_BUTTON *button;
  
          button = _find_button_bog(bog, valid_id);
@@@ -467,21 -636,19 +617,19 @@@
              GC_TRACE("_render_page(): bog %d: button %d not found\n", ii, valid_id);
  
          } else if (button->id == activated_button_id) {
 -            _render_button(gc, button, palette, BTN_ACTIVATED, &gc->bog_data[ii]);
 +            _render_button(gc, button, palette, BTN_ACTIVATED);
  
          } else if (button->id == selected_button_id) {
  
 -            _render_button(gc, button, palette, BTN_SELECTED, &gc->bog_data[ii]);
 +            _render_button(gc, button, palette, BTN_SELECTED);
  
-             bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, selected_button_id);
- 
-             if (button->auto_action_flag) {
+             if (button->auto_action_flag && cmds) {
                  cmds->num_nav_cmds = button->num_nav_cmds;
                  cmds->nav_cmds     = button->nav_cmds;
              }
  
          } else {
 -            _render_button(gc, button, palette, BTN_NORMAL, &gc->bog_data[ii]);
 +            _render_button(gc, button, palette, BTN_NORMAL);
  
          }
      }
@@@ -533,7 -700,7 +681,7 @@@ static int _user_input(GRAPHICS_CONTROL
  
      for (ii = 0; ii < page->num_bogs; ii++) {
          BD_IG_BOG *bog      = &page->bog[ii];
 -        unsigned   valid_id = gc->bog_data[ii].enabled_button;
 +        unsigned   valid_id = gc->enabled_button[ii];
          BD_IG_BUTTON *button = _find_button_bog(bog, valid_id);
          if (!button) {
              continue;
@@@ -585,7 -752,7 +733,7 @@@
      /* render page ? */
      if (new_btn_id != cur_btn_id || activated_btn_id >= 0) {
  
-         bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, new_btn_id);
+         _select_button(gc, new_btn_id);
  
          _render_page(gc, activated_btn_id, cmds);
  
@@@ -596,7 -763,7 +744,7 @@@
      return 0;
  }
  
 -static void _set_button_page(GRAPHICS_CONTROLLER *gc, uint32_t param)
 +static void _set_button_page(GRAPHICS_CONTROLLER *gc, uint32_t param, GC_NAV_CMDS *cmds)
  {
      unsigned page_flag   = param & 0x80000000;
      unsigned effect_flag = param & 0x40000000;
@@@ -668,11 -835,11 +816,11 @@@
      }
  
      if (button) {
-         gc->enabled_button[bog_idx] = button_id;
-         bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);
+         gc->bog_data[bog_idx].enabled_button = button_id;
+         _select_button(gc, button_id);
      }
  
 -    _render_page(gc, 0xffff, NULL);
 +    _render_page(gc, 0xffff, cmds);
  }
  
  static void _enable_button(GRAPHICS_CONTROLLER *gc, uint32_t button_id, unsigned enable)
@@@ -701,15 -868,15 +849,15 @@@
      }
  
      if (enable) {
 -        if (gc->bog_data[bog_idx].enabled_button == cur_btn_id) {
 +        if (gc->enabled_button[bog_idx] == cur_btn_id) {
              /* selected button goes to disabled state */
              bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, 0x10000|button_id);
          }
 -        gc->bog_data[bog_idx].enabled_button = button_id;
 +        gc->enabled_button[bog_idx] = button_id;
  
      } else {
 -        if (gc->bog_data[bog_idx].enabled_button == button_id) {
 -            gc->bog_data[bog_idx].enabled_button = 0xffff;
 +        if (gc->enabled_button[bog_idx] == button_id) {
 +            gc->enabled_button[bog_idx] = 0xffff;
          }
  
          if (cur_btn_id == button_id) {
@@@ -728,14 -895,14 +876,14 @@@ static void _update_selected_button(GRA
      /* special case: triggered only after enable button disables selected button */
      if (button_id & 0x10000) {
          button_id &= 0xffff;
-         bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);
+         _select_button(gc, button_id);
          GC_TRACE("_update_selected_button() -> #%d [last enabled]\n", button_id);
          return;
      }
  
      if (button_id == 0xffff) {
          button_id = _find_selected_button_id(gc);
-         bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);
+         _select_button(gc, button_id);
      }
  }
  
@@@ -751,7 -918,7 +899,7 @@@ static int _mouse_move(GRAPHICS_CONTROL
      gc->valid_mouse_position = 0;
  
      if (!gc->ig_drawn) {
-         GC_ERROR("_mouse_move(): menu not visible\n");
+         GC_TRACE("_mouse_move(): menu not visible\n");
          return -1;
      }
  
@@@ -764,7 -931,7 +912,7 @@@
  
      for (ii = 0; ii < page->num_bogs; ii++) {
          BD_IG_BOG    *bog      = &page->bog[ii];
 -        unsigned      valid_id = gc->bog_data[ii].enabled_button;
 +        unsigned      valid_id = gc->enabled_button[ii];
          BD_IG_BUTTON *button   = _find_button_bog(bog, valid_id);
  
          if (!button)
@@@ -774,7 -941,7 +922,7 @@@
              continue;
  
          /* Check for SELECTED state object (button that can be selected) */
-         BD_PG_OBJECT *object = _find_object_for_button(s, button, BTN_SELECTED);
+         BD_PG_OBJECT *object = _find_object_for_button(s, button, BTN_SELECTED, NULL);
          if (!object)
              continue;
  
@@@ -786,7 -953,7 +934,7 @@@
  
          /* is button already selected? */
          if (button->id == cur_btn_id) {
-             return 0;
+             return 1;
          }
  
          new_btn_id = button->id;
@@@ -794,7 -961,7 +942,7 @@@
      }
  
      if (new_btn_id != 0xffff) {
-         bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, new_btn_id);
+         _select_button(gc, new_btn_id);
  
          _render_page(gc, -1, cmds);
      }
@@@ -826,6 -993,7 +974,7 @@@ int gc_run(GRAPHICS_CONTROLLER *gc, gc_
  
              bd_mutex_unlock(&gc->mutex);
              return 0;
+ 
          default:;
      }
  
@@@ -839,7 -1007,7 +988,7 @@@
      switch (ctrl) {
  
          case GC_CTRL_SET_BUTTON_PAGE:
 -            _set_button_page(gc, param);
 +            _set_button_page(gc, param, cmds);
              break;
  
          case GC_CTRL_VK_KEY:
@@@ -884,6 -1052,7 +1033,7 @@@
          case GC_CTRL_MOUSE_MOVE:
              result = _mouse_move(gc, param >> 16, param & 0xffff, cmds);
              break;
+ 
          case GC_CTRL_RESET:
              /* already handled */
              break;
diff --combined src/libbluray/hdmv/hdmv_vm.c
index 3453d2f,715ed3a..24707d8
--- a/src/libbluray/hdmv/hdmv_vm.c
+++ b/src/libbluray/hdmv/hdmv_vm.c
@@@ -23,6 -23,7 +23,6 @@@
  #include "hdmv_insn.h"
  #include "../register.h"
  
 -#include "../bdnav/index_parse.h"
  #include "util/macro.h"
  #include "util/strutl.h"
  #include "util/logging.h"
@@@ -55,9 -56,16 +55,13 @@@ struct hdmv_vm_s 
      MOBJ_OBJECTS  *movie_objects; /* disc movie objects */
      MOBJ_OBJECT   *ig_object;     /* current object from IG stream */
  
+     /* object currently playing playlist */
+     MOBJ_OBJECT *playing_object;
+     int          playing_pc;
+ 
      /* suspended object */
      MOBJ_OBJECT *suspended_object;
      int          suspended_pc;
 -
 -    /* disc index (used to verify CALL_TITLE/JUMP_TITLE) */
 -    INDX_ROOT   *indx;
  };
  
  /*
@@@ -231,7 -239,7 +235,7 @@@ static int _queue_event(HDMV_VM *p, uin
   * vm init
   */
  
 -HDMV_VM *hdmv_vm_init(const char *disc_root, BD_REGISTERS *regs, INDX_ROOT *indx)
 +HDMV_VM *hdmv_vm_init(const char *disc_root, BD_REGISTERS *regs)
  {
      HDMV_VM *p = calloc(1, sizeof(HDMV_VM));
      char *file;
@@@ -246,6 -254,7 +250,6 @@@
      }
  
      p->regs         = regs;
 -    p->indx         = indx;
  
      bd_mutex_init(&p->mutex);
  
@@@ -278,6 -287,54 +282,54 @@@ void hdmv_vm_free(HDMV_VM **p
   * suspend/resume ("function call")
   */
  
+ static int _suspended_at_play_pl(HDMV_VM *p)
+ {
+     int play_pl = 0;
+     if (p && p->suspended_object) {
+         MOBJ_CMD  *cmd  = &p->suspended_object->cmds[p->suspended_pc];
+         HDMV_INSN *insn = &cmd->insn;
+         play_pl = (insn->grp     == INSN_GROUP_BRANCH &&
+                    insn->sub_grp == BRANCH_PLAY  &&
+                    (  insn->branch_opt == INSN_PLAY_PL ||
+                       insn->branch_opt == INSN_PLAY_PL_PI ||
+                       insn->branch_opt == INSN_PLAY_PL_PM));
+     }
+ 
+     return play_pl;
+ }
+ 
+ static int _suspend_for_play_pl(HDMV_VM *p)
+ {
+     if (p->playing_object) {
+         BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_for_play_pl(): object already playing playlist !\n");
+         return -1;
+     }
+ 
+     p->playing_object = p->object;
+     p->playing_pc     = p->pc;
+ 
+     p->object = NULL;
+ 
+     return 0;
+ }
+ 
+ static int _resume_from_play_pl(HDMV_VM *p)
+ {
+     if (!p->playing_object) {
+         BD_DEBUG(DBG_HDMV|DBG_CRIT, "_resume_from_play_pl(): object not playing playlist !\n");
+         return -1;
+     }
+ 
+     p->object = p->playing_object;
+     p->pc     = p->playing_pc + 1;
+ 
+     p->playing_object = NULL;
+ 
+     _free_ig_object(p);
+ 
+     return 0;
+ }
+ 
  static void _suspend_object(HDMV_VM *p, int psr_backup)
  {
      BD_DEBUG(DBG_HDMV, "_suspend_object()\n");
@@@ -291,10 -348,31 +343,31 @@@
          bd_psr_save_state(p->regs);
      }
  
-     p->suspended_object = p->object;
-     p->suspended_pc     = p->pc;
+     if (p->ig_object) {
+         if (!p->playing_object) {
+             BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_object: IG object tries to suspend, no playing object !\n");
+             return;
+         }
+         p->suspended_object = p->playing_object;
+         p->suspended_pc     = p->playing_pc;
+ 
+         p->playing_object = NULL;
+ 
+     } else {
+ 
+         if (p->playing_object) {
+             BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_object: Movie object tries to suspend, also playing object present !\n");
+             return;
+         }
+ 
+         p->suspended_object = p->object;
+         p->suspended_pc     = p->pc;
+ 
+     }
  
      p->object = NULL;
+ 
+     _free_ig_object(p);
  }
  
  static int _resume_object(HDMV_VM *p, int psr_restore)
@@@ -304,17 -382,33 +377,33 @@@
          return -1;
      }
  
-     p->object = p->suspended_object;
-     p->pc     = p->suspended_pc + 1;
+     p->object = NULL;
+     p->playing_object = NULL;
+     _free_ig_object(p);
  
      if (psr_restore) {
+         /* check if suspended in play_pl */
+         if (_suspended_at_play_pl(p)) {
+             BD_DEBUG(DBG_HDMV, "resuming playlist playback\n");
+             p->playing_object = p->suspended_object;
+             p->playing_pc     = p->suspended_pc;
+             p->suspended_object = NULL;
+             bd_psr_restore_state(p->regs);
+ 
+             return 0;
+         }
          bd_psr_restore_state(p->regs);
      }
  
-     BD_DEBUG(DBG_HDMV, "resuming object %p at %d\n", p->object, p->pc + 1);
+     p->object = p->suspended_object;
+     p->pc     = p->suspended_pc + 1;
  
      p->suspended_object = NULL;
  
+     BD_DEBUG(DBG_HDMV, "resuming object %p at %d\n", p->object, p->pc);
+ 
+     _queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
+ 
      return 0;
  }
  
@@@ -323,6 -417,21 +412,6 @@@
   * branching
   */
  
 -static int _is_valid_title(HDMV_VM *p, int title)
 -{
 -    if (title == 0 || title == 0xffff) {
 -        INDX_PLAY_ITEM *pi = (!title) ? &p->indx->top_menu : &p->indx->first_play;
 -
 -        if (pi->object_type == indx_object_type_hdmv &&  pi->hdmv.id_ref == 0xffff) {
 -            /* no top menu or first play title (5.2.3.3) */
 -            return 0;
 -        }
 -        return 1;
 -    }
 -
 -    return title > 0 && title <= p->indx->num_titles;
 -}
 -
  static int _jump_object(HDMV_VM *p, int object)
  {
      if (object < 0 || object >= p->movie_objects->num_objects) {
@@@ -332,8 -441,12 +421,12 @@@
  
      BD_DEBUG(DBG_HDMV, "_jump_object(): jumping to object %d\n", object);
  
+     _queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
+ 
      _free_ig_object(p);
  
+     p->playing_object = NULL;
+ 
      p->pc     = 0;
      p->object = &p->movie_objects->objects[object];
  
@@@ -344,11 -457,12 +437,12 @@@
  
  static int _jump_title(HDMV_VM *p, int title)
  {
 -    if (_is_valid_title(p, title)) {
 +    if (title >= 0 && title <= 0xffff) {
          BD_DEBUG(DBG_HDMV, "_jump_title(%d)\n", title);
  
          /* discard suspended object */
          p->suspended_object = NULL;
+         p->playing_object = NULL;
          bd_psr_reset_backup_registers(p->regs);
  
          _queue_event(p, HDMV_EVENT_TITLE, title);
@@@ -364,6 -478,7 +458,7 @@@ static int _call_object(HDMV_VM *p, in
  {
      BD_DEBUG(DBG_HDMV, "_call_object(%d)\n", object);
  
+     _queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
      _suspend_object(p, 1);
  
      return _jump_object(p, object);
@@@ -371,12 -486,13 +466,13 @@@
  
  static int _call_title(HDMV_VM *p, int title)
  {
 -    if (_is_valid_title(p, title)) {
 +    if (title >= 0 && title <= 0xffff) {
          BD_DEBUG(DBG_HDMV, "_call_title(%d)\n", title);
  
          _suspend_object(p, 1);
  
          _queue_event(p, HDMV_EVENT_TITLE, title);
+ 
          return 0;
      }
  
@@@ -410,7 -526,7 +506,7 @@@ static int _play_at(HDMV_VM *p, int pla
  
      if (playlist >= 0) {
          _queue_event(p, HDMV_EVENT_PLAY_PL, playlist);
-         _suspend_object(p, 0);
+         _suspend_for_play_pl(p);
      }
  
      if (playitem >= 0) {
@@@ -433,6 -549,7 +529,7 @@@ static int _play_stop(HDMV_VM *p
  
      BD_DEBUG(DBG_HDMV, "_play_stop()\n");
      _queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
+ 
      return 0;
  }
  
@@@ -954,7 -1071,7 +1051,7 @@@ uint32_t hdmv_vm_get_uo_mask(HDMV_VM *p
  
      bd_mutex_lock(&p->mutex);
  
-     if ((o = p->object ? p->object : p->suspended_object)) {
+     if ((o = p->object ? p->object : (p->playing_object ? p->playing_object : p->suspended_object))) {
          mask |= o->menu_call_mask;
          mask |= o->title_search_mask << 1;
      }
@@@ -968,19 -1085,36 +1065,36 @@@ int hdmv_vm_resume(HDMV_VM *p
      int result;
      bd_mutex_lock(&p->mutex);
  
-     result = _resume_object(p, 0);
+     result = _resume_from_play_pl(p);
  
      bd_mutex_unlock(&p->mutex);
      return result;
  }
  
 -int hdmv_vm_suspend_pl(HDMV_VM *p)
 +int hdmv_vm_suspend(HDMV_VM *p)
  {
      int result = -1;
      bd_mutex_lock(&p->mutex);
  
-     if (p->object && !p->ig_object) {
-         _suspend_object(p, 1);
+     if (p->object || p->ig_object) {
+         BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): HDMV VM is still running\n");
+ 
+     } else if (!p->playing_object) {
+         BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): No playing object\n");
+ 
+     } else if (!p->playing_object->resume_intention_flag) {
+         BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): no resume intention flag\n");
+ 
+         p->playing_object = NULL;
+         result = 0;
+ 
+     } else {
+         p->suspended_object = p->playing_object;
+         p->suspended_pc     = p->playing_pc;
+ 
+         p->playing_object = NULL;
+ 
+         bd_psr_save_state(p->regs);
          result = 0;
      }
  
@@@ -1008,6 -1142,13 +1122,13 @@@ static int _vm_run(HDMV_VM *p, HDMV_EVE
  
      while (--max_loop > 0) {
  
+         /* suspended ? */
+         if (!p->object) {
+             BD_DEBUG(DBG_HDMV, "hdmv_vm_run(): object suspended\n");
+             _get_event(p, ev);
+             return 0;
+         }
+ 
          /* terminated ? */
          if (p->pc >= p->object->num_cmds) {
              BD_DEBUG(DBG_HDMV, "terminated with PC=%d\n", p->pc);
diff --combined src/libbluray/register.c
index 07032f8,7621866..c83fd7a
--- a/src/libbluray/register.c
+++ b/src/libbluray/register.c
@@@ -252,6 -252,22 +252,22 @@@ void bd_psr_save_state(BD_REGISTERS *p
      memcpy(p->psr + 36, p->psr + 4,  sizeof(uint32_t) * 5);
      memcpy(p->psr + 42, p->psr + 10, sizeof(uint32_t) * 3);
  
+     /* generate save event */
+ 
+     if (p->num_cb) {
+         BD_PSR_EVENT ev = {
+             .ev_type = BD_PSR_SAVE,
+             .psr_idx = -1,
+             .old_val = 0,
+             .new_val = 0,
+         };
+ 
+         unsigned j;
+         for (j = 0; j < p->num_cb; j++) {
+             p->cb[j].cb(p->cb[j].handle, &ev);
+         }
+     }
+ 
      bd_psr_unlock(p);
  }
  
@@@ -268,17 -284,23 +284,17 @@@ void bd_psr_reset_backup_registers(BD_R
  
  void bd_psr_restore_state(BD_REGISTERS *p)
  {
 -    uint32_t old_psr[13];
 -    uint32_t new_psr[13];
 +    uint32_t old_psr[BD_PSR_COUNT];
  
      bd_psr_lock(p);
  
 -    if (p->num_cb) {
 -        memcpy(old_psr, p->psr, sizeof(old_psr[0]) * 13);
 -    }
 +    if (p->num_cb)
 +        memcpy(old_psr, p->psr, sizeof(old_psr));
  
      /* restore backup registers */
      memcpy(p->psr + 4,  p->psr + 36, sizeof(uint32_t) * 5);
      memcpy(p->psr + 10, p->psr + 42, sizeof(uint32_t) * 3);
  
 -    if (p->num_cb) {
 -        memcpy(new_psr, p->psr, sizeof(new_psr[0]) * 13);
 -    }
 -
      /* init backup registers to default */
      memcpy(p->psr + 36, bd_psr_init + 36, sizeof(uint32_t) * 5);
      memcpy(p->psr + 42, bd_psr_init + 42, sizeof(uint32_t) * 3);
@@@ -291,11 -313,11 +307,11 @@@
          ev.ev_type = BD_PSR_RESTORE;
  
          for (i = 4; i < 13; i++) {
 -            if (i != PSR_NAV_TIMER) {
 +            if (i != 9 && old_psr[i] != p->psr[i]) {
  
                  ev.psr_idx = i;
                  ev.old_val = old_psr[i];
 -                ev.new_val = new_psr[i];
 +                ev.new_val = p->psr[i];
  
                  for (j = 0; j < p->num_cb; j++) {
                      p->cb[j].cb(p->cb[j].handle, &ev);
@@@ -361,14 -383,11 +377,11 @@@ int bd_psr_setting_write(BD_REGISTERS *
          return -1;
      }
  
-     if (p->psr[reg] == val) {
-         BD_DEBUG(DBG_BLURAY, "bd_psr_write(%d, %d): no change in value\n", reg, val);
-         return 0;
-     }
- 
      bd_psr_lock(p);
  
-     if (bd_psr_name[reg]) {
+     if (p->psr[reg] == val) {
+         BD_DEBUG(DBG_BLURAY, "bd_psr_write(%d, %d): no change in value\n", reg, val);
+     } else if (bd_psr_name[reg]) {
          BD_DEBUG(DBG_BLURAY, "bd_psr_write(): PSR%-4d (%s) 0x%x -> 0x%x\n", reg, bd_psr_name[reg], p->psr[reg], val);
      } else {
          BD_DEBUG(DBG_BLURAY, "bd_psr_write(): PSR%-4d 0x%x -> 0x%x\n", reg, p->psr[reg], val);
@@@ -378,7 -397,7 +391,7 @@@
          BD_PSR_EVENT ev;
          unsigned i;
  
-         ev.ev_type = BD_PSR_CHANGE;
+         ev.ev_type = p->psr[reg] == val ? BD_PSR_WRITE : BD_PSR_CHANGE;
          ev.psr_idx = reg;
          ev.old_val = p->psr[reg];
          ev.new_val = val;

-- 
libbluray packaging



More information about the pkg-multimedia-commits mailing list