From db65cca9fc4dfe0bd29018b43995ca8c56b18fbf Mon Sep 17 00:00:00 2001
From: Richard <peper03@yahoo.com>
Date: Tue, 18 Jun 2013 22:44:47 +0200
Subject: [PATCH] Implement DVD bookmarks by saving and restoring the full DVD
 VM's state to make playback from bookmarks more reliable
 for all DVDs.

The state snapshot code is borrowed/adapted from or inspired by XBMC and Ogle.

Existing bookmarks are supported but will be converted if stored again.
---
 mythtv/libs/libmyth/programinfo.cpp                |   67 +++---
 mythtv/libs/libmythbase/mythversion.h              |    2 +-
 mythtv/libs/libmythdvdnav/dvdnav/dvdnav.c          |   58 ++++++
 mythtv/libs/libmythdvdnav/dvdnav/dvdnav.h          |   15 ++
 mythtv/libs/libmythdvdnav/dvdnav/vm/vm.c           |   57 ++++++
 mythtv/libs/libmythdvdnav/dvdnav/vm/vm.h           |    2 +
 mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.c |  215 ++++++++++++++++++++
 mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.h |    9 +
 mythtv/libs/libmythdvdnav/libmythdvdnav.pro        |    6 +-
 mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp        |   25 +++
 mythtv/libs/libmythtv/DVD/dvdringbuffer.h          |    2 +
 mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp        |  139 +++++++------
 mythtv/libs/libmythtv/DVD/mythdvdplayer.h          |    3 +-
 mythtv/libs/libmythtv/dbcheck.cpp                  |   11 +
 mythtv/programs/mythfrontend/main.cpp              |   12 +-
 15 files changed, 518 insertions(+), 105 deletions(-)
 create mode 100644 mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.c
 create mode 100644 mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.h

diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
index f59cf4a..f0d4102 100644
--- a/mythtv/libs/libmyth/programinfo.cpp
+++ b/mythtv/libs/libmyth/programinfo.cpp
@@ -2579,15 +2579,25 @@ QStringList ProgramInfo::QueryDVDBookmark(
 
     if (!(programflags & FL_IGNOREBOOKMARK))
     {
-        query.prepare(" SELECT title, framenum, audionum, subtitlenum "
+        query.prepare(" SELECT dvdstate, title, framenum, audionum, subtitlenum "
                         " FROM dvdbookmark "
                         " WHERE serialid = :SERIALID ");
         query.bindValue(":SERIALID", serialid);
 
         if (query.exec() && query.next())
         {
-            for(int i = 0; i < 4; i++)
-                fields.append(query.value(i).toString());
+            QString dvdstate = query.value(0).toString();
+
+            if (!dvdstate.isEmpty())
+            {
+                fields.append(dvdstate);
+            }
+            else
+            {
+                // Legacy bookmark
+                for(int i = 1; i < 5; i++)
+                    fields.append(query.value(i).toString());
+            }
         }
     }
 
@@ -2601,32 +2611,35 @@ void ProgramInfo::SaveDVDBookmark(const QStringList &fields) const
 
     QString serialid    = *(it);
     QString name        = *(++it);
-    QString title       = *(++it);
-    QString audionum    = *(++it);
-    QString subtitlenum = *(++it);
-    QString frame       = *(++it);
 
-    query.prepare("INSERT IGNORE INTO dvdbookmark "
-                    " (serialid, name)"
-                    " VALUES ( :SERIALID, :NAME );");
-    query.bindValue(":SERIALID", serialid);
-    query.bindValue(":NAME", name);
+    if( fields.count() == 3 )
+    {
+        // We have a state field, so update/create the bookmark
+        QString state = *(++it);
 
-    if (!query.exec())
-        MythDB::DBError("SetDVDBookmark inserting", query);
-
-    query.prepare(" UPDATE dvdbookmark "
-                    " SET title       = :TITLE , "
-                    "     audionum    = :AUDIONUM , "
-                    "     subtitlenum = :SUBTITLENUM , "
-                    "     framenum    = :FRAMENUM , "
-                    "     timestamp   = NOW() "
-                    " WHERE serialid = :SERIALID");
-    query.bindValue(":TITLE",title);
-    query.bindValue(":AUDIONUM",audionum);
-    query.bindValue(":SUBTITLENUM",subtitlenum);
-    query.bindValue(":FRAMENUM",frame);
-    query.bindValue(":SERIALID",serialid);
+        query.prepare("INSERT IGNORE INTO dvdbookmark "
+                        " (serialid, name)"
+                        " VALUES ( :SERIALID, :NAME );");
+        query.bindValue(":SERIALID", serialid);
+        query.bindValue(":NAME", name);
+
+        if (!query.exec())
+            MythDB::DBError("SetDVDBookmark inserting", query);
+
+        query.prepare(" UPDATE dvdbookmark "
+                        " SET dvdstate    = :STATE , "
+                        "     timestamp   = NOW() "
+                        " WHERE serialid = :SERIALID");
+        query.bindValue(":STATE",state);
+        query.bindValue(":SERIALID",serialid);
+    }
+    else
+    {
+        // No state field, delete the bookmark
+        query.prepare("DELETE FROM dvdbookmark "
+                        "WHERE serialid = :SERIALID");
+        query.bindValue(":SERIALID",serialid);
+    }
 
     if (!query.exec())
         MythDB::DBError("SetDVDBookmark updating", query);
diff --git a/mythtv/libs/libmythbase/mythversion.h b/mythtv/libs/libmythbase/mythversion.h
index c485b9b..dc53fc5 100644
--- a/mythtv/libs/libmythbase/mythversion.h
+++ b/mythtv/libs/libmythbase/mythversion.h
@@ -61,7 +61,7 @@
  *      mythtv/bindings/php/MythBackend.php
 #endif
 
-#define MYTH_DATABASE_VERSION "1312"
+#define MYTH_DATABASE_VERSION "1313"
 
 
  MBASE_PUBLIC  const char *GetMythSourceVersion();
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.c b/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.c
index 4fe270b..3a7952e 100644
--- a/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.c
+++ b/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.c
@@ -1224,3 +1224,61 @@ user_ops_t dvdnav_get_restrictions(dvdnav_t* this) {
 
   return ops.ops_struct;
 }
+
+char* dvdnav_get_state(dvdnav_t *this)
+{
+  char *state = NULL;
+
+  if(this && this->vm) {
+    pthread_mutex_lock(&this->vm_lock);
+
+    if( !(state = vm_get_state_str(this->vm)) )
+      printerr("Failed to get vm state.");
+
+    pthread_mutex_unlock(&this->vm_lock);
+  }
+
+  return state;
+}
+
+dvdnav_status_t dvdnav_set_state(dvdnav_t *this, const char *state_str)
+{
+  if(!this || !this->vm)
+  {
+    printerr("Passed a NULL pointer.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  if(!this->started) {
+    printerr("Virtual DVD machine not started.");
+    return DVDNAV_STATUS_ERR;
+  }
+
+  pthread_mutex_lock(&this->vm_lock);
+
+  /* reset the dvdnav state */
+  memset(&this->pci,0,sizeof(this->pci));
+  memset(&this->dsi,0,sizeof(this->dsi));
+  this->last_cmd_nav_lbn = SRI_END_OF_CELL;
+
+  /* Set initial values of flags */
+  this->position_current.still = 0;
+  this->skip_still = 0;
+  this->sync_wait = 0;
+  this->sync_wait_skip = 0;
+  this->spu_clut_changed = 0;
+
+
+  /* set the state. this will also start the vm on that state */
+  /* means the next read block should be comming from that new */
+  /* state */
+  if( !vm_set_state(this->vm, state_str) )
+  {
+    printerr("Failed to set vm state.");
+    pthread_mutex_unlock(&this->vm_lock);
+    return DVDNAV_STATUS_ERR;
+  }
+
+  pthread_mutex_unlock(&this->vm_lock);
+  return DVDNAV_STATUS_OK;
+}
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.h b/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.h
index 04fe99a..1e7fa08 100644
--- a/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.h
+++ b/mythtv/libs/libmythdvdnav/dvdnav/dvdnav.h
@@ -726,6 +726,21 @@ int8_t dvdnav_is_domain_vtsm(dvdnav_t *self);
  */
 int8_t dvdnav_is_domain_vts(dvdnav_t *self);
 
+/*********************************************************************
+ * Save/restore playback state                                       *
+ *********************************************************************/
+
+/*
+ * Get a text string representing a snapshot of the current internal state
+ * The calling application is responsible for freeing the returned buffer.
+ */
+char* dvdnav_get_state(dvdnav_t *self);
+
+/*
+ * Set the current internal state to an earlier snapshot
+ */
+dvdnav_status_t dvdnav_set_state(dvdnav_t *self, const char *state_str);
+
 
 #ifdef __cplusplus
 }
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.c b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.c
index 24e6fd8..7b8f401 100644
--- a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.c
+++ b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.c
@@ -45,6 +45,7 @@
 #include "decoder.h"
 #include "remap.h"
 #include "vm.h"
+#include "vm_serialize.h"
 #include "dvdnav_internal.h"
 
 #ifdef _MSC_VER
@@ -2017,6 +2018,62 @@ void vm_ifo_close(ifo_handle_t *ifo)
   ifoClose(ifo);
 }
 
+char *vm_get_state_str(vm_t *vm) {
+  char *str_state = NULL;
+
+  if(vm)
+    str_state = vm_serialize_dvd_state(&vm->state);
+
+  return str_state;
+}
+
+int vm_set_state(vm_t *vm, const char *state_str) {
+  /* restore state from save_state as taken from ogle */
+
+  dvd_state_t save_state;
+
+  if(state_str == NULL) {
+    return 0;
+  }
+
+  if(!vm_deserialize_dvd_state(state_str, &save_state)) {
+#ifdef TRACE
+    fprintf( MSG_OUT, "state_str invalid\n");
+#endif
+    return 0;
+  }
+
+  /* open the needed vts */
+  if( !ifoOpenNewVTSI(vm, vm->dvd, save_state.vtsN) ) return 0;
+  // sets state.vtsN
+
+  vm->state = save_state;
+  /* set state.domain before calling */
+  //calls get_pgcit()
+  //      needs state.domain and sprm[0] set
+  //      sets pgcit depending on state.domain
+  //writes: state.pgc
+  //        state.pgN
+  //        state.TT_PGCN_REG
+
+  if( !set_PGCN(vm, save_state.pgcN) ) return 0;
+  save_state.pgc = vm->state.pgc;
+
+  /* set the rest of state after the call */
+  vm->state = save_state;
+
+  /* if we are not in standard playback, we must get all data */
+  /* otherwise we risk loosing stillframes, and overlays */
+  if(vm->state.domain != VTS_DOMAIN)
+    vm->state.blockN = 0;
+
+  /* force a flush of data here */
+  /* we don't need a hop seek here as it's a complete state*/
+  vm->hop_channel++;
+
+  return 1;
+}
+
 /* Debug functions */
 
 #ifdef TRACE
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.h b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.h
index d94b3c1..3daf421 100644
--- a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.h
+++ b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm.h
@@ -171,6 +171,8 @@ audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN);
 subp_attr_t  vm_get_subp_attr(vm_t *vm, int streamN);
 ifo_handle_t *vm_get_title_ifo(vm_t *vm, uint32_t title);
 void vm_ifo_close(ifo_handle_t *ifo);
