Initial WVS for mpegplayer. Adjusts to the user's preferred font and uses FF/RW preferences set for playback. Picked a random color for the base WVS color but it could be configured. Some engine tweaks to accomodate it since certain nescessities are clearer now. Fix a clipped YUV output bug in the SIM.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15991 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/plugin.c b/apps/plugin.c
index 0192590..78c3aa9 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -580,6 +580,8 @@
     trigger_cpu_boost,
     cancel_cpu_boost,
 #endif
+    sound_unit,
+    sound_val2phys,
 #endif /* CONFIG_CODEC == SWCODEC */
 };
 
diff --git a/apps/plugin.h b/apps/plugin.h
index 5b6d7cf..771b573 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -119,7 +119,7 @@
 #define PLUGIN_MAGIC 0x526F634B /* RocK */
 
 /* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 92
+#define PLUGIN_API_VERSION 93
 
 /* update this to latest version if a change to the api struct breaks
    backwards compatibility (and please take the opportunity to sort in any
@@ -715,6 +715,8 @@
     void (*trigger_cpu_boost)(void);
     void (*cancel_cpu_boost)(void);
 #endif
+    const char * (*sound_unit)(int setting);
+    int (*sound_val2phys)(int setting, int value);
 #endif /* CONFIG_CODEC == SWCODEC */
 };
 
diff --git a/apps/plugins/bitmaps/mono/SOURCES b/apps/plugins/bitmaps/mono/SOURCES
index 36fd2b0..bc75c3a 100644
--- a/apps/plugins/bitmaps/mono/SOURCES
+++ b/apps/plugins/bitmaps/mono/SOURCES
@@ -38,6 +38,11 @@
 #endif
 #endif
 
+/* MPEGplayer */
+mpegplayer_status_icons_8x8x1.bmp
+mpegplayer_status_icons_12x12x1.bmp
+mpegplayer_status_icons_16x16x1.bmp
+
 #if LCD_WIDTH == 160 && LCD_HEIGHT == 128 && LCD_DEPTH < 16
 superdom_boarditems.160x128x1.bmp
 #endif
diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp
new file mode 100644
index 0000000..61f6a52
--- /dev/null
+++ b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp
new file mode 100644
index 0000000..944bd51
--- /dev/null
+++ b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp
Binary files differ
diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp
new file mode 100644
index 0000000..d4b71fe
--- /dev/null
+++ b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp
Binary files differ
diff --git a/apps/plugins/mpegplayer/Makefile b/apps/plugins/mpegplayer/Makefile
index c43dde0..97c9e6a 100644
--- a/apps/plugins/mpegplayer/Makefile
+++ b/apps/plugins/mpegplayer/Makefile
@@ -43,7 +43,7 @@
 ifndef SIMVER
 $(OBJDIR)/mpegplayer.elf: $(OBJS) $(LINKFILE) $(BUILDDIR)/libmad-mpegplayer.a
 	$(call PRINTS,LD $(@F))$(CC) $(CFLAGS) -o $@ $(OBJS) -L$(BUILDDIR) -lplugin -lmad-mpegplayer -lgcc\
-           -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/mpegplayer.map
+           $(LINKBITMAPS) -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/mpegplayer.map
 
 $(OUTPUT): $(OBJDIR)/mpegplayer.elf
 	$(call PRINTS,OBJCOPY $(@F))$(OC) -O binary $< $@
@@ -51,7 +51,7 @@
 # This is the SDL simulator version
 
 $(OUTPUT): $(OBJS) $(BUILDDIR)/libmad-mpegplayer.a