+char *vm_get_state_str(vm_t *vm);
+int vm_set_state(vm_t *vm, const char *state_str);
 
 /* Uncomment for VM command tracing */
 /* #define TRACE */
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.c b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.c
new file mode 100644
index 0000000..a4d201a
--- /dev/null
+++ b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.c
@@ -0,0 +1,215 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+
+#include <dvdread/nav_types.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/ifo_read.h>
+#include "dvdnav/dvdnav.h"
+#include "remap.h"
+#include "decoder.h"
+#include "vm.h"
+
+/*
+"navstate",<version>,<sprm x 24>,<gprm x 16>,
+<domain>,<vtsn>,<pgcn>,<pgn>,<celln>,<cell_restart>,<blockn>,
+<rsm_vts>,<rsm_blockn>,<rsm_pgcn>,<rsm_celln>,<rsm_sprm x 5>,"end"
+*/
+/* The serialized string starts with "navstate,1,", so that needs 11 chars.
+ * There are 10 integer fields (domain, vtsN, pgcN, pgN, cellN, cell_restart,
+ * blockN, rsm_vtsN, rsm_blockN, rsm_pgcN and rsm_cellN).
+ * None of the values can realistically be more than 32 bits (and most will
+ * be far less).  In worst case, each field would need 11 characters
+ * (-2147483648) plus a trailing comma, so we need 10 * (11+1) = 120 chars
+ * for those 10 fields.
+ * Each SPRM is 16 bits, so thats 6 chars + 1 per register ("0xffff,") * 24
+ * registers = 168 chars.
+ * Each GPRM is 16 bits plus a mode character plus two 32 bit counter values.
+ * In the format "[0xffff;0;0xffffffff;0xffffffff],", each register needs 33
+ * chars.  With 16 registers, that's 33 * 16 = 528
+ * There are five resume SPRM values, so 5 * (6 + !) = 42
+ * Finally, the string is terminated with "end" and a NULL char, so another 4 chars.
+ * In total then, 11 + 120 + 168 + 528 + 42 + 4 = 873 chars.  Round that up to 1024
+ * and there should never be any issues.  Just to be safe, we'll still check as
+ * we go along.
+ */
+
+#define BUFFER_SIZE    1024
+#define FORMAT_VERSION    1
+
+static void vm_serialize_int(int *stored, char **buf, size_t *remaining, int value)
+{
+  if(stored && buf && remaining && *stored > 0)
+  {
+    *stored = snprintf(*buf, *remaining, "%d,", value);
+    if(*stored > 0)
+    {
+      *remaining -= (size_t)(*stored);
+      *buf += (*stored);
+    }
+  }
+}
+
+static void vm_deserialize_int(int *consumed, char **buf, int* value)
+{
+  if(consumed && buf && *consumed > 0)
+  {
+    sscanf( *buf, "%d,%n", value, consumed);
+
+    if(*consumed > 0)
+      *buf += *consumed;
+  }
+}
+
+char *vm_serialize_dvd_state(const dvd_state_t *state)
+{
+  char   *str_state = 0;
+  char   *buf;
+  int    stored;
+  int    tmp;
+  int    idx;
+  size_t remaining = BUFFER_SIZE;
+
+  if(state)
+  {
+    str_state = malloc(BUFFER_SIZE);
+    buf = str_state;
+
+    stored = snprintf(buf, remaining, "navstat,%d,", FORMAT_VERSION);
+
+    if(stored > 0)
+    {
+      remaining -= (size_t)stored;
+      buf += stored;
+    }
+
+    // SPRM
+    for(idx = 0; idx < 24 && stored > 0; idx++)
+    {
+      stored = snprintf(buf, remaining, "0x%hx,", state->registers.SPRM[idx]);
+      if(stored > 0)
+      {
+        remaining -= (size_t)stored;
+        buf += stored;
+      }
+    }
+
+    // GPRM
+    for(idx = 0; idx < 16 && stored > 0; idx++)
+    {
+      stored = snprintf(buf, remaining,
+                        "[0x%hx;%d;0x%x;0x%x],", state->registers.GPRM[idx],
+                                                 state->registers.GPRM_mode[idx],
+                                                 state->registers.GPRM_time[idx].tv_sec,
+                                                 state->registers.GPRM_time[idx].tv_usec);
+      if(stored > 0)
+      {
+        remaining -= (size_t)stored;
+        buf += stored;
+      }
+    }
+
+    vm_serialize_int(&stored, &buf, &remaining, state->domain);
+    vm_serialize_int(&stored, &buf, &remaining, state->vtsN);
+    vm_serialize_int(&stored, &buf, &remaining, state->pgcN);
+    vm_serialize_int(&stored, &buf, &remaining, state->pgN);
+    vm_serialize_int(&stored, &buf, &remaining, state->cellN);
+    vm_serialize_int(&stored, &buf, &remaining, state->cell_restart);
+    vm_serialize_int(&stored, &buf, &remaining, state->blockN);
+    vm_serialize_int(&stored, &buf, &remaining, state->rsm_vtsN);
+    vm_serialize_int(&stored, &buf, &remaining, state->rsm_blockN);
+    vm_serialize_int(&stored, &buf, &remaining, state->rsm_pgcN);
+    vm_serialize_int(&stored, &buf, &remaining, state->rsm_cellN);
+
+    // Resume SPRM
+    for(idx = 0; idx < 5 && stored > 0; idx++)
+    {
+      stored = snprintf(buf, remaining, "0x%hx,", state->rsm_regs[idx]);
+      if(stored > 0)
+      {
+        remaining -= (size_t)stored;
+        buf += stored;
+      }
+    }
+
+    if(stored > 0 && remaining >= 4)
+    {
+      // Done.  Terminating the string.
+      strcpy(buf, "end");
+    }
+    else
+    {
+      // Error
+      free(str_state);
+      str_state = 0;
+    }
+  }
+  return str_state;
+}
+
+int vm_deserialize_dvd_state(const char* serialized, dvd_state_t *state)
+{
+  char        *buf = serialized;
+  int         consumed;
+  int         version;
+  int         tmp;
+  int         idx;
+  int         ret = 0; /* assume an error */
+  dvd_state_t new_state;
+
+  sscanf( buf, "navstat,%d,%n", &version, &consumed);
+  if(version == 1)
+  {
+    buf += consumed;
+
+    // SPRM
+    for(idx = 0; idx < 24 && consumed > 0; idx++)
+    {
+      sscanf(buf, "0x%hx,%n", &new_state.registers.SPRM[idx], &consumed);
+      buf += consumed;
+    }
+
+    // GPRM
+    for(idx = 0; idx < 16 && consumed > 0; idx++)
+    {
+      sscanf(buf, "[0x%hx;%d;0x%x;0x%x],%n", &new_state.registers.GPRM[idx],
+                                             &new_state.registers.GPRM_mode[idx],
+                                             &new_state.registers.GPRM_time[idx].tv_sec,
+                                             &new_state.registers.GPRM_time[idx].tv_usec,
+                                             &consumed);
+      buf += consumed;
+    }
+
+    vm_deserialize_int(&consumed, &buf, &new_state.domain);
+    vm_deserialize_int(&consumed, &buf, &new_state.vtsN);
+    vm_deserialize_int(&consumed, &buf, &new_state.pgcN);
+    vm_deserialize_int(&consumed, &buf, &new_state.pgN);
+    vm_deserialize_int(&consumed, &buf, &new_state.cellN);
+    vm_deserialize_int(&consumed, &buf, &tmp);
+    new_state.cell_restart = tmp;
+    vm_deserialize_int(&consumed, &buf, &new_state.blockN);
+    vm_deserialize_int(&consumed, &buf, &new_state.rsm_vtsN);
+    vm_deserialize_int(&consumed, &buf, &new_state.rsm_blockN);
+    vm_deserialize_int(&consumed, &buf, &new_state.rsm_pgcN);
+    vm_deserialize_int(&consumed, &buf, &new_state.rsm_cellN);
+
+    // Resume SPRM
+    for(idx = 0; idx < 5 && consumed > 0; idx++)
+    {
+      sscanf(buf, "0x%hx,%n", &new_state.registers.SPRM[idx], &consumed);
+      buf += consumed;
+    }
+
+    if(strcmp(buf,"end") == 0)
+    {
+      /* Success! */
+      *state = new_state;
+      state->pgc = NULL;
+      ret = 1;
+    }
+  }
+
+  return ret;
+}
+
diff --git a/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.h b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.h
new file mode 100644
index 0000000..e26e9e0
--- /dev/null
+++ b/mythtv/libs/libmythdvdnav/dvdnav/vm/vm_serialize.h
@@ -0,0 +1,9 @@
+#ifndef LIBDVDNAV_VM_SERIALIZE_H
+#define LIBDVDNAV_VM_SERIALIZE_H
+
+#include "vm.h"
+
+char *vm_serialize_dvd_state(const dvd_state_t *state);
+int vm_deserialize_dvd_state(const char* serialized, dvd_state_t *state);
+
+#endif /* LIBDVDNAV_VM_SERIALIZE_H */
diff --git a/mythtv/libs/libmythdvdnav/libmythdvdnav.pro b/mythtv/libs/libmythdvdnav/libmythdvdnav.pro
index d9c01a4..870bdc6 100644
--- a/mythtv/libs/libmythdvdnav/libmythdvdnav.pro
+++ b/mythtv/libs/libmythdvdnav/libmythdvdnav.pro
@@ -21,10 +21,12 @@ DEFINES += HAVE_AV_CONFIG_H
 QMAKE_CLEAN += $(TARGET) $(TARGETA) $(TARGETD) $(TARGET0) $(TARGET1) $(TARGET2)
 
 # dvdnav