-	$(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin -lmad-mpegplayer -o $@
+	$(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -lmad-mpegplayer -o $@
 ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
 # 'x' must be kept or you'll have "Win32 error 5"
 #     $ fgrep 5 /usr/include/w32api/winerror.h | head -1
diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c
index ee6ff05..78d28e4 100644
--- a/apps/plugins/mpegplayer/audio_thread.c
+++ b/apps/plugins/mpegplayer/audio_thread.c
@@ -140,9 +140,8 @@
     audio_queue.used = 0;
     audio_queue.read = 0;
     audio_queue.write = 0;
+    rb->memset(audio_queue.descs, 0, sizeof (audio_queue.descs));
     audio_queue.curr = audiodesc_queue_head();
-    audio_queue.curr->time = 0;
-    audio_queue.curr->size = 0;
 }
 
 static void audio_queue_advance_pos(ssize_t len)
diff --git a/apps/plugins/mpegplayer/disk_buf.h b/apps/plugins/mpegplayer/disk_buf.h
index 90e72fa..79c3328 100644
--- a/apps/plugins/mpegplayer/disk_buf.h
+++ b/apps/plugins/mpegplayer/disk_buf.h
@@ -104,6 +104,9 @@
 bool disk_buf_init(void);
 void disk_buf_exit(void);
 
+static inline int disk_buf_status(void)
+    { return disk_buf.status; }
+
 int disk_buf_open(const char *filename);
 void disk_buf_close(void);
 ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap,
diff --git a/apps/plugins/mpegplayer/mpeg_parser.c b/apps/plugins/mpegplayer/mpeg_parser.c
index d4b0ff1..5b863da 100644
--- a/apps/plugins/mpegplayer/mpeg_parser.c
+++ b/apps/plugins/mpegplayer/mpeg_parser.c
@@ -1012,15 +1012,17 @@
         switch (id)
         {
         case VIDEO_DISPLAY_SHOW:
-            if (data != 0 && stream_status() != STREAM_PLAYING)
+            if (data != 0 && disk_buf_status() == STREAM_STOPPED)
             {   /* Only prepare image if showing and not playing */
                 prepare_image(str_parser.last_seek_time);
             }
             break;
 
         case VIDEO_PRINT_FRAME:
+            if (data)
+                break;
         case VIDEO_PRINT_THUMBNAIL:
-            if (stream_status() == STREAM_PLAYING)
+            if (disk_buf_status() != STREAM_STOPPED)
                 break; /* Prepare image if not playing */
 
             if (!prepare_image(str_parser.last_seek_time))
diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c
index 03ec5ba..e9f6b90 100644
--- a/apps/plugins/mpegplayer/mpegplayer.c
+++ b/apps/plugins/mpegplayer/mpegplayer.c
@@ -116,6 +116,8 @@
 #define MPEG_PAUSE      BUTTON_ON
 #define MPEG_VOLDOWN    BUTTON_DOWN
 #define MPEG_VOLUP      BUTTON_UP
+#define MPEG_RW         BUTTON_LEFT
+#define MPEG_FF         BUTTON_RIGHT
 
 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
       (CONFIG_KEYPAD == IPOD_1G2G_PAD)
@@ -124,6 +126,8 @@
 #define MPEG_STOP       (BUTTON_PLAY | BUTTON_REPEAT)
 #define MPEG_VOLDOWN    BUTTON_SCROLL_BACK
 #define MPEG_VOLUP      BUTTON_SCROLL_FWD
+#define MPEG_RW         BUTTON_LEFT
+#define MPEG_FF         BUTTON_RIGHT
 
 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
 #define MPEG_MENU       (BUTTON_REC | BUTTON_REL)
@@ -131,6 +135,8 @@
 #define MPEG_PAUSE      BUTTON_PLAY
 #define MPEG_VOLDOWN    BUTTON_DOWN
 #define MPEG_VOLUP      BUTTON_UP
+#define MPEG_RW         BUTTON_LEFT
+#define MPEG_FF         BUTTON_RIGHT
 
 #elif CONFIG_KEYPAD == GIGABEAT_PAD
 #define MPEG_MENU       BUTTON_MENU
@@ -141,13 +147,17 @@
 #define MPEG_VOLUP      BUTTON_RIGHT
 #define MPEG_VOLDOWN2   BUTTON_VOL_DOWN
 #define MPEG_VOLUP2     BUTTON_VOL_UP
+#define MPEG_RW         BUTTON_UP
+#define MPEG_FF         BUTTON_DOWN
 
 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
-#define MPEG_MENU       (BUTTON_REW | BUTTON_REL)
+#define MPEG_MENU       BUTTON_LEFT
 #define MPEG_STOP       BUTTON_POWER
 #define MPEG_PAUSE      BUTTON_PLAY
 #define MPEG_VOLDOWN    BUTTON_SCROLL_DOWN
 #define MPEG_VOLUP      BUTTON_SCROLL_UP
+#define MPEG_RW         BUTTON_REW
+#define MPEG_FF         BUTTON_FF
 
 #elif CONFIG_KEYPAD == SANSA_E200_PAD
 #define MPEG_MENU       BUTTON_SELECT
@@ -155,6 +165,8 @@
 #define MPEG_PAUSE      BUTTON_UP
 #define MPEG_VOLDOWN    BUTTON_SCROLL_UP
 #define MPEG_VOLUP      BUTTON_SCROLL_DOWN
+#define MPEG_RW         BUTTON_LEFT
+#define MPEG_FF         BUTTON_RIGHT
 
 #elif CONFIG_KEYPAD == SANSA_C200_PAD
 #define MPEG_MENU       BUTTON_SELECT
@@ -162,6 +174,8 @@
 #define MPEG_PAUSE      BUTTON_UP
 #define MPEG_VOLDOWN    BUTTON_VOL_DOWN
 #define MPEG_VOLUP      BUTTON_VOL_UP
+#define MPEG_RW         BUTTON_LEFT
+#define MPEG_FF         BUTTON_RIGHT
 
 #elif CONFIG_KEYPAD == MROBE500_PAD
 #define MPEG_MENU       BUTTON_RC_HEART
@@ -169,6 +183,8 @@
 #define MPEG_PAUSE      BUTTON_TOUCHPAD
 #define MPEG_VOLDOWN    BUTTON_RC_VOL_DOWN
 #define MPEG_VOLUP      BUTTON_RC_VOL_UP
+#define MPEG_RW         BUTTON_RC_REW
+#define MPEG_FF         BUTTON_RC_FF
 
 #else
 #error MPEGPLAYER: Unsupported keypad
@@ -179,31 +195,997 @@
 CACHE_FUNCTION_WRAPPERS(rb);
 ALIGN_BUFFER_WRAPPER(rb);
 
-static bool button_loop(void)
-{
-    bool ret = true;
+/* One thing we can do here for targets with remotes is having a display
+ * always on the remote instead of always forcing a popup on the main display */
 
+#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ 
+                                /* 3% of 30min file == 54s step size */
+#define MIN_FF_REWIND_STEP (TS_SECOND/2)
+#define WVS_MIN_UPDATE_INTERVAL (HZ/2)
+
+/* WVS status - same order as icon array */
+enum wvs_status_enum
+{
+    WVS_STATUS_STOPPED = 0,
+    WVS_STATUS_PAUSED,
+    WVS_STATUS_PLAYING,
+    WVS_STATUS_FF,
+    WVS_STATUS_RW,
+    WVS_STATUS_COUNT,
+    WVS_STATUS_MASK = 0x7
+};
+
+enum wvs_bits
+{
+    WVS_REFRESH_DEFAULT    = 0x0000,
+    WVS_REFRESH_VOLUME     = 0x0001,
+    WVS_REFRESH_TIME       = 0x0002,
+    WVS_REFRESH_STATUS     = 0x0004,
+    WVS_REFRESH_BACKGROUND = 0x0008,
+    WVS_REFRESH_VIDEO      = 0x0010,
+    WVS_REFRESH_RESUME     = 0x0020,
+    WVS_NODRAW             = 0x8000,
+    WVS_SHOW               = 0x4000,
+    WVS_HIDE               = 0x0000,
+    WVS_REFRESH_ALL        = 0x001f,
+};
+
+extern const unsigned char mpegplayer_status_icons_8x8x1[];
+extern const unsigned char mpegplayer_status_icons_12x12x1[];
+extern const unsigned char mpegplayer_status_icons_16x16x1[];
+
+#define WVS_BDR_L 2
+#define WVS_BDR_T 2
+#define WVS_BDR_R 2
+#define WVS_BDR_B 2
+
+struct wvs
+{
+    long hide_tick;
+    long show_for;
+    long print_tick;
+    long print_delay;
+    long resume_tick;
+    long resume_delay;
+    long next_auto_refresh;
+    int x;
+    int y;
+    int width;
+    int height;
+    unsigned fgcolor;
+    unsigned bgcolor;
+#ifdef HAVE_LCD_COLOR
+    unsigned prog_fillcolor;
+    struct vo_rect update_rect;
+#endif
+    struct vo_rect prog_rect;
+    struct vo_rect time_rect;
+    struct vo_rect dur_rect;
+    struct vo_rect vol_rect;
+    const unsigned char *icons;
+    struct vo_rect stat_rect;
+    int status;
+    uint32_t curr_time;
+    unsigned auto_refresh;
+    unsigned flags;
+};
+
+static struct wvs wvs;
+
+static void wvs_show(unsigned show);
+
+#ifdef LCD_LANDSCAPE
+    #define _X (x + wvs.x)
+    #define _Y (y + wvs.y)
+    #define _W width
+    #define _H height
+#else
+    #define _X (LCD_WIDTH - (y + wvs.y) - height)
+    #define _Y (x + wvs.x)
+    #define _W height
+    #define _H width
+#endif
+
+#ifdef HAVE_LCD_COLOR
+static unsigned draw_blendcolor(unsigned c1, unsigned c2, unsigned char amount)
+{
+    int r1 = RGB_UNPACK_RED(c1);
+    int g1 = RGB_UNPACK_GREEN(c1);
+    int b1 = RGB_UNPACK_BLUE(c1);
+
+    int r2 = RGB_UNPACK_RED(c2);
+    int g2 = RGB_UNPACK_GREEN(c2);
+    int b2 = RGB_UNPACK_BLUE(c2);
+
+    return LCD_RGBPACK(amount*(r2 - r1) / 255 + r1,
+                       amount*(g2 - g1) / 255 + g1,
+                       amount*(b2 - b1) / 255 + b1);
+}
+#endif
+
+static void draw_fill_rect(int x, int y, int width, int height)
+{
+    rb->lcd_fillrect(_X, _Y, _W, _H);
+}
+
+#ifdef HAVE_LCD_COLOR
+static void draw_update_rect(int x, int y, int width, int height)
+{
+    rb->lcd_update_rect(_X, _Y, _W, _H);
+}
+#endif
+
+static void draw_clear_area(int x, int y, int width, int height)
+{
+    rb->screen_clear_area(rb->screens[SCREEN_MAIN], _X, _Y, _W, _H);
+}
+
+static void draw_clear_area_rect(const struct vo_rect *rc)
+{
+    int x = rc->l;
+    int y = rc->t;
+    int width = rc->r - rc->l;
+    int height = rc->b - rc->t;
+    rb->screen_clear_area(rb->screens[SCREEN_MAIN], _X, _Y, _W, _H);
+}
+
+static void draw_scrollbar_draw(int x, int y, int width, int height,
+                                int items, int min_shown, int max_shown)
+{
+#ifdef HAVE_LCD_COLOR
+    int oldbg = rb->lcd_get_background();
+    rb->lcd_set_background(wvs.prog_fillcolor);
+#endif
+
+    rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], _X, _Y,
+                           _W, _H, items, min_shown, max_shown,
+                           0
+#ifdef LCD_LANDSCAPE
+                           | HORIZONTAL
+#endif
+#ifdef HAVE_LCD_COLOR
+                           | INNER_BGFILL | FOREGROUND
+#endif
+                           );
+
+#ifdef HAVE_LCD_COLOR
+    rb->lcd_set_background(oldbg);
+#endif
+}
+
+static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int items,
+                                     int min_shown, int max_shown)
+{
+    draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t,
+                        items, min_shown, max_shown);
+}
+
+static void draw_hline(int x1, int x2, int y)
+{
+#ifdef LCD_LANDSCAPE
+    rb->lcd_hline(x1 + wvs.x, x2 + wvs.x, y + wvs.y);
+#else
+    y = LCD_WIDTH - (y + wvs.y) - 1;
+    rb->lcd_vline(y, x1 + wvs.x, x2 + wvs.x);
+#endif
+}
+
+#ifdef LCD_PORTRAIT
+/* Portrait displays need rotated text rendering */
+
+/* Limited function that only renders in DRMODE_FG */
+static void draw_oriented_mono_bitmap_part(const unsigned char *src,
+                                           int src_x, int src_y,
+                                           int stride, int x, int y,
+                                           int width, int height)
+{
+    const unsigned char *src_end;
+    fb_data *dst, *dst_end;
+    unsigned fg_pattern, bg_pattern;
+
+    if (x + width > SCREEN_WIDTH)
+        width = SCREEN_WIDTH - x; /* Clip right */
+    if (x < 0)
+        width += x, x = 0; /* Clip left */
+    if (width <= 0)
+        return; /* nothing left to do */
+
+    if (y + height > SCREEN_HEIGHT)
+        height = SCREEN_HEIGHT - y; /* Clip bottom */
+    if (y < 0)
+        height += y, y = 0; /* Clip top */
+    if (height <= 0)
+        return; /* nothing left to do */
+
+    fg_pattern = rb->lcd_get_foreground();
+    bg_pattern = rb->lcd_get_background();
+
+    src += stride * (src_y >> 3) + src_x; /* move starting point */
+    src_y  &= 7;
+    src_end = src + width;
+
+    dst = rb->lcd_framebuffer + (LCD_WIDTH - y) + x*LCD_WIDTH;
+    do 
+    {
+        const unsigned char *src_col = src++;
+        unsigned data = *src_col >> src_y;
+        int numbits = 8 - src_y;
+
+        fb_data *dst_col = dst;
+        dst_end = dst_col - height;
+        dst += LCD_WIDTH;
+
+        do 
+        {
+            dst_col--;
+
+            if (data & 1)
+                *dst_col = fg_pattern;
+#if 0
+            else
+                *dst_col = bg_pattern;
+#endif
+            data >>= 1;
+            if (--numbits == 0) {
+                src_col += stride;
+                data = *src_col;
+                numbits = 8;
+            }
+        }
+        while (dst_col > dst_end);
+    }
+    while (src < src_end);
+}
+
+static void draw_putsxy_oriented(int x, int y, const char *str)
+{
+    unsigned short ch;
+    unsigned short *ucs;
+    int ofs = MIN(x, 0);
+    struct font* pf = rb->font_get(FONT_UI);
+
+    ucs = rb->bidi_l2v(str, 1);
+
+    x += wvs.x;
+    y += wvs.y;
+
+    while ((ch = *ucs++) != 0 && x < SCREEN_WIDTH)
+    {
+        int width;
+        const unsigned char *bits;
+
+        /* get proportional width and glyph bits */
+        width = rb->font_get_width(pf, ch);
+
+        if (ofs > width) {
+            ofs -= width;
+            continue;
+        }
+
+        bits = rb->font_get_bits(pf, ch);
+
+        draw_oriented_mono_bitmap_part(bits, ofs, 0, width, x, y,
+                                       width - ofs, pf->height);
+
+        x += width - ofs;
+        ofs = 0;
+    }
+}
+#else
+static void draw_oriented_mono_bitmap_part(const unsigned char *src,
+                                           int src_x, int src_y,
+                                           int stride, int x, int y,
+                                           int width, int height)
+{
+    int mode = rb->lcd_get_drawmode();
+    rb->lcd_set_drawmode(DRMODE_FG);
+    rb->lcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height);
+    rb->lcd_set_drawmode(mode);
+}
+
+static void draw_putsxy_oriented(int x, int y, const char *str)
+{
+    int mode = rb->lcd_get_drawmode();
+    rb->lcd_set_drawmode(DRMODE_FG);
+    rb->lcd_putsxy(x + wvs.x, y + wvs.y, str);
+    rb->lcd_set_drawmode(mode);
+}
+#endif /* LCD_PORTRAIT */
+
+
+static void wvs_text_init(void)
+{
+    struct hms hms;
+    char buf[32];
+    int phys;
+    int spc_width;
+
+    rb->lcd_setfont(FONT_UI);
+
+    wvs.x = 0;
+    wvs.width = SCREEN_WIDTH;
+
+    vo_rect_clear(&wvs.time_rect);
+    vo_rect_clear(&wvs.stat_rect);
+    vo_rect_clear(&wvs.prog_rect);
+    vo_rect_clear(&wvs.vol_rect);
+
+    ts_to_hms(stream_get_duration(), &hms);
+    hms_format(buf, sizeof (buf), &hms);
+    rb->lcd_getstringsize(buf, &wvs.time_rect.r,
+                               &wvs.time_rect.b);
+
+    /* Choose well-sized bitmap images relative to font height */
+    if (wvs.time_rect.b < 12) {
+        wvs.icons = mpegplayer_status_icons_8x8x1;
+        wvs.stat_rect.r = wvs.stat_rect.b = 8;
+    } else if (wvs.time_rect.b < 16) {
+        wvs.icons = mpegplayer_status_icons_12x12x1;
+        wvs.stat_rect.r = wvs.stat_rect.b = 12;
+    } else {
+        wvs.icons = mpegplayer_status_icons_16x16x1;
+        wvs.stat_rect.r = wvs.stat_rect.b = 16;
+    }
+
+    if (wvs.stat_rect.b < wvs.time_rect.b) {
+        vo_rect_offset(&wvs.stat_rect, 0,
+                       (wvs.time_rect.b - wvs.stat_rect.b) / 2 + WVS_BDR_T);
+        vo_rect_offset(&wvs.time_rect, WVS_BDR_L, WVS_BDR_T);
+    } else {
+        vo_rect_offset(&wvs.time_rect, WVS_BDR_L,
+                       wvs.stat_rect.b - wvs.time_rect.b + WVS_BDR_T);
+        vo_rect_offset(&wvs.stat_rect, 0, WVS_BDR_T);
+    }
+
+    wvs.dur_rect = wvs.time_rect;
+
+    phys = rb->sound_val2phys(SOUND_VOLUME, rb->sound_min(SOUND_VOLUME));
+    rb->snprintf(buf, sizeof(buf), "%d%s", phys,
+                 rb->sound_unit(SOUND_VOLUME));
+
+    rb->lcd_getstringsize(" ", &spc_width, NULL);
+    rb->lcd_getstringsize(buf, &wvs.vol_rect.r, &wvs.vol_rect.b);
+
+    wvs.prog_rect.r = SCREEN_WIDTH - WVS_BDR_L - spc_width -
+                           wvs.vol_rect.r - WVS_BDR_R;
+    wvs.prog_rect.b = 3*wvs.stat_rect.b / 4;
+    vo_rect_offset(&wvs.prog_rect, wvs.time_rect.l,
+                   wvs.time_rect.b);
+
+    vo_rect_offset(&wvs.stat_rect,
+                   (wvs.prog_rect.r + wvs.prog_rect.l - wvs.stat_rect.r) / 2,
+                   0);
+
+    vo_rect_offset(&wvs.dur_rect,
+                   wvs.prog_rect.r - wvs.dur_rect.r, 0);
+
+    vo_rect_offset(&wvs.vol_rect, wvs.prog_rect.r + spc_width,
+                   (wvs.prog_rect.b + wvs.prog_rect.t - wvs.vol_rect.b) / 2);
+
+    wvs.height = WVS_BDR_T + MAX(wvs.prog_rect.b, wvs.vol_rect.b) -
+                    MIN(wvs.time_rect.t, wvs.stat_rect.t) + WVS_BDR_B;
+
+#if LCD_PIXELFORMAT == VERTICAL_PACKING
+    wvs.height = ALIGN_UP(wvs.height, 8);
+#else
+    wvs.height = ALIGN_UP(wvs.height, 2);
+#endif
+    wvs.y = SCREEN_HEIGHT - wvs.height;
+
+    rb->lcd_setfont(FONT_SYSFIXED);
+}
+
+static void wvs_init(void)
+{
+    wvs.flags = 0;
+    wvs.show_for = HZ*4;
+    wvs.print_delay = 75*HZ/100;
+    wvs.resume_delay = HZ/2;
+#ifdef HAVE_LCD_COLOR
+    wvs.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd);
+    wvs.fgcolor = LCD_WHITE;
+    wvs.prog_fillcolor = LCD_BLACK;
+#else
+    wvs.bgcolor = LCD_LIGHTGRAY;
+    wvs.fgcolor = LCD_BLACK;
+#endif
+    wvs.curr_time = 0;
+    wvs.status = WVS_STATUS_STOPPED;
+    wvs.auto_refresh = WVS_REFRESH_TIME;
+    wvs.next_auto_refresh = *rb->current_tick;
+    wvs_text_init();
+}
+
+static void wvs_schedule_refresh(unsigned refresh)
+{
+    long tick = *rb->current_tick;
+
+    if (refresh & WVS_REFRESH_VIDEO)
+        wvs.print_tick = tick + wvs.print_delay;
+
+    if (refresh & WVS_REFRESH_RESUME)
+        wvs.resume_tick = tick + wvs.resume_delay;
+
+    wvs.auto_refresh |= refresh;
+}
+
+static void wvs_cancel_refresh(unsigned refresh)
+{
+    wvs.auto_refresh &= ~refresh;
+}
+
+static void wvs_refresh_background(void)
+{
+    char buf[32];
+    struct hms hms;
+
+    int bg = rb->lcd_get_background();
+    rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
+
+#ifdef HAVE_LCD_COLOR
+    rb->lcd_set_background(draw_blendcolor(bg, LCD_WHITE, 192));
+    draw_hline(0, wvs.width, 0);
+
+    rb->lcd_set_background(draw_blendcolor(bg, LCD_WHITE, 80));
+    draw_hline(0, wvs.width, 1);
+
+    rb->lcd_set_background(draw_blendcolor(bg, LCD_BLACK, 48));
+    draw_hline(0, wvs.width, wvs.height-2);
+
+    rb->lcd_set_background(draw_blendcolor(bg, LCD_BLACK, 128));
+    draw_hline(0, wvs.width, wvs.height-1);
+
+    rb->lcd_set_background(bg);
+    draw_clear_area(0, 2, wvs.width, wvs.height - 4);
+
+    vo_rect_set_ext(&wvs.update_rect, 0, 0, wvs.width, wvs.height);
+#else
+    rb->lcd_set_background(LCD_DARKGRAY);
+    draw_hline(0, wvs.width, 0);
+
+    rb->lcd_set_background(bg);
+    draw_clear_area(0, 1, wvs.width, wvs.height - 1);
+#endif
+
+    rb->lcd_set_drawmode(DRMODE_SOLID);
+
+    if (stream_get_duration() != INVALID_TIMESTAMP) {
+        /* Don't know the duration */
+        ts_to_hms(stream_get_duration(), &hms);
+        hms_format(buf, sizeof (buf), &hms);
+        draw_putsxy_oriented(wvs.dur_rect.l, wvs.dur_rect.t, buf);
+    }
+}
+
+static void wvs_refresh_time(void)
+{
+    char buf[32];
+    struct hms hms;
+
+    uint32_t duration = stream_get_duration();
+
+    draw_scrollbar_draw_rect(&wvs.prog_rect, duration, 0,
+                             wvs.curr_time);
+
+    ts_to_hms(wvs.curr_time, &hms);
+    hms_format(buf, sizeof (buf), &hms);
+
+    draw_clear_area_rect(&wvs.time_rect);
+    draw_putsxy_oriented(wvs.time_rect.l, wvs.time_rect.t, buf);    
+
+#ifdef HAVE_LCD_COLOR
+    vo_rect_union(&wvs.update_rect, &wvs.update_rect,
+                  &wvs.prog_rect);
+    vo_rect_union(&wvs.update_rect, &wvs.update_rect,
+                  &wvs.time_rect);
+#endif
+}
+
+static void wvs_refresh_volume(void)
+{
+    char buf[32];
+    int width;
+
+    int volume = rb->global_settings->volume;
+    rb->snprintf(buf, sizeof (buf), "%d%s",
+                 rb->sound_val2phys(SOUND_VOLUME, volume),
+                 rb->sound_unit(SOUND_VOLUME));
+    rb->lcd_getstringsize(buf, &width, NULL);
+
+    /* Right-justified */
+    draw_clear_area_rect(&wvs.vol_rect);
+    draw_putsxy_oriented(wvs.vol_rect.r - width, wvs.vol_rect.t, buf);
+
+#ifdef HAVE_LCD_COLOR
+    vo_rect_union(&wvs.update_rect, &wvs.update_rect, &wvs.vol_rect);
+#endif
+}
+
+static void wvs_refresh_status(void)
+{
+    int icon_size = wvs.stat_rect.r - wvs.stat_rect.l;
+
+    draw_clear_area_rect(&wvs.stat_rect);
+
+#ifdef HAVE_LCD_COLOR
+    /* Draw status icon with a drop shadow */
+    unsigned oldfg = rb->lcd_get_foreground();
+    int i = 1;
+
+    rb->lcd_set_foreground(draw_blendcolor(rb->lcd_get_background(),
+                           LCD_BLACK, 96));
+
+    while (1)
+    {
+        draw_oriented_mono_bitmap_part(wvs.icons,
+                                       icon_size*wvs.status,
+                                       0,
+                                       icon_size*WVS_STATUS_COUNT,
+                                       wvs.stat_rect.l + wvs.x + i,
+                                       wvs.stat_rect.t + wvs.y + i,
+                                       icon_size, icon_size);
+
+        if (--i < 0)
+            break;
+
+        rb->lcd_set_foreground(oldfg);
+    }
+
+    vo_rect_union(&wvs.update_rect, &wvs.update_rect, &wvs.stat_rect);
+#else
+    draw_oriented_mono_bitmap_part(wvs.icons,
+                                   icon_size*wvs.status,
+                                   0,
+                                   icon_size*WVS_STATUS_COUNT,
+                                   wvs.stat_rect.l + wvs.x,
+                                   wvs.stat_rect.t + wvs.y,
+                                   icon_size, icon_size);
+#endif
+}
+
+static bool wvs_update_status(void)
+{
+    int status;
+
+    switch (stream_status())
+    {
+    default:
+        status = WVS_STATUS_STOPPED;
+        break;
+    case STREAM_PAUSED:
+        status = (wvs.auto_refresh & WVS_REFRESH_RESUME) ?
+            WVS_STATUS_PLAYING : WVS_STATUS_PAUSED;
+        break;
+    case STREAM_PLAYING:
+        status = WVS_STATUS_PLAYING;
+        break;
+    }
+
+    if (status != wvs.status) {
+        wvs.status = status;
+        return true;
+    }
+
+    return false;
+}
+
+static void wvs_update_time(void)
+{
+    uint32_t start;
+    wvs.curr_time = stream_get_seek_time(&start);
+    wvs.curr_time -= start;
+}
+
+static void wvs_refresh(int hint)
+{
+    long tick;
+    unsigned oldbg, oldfg;
+
+    tick = *rb->current_tick;
+
+    if (hint == WVS_REFRESH_DEFAULT) {
+
+        if ((wvs.auto_refresh & WVS_REFRESH_VIDEO) &&
+            TIME_AFTER(tick, wvs.print_tick)) {
+            wvs.auto_refresh &= ~WVS_REFRESH_VIDEO;
+            stream_draw_frame(false);
+        }
+
+        if ((wvs.auto_refresh & WVS_REFRESH_RESUME) &&
+            TIME_AFTER(tick, wvs.resume_tick)) {
+            wvs.auto_refresh &= ~(WVS_REFRESH_RESUME | WVS_REFRESH_VIDEO);
+            stream_resume();
+        }
+
+        if (!(wvs.flags & WVS_SHOW))
+            return;
+
+        if (TIME_AFTER(tick, wvs.hide_tick)) {
+            wvs_show(WVS_HIDE);
+            return;
+        }
+    } else {
+        if (!(wvs.flags & WVS_SHOW)) {
+            wvs_show(WVS_SHOW | WVS_NODRAW);
+            hint = WVS_REFRESH_ALL;
+        }
+
+        wvs.print_tick = tick + wvs.print_delay;
+        wvs.hide_tick = tick + wvs.show_for;
+    }
+
+    if (TIME_AFTER(tick, wvs.next_auto_refresh)) {
+        wvs.next_auto_refresh = tick + WVS_MIN_UPDATE_INTERVAL;
+
+        if (wvs.auto_refresh & WVS_REFRESH_STATUS) {
+            if (wvs_update_status())
+                hint |= WVS_REFRESH_STATUS;
+        }
+
+        if (wvs.auto_refresh & WVS_REFRESH_TIME) {
+            wvs_update_time();
+            hint |= WVS_REFRESH_TIME;
+        }
+    }
+
+    if (hint == 0)
+        return;
+
+    oldfg = rb->lcd_get_foreground();
+    oldbg = rb->lcd_get_background();
+
+    rb->lcd_setfont(FONT_UI);
+    rb->lcd_set_foreground(wvs.fgcolor);
+    rb->lcd_set_background(wvs.bgcolor);
+
+#ifdef HAVE_LCD_COLOR
+    vo_rect_clear(&wvs.update_rect);
+#endif
+
+    if (hint & WVS_REFRESH_BACKGROUND) {
+        wvs_refresh_background();
+        hint |= WVS_REFRESH_ALL; /* Requires a redraw of everything */
+    }
+
+    if (hint & WVS_REFRESH_TIME) {
+        wvs_refresh_time();
+    }
+
+    if (hint & WVS_REFRESH_VOLUME) {
+        wvs_refresh_volume();
+    }
+
+    if (hint & WVS_REFRESH_STATUS) {
+        wvs_refresh_status();
+    }
+
+    rb->lcd_setfont(FONT_SYSFIXED);
+    rb->lcd_set_foreground(oldfg);
+    rb->lcd_set_background(oldbg);
+
+#ifdef HAVE_LCD_COLOR
+    vo_lock();
+
+    draw_update_rect(wvs.update_rect.l,
+                     wvs.update_rect.t,
+                     wvs.update_rect.r - wvs.update_rect.l,
+                     wvs.update_rect.b - wvs.update_rect.t);
+
+    vo_unlock();
+#else
+    gray_deferred_lcd_update();
+#endif
+}
+
+static void wvs_show(unsigned show)
+{
+    if (((show ^ wvs.flags) & WVS_SHOW) == 0)
+        return;
+
+    if (show & WVS_SHOW) {
+        struct vo_rect rc = { 0, 0, SCREEN_WIDTH, wvs.y };
+
+        wvs.flags |= WVS_SHOW;
+
+        stream_vo_set_clip(&rc);
+
+        if (!(show & WVS_NODRAW))
+            wvs_refresh(WVS_REFRESH_ALL);
+    } else {
+        wvs.flags &= ~WVS_SHOW;
+
+        stream_vo_set_clip(NULL);
+
+        draw_clear_area(0, 0, wvs.width, wvs.height);
+
+        if (!(show & WVS_NODRAW)) {
+#ifdef HAVE_LCD_COLOR
+            vo_lock();
+            draw_update_rect(0, 0, wvs.width, wvs.height);
+            vo_unlock();
+#endif
+            stream_draw_frame(false);
+        }
+    }
+}
+
+static void wvs_set_status(int status)
+{
+    bool draw = (status & WVS_NODRAW) == 0;
+
+    status &= WVS_STATUS_MASK;
+
+    if (wvs.status != status) {
+
+        wvs.status = status;
+
+        if (draw)
+            wvs_refresh(WVS_REFRESH_STATUS);
+    }
+}
+
+/* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;) */
+static uint32_t wvs_ff_rw(int btn, unsigned refresh)
+{
+    unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step;
+    const long ff_rw_accel = rb->global_settings->ff_rewind_accel;
+    long accel_tick = *rb->current_tick + ff_rw_accel*HZ;
+    uint32_t start;
+    uint32_t time = stream_get_seek_time(&start);
+    const uint32_t duration = stream_get_duration();
+    unsigned int max_step = 0;
+    uint32_t ff_rw_count = 0;
+    unsigned status = wvs.status;
+
+    wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME |
+                       WVS_REFRESH_TIME);
+
+    time -= start; /* Absolute clock => stream-relative */
+
+    switch (btn)
+    {
+    case MPEG_FF:
+        wvs_set_status(WVS_STATUS_FF);
+        break;
+    case MPEG_RW:
+        wvs_set_status(WVS_STATUS_RW);
+        break;
+    default:
+        btn = -1;
+    }
+
+    btn |= BUTTON_REPEAT;
+
+    while (1)
+    {
+        long tick = *rb->current_tick;
+        stream_keep_disk_active();
+
+        switch (btn)
+        {
+        case BUTTON_NONE:
+            wvs_refresh(WVS_REFRESH_DEFAULT);
+            break;
+
+        case MPEG_FF | BUTTON_REPEAT:
+        case MPEG_RW | BUTTON_REPEAT:
+            break;
+
+        case MPEG_FF | BUTTON_REL:
+        case MPEG_RW | BUTTON_REL:
+            if (wvs.status == WVS_STATUS_FF)
+                time += ff_rw_count;
+            else if (wvs.status == WVS_STATUS_RW)
+                time -= ff_rw_count;
+
+            /* Fall-through */
+        case -1:
+        default:
+            wvs_schedule_refresh(refresh);
+            wvs_set_status(status);
+            wvs_schedule_refresh(WVS_REFRESH_TIME);
+            return time;
+        }
+
+        if (wvs.status == WVS_STATUS_FF) {
+            /* fast forwarding, calc max step relative to end */
+            max_step = muldiv_uint32(duration - (time + ff_rw_count),
+                                     FF_REWIND_MAX_PERCENT, 100);
+        } else {
+            /* rewinding, calc max step relative to start */
+            max_step = muldiv_uint32(time - ff_rw_count,
+                                     FF_REWIND_MAX_PERCENT, 100);
+        }
+
+        max_step = MAX(max_step, MIN_FF_REWIND_STEP);
+
+        if (step > max_step)
+            step = max_step;
+
+        ff_rw_count += step;
+
+        if (ff_rw_accel != 0 && TIME_AFTER(tick, accel_tick)) { 
+            step *= 2;
+            accel_tick = tick + ff_rw_accel*HZ; 
+        }
+
+        if (wvs.status == WVS_STATUS_FF) {
+            if (duration - time <= ff_rw_count)
+                ff_rw_count = duration - time;
+
+            wvs.curr_time = time + ff_rw_count;
+        } else {
+            if (time <= ff_rw_count)
+                ff_rw_count = time;
+
+            wvs.curr_time = time - ff_rw_count;
+        }
+
+        wvs_refresh(WVS_REFRESH_TIME);
+
+        btn = rb->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL);
+    }
+}
+
+static int wvs_status(void)
+{
+    int status = stream_status();
+
+    /* Coerce to STREAM_PLAYING if paused with a pending resume */
+    if (status == STREAM_PAUSED) {
+        if (wvs.auto_refresh & WVS_REFRESH_RESUME)
+            status = STREAM_PLAYING;
+    }
+
+    return status;
+}
+
+static void wvs_set_volume(int delta)
+{
+    int vol = rb->global_settings->volume;
+    int limit;
+
+    vol += delta;
+
+    if (delta < 0) {
+        limit = rb->sound_min(SOUND_VOLUME);
+        if (vol < limit)
+            vol = limit;
+    } else {
+        limit = rb->sound_max(SOUND_VOLUME);
+        if (vol > limit)
+            vol = limit;
+    }
+
+    if (vol != rb->global_settings->volume) {
+        rb->sound_set(SOUND_VOLUME, vol);
+        rb->global_settings->volume = vol;
+    }
+
+    wvs_refresh(WVS_REFRESH_VOLUME);
+}
+
+static int wvs_play(uint32_t time)
+{
+    int retval;
+
+    wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
+
+    retval = stream_seek(time, SEEK_SET);
+
+    if (retval >= STREAM_OK) {
+        stream_show_vo(true);
+        retval = stream_play();
+
+        if (retval >= STREAM_OK)
+            wvs_set_status(WVS_STATUS_PLAYING | WVS_NODRAW);
+    }
+
+    return retval;
+}
+
+static int wvs_halt(void)
+{
+    int status = stream_pause();
+
+    /* Coerce to STREAM_PLAYING if paused with a pending resume */
+    if (status == STREAM_PAUSED) {
+        if (wvs.auto_refresh & WVS_REFRESH_RESUME)
+            status = STREAM_PLAYING;
+    }
+
+    wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
+
+    return status;
+}
+
+static int wvs_pause(void)
+{
+    unsigned refresh = wvs.auto_refresh;
+    int status = wvs_halt();
+
+    if (status == STREAM_PLAYING && (refresh & WVS_REFRESH_RESUME)) {
+        wvs_cancel_refresh(WVS_REFRESH_RESUME);
+        wvs_schedule_refresh(WVS_REFRESH_VIDEO);
+    }
+
+    wvs_set_status(WVS_STATUS_PAUSED);
+
+    return status;
+}
+
+static void wvs_resume(void)
+{
+    wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
+    wvs_set_status(WVS_STATUS_PLAYING);
+    stream_resume();
+}
+
+static void wvs_stop(void)
+{
+    wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
+    wvs_show(WVS_HIDE | WVS_NODRAW);
+
+    if (stream_stop() != STREAM_STOPPED)
+        settings.resume_time = stream_get_resume_time();
+}
+
+static void wvs_seek(int btn)
+{
+    int status;
+    unsigned refresh;
+    uint32_t time;
+
+    if (!stream_can_seek())
+        return;
+
+    status = wvs_halt();
+
+    if (status == STREAM_STOPPED)
+        return;
+
+    wvs_show(WVS_SHOW);
+
+    if (status == STREAM_PLAYING)
+        refresh = WVS_REFRESH_RESUME; /* delay resume if playing */
+    else
+        refresh = WVS_REFRESH_VIDEO;  /* refresh if paused */
+
+    time = wvs_ff_rw(btn, refresh);
+
+    stream_seek(time, SEEK_SET);
+}
+
+static void button_loop(void)
+{
     rb->lcd_setfont(FONT_SYSFIXED);
     rb->lcd_clear_display();
     rb->lcd_update();
 
+    /* Turn off backlight timeout */
+    /* backlight control in lib/helper.c */
+    backlight_force_on(rb);
+
+    wvs_init();
+
     /* Start playback at the specified starting time */
-    if (stream_seek(settings.resume_time, SEEK_SET) < STREAM_OK ||
-        (stream_show_vo(true), stream_play()) < STREAM_OK)
-    {
+    if (wvs_play(settings.resume_time) < STREAM_OK) {
         rb->splash(HZ*2, "Playback failed");
-        return false;
+        return;
     }
 
     /* Gently poll the video player for EOS and handle UI */
     while (stream_status() != STREAM_STOPPED)
     {
-        int button = rb->button_get_w_tmo(HZ/2);
+        int button = rb->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL);
 
         switch (button)
         {
         case BUTTON_NONE:
+        {
+            wvs_refresh(WVS_REFRESH_DEFAULT);
             continue;
+            } /* BUTTON_NONE: */
 
         case MPEG_VOLUP:
         case MPEG_VOLUP|BUTTON_REPEAT:
@@ -212,14 +1194,7 @@
         case MPEG_VOLUP2|BUTTON_REPEAT:
 #endif
         {
-            int vol = rb->global_settings->volume;
-            int maxvol = rb->sound_max(SOUND_VOLUME);
-
-            if (vol < maxvol) {
-                vol++;
-                rb->sound_set(SOUND_VOLUME, vol);
-                rb->global_settings->volume = vol;
-            }
+            wvs_set_volume(+1);
             break;
             } /* MPEG_VOLUP*: */
 
@@ -230,23 +1205,17 @@
         case MPEG_VOLDOWN2|BUTTON_REPEAT:
 #endif
         {
-            int vol = rb->global_settings->volume;
-            int minvol = rb->sound_min(SOUND_VOLUME);
-
-            if (vol > minvol) {
-                vol--;
-                rb->sound_set(SOUND_VOLUME, vol);
-                rb->global_settings->volume = vol;
-            }
+            wvs_set_volume(-1);
             break;
             } /* MPEG_VOLDOWN*: */
 
         case MPEG_MENU:
         {
-            int state = stream_pause(); /* save previous state */
+            int state = wvs_halt(); /* save previous state */
             int result;
 
             /* Hide video output */
+            wvs_show(WVS_HIDE | WVS_NODRAW);
             stream_show_vo(false);
             backlight_use_settings(rb);
 
@@ -258,17 +1227,19 @@
             switch (result)
             {
             case MPEG_MENU_QUIT:
-                stream_stop();
+                wvs_stop();
                 break;
             default:
                 /* If not stopped, show video again */
-                if (state != STREAM_STOPPED)
+                if (state != STREAM_STOPPED) {
+                    wvs_show(WVS_SHOW);
                     stream_show_vo(true);
+                }
 
                 /* If stream was playing, restart it */
                 if (state == STREAM_PLAYING) {
                     backlight_force_on(rb);
-                    stream_resume();
+                    wvs_resume();
                 }
                 break;
             }
@@ -277,7 +1248,7 @@
 
         case MPEG_STOP:
         {
-            stream_stop();
+            wvs_stop();
             break;
             } /* MPEG_STOP: */
 
@@ -286,27 +1257,34 @@
         case MPEG_PAUSE2:
 #endif
         {
-            if (stream_status() == STREAM_PLAYING) {
+            int status = wvs_status();
+
+            if (status == STREAM_PLAYING) {
                 /* Playing => Paused */
-                stream_pause();
+                wvs_pause();
                 backlight_use_settings(rb);
             }
-            else if (stream_status() == STREAM_PAUSED) {
+            else if (status == STREAM_PAUSED) {
                 /* Paused => Playing */
                 backlight_force_on(rb);
-                stream_resume();
+                wvs_resume();
             }
 
             break;
             } /* MPEG_PAUSE*: */
 
+        case MPEG_RW:
+        case MPEG_FF:
+        {
+            wvs_seek(button);
+            break;
+            } /* MPEG_RW: MPEG_FF: */
+
         case SYS_POWEROFF:
         case SYS_USB_CONNECTED:
             /* Stop and get the resume time before closing the file early */
-            stream_stop();
-            settings.resume_time = stream_get_resume_time();
+            wvs_stop();
             stream_close();
-            ret = false;
         /* Fall-through */
         default:
             rb->default_event_handler(button);
@@ -316,9 +1294,12 @@
         rb->yield();
     } /* end while */
 
+    wvs_stop();
+
     rb->lcd_setfont(FONT_UI);
 
-    return ret;
+    /* Turn on backlight timeout (revert to settings) */
+    backlight_use_settings(rb);
 }
 
 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
@@ -367,17 +1348,8 @@
             result = mpeg_start_menu(stream_get_duration());
 
             if (result != MPEG_START_QUIT) {
-                /* Turn off backlight timeout */
-                /* backlight control in lib/helper.c */
-                backlight_force_on(rb);
-
                 /* Enter button loop and process UI */
-                if (button_loop()) {
-                     settings.resume_time = stream_get_resume_time();
-                }
-
-                /* Turn on backlight timeout (revert to settings) */
-                backlight_use_settings(rb);
+                button_loop();
             }
 
             stream_close();
diff --git a/apps/plugins/mpegplayer/stream_mgr.c b/apps/plugins/mpegplayer/stream_mgr.c
index 06f269c..1baa203 100644
--- a/apps/plugins/mpegplayer/stream_mgr.c
+++ b/apps/plugins/mpegplayer/stream_mgr.c
@@ -251,6 +251,7 @@
 static uint32_t time_from_whence(uint32_t time, int whence)
 {
     int64_t currtime;
+    uint32_t start;
 
     switch (whence)
     {
@@ -262,12 +263,8 @@
     case SEEK_CUR:
         /* Seek forward or backward from the current time
          * (time = signed offset from current) */
-        if (stream_mgr.seeked)
-            currtime = str_parser.last_seek_time;
-        else
-            currtime = TICKS_TO_TS(pcm_output_get_clock());
-
-        currtime -= str_parser.start_pts;
+        currtime = stream_get_seek_time(&start);
+        currtime -= start;
         currtime += (int32_t)time;
 
         if (currtime < 0)
@@ -525,27 +522,28 @@
 
     if (status != STREAM_STOPPED)
     {
-        /* Not stopped = paused or playing */
-        stream_mgr.seeked = false;
-
         /* Pause the clock */
         pcm_output_play_pause(false);
 
+        /* Assume invalidity */
+        stream_mgr.resume_time = 0;
+
         if (stream_can_seek())
         {
-            /* Read the current stream time */
-            uint32_t time = TICKS_TO_TS(pcm_output_get_clock());
-
-            /* Assume invalidity */
-            stream_mgr.resume_time = 0;
+            /* Read the current stream time or the last seeked position */
+            uint32_t start;
+            uint32_t time = stream_get_seek_time(&start);
 
             if (time >= str_parser.start_pts && time < str_parser.end_pts)
             {
                 /* Save the current stream time */
-                stream_mgr.resume_time = time - str_parser.start_pts;
+                stream_mgr.resume_time = time - start;
             }
         }
 
+        /* Not stopped = paused or playing */
+        stream_mgr.seeked = false;
+
         /* Stop buffering */
         disk_buf_send_msg(STREAM_STOP, 0);
 
@@ -578,10 +576,10 @@
         if (stream_mgr.filename == NULL)
             break;
 
-        stream_mgr_reply_msg(STREAM_OK);
-
         stream_keep_disk_active();
 
+        stream_mgr_reply_msg(STREAM_OK);
+
         stream_mgr_lock();
 
         if (!stream_can_seek())
@@ -705,9 +703,11 @@
 
     vis = parser_send_video_msg(VIDEO_DISPLAY_SHOW, show);
 #ifndef HAVE_LCD_COLOR
-    GRAY_VIDEO_FLUSH_ICACHE();
+    GRAY_VIDEO_INVALIDATE_ICACHE();
     GRAY_INVALIDATE_ICACHE();
+
     gray_show(show);
+
     GRAY_FLUSH_ICACHE();
 #endif
     stream_mgr_unlock();
@@ -743,6 +743,32 @@
     return retval;
 }
 
+void stream_vo_set_clip(const struct vo_rect *rc)
+{
+    stream_mgr_lock();
+
+    if (rc)
+    {
+        stream_mgr.parms.rc = *rc;
+        rc = &stream_mgr.parms.rc;
+    }
+#ifndef HAVE_LCD_COLOR
+    else
+    {
+        vo_rect_set_ext(&stream_mgr.parms.rc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+        rc = &stream_mgr.parms.rc;
+    }
+#endif
+
+    parser_send_video_msg(VIDEO_SET_CLIP_RECT, (intptr_t)rc);
+
+#ifndef HAVE_LCD_COLOR
+    stream_set_gray_rect(rc);
+#endif
+
+    stream_mgr_unlock();
+}
+
 #ifndef HAVE_LCD_COLOR
 /* Set the rectangle for the gray video overlay - clipped to screen */
 bool stream_set_gray_rect(const struct vo_rect *rc)
@@ -756,21 +782,38 @@
 
     if (vo_rect_intersect(&rc_gray, &rc_gray, rc))
     {
-        bool vo_vis = stream_show_vo(false);
+        bool vis = parser_send_video_msg(VIDEO_DISPLAY_SHOW, false);
 
-        GRAY_VIDEO_FLUSH_ICACHE();
+        /* The impudence! Keeps the image from disappearing anyway. */
+#ifdef SIMULATOR
+        rb->sim_lcd_ex_init(0, NULL);
+#else
+        rb->timer_unregister();
+#endif
+        GRAY_VIDEO_INVALIDATE_ICACHE();
         GRAY_INVALIDATE_ICACHE();
 
+        vo_lock();
+
         gray_init(rb, stream_mgr.graymem, stream_mgr.graysize,
                   false, rc_gray.r - rc_gray.l, rc_gray.b - rc_gray.t,
                   32, 2<<8, NULL);
 
         gray_set_position(rc_gray.l, rc_gray.t);
-        GRAY_FLUSH_ICACHE();
 
-        if (vo_vis)
+        vo_unlock();
+
+        GRAY_INVALIDATE_ICACHE();
+
+        if (stream_mgr.status != STREAM_PLAYING)
+            parser_send_video_msg(VIDEO_PRINT_FRAME, true);
+
+        GRAY_VIDEO_FLUSH_ICACHE();
+
+        if (vis)
         {
-            stream_show_vo(true);
+            gray_show(true);
+            parser_send_video_msg(VIDEO_DISPLAY_SHOW, true);
         }
     }
 
@@ -784,9 +827,11 @@
 {
     stream_mgr_lock();
 
-    GRAY_VIDEO_FLUSH_ICACHE();
+    GRAY_VIDEO_INVALIDATE_ICACHE();
     GRAY_INVALIDATE_ICACHE();
+
     gray_show(show);
+
     GRAY_FLUSH_ICACHE();
 
     stream_mgr_unlock();
@@ -803,11 +848,32 @@
 
     stream_mgr_lock();
 
+    GRAY_INVALIDATE_ICACHE();
+
     stream_mgr.parms.rc = *rc;
     retval = parser_send_video_msg(VIDEO_PRINT_THUMBNAIL,
                 (intptr_t)&stream_mgr.parms.rc);
 
+    GRAY_VIDEO_FLUSH_ICACHE();
+
     stream_mgr_unlock();
+
+    return retval;
+}
+
+bool stream_draw_frame(bool no_prepare)
+{
+    bool retval;
+    stream_mgr_lock();
+
+    GRAY_INVALIDATE_ICACHE();
+
+    retval = parser_send_video_msg(VIDEO_PRINT_FRAME, no_prepare);
+
+    GRAY_VIDEO_FLUSH_ICACHE();
+
+    stream_mgr_unlock();
+
     return retval;
 }
 
@@ -826,6 +892,25 @@
     return resume_time;
 }
 
+uint32_t stream_get_seek_time(uint32_t *start)
+{
+    uint32_t time;
+
+    stream_mgr_lock();
+
+    if (stream_mgr.seeked)
+        time = str_parser.last_seek_time;
+    else
+        time = TICKS_TO_TS(pcm_output_get_clock());
+
+    if (start != NULL)
+        *start = str_parser.start_pts;
+
+    stream_mgr_unlock();
+
+    return time;
+}
+
 /* Returns the smallest file window that includes all active streams'
  * windows */
 static bool stream_get_window_callback(struct list_item *item,
diff --git a/apps/plugins/mpegplayer/stream_mgr.h b/apps/plugins/mpegplayer/stream_mgr.h
index 63452ec..dd5d8ca 100644
--- a/apps/plugins/mpegplayer/stream_mgr.h
+++ b/apps/plugins/mpegplayer/stream_mgr.h
@@ -105,6 +105,9 @@
 /* Show/Hide the video image at the current seekpoint */
 bool stream_show_vo(bool show);
 
+/* Set the visible section of video */
+void stream_vo_set_clip(const struct vo_rect *rc);
+
 #ifndef HAVE_LCD_COLOR
 /* Set the gray overlay rectangle */
 bool stream_set_gray_rect(const struct vo_rect *rc);
@@ -114,12 +117,19 @@
 /* Display thumbnail of the current seekpoint */
 bool stream_display_thumb(const struct vo_rect *rc);
 
+/* Draw the frame at the current position */
+bool stream_draw_frame(bool no_prepare);
+
 /* Return video dimensions */
 bool stream_vo_get_size(struct vo_ext *sz);
 
 /* Returns the resume time in timestamp ticks */
 uint32_t stream_get_resume_time(void);
 
+/* Returns stream_get_time if no seek is pending or else the
+   last time give to seek */
+uint32_t stream_get_seek_time(uint32_t *start);
+
 /* Return the absolute stream time in clock ticks - adjusted by
  * master clock stream via audio timestamps */
 static inline uint32_t stream_get_time(void)
diff --git a/apps/plugins/mpegplayer/stream_thread.h b/apps/plugins/mpegplayer/stream_thread.h
index 1962a66..58cb7b9 100644
--- a/apps/plugins/mpegplayer/stream_thread.h
+++ b/apps/plugins/mpegplayer/stream_thread.h
@@ -106,6 +106,7 @@
     VIDEO_GET_SIZE,           /* Get the video dimensions */
     VIDEO_PRINT_FRAME,        /* Print the frame at the current position */
     VIDEO_PRINT_THUMBNAIL,    /* Print a thumbnail of the current position */
+    VIDEO_SET_CLIP_RECT,      /* Set the visible video area */
 #ifdef GRAY_CACHE_MAINT
     VIDEO_GRAY_CACHEOP,
 #endif
diff --git a/apps/plugins/mpegplayer/video_out.h b/apps/plugins/mpegplayer/video_out.h
index 08cd7aa..ed8c4c5 100644
--- a/apps/plugins/mpegplayer/video_out.h
+++ b/apps/plugins/mpegplayer/video_out.h
@@ -24,6 +24,16 @@
 #ifndef VIDEO_OUT_H
 #define VIDEO_OUT_H
 
+#if LCD_WIDTH >= LCD_HEIGHT
+#define SCREEN_WIDTH LCD_WIDTH
+#define SCREEN_HEIGHT LCD_HEIGHT
+#define LCD_LANDSCAPE
+#else /* Assume the screen is rotated on portrait LCDs */
+#define SCREEN_WIDTH LCD_HEIGHT
+#define SCREEN_HEIGHT LCD_WIDTH
+#define LCD_PORTRAIT
+#endif
+
 /* Structure to hold width and height values */
 struct vo_ext
 {
@@ -43,9 +53,18 @@
 bool vo_show (bool show);
 bool vo_is_visible(void);
 void vo_setup (const mpeg2_sequence_t * sequence);
+void vo_set_clip_rect(const struct vo_rect *rc);
 void vo_dimensions(struct vo_ext *sz);
 void vo_cleanup (void);
 
+#if NUM_CORES > 1 || !defined (HAVE_LCD_COLOR)
+void vo_lock(void);
+void vo_unlock(void);
+#else
+static inline void vo_lock(void) {}
+static inline void vo_unlock(void) {}
+#endif
+
 /* Sets all coordinates of a vo_rect to 0 */
 void vo_rect_clear(struct vo_rect *rc);
 /* Returns true if left >= right or top >= bottom */
@@ -68,4 +87,10 @@
                        const struct vo_rect *rc1,
                        const struct vo_rect *rc2);
 
+bool vo_rect_union(struct vo_rect *rc_dst,
+                   const struct vo_rect *rc1,
+                   const struct vo_rect *rc2);
+
+void vo_rect_offset(struct vo_rect *rc, int dx, int dy);
+
 #endif /* VIDEO_OUT_H */
diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c
index ff0d39e..6efbaf0 100644
--- a/apps/plugins/mpegplayer/video_out_rockbox.c
+++ b/apps/plugins/mpegplayer/video_out_rockbox.c
@@ -21,6 +21,9 @@
 #include "plugin.h"
 #include "mpegplayer.h"
 
+#define VO_NON_NULL_RECT 0x1
+#define VO_VISIBLE       0x2
+
 struct vo_data
 {
     int image_width;
@@ -34,8 +37,9 @@
     int output_width;
     int output_height;
     bool visible;
-    bool thumb_mode;
-    void *last;
+    unsigned flags;
+    struct vo_rect rc_vid;
+    struct vo_rect rc_clip;
 };
 
 #ifdef PROC_NEEDS_CACHEALIGN
@@ -48,10 +52,40 @@
 static struct vo_data vo;
 #endif
 
+#if NUM_CORES > 1
+static struct mutex vo_mtx NOCACHEBSS_ATTR;
+#endif
+
+static inline void video_lock_init(void)
+{
+#if NUM_CORES > 1
+    rb->mutex_init(&vo_mtx);
+#endif
+}
+
+static inline void video_lock(void)
+{
+#if NUM_CORES > 1
+    rb->mutex_lock(&vo_mtx);
+#endif
+}
+
+static inline void video_unlock(void)
+{
+#if NUM_CORES > 1
+    rb->mutex_unlock(&vo_mtx);
+#endif
+}
+
+
 /* Draw a black rectangle if no video frame is available */
 static void vo_draw_black(void)
 {
-    int foreground = lcd_(get_foreground)();
+    int foreground;
+
+    video_lock();
+
+    foreground = lcd_(get_foreground)();
 
     lcd_(set_foreground)(DRAW_BLACK);
 
@@ -61,21 +95,27 @@
                       vo.output_height);
 
     lcd_(set_foreground)(foreground);
+
+    video_unlock();
 }
 
 static inline void yuv_blit(uint8_t * const * buf, int src_x, int src_y,
                             int stride, int x, int y, int width, int height)
 {
+    video_lock();
+
 #ifdef HAVE_LCD_COLOR
     rb->lcd_yuv_blit(buf, src_x, src_y, stride, x, y , width, height);
 #else
     gray_ub_gray_bitmap_part(buf[0], src_x, src_y, stride, x, y, width, height);
 #endif
+
+    video_unlock();
 }
 
 void vo_draw_frame(uint8_t * const * buf)
 {
-    if (!vo.visible)
+    if (vo.flags == 0)
     {
         /* Frame is hidden - copout */
         DEBUGF("vo hidden\n");
@@ -94,14 +134,6 @@
              vo.output_height);
 }
 
-#if LCD_WIDTH >= LCD_HEIGHT
-#define SCREEN_WIDTH LCD_WIDTH
-#define SCREEN_HEIGHT LCD_HEIGHT
-#else /* Assume the screen is rotated on portrait LCDs */
-#define SCREEN_WIDTH LCD_HEIGHT
-#define SCREEN_HEIGHT LCD_WIDTH
-#endif
-
 static inline void vo_rect_clear_inl(struct vo_rect *rc)
 {
     rc->l = rc->t = rc->r = rc->b = 0;
@@ -172,6 +204,48 @@
     return false;
 }
 
+bool vo_rect_union(struct vo_rect *rc_dst,
+                   const struct vo_rect *rc1,
+                   const struct vo_rect *rc2)
+{
+    if (rc_dst != NULL)
+    {
+        if (!vo_rect_empty_inl(rc1))
+        {
+            if (!vo_rect_empty_inl(rc2))
+            {
+                rc_dst->l = MIN(rc1->l, rc2->l);
+                rc_dst->t = MIN(rc1->t, rc2->t);
+                rc_dst->r = MAX(rc1->r, rc2->r);
+                rc_dst->b = MAX(rc1->b, rc2->b);
+            }
+            else
+            {
+                *rc_dst = *rc1;
+            }
+
+            return true;
+        }
+        else if (!vo_rect_empty(rc2))
+        {
+            *rc_dst = *rc2;
+            return true;
+        }
+
+        vo_rect_clear_inl(rc_dst);
+    }
+
+    return false;
+}
+
+void vo_rect_offset(struct vo_rect *rc, int dx, int dy)
+{
+    rc->l += dx;
+    rc->t += dy;
+    rc->r += dx;
+    rc->b += dy;
+}
+
 /* Shink or stretch each axis - rotate counter-clockwise to retain upright
  * orientation on rotated displays (they rotate clockwise) */
 void stretch_image_plane(const uint8_t * src, uint8_t *dst, int stride,
@@ -350,25 +424,27 @@
 
     if (sequence->display_width >= SCREEN_WIDTH)
     {
-        vo.output_width = SCREEN_WIDTH;
-        vo.output_x = 0;
+        vo.rc_vid.l = 0;
+        vo.rc_vid.r = SCREEN_WIDTH;
     }
     else
     {
-        vo.output_width = sequence->display_width;
-        vo.output_x = (SCREEN_WIDTH - sequence->display_width) / 2;
+        vo.rc_vid.l = (SCREEN_WIDTH - sequence->display_width) / 2;
+        vo.rc_vid.r = vo.rc_vid.l + sequence->display_width;
     }
 
     if (sequence->display_height >= SCREEN_HEIGHT)
     {
-        vo.output_height = SCREEN_HEIGHT;
-        vo.output_y = 0;
+        vo.rc_vid.t = 0;
+        vo.rc_vid.b = SCREEN_HEIGHT;
     }
     else
     {
-        vo.output_height = sequence->display_height;
-        vo.output_y = (SCREEN_HEIGHT - sequence->display_height) / 2;
+        vo.rc_vid.t = (SCREEN_HEIGHT - sequence->display_height) / 2;
+        vo.rc_vid.b = vo.rc_vid.t + sequence->display_height;
     }
+
+    vo_set_clip_rect(&vo.rc_clip);
 }
 
 void vo_dimensions(struct vo_ext *sz)
@@ -379,23 +455,68 @@
 
 bool vo_init(void)
 {
-    vo.visible = false;
+    vo.flags = 0;
+    vo_rect_set_ext(&vo.rc_clip, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+    video_lock_init();
     return true;
 }
 
 bool vo_show(bool show)
 {
-    bool vis = vo.visible;
-    vo.visible = show;
+    bool vis = vo.flags & VO_VISIBLE;
+
+    if (show)
+        vo.flags |= VO_VISIBLE;
+    else
+        vo.flags &= ~VO_VISIBLE;
+
     return vis;
 }
 
 bool vo_is_visible(void)
 {
-    return vo.visible;
+    return vo.flags & VO_VISIBLE;
 }
 
 void vo_cleanup(void)
 {
-    vo.visible = false;
+    vo.flags = 0;
 }
+
+void vo_set_clip_rect(const struct vo_rect *rc)
+{
+    struct vo_rect rc_out;
+
+    if (rc == NULL)
+        vo_rect_set_ext(&vo.rc_clip, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+    else
+        vo.rc_clip = *rc;
+
+    if (!vo_rect_intersect(&rc_out, &vo.rc_vid, &vo.rc_clip))
+        vo.flags &= ~VO_NON_NULL_RECT;
+    else
+        vo.flags |= VO_NON_NULL_RECT;
+
+    vo.output_x = rc_out.l;
+    vo.output_y = rc_out.t;
+    vo.output_width = rc_out.r - rc_out.l;
+    vo.output_height = rc_out.b - rc_out.t;
+}
+
+#if NUM_CORES > 1 || !defined (HAVE_LCD_COLOR)
+void vo_lock(void)
+{
+#ifndef HAVE_LCD_COLOR
+    set_irq_level(HIGHEST_IRQ_LEVEL);
+#endif
+    video_lock();
+}
+
+void vo_unlock(void)
+{
+    video_unlock();
+#ifndef HAVE_LCD_COLOR
+    set_irq_level(0);
+#endif
+}
+#endif
diff --git a/apps/plugins/mpegplayer/video_thread.c b/apps/plugins/mpegplayer/video_thread.c
index aeaf942..950d49a 100644
--- a/apps/plugins/mpegplayer/video_thread.c
+++ b/apps/plugins/mpegplayer/video_thread.c
@@ -75,7 +75,10 @@
                  fps / 100, fps % 100, td->num_skipped,
                  td->info->display_picture->temporal_reference);
     rb->lcd_putsxy(0, 0, str);
+
+    vo_lock();
     rb->lcd_update_rect(0, 0, LCD_WIDTH, 8);
+    vo_unlock();
 
     td->last_showfps = *rb->current_tick;
 }
@@ -522,7 +525,9 @@
             else
             {
                 IF_COP(invalidate_icache());
+                vo_lock();
                 rb->lcd_update();
+                vo_unlock();
             }
 #else
             GRAY_FLUSH_ICACHE();
@@ -580,6 +585,10 @@
             }
             break;
 
+        case VIDEO_SET_CLIP_RECT:
+            vo_set_clip_rect((const struct vo_rect *)td->ev.data);
+            break;
+
         case VIDEO_PRINT_FRAME:
             /* Print the last frame decoded */
             if (td->info != NULL && td->info->display_fbuf != NULL)
diff --git a/uisimulator/sdl/lcd-bitmap.c b/uisimulator/sdl/lcd-bitmap.c
index 12cc063..7b6e221 100644
--- a/uisimulator/sdl/lcd-bitmap.c
+++ b/uisimulator/sdl/lcd-bitmap.c
@@ -359,7 +359,7 @@
 #if LCD_WIDTH >= LCD_HEIGHT
     lcd_update_rect(x, y, width, height);
 #else
-    lcd_update_rect(y, x, height, width);
+    lcd_update_rect(LCD_WIDTH - y - height, x, height, width);
 #endif
 }
 #endif /* HAVE_LCD_COLOR */