-HEADERS += dvdnav/dvdnav_internal.h dvdnav/read_cache.h dvdnav/remap.h
+HEADERS += dvdnav/dvdnav_internal.h dvdnav/read_cache.h dvdnav/remap.h \
+    dvdnav/vm/vm_serialize.h
 HEADERS += dvdnav/vm/decoder.h dvdnav/vm/vm.h dvdnav/vm/vmcmd.h
 
-SOURCES += dvdnav/dvdnav.c dvdnav/read_cache.c dvdnav/navigation.c
+SOURCES += dvdnav/dvdnav.c dvdnav/read_cache.c dvdnav/navigation.c \
+    dvdnav/vm/vm_serialize.c
 SOURCES += dvdnav/highlight.c dvdnav/searching.c dvdnav/settings.c
 SOURCES += dvdnav/remap.c dvdnav/vm/decoder.c dvdnav/vm/vm.c
 SOURCES += dvdnav/vm/vmcmd.c
diff --git a/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp b/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
index cb854d7..16b0eb4 100644
--- a/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
+++ b/mythtv/libs/libmythtv/DVD/dvdringbuffer.cpp
@@ -1895,6 +1895,31 @@ bool DVDRingBuffer::GetNameAndSerialNum(QString& _name, QString& _serial)
     return true;
 }
 
+/** \brief Get a snapshot of the current DVD VM state
+ */
+bool DVDRingBuffer::GetDVDStateSnapshot(QString& state)
+{
+    state.clear();
+    char* dvdstate = dvdnav_get_state(m_dvdnav);
+
+    if (dvdstate)
+    {
+        state = dvdstate;
+        free(dvdstate);
+    }
+
+    return (!state.isEmpty());
+}
+
+/** \brief Restore a DVD VM from a snapshot
+ */
+bool DVDRingBuffer::RestoreDVDStateSnapshot(QString& state)
+{
+    QByteArray ba_state = state.toAscii();
+
+    return (dvdnav_set_state(m_dvdnav, ba_state.constData()) == DVDNAV_STATUS_OK);
+}
+
 /** \brief used by DecoderBase for the total frame number calculation
  * for position map support and ffw/rew.
  * FPS for a dvd is determined by AFD::normalized_fps
diff --git a/mythtv/libs/libmythtv/DVD/dvdringbuffer.h b/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
index c5e4d36..9e2159f 100644
--- a/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
+++ b/mythtv/libs/libmythtv/DVD/dvdringbuffer.h
@@ -132,6 +132,8 @@ class MTV_PUBLIC DVDRingBuffer : public RingBuffer
     int  GetAudioTrackType(uint stream_id);
 
     bool GetNameAndSerialNum(QString& _name, QString& _serialnum);
+    bool GetDVDStateSnapshot(QString& state);
+    bool RestoreDVDStateSnapshot(QString& state);
     double GetFrameRate(void);
     bool StartOfTitle(void) { return (m_part == 0); }
     bool EndOfTitle(void)   { return ((!m_titleParts) ||
diff --git a/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp b/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
index 34aa40b..82e65a4 100644
--- a/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
+++ b/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
@@ -109,6 +109,14 @@ void MythDVDPlayer::PreProcessNormalFrame(void)
     DisplayDVDButton();
 }
 
+void MythDVDPlayer::VideoStart(void)
+{
+    if (!m_initial_dvdstate.isEmpty())
+        player_ctx->buffer->DVD()->RestoreDVDStateSnapshot(m_initial_dvdstate);
+
+    MythPlayer::VideoStart();
+}
+
 bool MythDVDPlayer::VideoLoop(void)
 {
     if (!player_ctx->buffer->IsDVD())
@@ -286,6 +294,7 @@ void MythDVDPlayer::EventStart(void)
 void MythDVDPlayer::InitialSeek(void)
 {
     player_ctx->buffer->IgnoreWaitStates(true);
+
     if (m_initial_title > -1)
         player_ctx->buffer->DVD()->PlayTitleAndPart(m_initial_title, 1);
 
@@ -298,7 +307,6 @@ void MythDVDPlayer::InitialSeek(void)
 
     if (bookmarkseek > 30)
     {
-
         // we need to trigger a dvd cell change to ensure the new title length
         // is set and the position map updated accordingly
         decodeOneFrame = true;
@@ -334,10 +342,49 @@ bool MythDVDPlayer::PrepareAudioSample(int64_t &timecode)
 
 void MythDVDPlayer::SetBookmark(bool clear)
 {
-    if (player_ctx->buffer->IsInDiscMenuOrStillFrame() || clear)
-        SetDVDBookmark(0);
-    else
-        SetDVDBookmark(framesPlayed);
+    if (!player_ctx->buffer->IsDVD())
+        return;
+
+    QStringList fields;
+    QString name;
+    QString serialid;
+    QString dvdstate;
+
+    if (player_ctx->buffer->IsBookmarkAllowed() || clear)
+    {
+        if (!player_ctx->buffer->DVD()->GetNameAndSerialNum(name, serialid))
+        {
+            LOG(VB_GENERAL, LOG_ERR, LOC +
+                "DVD has no name and serial number. Cannot set bookmark.");
+            return;
+        }
+
+        if (!clear && !player_ctx->buffer->DVD()->GetDVDStateSnapshot(dvdstate))
+        {
+            LOG(VB_GENERAL, LOG_ERR, LOC +
+                "Unable to retrieve DVD state. Cannot set bookmark.");
+            return;
+        }
+
+        player_ctx->LockPlayingInfo(__FILE__, __LINE__);
+        if (player_ctx->playingInfo)
+        {
+            fields += serialid;
+            fields += name;
+
+            if (!clear)
+            {
+                LOG(VB_PLAYBACK, LOG_INFO, LOC + "Set bookmark");
+                fields += dvdstate;
+            }
+            else
+                LOG(VB_PLAYBACK, LOG_INFO, LOC + "Clear bookmark");
+
+            player_ctx->playingInfo->SaveDVDBookmark(fields);
+
+        }
+        player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
+    }
 }
 
 uint64_t MythDVDPlayer::GetBookmark(void)
@@ -357,19 +404,31 @@ uint64_t MythDVDPlayer::GetBookmark(void)
             player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
             return 0;
         }
+
         dvdbookmark = player_ctx->playingInfo->QueryDVDBookmark(serialid);
+
         if (!dvdbookmark.empty())
         {
             QStringList::Iterator it = dvdbookmark.begin();
-            m_initial_title = (*it).toInt();
-            frames = (long long)((*++it).toLongLong() & 0xffffffffLL);
-            m_initial_audio_track    = (*++it).toInt();
-            m_initial_subtitle_track = (*++it).toInt();
-            LOG(VB_PLAYBACK, LOG_INFO, LOC +
-                QString("Get Bookmark: title %1 audiotrack %2 subtrack %3 "
-                        "frame %4")
-                .arg(m_initial_title).arg(m_initial_audio_track)
-                .arg(m_initial_subtitle_track).arg(frames));
+
+            if (dvdbookmark.count() == 1)
+            {
+                m_initial_dvdstate = *it;
+                LOG(VB_PLAYBACK, LOG_INFO, LOC + "Get Bookmark: bookmark found");
+            }
+            else
+            {
+                // Legacy bookmarks
+                m_initial_title = (*it).toInt();
+                frames = (long long)((*++it).toLongLong() & 0xffffffffLL);
+                m_initial_audio_track    = (*++it).toInt();
+                m_initial_subtitle_track = (*++it).toInt();
+                LOG(VB_PLAYBACK, LOG_INFO, LOC +
+                    QString("Get Bookmark: title %1 audiotrack %2 subtrack %3 "
+                            "frame %4")
+                    .arg(m_initial_title).arg(m_initial_audio_track)
+                    .arg(m_initial_subtitle_track).arg(frames));
+            }
         }
     }
     player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
@@ -592,58 +651,6 @@ void MythDVDPlayer::GoToDVDProgram(bool direction)
         player_ctx->buffer->DVD()->GoToNextProgram();
 }
 
-void MythDVDPlayer::SetDVDBookmark(uint64_t frame)
-{
-    if (!player_ctx->buffer->IsDVD())
-        return;
-
-    uint64_t framenum = frame;
-    QStringList fields;
-    QString name;
-    QString serialid;
-    int title = 0;
-    int part;
-    int audiotrack = -1;
-    int subtitletrack = -1;
-    if (!player_ctx->buffer->DVD()->GetNameAndSerialNum(name, serialid))
-    {
-        LOG(VB_GENERAL, LOG_ERR, LOC +
-            "DVD has no name and serial number. Cannot set bookmark.");
-        return;
-    }
-
-    if (!player_ctx->buffer->IsInDiscMenuOrStillFrame() &&
-        player_ctx->buffer->DVD()->
-        GetTotalTimeOfTitle() > 120 && frame > 0)
-    {
-        audiotrack = GetTrack(kTrackTypeAudio);
-        if (GetCaptionMode() == kDisplayAVSubtitle)
-        {
-            subtitletrack = player_ctx->buffer->DVD()->GetTrack(
-                kTrackTypeSubtitle);
-        }
-        player_ctx->buffer->DVD()->GetPartAndTitle(part, title);
-    }
-    else
-        framenum = 0;
-
-    player_ctx->LockPlayingInfo(__FILE__, __LINE__);
-    if (player_ctx->playingInfo)
-    {
-        fields += serialid;
-        fields += name;
-        fields += QString("%1").arg(title);
-        fields += QString("%1").arg(audiotrack);
-        fields += QString("%1").arg(subtitletrack);
-        fields += QString("%1").arg(framenum);
-        player_ctx->playingInfo->SaveDVDBookmark(fields);
-        LOG(VB_PLAYBACK, LOG_INFO, LOC +
-            QString("Set Bookmark: title %1 audiotrack %2 subtrack %3 frame %4")
-            .arg(title).arg(audiotrack).arg(subtitletrack).arg(framenum));
-    }
-    player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
-}
-
 int MythDVDPlayer::GetNumAngles(void) const
 {
     if (player_ctx->buffer->DVD() && player_ctx->buffer->DVD()->IsOpen())
diff --git a/mythtv/libs/libmythtv/DVD/mythdvdplayer.h b/mythtv/libs/libmythtv/DVD/mythdvdplayer.h
index f32620a..01be07f 100644
--- a/mythtv/libs/libmythtv/DVD/mythdvdplayer.h
+++ b/mythtv/libs/libmythtv/DVD/mythdvdplayer.h
@@ -63,6 +63,7 @@ class MythDVDPlayer : public MythPlayer
     virtual void AVSync(VideoFrame *buffer, bool limit_delay = false);
     virtual void DisplayPauseFrame(void);
     virtual void PreProcessNormalFrame(void);
+    virtual void VideoStart(void);
     virtual bool VideoLoop(void);
     virtual void EventStart(void);
     virtual void EventEnd(void);
@@ -98,7 +99,6 @@ class MythDVDPlayer : public MythPlayer
 
   private:
     void DoChangeDVDTrack(void);
-    void SetDVDBookmark(uint64_t frame);
     void DisplayDVDButton(void);
 
     void DisplayLastFrame(void);
@@ -110,6 +110,7 @@ class MythDVDPlayer : public MythPlayer
     int m_initial_title;
     int m_initial_audio_track;
     int m_initial_subtitle_track;
+    QString m_initial_dvdstate;
 
     // still frame timing
     MythTimer m_stillFrameTimer;
diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp
index 0bebaf8..8516790 100644
--- a/mythtv/libs/libmythtv/dbcheck.cpp
+++ b/mythtv/libs/libmythtv/dbcheck.cpp
@@ -2401,6 +2401,17 @@ NULL
             return false;
     }
 
+    if (dbver == "1312")
+    {
+        const char *updates[] = {
+// DVD bookmark updates
+"DELETE FROM `dvdbookmark` WHERE `framenum` = 0;",
+"ALTER TABLE dvdbookmark ADD COLUMN dvdstate varchar(1024) NOT NULL DEFAULT '';",
+NULL
+};
+        if (!performActualUpdate(&updates[0], "1313", dbver))
+            return false;
+    }
     return true;
 }
 
diff --git a/mythtv/programs/mythfrontend/main.cpp b/mythtv/programs/mythfrontend/main.cpp
index 717978a..f596763 100644
--- a/mythtv/programs/mythfrontend/main.cpp
+++ b/mythtv/programs/mythfrontend/main.cpp
@@ -1118,7 +1118,7 @@ static int internal_play_media(const QString &mrl, const QString &plot,
 
     pginfo->SetProgramInfoType(pginfo->DiscoverProgramInfoType());
 
-    int64_t pos = 0;
+    bool bookmarkPresent = false;
 
     if (pginfo->IsVideoDVD())
     {
@@ -1130,11 +1130,7 @@ static int internal_play_media(const QString &mrl, const QString &plot,
             if (dvd->GetNameAndSerialNum(name, serialid))
             {
                 QStringList fields = pginfo->QueryDVDBookmark(serialid);
-                if (!fields.empty())
-                {
-                    QStringList::Iterator it = fields.begin();
-                    pos = (int64_t)((*++it).toLongLong() & 0xffffffffLL);
-                }
+                bookmarkPresent = (fields.count() > 0);
             }
         }
         else
@@ -1147,9 +1143,9 @@ static int internal_play_media(const QString &mrl, const QString &plot,
         delete dvd;
     }
     else if (pginfo->IsVideo())
-        pos = pginfo->QueryBookmark();
+        bookmarkPresent = (pginfo->QueryBookmark() > 0);
 
-    if (useBookmark && pos > 0)
+    if (useBookmark && bookmarkPresent)
     {
         MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();
         BookmarkDialog *bookmarkdialog = new BookmarkDialog(pginfo, mainStack);
-- 
1.7.9.5

