/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * mpegplayer main entrypoint and UI implementation
 *
 * Copyright (c) 2007 Michael Sevakis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/****************************************************************************
 * NOTES:
 *
 * mpegplayer is structured as follows:
 *
 *                       +-->Video Thread-->Video Output-->LCD
 *                       |
 * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device
 *         |       |     |                        |     (ref. clock)
 *         |       |     +-->Buffer Thread        |
 *    Stream Data  |             |          (clock intf./
 *     Requests    |         File Cache      drift adj.)
 *                 |          Disk I/O
 *         Stream services
 *          (timing, etc.)
 *
 * Thread list:
 *  1) The main thread - Handles user input, settings, basic playback control
 *     and USB connect.
 *
 *  2) Stream Manager thread - Handles playback state, events from streams
 *     such as when a stream is finished, stream commands, PCM state. The
 *     layer in which this thread run also handles arbitration of data
 *     requests between the streams and the disk buffer. The actual specific
 *     transport layer code may get moved out to support multiple container
 *     formats.
 *
 *  3) Buffer thread - Buffers data in the background, generates notifications
 *     to streams when their data has been buffered, and watches streams'
 *     progress to keep data available during playback. Handles synchronous
 *     random access requests when the file cache is missed.
 *
 *  4) Video thread (running on the COP for PortalPlayer targets) - Decodes
 *     the video stream and renders video frames to the LCD. Handles
 *     miscellaneous video tasks like frame and thumbnail printing.
 *
 *  5) Audio thread (running on the main CPU to maintain consistency with the
 *     audio FIQ hander on PP) - Decodes audio frames and places them into
 *     the PCM buffer for rendering by the audio device.
 *
 * Streams are neither aware of one another nor care about one another. All
 * streams shall have their own thread (unless it is _really_ efficient to
 * have a single thread handle a couple minor streams). All coordination of
 * the streams is done through the stream manager. The clocking is controlled
 * by and exposed by the stream manager to other streams and implemented at
 * the PCM level.
 *
 * Notes about MPEG files:
 *
 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
 *
 * FPS is represented in terms of a frame period - this is always an
 * integer number of 27MHz ticks.
 *
 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
 * 900900 27MHz ticks.
 *
 * In libmpeg2, info->sequence->frame_period contains the frame_period.
 *
 * Working with Rockbox's 100Hz tick, the common frame rates would need
 * to be as follows (1):
 *
 * FPS     | 27Mhz   | 100Hz          | 44.1KHz   | 48KHz
 * --------|-----------------------------------------------------------
 * 10*     | 2700000 | 10             | 4410      | 4800
 * 12*     | 2250000 |  8.3333        | 3675      | 4000
 * 15*     | 1800000 |  6.6667        | 2940      | 3200
 * 23.9760 | 1126125 |  4.170833333   | 1839.3375 | 2002
 * 24      | 1125000 |  4.166667      | 1837.5    | 2000
 * 25      | 1080000 |  4             | 1764      | 1920
 * 29.9700 |  900900 |  3.336667      | 1471,47   | 1601.6
 * 30      |  900000 |  3.333333      | 1470      | 1600
 *
 * *Unofficial framerates
 *
 * (1) But we don't really care since the audio clock is used anyway and has
 *     very fine resolution ;-)
 *****************************************************************************/
#include "plugin.h"
#include "mpegplayer.h"
#include "lib/helper.h"
#include "mpeg_settings.h"
#include "video_out.h"
#include "stream_thread.h"
#include "stream_mgr.h"


/* button definitions */
#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define MPEG_MENU       BUTTON_MODE
#define MPEG_STOP       BUTTON_OFF
#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)
#define MPEG_MENU       BUTTON_MENU
#define MPEG_PAUSE      (BUTTON_PLAY | BUTTON_REL)
#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)
#define MPEG_STOP       BUTTON_POWER
#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
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_SELECT
#define MPEG_PAUSE2     BUTTON_A
#define MPEG_VOLDOWN    BUTTON_LEFT
#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

#define MPEG_RC_MENU    BUTTON_RC_DSP
#define MPEG_RC_STOP    (BUTTON_RC_PLAY | BUTTON_REPEAT)
#define MPEG_RC_PAUSE   (BUTTON_RC_PLAY | BUTTON_REL)
#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
#define MPEG_RC_VOLUP   BUTTON_RC_VOL_UP
#define MPEG_RC_RW      BUTTON_RC_REW
#define MPEG_RC_FF      BUTTON_RC_FF

#elif CONFIG_KEYPAD == GIGABEAT_S_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_SELECT
#define MPEG_PAUSE2     BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_LEFT
#define MPEG_VOLUP      BUTTON_RIGHT
#define MPEG_VOLDOWN2   BUTTON_VOL_DOWN
#define MPEG_VOLUP2     BUTTON_VOL_UP
#define MPEG_RW         BUTTON_UP
#define MPEG_RW2        BUTTON_PREV
#define MPEG_FF         BUTTON_DOWN
#define MPEG_FF2        BUTTON_NEXT
#define MPEG_SHOW_OSD   BUTTON_BACK

#define MPEG_RC_MENU    BUTTON_RC_DSP
#define MPEG_RC_STOP    (BUTTON_RC_PLAY | BUTTON_REPEAT)
#define MPEG_RC_PAUSE   (BUTTON_RC_PLAY | BUTTON_REL)
#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
#define MPEG_RC_VOLUP   BUTTON_RC_VOL_UP
#define MPEG_RC_RW      BUTTON_RC_REW
#define MPEG_RC_FF      BUTTON_RC_FF

#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#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
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_RIGHT
#define MPEG_VOLDOWN    BUTTON_SCROLL_BACK
#define MPEG_VOLUP      BUTTON_SCROLL_FWD
#define MPEG_RW         BUTTON_UP
#define MPEG_FF         BUTTON_DOWN

#elif CONFIG_KEYPAD == SANSA_FUZE_PAD
#define MPEG_MENU       BUTTON_SELECT
#define MPEG_STOP       (BUTTON_HOME|BUTTON_REPEAT)
#define MPEG_PAUSE      BUTTON_UP
#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 == SANSA_C200_PAD || \
CONFIG_KEYPAD == SANSA_CLIP_PAD || \
CONFIG_KEYPAD == SANSA_M200_PAD
#define MPEG_MENU       BUTTON_SELECT
#define MPEG_STOP       BUTTON_POWER
#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_STOP       BUTTON_POWER

#define MPEG_RC_MENU    BUTTON_RC_HEART
#define MPEG_RC_STOP    BUTTON_RC_DOWN
#define MPEG_RC_PAUSE   BUTTON_RC_PLAY
#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
#define MPEG_RC_VOLUP   BUTTON_RC_VOL_UP
#define MPEG_RC_RW      BUTTON_RC_REW
#define MPEG_RC_FF      BUTTON_RC_FF

#elif CONFIG_KEYPAD == MROBE100_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#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 == IAUDIO_M3_PAD
#define MPEG_MENU       BUTTON_RC_MENU
#define MPEG_STOP       BUTTON_RC_REC
#define MPEG_PAUSE      BUTTON_RC_PLAY
#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

#elif CONFIG_KEYPAD == COWON_D2_PAD
#define MPEG_MENU       (BUTTON_MENU|BUTTON_REL)
//#define MPEG_STOP       BUTTON_POWER
#define MPEG_VOLDOWN    BUTTON_MINUS
#define MPEG_VOLUP      BUTTON_PLUS

#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_BACK
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_UP
#define MPEG_VOLUP      BUTTON_DOWN
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD ==  CREATIVE_ZENXFI3_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       (BUTTON_PLAY|BUTTON_REPEAT)
#define MPEG_PAUSE      (BUTTON_PLAY|BUTTON_REL)
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_DOWN
#define MPEG_FF         BUTTON_UP

#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_SELECT
#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 == PHILIPS_HDD6330_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_UP
#define MPEG_FF         BUTTON_DOWN

#elif CONFIG_KEYPAD == ONDAVX747_PAD
#define MPEG_MENU       (BUTTON_MENU|BUTTON_REL)
//#define MPEG_STOP       BUTTON_POWER
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP

#elif CONFIG_KEYPAD == ONDAVX777_PAD
#define MPEG_MENU       BUTTON_POWER

#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
      (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)
#define MPEG_MENU       BUTTON_REW
#define MPEG_STOP       (BUTTON_PLAY | BUTTON_REPEAT)
#define MPEG_PAUSE      (BUTTON_PLAY | BUTTON_REL)
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT
#define MPEG_SHOW_OSD   BUTTON_FFWD

#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_REC
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == MPIO_HD200_PAD
#define MPEG_MENU       BUTTON_FUNC
#define MPEG_PAUSE      (BUTTON_PLAY | BUTTON_REL)
#define MPEG_STOP       (BUTTON_PLAY | BUTTON_REPEAT)
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_REW
#define MPEG_FF         BUTTON_FF

#elif CONFIG_KEYPAD == MPIO_HD300_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_PAUSE      (BUTTON_PLAY | BUTTON_REL)
#define MPEG_STOP       (BUTTON_PLAY | BUTTON_REPEAT)
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_REW
#define MPEG_FF         BUTTON_FF

#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      (BUTTON_PLAYPAUSE | BUTTON_REL)
#define MPEG_STOP       (BUTTON_PLAYPAUSE | BUTTON_REPEAT)
#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 == SANSA_CONNECT_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      (BUTTON_SELECT | BUTTON_REL)
#define MPEG_STOP       (BUTTON_SELECT | BUTTON_REPEAT)
#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 == SAMSUNG_YPR0_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_PAUSE      BUTTON_SELECT
#define MPEG_STOP       BUTTON_POWER
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD == HM60X_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_SELECT
#define MPEG_STOP       (BUTTON_SELECT | BUTTON_POWER)
#define MPEG_VOLDOWN    (BUTTON_POWER | BUTTON_DOWN)
#define MPEG_VOLUP      (BUTTON_POWER | BUTTON_UP)
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD == HM801_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_STOP       (BUTTON_POWER | BUTTON_PLAY)
#define MPEG_VOLDOWN    (BUTTON_POWER | BUTTON_DOWN)
#define MPEG_VOLUP      (BUTTON_POWER | BUTTON_UP)
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == SONY_NWZ_PAD
#define MPEG_MENU       BUTTON_BACK
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_STOP       BUTTON_POWER
#define MPEG_VOLDOWN    BUTTON_UP
#define MPEG_VOLUP      BUTTON_DOWN
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_PAUSE      BUTTON_PLAYPAUSE
#define MPEG_STOP       BUTTON_BACK
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD == DX50_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_STOP       (BUTTON_PLAY|BUTTON_REPEAT)

#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_MENU
#define MPEG_STOP       (BUTTON_MENU|BUTTON_REPEAT)

#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
#define MPEG_MENU       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_SELECT
#define MPEG_STOP       BUTTON_DOWN
#define MPEG_VOLDOWN    BUTTON_VOLDOWN
#define MPEG_VOLUP      BUTTON_VOLUP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#elif CONFIG_KEYPAD == XDUOO_X3_PAD
#define MPEG_MENU       BUTTON_PLAY
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_HOME
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD
#define MPEG_MENU       BUTTON_PLAY
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_HOME
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD
#define MPEG_MENU       BUTTON_PLAY
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_HOME
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD
#define MPEG_MENU       BUTTON_PLAY
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_HOME
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == EROSQ_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_PLAY
#define MPEG_VOLDOWN    BUTTON_VOL_DOWN
#define MPEG_VOLUP      BUTTON_VOL_UP
#define MPEG_RW         BUTTON_PREV
#define MPEG_FF         BUTTON_NEXT

#elif CONFIG_KEYPAD == FIIO_M3K_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_POWER
#define MPEG_PAUSE      BUTTON_PLAY
#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 == MA_PAD
#define MPEG_MENU       BUTTON_MENU
#define MPEG_STOP       BUTTON_BACK
#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 == SHANLING_Q1_PAD
/* use touchscreen */

#elif CONFIG_KEYPAD == RG_NANO_PAD
#define MPEG_MENU       BUTTON_START
#define MPEG_STOP       BUTTON_X
#define MPEG_PAUSE      BUTTON_A
#define MPEG_VOLDOWN    BUTTON_DOWN
#define MPEG_VOLUP      BUTTON_UP
#define MPEG_RW         BUTTON_LEFT
#define MPEG_FF         BUTTON_RIGHT

#else
#error No keymap defined!
#endif

#ifdef HAVE_TOUCHSCREEN
#ifndef MPEG_MENU
#define MPEG_MENU      (BUTTON_TOPRIGHT|BUTTON_REL)
#endif
#ifndef MPEG_STOP
#define MPEG_STOP       BUTTON_TOPLEFT
#endif
#ifndef MPEG_PAUSE
#define MPEG_PAUSE      BUTTON_CENTER
#endif
#ifndef MPEG_VOLDOWN
#define MPEG_VOLDOWN    BUTTON_BOTTOMMIDDLE
#endif
#ifndef MPEG_VOLUP
#define MPEG_VOLUP      BUTTON_TOPMIDDLE
#endif
#ifndef MPEG_RW
#define MPEG_RW         BUTTON_MIDLEFT
#endif
#ifndef MPEG_FF
#define MPEG_FF         BUTTON_MIDRIGHT
#endif
#endif

/* 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 OSD_MIN_UPDATE_INTERVAL (HZ/2)
#define FPS_UPDATE_INTERVAL (HZ) /* Get new FPS reading each second */

enum video_action
{
    VIDEO_STOP = 0,
    VIDEO_PREV,
    VIDEO_NEXT,
    VIDEO_ACTION_MANUAL = 0x8000, /* Flag that says user did it */
};

/* OSD status - same order as icon array */
enum osd_status_enum
{
    OSD_STATUS_STOPPED = 0,
    OSD_STATUS_PAUSED,
    OSD_STATUS_PLAYING,
    OSD_STATUS_FF,
    OSD_STATUS_RW,
    OSD_STATUS_COUNT,
    OSD_STATUS_MASK = 0x7
};

enum osd_bits
{
    OSD_REFRESH_DEFAULT    = 0x0000, /* Only refresh elements when due */
                                     /* Refresh the... */
    OSD_REFRESH_VOLUME     = 0x0001, /* ...volume display */
    OSD_REFRESH_TIME       = 0x0002, /* ...time display+progress */
    OSD_REFRESH_STATUS     = 0x0004, /* ...playback status icon */
    OSD_REFRESH_BACKGROUND = 0x0008, /* ...background (implies ALL) */
    OSD_REFRESH_VIDEO      = 0x0010, /* ...video image upon timeout */
    OSD_REFRESH_RESUME     = 0x0020, /* Resume playback upon timeout */
    OSD_NODRAW             = 0x8000, /* OR bitflag - don't draw anything */
    OSD_SHOW               = 0x4000, /* OR bitflag - show the OSD */
#ifdef HAVE_HEADPHONE_DETECTION
    OSD_HP_PAUSE           = 0x2000, /* OR bitflag - headphones caused pause */
#endif
    OSD_HIDE               = 0x0000, /* hide the OSD (aid readability) */
    OSD_REFRESH_ALL        = 0x000f, /* Only immediate graphical elements */
};

/* Status icons selected according to font height */
extern const unsigned char mpegplayer_status_icons_8x8x1[];
extern const unsigned char mpegplayer_status_icons_12x12x1[];
extern const unsigned char mpegplayer_status_icons_16x16x1[];

/* Main border areas that contain OSD elements */
#define OSD_BDR_L 2
#define OSD_BDR_T 2
#define OSD_BDR_R 2
#define OSD_BDR_B 2

struct osd
{
    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;
    unsigned prog_fillcolor;
    struct vo_rect update_rect;
    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;
    int font;
};

struct fps
{
    /* FPS Display */
    struct vo_rect rect;    /* OSD coordinates */
    int pf_x;               /* Screen coordinates */
    int pf_y;
    int pf_width;
    int pf_height;
    long update_tick;       /* When to next update FPS reading */
    #define FPS_FORMAT  "%d.%02d"
    #define FPS_DIMSTR  "999.99" /* For establishing rect size */
    #define FPS_BUFSIZE sizeof("999.99")
};

static struct osd osd;
static struct fps fps NOCACHEBSS_ATTR; /* Accessed on other processor */

#ifdef LCD_PORTRAIT
static fb_data* get_framebuffer(void)
{
    struct viewport *vp_main = *(rb->screens[SCREEN_MAIN]->current_viewport);
    return vp_main->buffer->fb_ptr;
}
#endif

static void osd_show(unsigned show);

#ifdef LCD_LANDSCAPE
    #define __X (x + osd.x)
    #define __Y (y + osd.y)
    #define __W width
    #define __H height
#else
    #define __X (LCD_WIDTH - (y + osd.y) - height)
    #define __Y (x + osd.x)
    #define __W height
    #define __H width
#endif

#ifdef HAVE_LCD_COLOR
/* Blend two colors in 0-100% (0-255) mix of c2 into c1 */
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

#ifdef PLUGIN_USE_IRAM
/* IRAM preserving mechanism to enable talking menus */
static char *iram_saved_copy;
extern char iramstart[], iramend[];

static void iram_saving_init(void)
{
#ifndef SIMULATOR
    size_t size;
    iram_saved_copy = (char *)rb->plugin_get_buffer(&size);

    if (size >= (size_t)(iramend-iramstart))
        iram_saved_copy += size - (size_t)(iramend - iramstart);
    else
#endif
        iram_saved_copy = NULL;

    return;
}

void mpegplayer_iram_preserve(void)
{
    if (iram_saved_copy)
    {
        rb->memcpy(iram_saved_copy, iramstart, iramend-iramstart);
#ifdef HAVE_CPUCACHE_INVALIDATE
        /* make the icache (if it exists) up to date with the new code */
        rb->cpucache_invalidate();
#endif /* HAVE_CPUCACHE_INVALIDATE */
    }
    return;
}

void mpegplayer_iram_restore(void)
{
    if (iram_saved_copy)
    {
        rb->audio_hard_stop();
        rb->memcpy(iramstart, iram_saved_copy, iramend-iramstart);
#ifdef HAVE_CPUCACHE_INVALIDATE
        /* make the icache (if it exists) up to date with the new code */
        rb->cpucache_invalidate();
#endif /* HAVE_CPUCACHE_INVALIDATE */
    }
    return;
}
#endif

/* Drawing functions that operate rotated on LCD_PORTRAIT displays -
 * most are just wrappers of lcd_* functions with transforms applied.
 * The origin is the upper-left corner of the OSD area */
static void draw_update_rect(int x, int y, int width, int height)
{
    mylcd_update_rect(__X, __Y, __W, __H);
}

static void draw_clear_area(int x, int y, int width, int height)
{
#ifdef HAVE_LCD_COLOR
    rb->screen_clear_area(rb->screens[SCREEN_MAIN], __X, __Y, __W, __H);
#else
    int oldmode = grey_get_drawmode();
    grey_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
    grey_fillrect(__X, __Y, __W, __H);
    grey_set_drawmode(oldmode);
#endif
}

static void draw_clear_area_rect(const struct vo_rect *rc)
{
    draw_clear_area(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t);
}

static void draw_fillrect(int x, int y, int width, int height)
{
    mylcd_fillrect(__X, __Y, __W, __H);
}

static void draw_hline(int x1, int x2, int y)
{
#ifdef LCD_LANDSCAPE
    mylcd_hline(x1 + osd.x, x2 + osd.x, y + osd.y);
#else
    y = LCD_WIDTH - (y + osd.y) - 1;
    mylcd_vline(y, x1 + osd.x, x2 + osd.x);
#endif
}

static void draw_vline(int x, int y1, int y2)
{
#ifdef LCD_LANDSCAPE
    mylcd_vline(x + osd.x, y1 + osd.y, y2 + osd.y);
#else
    y1 = LCD_WIDTH - (y1 + osd.y) - 1;
    y2 = LCD_WIDTH - (y2 + osd.y) - 1;
    mylcd_hline(y1, y2, x + osd.x);
#endif
}

static void draw_scrollbar_draw(int x, int y, int width, int height,
                                uint32_t min, uint32_t max, uint32_t val)
{
    unsigned oldfg = mylcd_get_foreground();

    draw_hline(x + 1, x + width - 2, y);
    draw_hline(x + 1, x + width - 2, y + height - 1);
    draw_vline(x, y + 1, y + height - 2);
    draw_vline(x + width - 1, y + 1, y + height - 2);

    val = muldiv_uint32(width - 2, val, max - min);
    val = MIN(val, (uint32_t)(width - 2));

    draw_fillrect(x + 1, y + 1, val, height - 2);

    mylcd_set_foreground(osd.prog_fillcolor);

    draw_fillrect(x + 1 + val, y + 1, width - 2 - val, height - 2);

    mylcd_set_foreground(oldfg);
}

static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int min,
                                     int max, int val)
{
    draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t,
                        min, max, val);
}

static void draw_setfont(int font)
{
    osd.font = font;
    mylcd_setfont(font);
}

#ifdef LCD_PORTRAIT
/* Portrait displays need rotated text rendering */

/* Limited function that only renders in DRMODE_FG and uses absolute screen
 * coordinates */
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;

    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 = get_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 = FB_SCALARPACK(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);
}


#define ALPHA_COLOR_FONT_DEPTH 2
#define ALPHA_COLOR_LOOKUP_SHIFT (1 << ALPHA_COLOR_FONT_DEPTH)
#define ALPHA_COLOR_LOOKUP_SIZE ((1 << ALPHA_COLOR_LOOKUP_SHIFT) - 1)
#define ALPHA_COLOR_PIXEL_PER_BYTE (8 >> ALPHA_COLOR_FONT_DEPTH)
#define ALPHA_COLOR_PIXEL_PER_WORD (32 >> ALPHA_COLOR_FONT_DEPTH)
#ifdef CPU_ARM
#define BLEND_INIT do {} while (0)
#define BLEND_FINISH do {} while(0)
#define BLEND_START(acc, color, alpha) \
    asm volatile("mul %0, %1, %2" : "=&r" (acc) : "r" (color), "r" (alpha))
#define BLEND_CONT(acc, color, alpha) \
    asm volatile("mla %0, %1, %2, %0" : "+&r" (acc) : "r" (color), "r" (alpha))
#define BLEND_OUT(acc) do {} while (0)
#elif defined(CPU_COLDFIRE)
#define ALPHA_BITMAP_READ_WORDS
#define BLEND_INIT \
    unsigned long _macsr = coldfire_get_macsr(); \
    coldfire_set_macsr(EMAC_UNSIGNED)
#define BLEND_FINISH \
    coldfire_set_macsr(_macsr)
#define BLEND_START(acc, color, alpha) \
    asm volatile("mac.l %0, %1, %%acc0" :: "%d" (color), "d" (alpha))
#define BLEND_CONT BLEND_START
#define BLEND_OUT(acc) asm volatile("movclr.l %%acc0, %0" : "=d" (acc))
#else
#define BLEND_INIT do {} while (0)
#define BLEND_FINISH do {} while(0)
#define BLEND_START(acc, color, alpha) ((acc) = (color) * (alpha))
#define BLEND_CONT(acc, color, alpha) ((acc) += (color) * (alpha))
#define BLEND_OUT(acc) do {} while (0)
#endif

/* Blend the given two colors */
static inline unsigned blend_two_colors(unsigned c1, unsigned c2, unsigned a)
{
#if LCD_DEPTH == 16
    a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1);
#if (LCD_PIXELFORMAT == RGB565SWAPPED)
    c1 = swap16(c1);
    c2 = swap16(c2);
#endif
    unsigned c1l = (c1 | (c1 << 16)) & 0x07e0f81f;
    unsigned c2l = (c2 | (c2 << 16)) & 0x07e0f81f;
    unsigned p;
    BLEND_START(p, c1l, a);
    BLEND_CONT(p, c2l, ALPHA_COLOR_LOOKUP_SIZE + 1 - a);
    BLEND_OUT(p);
    p = (p >> ALPHA_COLOR_LOOKUP_SHIFT) & 0x07e0f81f;
    p |= (p >> 16);
#if (LCD_PIXELFORMAT == RGB565SWAPPED)
    return swap16(p);
#else
    return p;
#endif

#else /* LCD_DEPTH == 24 */
    unsigned s = c1;
    unsigned d = c2;
    unsigned s1 = s & 0xff00ff;
    unsigned d1 = d & 0xff00ff;
    a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1);
    d1 = (d1 + ((s1 - d1) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00ff;
    s &= 0xff00;
    d &= 0xff00;
    d = (d + ((s - d) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00;

    return d1 | d;
#endif
}

static void draw_oriented_alpha_bitmap_part(const unsigned char *src,
                                            int src_x, int src_y,
                                            int stride, int x, int y,
                                            int width, int height)
{
    fb_data *dst, *dst_start;
    unsigned fg_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 */

    /* initialize blending */
    BLEND_INIT;

    fg_pattern =    rb->lcd_get_foreground();
    /*bg_pattern=*/ rb->lcd_get_background();

    dst_start = get_framebuffer() + (LCD_WIDTH - y - 1) + x*LCD_WIDTH;
    int col, row = height;
    unsigned data, pixels;
    unsigned skip_end = (stride - width);
    unsigned skip_start = src_y * stride + src_x;

#ifdef ALPHA_BITMAP_READ_WORDS
    uint32_t *src_w = (uint32_t *)((uintptr_t)src & ~3);
    skip_start += ALPHA_COLOR_PIXEL_PER_BYTE * ((uintptr_t)src & 3);
    src_w += skip_start / ALPHA_COLOR_PIXEL_PER_WORD;
    data = letoh32(*src_w++);
#else
    src += skip_start / ALPHA_COLOR_PIXEL_PER_BYTE;
    data = *src;
#endif
    pixels = skip_start % ALPHA_COLOR_PIXEL_PER_WORD;
    data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
#ifdef ALPHA_BITMAP_READ_WORDS
    pixels = 8 - pixels;
#endif

    do
    {
        col = width;
        dst = dst_start--;
#ifdef ALPHA_BITMAP_READ_WORDS
#define UPDATE_SRC_ALPHA    do { \
            if (--pixels) \
                data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
            else \
            { \
                data = letoh32(*src_w++); \
                pixels = ALPHA_COLOR_PIXEL_PER_WORD; \
            } \
        } while (0)
#elif ALPHA_COLOR_PIXEL_PER_BYTE == 2
#define UPDATE_SRC_ALPHA    do { \
            if (pixels ^= 1) \
                data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
            else \
                data = *(++src); \
        } while (0)
#else
#define UPDATE_SRC_ALPHA    do { \
            if (pixels = (++pixels % ALPHA_COLOR_PIXEL_PER_BYTE)) \
                data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
            else \
                data = *(++src); \
        } while (0)
#endif
        do
        {
            unsigned color = blend_two_colors(FB_UNPACK_SCALAR_LCD(*dst), fg_pattern,
                                    data & ALPHA_COLOR_LOOKUP_SIZE );
            *dst= FB_SCALARPACK(color);
            dst += LCD_WIDTH;
            UPDATE_SRC_ALPHA;
        }
        while (--col);
#ifdef ALPHA_BITMAP_READ_WORDS
        if (skip_end < pixels)
        {
            pixels -= skip_end;
            data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT;
        } else {
            pixels = skip_end - pixels;
            src_w += pixels / ALPHA_COLOR_PIXEL_PER_WORD;
            pixels %= ALPHA_COLOR_PIXEL_PER_WORD;
            data = letoh32(*src_w++);
            data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
            pixels = 8 - pixels;
        }
#else
        if (skip_end)
        {
            pixels += skip_end;
            if (pixels >= ALPHA_COLOR_PIXEL_PER_BYTE)
            {
                src += pixels / ALPHA_COLOR_PIXEL_PER_BYTE;
                pixels %= ALPHA_COLOR_PIXEL_PER_BYTE;
                data = *src;
                data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
            } else
                data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT;
        }
#endif
    } while (--row);
}

static void draw_putsxy_oriented(int x, int y, const char *str)
{
    ucschar_t ch;
    ucschar_t *ucs;
    int ofs = MIN(x, 0);
    struct font* pf = rb->font_get(osd.font);

    ucs = rb->bidi_l2v(str, 1);

    x += osd.x;
    y += osd.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);

        if (pf->depth)
            draw_oriented_alpha_bitmap_part(bits, ofs, 0, width, x, y,
                                            width - ofs, pf->height);
        else
            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 = mylcd_get_drawmode();
    mylcd_set_drawmode(DRMODE_FG);
    mylcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height);
    mylcd_set_drawmode(mode);
}

static void draw_putsxy_oriented(int x, int y, const char *str)
{
    int mode = mylcd_get_drawmode();
    mylcd_set_drawmode(DRMODE_FG);
    mylcd_putsxy(x + osd.x, y + osd.y, str);
    mylcd_set_drawmode(mode);
}
#endif /* LCD_PORTRAIT */

/** FPS Display **/

/* Post-frame callback (on video thread) - update the FPS rectangle from the
 * framebuffer */
static void fps_post_frame_callback(void)
{
    vo_lock();
    mylcd_update_rect(fps.pf_x, fps.pf_y,
                      fps.pf_width, fps.pf_height);
    vo_unlock();
}

/* Set up to have the callback only update the intersection of the video
 * rectangle and the FPS text rectangle - if they don't intersect, then
 * the callback is set to NULL */
static void fps_update_post_frame_callback(void)
{
    void (*cb)(void) = NULL;

    if (settings.showfps) {
        struct vo_rect cliprect;

        if (stream_vo_get_clip(&cliprect)) {
            /* Oriented screen coordinates -> OSD coordinates */
            vo_rect_offset(&cliprect, -osd.x, -osd.y);

            if (vo_rect_intersect(&cliprect, &cliprect, &fps.rect)) {
                int x = cliprect.l;
                int y = cliprect.t;
                int width = cliprect.r - cliprect.l;
                int height = cliprect.b - cliprect.t;

                /* OSD coordinates -> framebuffer coordinates */
                fps.pf_x = __X;
                fps.pf_y = __Y;
                fps.pf_width = __W;
                fps.pf_height = __H;

                cb = fps_post_frame_callback;
            }
        }
    }

    stream_set_callback(VIDEO_SET_POST_FRAME_CALLBACK, cb);
}

/* Refresh the FPS display */
static void fps_refresh(void)
{
    char str[FPS_BUFSIZE];
    struct video_output_stats stats;
    int w, h, sw;
    long tick;

    tick = *rb->current_tick;

    if (TIME_BEFORE(tick, fps.update_tick))
        return;

    fps.update_tick = tick + FPS_UPDATE_INTERVAL;

    stream_video_stats(&stats);

    rb->snprintf(str, FPS_BUFSIZE, FPS_FORMAT,
                 stats.fps / 100, stats.fps % 100);

    w = fps.rect.r - fps.rect.l;
    h = fps.rect.b - fps.rect.t;

    draw_clear_area(fps.rect.l, fps.rect.t, w, h);
    mylcd_getstringsize(str, &sw, NULL);
    draw_putsxy_oriented(fps.rect.r - sw, fps.rect.t, str);

    vo_lock();
    draw_update_rect(fps.rect.l, fps.rect.t, w, h);
    vo_unlock();
}

/* Initialize the FPS display */
static void fps_init(void)
{
    fps.update_tick = *rb->current_tick;
    fps.rect.l = fps.rect.t = 0;
    mylcd_getstringsize(FPS_DIMSTR, &fps.rect.r, &fps.rect.b);
    vo_rect_offset(&fps.rect, -osd.x, -osd.y);
    fps_update_post_frame_callback();
}

/** OSD **/

#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
/* So we can refresh the overlay */
static void osd_lcd_enable_hook(unsigned short id, void* param)
{
    (void)id;
    (void)param;
    rb->button_queue_post(LCD_ENABLE_EVENT_1, 0);
}
#endif

static void osdbacklight_hw_on_video_mode(bool video_on)
{
    if (video_on) {
        /* Turn off backlight timeout */
        backlight_ignore_timeout();

#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
        rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
#endif
    } else {
#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
        rb->add_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
#endif
        /* Revert to user's backlight settings */
        backlight_use_settings();
    }
}

#ifdef HAVE_BACKLIGHT_BRIGHTNESS
static void osd_backlight_brightness_video_mode(bool video_on)
{
    if (settings.backlight_brightness < 0)
        return;

    mpeg_backlight_update_brightness(
        video_on ? settings.backlight_brightness : -1);
}
#else
#define osd_backlight_brightness_video_mode(video_on)
#endif /* HAVE_BACKLIGHT_BRIGHTNESS */

static void osd_text_init(void)
{
    struct hms hms;
    char buf[32];
    int phys;
    int spc_width;

    draw_setfont(FONT_UI);

    osd.x = 0;
    osd.width = SCREEN_WIDTH;

    vo_rect_clear(&osd.time_rect);
    vo_rect_clear(&osd.stat_rect);
    vo_rect_clear(&osd.prog_rect);
    vo_rect_clear(&osd.vol_rect);

    ts_to_hms(stream_get_duration(), &hms);
    hms_format(buf, sizeof (buf), &hms);
    mylcd_getstringsize(buf, &osd.time_rect.r, &osd.time_rect.b);

    /* Choose well-sized bitmap images relative to font height */
    if (osd.time_rect.b < 12) {
        osd.icons = mpegplayer_status_icons_8x8x1;
        osd.stat_rect.r = osd.stat_rect.b = 8;
    } else if (osd.time_rect.b < 16) {
        osd.icons = mpegplayer_status_icons_12x12x1;
        osd.stat_rect.r = osd.stat_rect.b = 12;
    } else {
        osd.icons = mpegplayer_status_icons_16x16x1;
        osd.stat_rect.r = osd.stat_rect.b = 16;
    }

    if (osd.stat_rect.b < osd.time_rect.b) {
        vo_rect_offset(&osd.stat_rect, 0,
                       (osd.time_rect.b - osd.stat_rect.b) / 2 + OSD_BDR_T);
        vo_rect_offset(&osd.time_rect, OSD_BDR_L, OSD_BDR_T);
    } else {
        vo_rect_offset(&osd.time_rect, OSD_BDR_L,
                       osd.stat_rect.b - osd.time_rect.b + OSD_BDR_T);
        vo_rect_offset(&osd.stat_rect, 0, OSD_BDR_T);
    }

    osd.dur_rect = osd.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));

    mylcd_getstringsize(" ", &spc_width, NULL);
    mylcd_getstringsize(buf, &osd.vol_rect.r, &osd.vol_rect.b);

    osd.prog_rect.r = SCREEN_WIDTH - OSD_BDR_L - spc_width -
                           osd.vol_rect.r - OSD_BDR_R;
    osd.prog_rect.b = 3*osd.stat_rect.b / 4;
    vo_rect_offset(&osd.prog_rect, osd.time_rect.l,
                   osd.time_rect.b);

    vo_rect_offset(&osd.stat_rect,
                   (osd.prog_rect.r + osd.prog_rect.l - osd.stat_rect.r) / 2,
                   0);

    vo_rect_offset(&osd.dur_rect,
                   osd.prog_rect.r - osd.dur_rect.r, 0);

    vo_rect_offset(&osd.vol_rect, osd.prog_rect.r + spc_width,
                   (osd.prog_rect.b + osd.prog_rect.t - osd.vol_rect.b) / 2);

    osd.height = OSD_BDR_T + MAX(osd.prog_rect.b, osd.vol_rect.b) -
                    MIN(osd.time_rect.t, osd.stat_rect.t) + OSD_BDR_B;

#ifdef HAVE_LCD_COLOR
    osd.height = ALIGN_UP(osd.height, 2);
#endif
    osd.y = SCREEN_HEIGHT - osd.height;

    draw_setfont(FONT_SYSFIXED);
}

static void osd_init(void)
{
    osd.flags = 0;
    osd.show_for = HZ*4;
    osd.print_delay = 75*HZ/100;
    osd.resume_delay = HZ/2;
#ifdef HAVE_LCD_COLOR
    osd.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd);
    osd.fgcolor = LCD_WHITE;
    osd.prog_fillcolor = LCD_BLACK;
#else
    osd.bgcolor = GREY_LIGHTGRAY;
    osd.fgcolor = GREY_BLACK;
    osd.prog_fillcolor = GREY_WHITE;
#endif
    osd.curr_time = 0;
    osd.status = OSD_STATUS_STOPPED;
    osd.auto_refresh = OSD_REFRESH_TIME;
    osd.next_auto_refresh = *rb->current_tick;
    osd_text_init();
    fps_init();
}

#ifdef HAVE_HEADPHONE_DETECTION
static void osd_set_hp_pause_flag(bool set)
{
    if (set)
        osd.flags |= OSD_HP_PAUSE;
    else
        osd.flags &= ~OSD_HP_PAUSE;
}
#else
#define osd_set_hp_pause_flag(set)
#endif /* HAVE_HEADPHONE_DETECTION */

static void osd_schedule_refresh(unsigned refresh)
{
    long tick = *rb->current_tick;

    if (refresh & OSD_REFRESH_VIDEO)
        osd.print_tick = tick + osd.print_delay;

    if (refresh & OSD_REFRESH_RESUME)
        osd.resume_tick = tick + osd.resume_delay;

    osd.auto_refresh |= refresh;
}

static void osd_cancel_refresh(unsigned refresh)
{
    osd.auto_refresh &= ~refresh;
}

/* Refresh the background area */
static void osd_refresh_background(void)
{
    char buf[32];
    struct hms hms;

    unsigned bg = mylcd_get_background();
    mylcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);

#ifdef HAVE_LCD_COLOR
    /* Draw a "raised" area for our graphics */
    mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 192));
    draw_hline(0, osd.width, 0);

    mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 80));
    draw_hline(0, osd.width, 1);

    mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 48));
    draw_hline(0, osd.width, osd.height-2);

    mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 128));
    draw_hline(0, osd.width, osd.height-1);

    mylcd_set_background(bg);
    draw_clear_area(0, 2, osd.width, osd.height - 4);
#else
    /* Give contrast with the main background */
    mylcd_set_background(MYLCD_WHITE);
    draw_hline(0, osd.width, 0);

    mylcd_set_background(MYLCD_DARKGRAY);
    draw_hline(0, osd.width, osd.height-1);

    mylcd_set_background(bg);
    draw_clear_area(0, 1, osd.width, osd.height - 2);
#endif

    vo_rect_set_ext(&osd.update_rect, 0, 0, osd.width, osd.height);
    mylcd_set_drawmode(DRMODE_SOLID);

    if (stream_get_duration() != INVALID_TIMESTAMP) {
        /* Draw the movie duration */
        ts_to_hms(stream_get_duration(), &hms);
        hms_format(buf, sizeof (buf), &hms);
        draw_putsxy_oriented(osd.dur_rect.l, osd.dur_rect.t, buf);
    }
    /* else don't know the duration */
}

/* Refresh the current time display + the progress bar */
static void osd_refresh_time(void)
{
    char buf[32];
    struct hms hms;

    uint32_t duration = stream_get_duration();

    draw_scrollbar_draw_rect(&osd.prog_rect, 0, duration,
                             osd.curr_time);

    ts_to_hms(osd.curr_time, &hms);
    hms_format(buf, sizeof (buf), &hms);

    draw_clear_area_rect(&osd.time_rect);
    draw_putsxy_oriented(osd.time_rect.l, osd.time_rect.t, buf);

    vo_rect_union(&osd.update_rect, &osd.update_rect,
                  &osd.prog_rect);
    vo_rect_union(&osd.update_rect, &osd.update_rect,
                  &osd.time_rect);
}

/* Refresh the volume display area */
static void osd_refresh_volume(void)
{
    char buf[32];
    int width;

    int volume = rb->global_status->volume;
    rb->snprintf(buf, sizeof (buf), "%d%s",
                 rb->sound_val2phys(SOUND_VOLUME, volume),
                 rb->sound_unit(SOUND_VOLUME));
    mylcd_getstringsize(buf, &width, NULL);

    /* Right-justified */
    draw_clear_area_rect(&osd.vol_rect);
    draw_putsxy_oriented(osd.vol_rect.r - width, osd.vol_rect.t, buf);

    vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.vol_rect);
}

/* Refresh the status icon */
static void osd_refresh_status(void)
{
    int icon_size = osd.stat_rect.r - osd.stat_rect.l;

    draw_clear_area_rect(&osd.stat_rect);

#ifdef HAVE_LCD_COLOR
    /* Draw status icon with a drop shadow */
    unsigned oldfg = mylcd_get_foreground();
    int i = 1;

    mylcd_set_foreground(draw_blendcolor(mylcd_get_background(),
                        MYLCD_BLACK, 96));

    while (1)
    {
        draw_oriented_mono_bitmap_part(osd.icons,
                                       icon_size*osd.status,
                                       0,
                                       icon_size*OSD_STATUS_COUNT,
                                       osd.stat_rect.l + osd.x + i,
                                       osd.stat_rect.t + osd.y + i,
                                       icon_size, icon_size);

        if (--i < 0)
            break;

        mylcd_set_foreground(oldfg);
    }

    vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
#else
    draw_oriented_mono_bitmap_part(osd.icons,
                                   icon_size*osd.status,
                                   0,
                                   icon_size*OSD_STATUS_COUNT,
                                   osd.stat_rect.l + osd.x,
                                   osd.stat_rect.t + osd.y,
                                   icon_size, icon_size);
    vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
#endif
}

/* Update the current status which determines which icon is displayed */
static bool osd_update_status(void)
{
    int status;

    switch (stream_status())
    {
    default:
        status = OSD_STATUS_STOPPED;
        break;
    case STREAM_PAUSED:
        /* If paused with a pending resume, coerce it to OSD_STATUS_PLAYING */
        status = (osd.auto_refresh & OSD_REFRESH_RESUME) ?
            OSD_STATUS_PLAYING : OSD_STATUS_PAUSED;
        break;
    case STREAM_PLAYING:
        status = OSD_STATUS_PLAYING;
        break;
    }

    if (status != osd.status) {
        /* A refresh is needed */
        osd.status = status;
        return true;
    }

    return false;
}

/* Update the current time that will be displayed */
static void osd_update_time(void)
{
    uint32_t start;
    osd.curr_time = stream_get_seek_time(&start);
    osd.curr_time -= start;
}

/* Refresh various parts of the OSD - showing it if it is hidden */
static void osd_refresh(int hint)
{
    long tick;
    unsigned oldbg, oldfg;

    tick = *rb->current_tick;

    if (settings.showfps)
        fps_refresh();

    if (hint == OSD_REFRESH_DEFAULT) {
        /* The default which forces no updates */

        /* Make sure Rockbox doesn't turn off the player because of
           too little activity */
        if (osd.status == OSD_STATUS_PLAYING)
            rb->reset_poweroff_timer();

        /* Redraw the current or possibly extract a new video frame */
        if ((osd.auto_refresh & OSD_REFRESH_VIDEO) &&
            TIME_AFTER(tick, osd.print_tick)) {
            osd.auto_refresh &= ~OSD_REFRESH_VIDEO;
            stream_draw_frame(false);
        }

        /* Restart playback if the timout was reached */
        if ((osd.auto_refresh & OSD_REFRESH_RESUME) &&
            TIME_AFTER(tick, osd.resume_tick)) {
            osd.auto_refresh &= ~(OSD_REFRESH_RESUME | OSD_REFRESH_VIDEO);
            stream_resume();
        }

        /* If not visible, return */
        if (!(osd.flags & OSD_SHOW))
            return;

        /* Hide if the visibility duration was reached */
        if (TIME_AFTER(tick, osd.hide_tick)) {
            osd_show(OSD_HIDE);
            return;
        }
    } else {
        /* A forced update of some region */

        /* Show if currently invisible */
        if (!(osd.flags & OSD_SHOW)) {
            /* Avoid call back into this function - it will be drawn */
            osd_show(OSD_SHOW | OSD_NODRAW);
            hint = OSD_REFRESH_ALL;
        }

        /* Move back timeouts for frame print and hide */
        osd.print_tick = tick + osd.print_delay;
        osd.hide_tick = tick + osd.show_for;
    }

    if (TIME_AFTER(tick, osd.next_auto_refresh)) {
        /* Refresh whatever graphical elements are due automatically */
        osd.next_auto_refresh = tick + OSD_MIN_UPDATE_INTERVAL;

        if (osd.auto_refresh & OSD_REFRESH_STATUS) {
            if (osd_update_status())
                hint |= OSD_REFRESH_STATUS;
        }

        if (osd.auto_refresh & OSD_REFRESH_TIME) {
            osd_update_time();
            hint |= OSD_REFRESH_TIME;
        }
    }

    if (hint == 0)
        return; /* No drawing needed */

    /* Set basic drawing params that are used. Elements that perform variations
     * will restore them. */
    oldfg = mylcd_get_foreground();
    oldbg = mylcd_get_background();

    draw_setfont(FONT_UI);
    mylcd_set_foreground(osd.fgcolor);
    mylcd_set_background(osd.bgcolor);

    vo_rect_clear(&osd.update_rect);

    if (hint & OSD_REFRESH_BACKGROUND) {
        osd_refresh_background();
        hint |= OSD_REFRESH_ALL; /* Requires a redraw of everything */
    }

    if (hint & OSD_REFRESH_TIME) {
        osd_refresh_time();
    }

    if (hint & OSD_REFRESH_VOLUME) {
        osd_refresh_volume();
    }

    if (hint & OSD_REFRESH_STATUS) {
        osd_refresh_status();
    }

    /* Go back to defaults */
    draw_setfont(FONT_SYSFIXED);
    mylcd_set_foreground(oldfg);
    mylcd_set_background(oldbg);

    /* Update the dirty rectangle */
    vo_lock();

    draw_update_rect(osd.update_rect.l,
                     osd.update_rect.t,
                     osd.update_rect.r - osd.update_rect.l,
                     osd.update_rect.b - osd.update_rect.t);

    vo_unlock();
}

/* Show/Hide the OSD */
static void osd_show(unsigned show)
{
    if (((show ^ osd.flags) & OSD_SHOW) == 0)
    {
        if (show & OSD_SHOW) {
            osd.hide_tick = *rb->current_tick + osd.show_for;
        }
        return;
    }

    if (show & OSD_SHOW) {
        /* Clip away the part of video that is covered */
        struct vo_rect rc = { 0, 0, SCREEN_WIDTH, osd.y };

        osd.flags |= OSD_SHOW;

        if (osd.status != OSD_STATUS_PLAYING) {
            /* Not playing - set brightness to mpegplayer setting */
            osd_backlight_brightness_video_mode(true);
        }

        stream_vo_set_clip(&rc);

        if (!(show & OSD_NODRAW))
            osd_refresh(OSD_REFRESH_ALL);
    } else {
        /* Uncover clipped video area and redraw it */
        osd.flags &= ~OSD_SHOW;

        draw_clear_area(0, 0, osd.width, osd.height);

        if (!(show & OSD_NODRAW)) {
            vo_lock();
            draw_update_rect(0, 0, osd.width, osd.height);
            vo_unlock();

            stream_vo_set_clip(NULL);
            stream_draw_frame(false);
        } else {
            stream_vo_set_clip(NULL);
        }

        if (osd.status != OSD_STATUS_PLAYING) {
            /* Not playing - restore backlight brightness */
            osd_backlight_brightness_video_mode(false);
        }
    }
}

/* Set the current status - update screen if specified */
static void osd_set_status(int status)
{
    bool draw = (status & OSD_NODRAW) == 0;

    status &= OSD_STATUS_MASK;

    if (osd.status != status) {

        osd.status = status;

        if (draw)
            osd_refresh(OSD_REFRESH_STATUS);
    }
}

/* Get the current status value */
static int osd_get_status(void)
{
    return osd.status & OSD_STATUS_MASK;
}

/* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;)
 * Returns last button code
 */
static int osd_ff_rw(int btn, unsigned refresh, uint32_t *new_time)
{
    unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step;
    const long ff_rw_accel = (rb->global_settings->ff_rewind_accel + 3);
    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 = osd.status;
    int new_btn;

    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME |
                       OSD_REFRESH_TIME);

    time -= start; /* Absolute clock => stream-relative */

    switch (btn)
    {
    case MPEG_FF:
#ifdef MPEG_FF2
    case MPEG_FF2:
#endif
#ifdef MPEG_RC_FF
    case MPEG_RC_FF:
#endif
        osd_set_status(OSD_STATUS_FF);
        new_btn = btn | BUTTON_REPEAT; /* simplify code below */
        break;
    case MPEG_RW:
#ifdef MPEG_RW2
    case MPEG_RW2:
#endif
#ifdef MPEG_RC_RW
    case MPEG_RC_RW:
#endif
        osd_set_status(OSD_STATUS_RW);
        new_btn = btn | BUTTON_REPEAT; /* simplify code below */
        break;
    default:
        new_btn = BUTTON_NONE; /* Fail tests below but still do proper exit */
    }

    while (1)
    {
        stream_keep_disk_active();

        if (new_btn == (btn | BUTTON_REPEAT)) {
            if (osd.status == OSD_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;

            /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
            step += step >> ff_rw_accel;

            if (osd.status == OSD_STATUS_FF) {
                if (duration - time <= ff_rw_count)
                    ff_rw_count = duration - time;

                osd.curr_time = time + ff_rw_count;
            } else {
                if (time <= ff_rw_count)
                    ff_rw_count = time;

                osd.curr_time = time - ff_rw_count;
            }

            osd_refresh(OSD_REFRESH_TIME);

            new_btn = mpeg_button_get(TIMEOUT_BLOCK);
        }
        else {
            if (new_btn == (btn | BUTTON_REL)) {
                if (osd.status == OSD_STATUS_FF)
                    time += ff_rw_count;
                else if (osd.status == OSD_STATUS_RW)
                    time -= ff_rw_count;
            }

            *new_time = time;

            osd_schedule_refresh(refresh);
            osd_set_status(status);
            osd_schedule_refresh(OSD_REFRESH_TIME);

            return new_btn;
        }
    }
}

/* Return adjusted STREAM_* status */
static int osd_stream_status(void)
{
    int status = stream_status();

    /* Coerce to STREAM_PLAYING if paused with a pending resume */
    if (status == STREAM_PAUSED) {
        if (osd.auto_refresh & OSD_REFRESH_RESUME)
            status = STREAM_PLAYING;
    }

    return status;
}

/* Change the current audio volume by a specified amount */
static void osd_set_volume(int delta)
{
    int vol = rb->global_status->volume;
    int limit;

    vol += delta;

    if (delta < 0) {
        /* Volume down - clip to lower limit */
        limit = rb->sound_min(SOUND_VOLUME);
        if (vol < limit)
            vol = limit;
    } else {
        /* Volume up - clip to upper limit */
        limit = rb->sound_max(SOUND_VOLUME);
        if (vol > limit)
            vol = limit;
    }

    /* Sync the global settings */
    if (vol != rb->global_status->volume)
        rb->sound_set(SOUND_VOLUME, vol);

    /* Update the volume display */
    osd_refresh(OSD_REFRESH_VOLUME);
}

/* Begin playback at the specified time */
static int osd_play(uint32_t time)
{
    int retval;

    osd_set_hp_pause_flag(false);
    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);

    retval = stream_seek(time, SEEK_SET);

    if (retval >= STREAM_OK) {
        osdbacklight_hw_on_video_mode(true);
        osd_backlight_brightness_video_mode(true);
        stream_show_vo(true);

        retval = stream_play();

        if (retval >= STREAM_OK)
            osd_set_status(OSD_STATUS_PLAYING | OSD_NODRAW);
    }

    return retval;
}

/* Halt playback - pause engine and return logical state */
static int osd_halt(void)
{
    int status = stream_pause();

    /* Coerce to STREAM_PLAYING if paused with a pending resume */
    if (status == STREAM_PAUSED) {
        if (osd_get_status() == OSD_STATUS_PLAYING)
            status = STREAM_PLAYING;
    }

    /* Cancel some auto refreshes - caller will restart them if desired */
    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);

    /* No backlight fiddling here - callers does the right thing */

    return status;
}

/* Pause playback if playing */
static int osd_pause(void)
{
    unsigned refresh = osd.auto_refresh;
    int status = osd_halt();

    osd_set_hp_pause_flag(false);

    if (status == STREAM_PLAYING && (refresh & OSD_REFRESH_RESUME)) {
        /* Resume pending - change to a still video frame update */
        osd_schedule_refresh(OSD_REFRESH_VIDEO);
    }

    osd_set_status(OSD_STATUS_PAUSED);

    osdbacklight_hw_on_video_mode(false);
    /* Leave brightness alone and restore it when OSD is hidden */

    if (stream_can_seek() && rb->global_settings->pause_rewind) {
        stream_seek(-rb->global_settings->pause_rewind*TS_SECOND,
                    SEEK_CUR);
        osd_schedule_refresh(OSD_REFRESH_VIDEO);
        /* Update time display now */
        osd_update_time();
        osd_refresh(OSD_REFRESH_TIME);
    }

    return status;
}

/* Resume playback if halted or paused */
static void osd_resume(void)
{
    /* Cancel video and resume auto refresh - the resyc when starting
     * playback will perform those tasks */
    osd_set_hp_pause_flag(false);
    osdbacklight_hw_on_video_mode(true);
    osd_backlight_brightness_video_mode(true);
    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
    osd_set_status(OSD_STATUS_PLAYING);
    stream_resume();
}

/* Stop playback - remember the resume point if not closed */
static void osd_stop(void)
{
    uint32_t resume_time;

    osd_set_hp_pause_flag(false);
    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
    osd_set_status(OSD_STATUS_STOPPED | OSD_NODRAW);
    osd_show(OSD_HIDE);

    stream_stop();

    resume_time = stream_get_resume_time();

    if (resume_time != INVALID_TIMESTAMP)
        settings.resume_time = resume_time;

    osdbacklight_hw_on_video_mode(false);
    osd_backlight_brightness_video_mode(false);
}

/* Perform a seek by button if seeking is possible for this stream.
 *
 * A delay will be inserted before restarting in case the user decides to
 * seek again soon after.
 *
 * Returns last button code
 */
static int osd_seek_btn(int btn)
{
    int status;
    unsigned refresh = 0;
    uint32_t time;

    if (!stream_can_seek())
        return true;

    /* Halt playback - not strictly necessary but nice when doing
     * buttons */
    status = osd_halt();

    if (status == STREAM_STOPPED)
        return true;

    osd_show(OSD_SHOW);

    /* Obtain a new playback point according to the buttons */
    if (status == STREAM_PLAYING)
        refresh = OSD_REFRESH_RESUME; /* delay resume if playing */
    else
        refresh = OSD_REFRESH_VIDEO;  /* refresh if paused */

    btn = osd_ff_rw(btn, refresh, &time);

    /* Tell engine to resume at that time */
    stream_seek(time, SEEK_SET);

    return btn;
}

/* Perform a seek by time if seeking is possible for this stream
 *
 * If playing, the seeking is immediate, otherise a delay is added to showing
 * a still if paused in case the user does another seek soon after.
 *
 * If seeking isn't possible, a time of zero performs a skip to the
 * beginning.
 */
static void osd_seek_time(uint32_t time)
{
    int status;
    unsigned refresh = 0;

    if (!stream_can_seek() && time != 0)
        return;

    stream_wait_status();
    status = osd_stream_status();

    if (status == STREAM_STOPPED)
        return;

    if (status == STREAM_PLAYING)    /* merely preserve resume */
        refresh = osd.auto_refresh & OSD_REFRESH_RESUME;
    else
        refresh = OSD_REFRESH_VIDEO; /* refresh if paused */

    /* Cancel print or resume if pending */
    osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);

    /* Tell engine to seek to the given time - no state change */
    stream_seek(time, SEEK_SET);

    osd_update_time();
    osd_refresh(OSD_REFRESH_TIME);
    osd_schedule_refresh(refresh);
}

/* Has this file one of the supported extensions? */
static bool is_videofile(const char* file)
{
    static const char * const extensions[] =
    {
        /* Should match apps/plugins/viewers.config */
        "mpg", "mpeg", "mpv", "m2v"
    };

    const char* ext = rb->strrchr(file, '.');
    int i;

    if (!ext)
        return false;

    for (i = ARRAYLEN(extensions) - 1; i >= 0; i--)
    {
        if (!rb->strcasecmp(ext + 1, extensions[i]))
            break;
    }

    return i >= 0;
}

/* deliver the next/previous video file in the current directory.
   returns false if there is none. */
static bool get_videofile(int direction, char* videofile, size_t bufsize)
{
    struct tree_context *tree = rb->tree_get_context();
    struct entry *dircache = rb->tree_get_entries(tree);
    int i, step, end, found = 0;
    char *videoname = rb->strrchr(videofile, '/') + 1;
    size_t rest = bufsize - (videoname - videofile) - 1;

    if (direction == VIDEO_NEXT) {
        i = 0;
        step = 1;
        end = tree->filesindir;
    } else {
        i = tree->filesindir-1;
        step = -1;
        end = -1;
    }
    for (; i != end; i += step)
    {
        const char* name = dircache[i].name;
        if (!rb->strcmp(name, videoname)) {
            found = 1;
            continue;
        }
        if (found && rb->strlen(name) <= rest &&
            !(dircache[i].attr & ATTR_DIRECTORY) && is_videofile(name))
        {
            rb->strcpy(videoname, name);
            return true;
        }
    }

    return false;
}

#ifdef HAVE_HEADPHONE_DETECTION
/* Handle SYS_PHONE_PLUGGED/UNPLUGGED */
static void osd_handle_phone_plug(bool inserted)
{
    if (rb->global_settings->unplug_mode == 0)
        return;

    /* Wait for any incomplete state transition to complete first */
    stream_wait_status();

    int status = osd_stream_status();

    if (inserted) {
        if (rb->global_settings->unplug_mode > 1) {
            if (status == STREAM_PAUSED &&
                (osd.flags & OSD_HP_PAUSE)) {
                osd_resume();
            }
        }
    } else {
        if (status == STREAM_PLAYING) {
            osd_pause();

            osd_set_hp_pause_flag(true);
        }
    }
}
#endif

static int button_loop(void)
{
    int next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;

    rb->lcd_setfont(FONT_SYSFIXED);
#ifdef HAVE_LCD_COLOR
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
#endif
    rb->lcd_clear_display();
    rb->lcd_update();

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
    rb->lcd_set_mode(LCD_MODE_YUV);
#endif

    osd_init();

    /* Start playback at the specified starting time */
    if (osd_play(settings.resume_time) < STREAM_OK) {
        rb->splash(HZ*2, "Playback failed");
        return VIDEO_STOP;
    }

    /* Gently poll the video player for EOS and handle UI */
    while (stream_status() != STREAM_STOPPED)
    {
        int button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL/2);

        switch (button)
        {
        case BUTTON_NONE:
        {
            osd_refresh(OSD_REFRESH_DEFAULT);
            continue;
            } /* BUTTON_NONE: */

#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
        case LCD_ENABLE_EVENT_1:
        {
            /* Draw the current frame if prepared already */
            stream_draw_frame(true);
            break;
            } /* LCD_ENABLE_EVENT_1: */
#endif

        case MPEG_VOLUP:
        case MPEG_VOLUP|BUTTON_REPEAT:
#ifdef MPEG_VOLUP2
        case MPEG_VOLUP2:
        case MPEG_VOLUP2|BUTTON_REPEAT:
#endif
#ifdef MPEG_RC_VOLUP
        case MPEG_RC_VOLUP:
        case MPEG_RC_VOLUP|BUTTON_REPEAT:
#endif
        {
            osd_set_volume(+1);
            break;
            } /* MPEG_VOLUP*: */

        case MPEG_VOLDOWN:
        case MPEG_VOLDOWN|BUTTON_REPEAT:
#ifdef MPEG_VOLDOWN2
        case MPEG_VOLDOWN2:
        case MPEG_VOLDOWN2|BUTTON_REPEAT:
#endif
#ifdef MPEG_RC_VOLDOWN
        case MPEG_RC_VOLDOWN:
        case MPEG_RC_VOLDOWN|BUTTON_REPEAT:
#endif
        {
            osd_set_volume(-1);
            break;
            } /* MPEG_VOLDOWN*: */

        case MPEG_MENU:
#ifdef MPEG_RC_MENU
        case MPEG_RC_MENU:
#endif
        {
            int state = osd_halt(); /* save previous state */
            int result;

            /* Hide video output */
            osd_show(OSD_HIDE | OSD_NODRAW);
            stream_show_vo(false);
            osd_backlight_brightness_video_mode(false);

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
            rb->lcd_set_mode(LCD_MODE_RGB565);
#endif

            result = mpeg_menu();

            next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;

            fps_update_post_frame_callback();

            /* The menu can change the font, so restore */
            rb->lcd_setfont(FONT_SYSFIXED);
#ifdef HAVE_LCD_COLOR
            rb->lcd_set_foreground(LCD_WHITE);
            rb->lcd_set_background(LCD_BLACK);
#endif
            rb->lcd_clear_display();
            rb->lcd_update();

            switch (result)
            {
            case MPEG_MENU_QUIT:
                next_action = VIDEO_STOP;
                osd_stop();
                break;

            default:
#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
                rb->lcd_set_mode(LCD_MODE_YUV);
#endif
                /* If not stopped, show video again */
                if (state != STREAM_STOPPED) {
                    osd_show(OSD_SHOW);
                    stream_show_vo(true);
                }

                /* If stream was playing, restart it */
                if (state == STREAM_PLAYING) {
                    osd_resume();
                }
                break;
            }
            break;
            } /* MPEG_MENU: */

#ifdef MPEG_SHOW_OSD
        case MPEG_SHOW_OSD:
        case MPEG_SHOW_OSD | BUTTON_REPEAT:
            /* Show if not visible */
            osd_show(OSD_SHOW);
            /* Make sure it refreshes */
            osd_refresh(OSD_REFRESH_DEFAULT);
            break;
#endif

        case MPEG_STOP:
#ifdef MPEG_RC_STOP
        case MPEG_RC_STOP:
#endif
        case ACTION_STD_CANCEL:
        {
        cancel_playback:
            next_action = VIDEO_STOP;
            osd_stop();
            break;
            } /* MPEG_STOP: */

        case MPEG_PAUSE:
#ifdef MPEG_PAUSE2
        case MPEG_PAUSE2:
#endif
#ifdef MPEG_RC_PAUSE
        case MPEG_RC_PAUSE:
#endif
        {
            int status = osd_stream_status();

            if (status == STREAM_PLAYING) {
                /* Playing => Paused */
                osd_pause();
            }
            else if (status == STREAM_PAUSED) {
                /* Paused => Playing */
                osd_resume();
            }

            break;
            } /* MPEG_PAUSE*: */

        case MPEG_RW:
#ifdef MPEG_RW2
        case MPEG_RW2:
#endif
#ifdef MPEG_RC_RW
        case MPEG_RC_RW:
#endif
        {
            int old_button = button;

            /* If button has been released: skip to next/previous file */
            button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL);

            if ((old_button | BUTTON_REL) == button) {
                /* Check current playback position */
                osd_update_time();

                if (settings.play_mode == 0 || osd.curr_time >= 3*TS_SECOND) {
                    /* Start the current video from the beginning */
                    osd_seek_time(0*TS_SECOND);
                }
                else {
                    /* Release within 3 seconds of start: skip to previous
                     * file */
                    osd_stop();
                    next_action = VIDEO_PREV | VIDEO_ACTION_MANUAL;
                }
            }
            else if ((button & ~BUTTON_REPEAT) == old_button) {
                button = osd_seek_btn(old_button);
            }

            if (button == ACTION_STD_CANCEL)
                goto cancel_playback; /* jump to stop handling above */

            rb->default_event_handler(button);
            break;
            } /* MPEG_RW: */

        case MPEG_FF:
#ifdef MPEG_FF2
        case MPEG_FF2:
#endif
#ifdef MPEG_RC_FF
        case MPEG_RC_FF:
#endif
        {
            int old_button = button;

            if (settings.play_mode != 0)
                button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL);

            if ((old_button | BUTTON_REL) == button) {
                /* If button has been released: skip to next file */
                osd_stop();
                next_action = VIDEO_NEXT | VIDEO_ACTION_MANUAL;
            }
            else if ((button & ~BUTTON_REPEAT) == old_button) {
                button = osd_seek_btn(old_button);
            }

            if (button == ACTION_STD_CANCEL)
                goto cancel_playback; /* jump to stop handling above */

            rb->default_event_handler(button);
            break;
            } /* MPEG_FF: */

#ifdef HAVE_HEADPHONE_DETECTION
        case SYS_PHONE_PLUGGED:
        case SYS_PHONE_UNPLUGGED:
        {
            osd_handle_phone_plug(button == SYS_PHONE_PLUGGED);
            break;
            } /* SYS_PHONE_*: */
#endif

        default:
        {
            osd_refresh(OSD_REFRESH_DEFAULT);
            rb->default_event_handler(button);
            break;
            } /* default: */
        }

        rb->yield();
    } /* end while */

    osd_stop();

#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
    /* Be sure hook is removed before exiting since the stop will put it
     * back because of the backlight restore. */
    rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
#endif

    rb->lcd_setfont(FONT_UI);

    return next_action;
}

enum plugin_status plugin_start(const void* parameter)
{
    static char videofile[MAX_PATH];
    int status = PLUGIN_OK; /* assume success */
    bool quit = false;

#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR)
    bool preserved_talk_state;
#endif

    if (parameter == NULL) {
        /* No file = GTFO */
        rb->splash(HZ*2, "No File");
        return PLUGIN_ERROR;
    }

    /* Disable all talking before initializing IRAM */
    rb->talk_disable(true);

#ifdef PLUGIN_USE_IRAM
    iram_saving_init();

#ifndef SIMULATOR
    preserved_talk_state = rb->global_settings->talk_menu;
    if (!iram_saved_copy)
        rb->global_settings->talk_menu = false;
#endif
#endif

#ifdef HAVE_LCD_COLOR
    rb->lcd_set_backdrop(NULL);
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
#endif

    rb->lcd_clear_display();
    rb->lcd_update();

    rb->strcpy(videofile, (const char*) parameter);

    if (stream_init() < STREAM_OK) {
        /* Fatal because this should not fail */
        DEBUGF("Could not initialize streams\n");
        status = PLUGIN_ERROR;
    } else {
        int next_action = VIDEO_STOP;
        bool get_videofile_says = true;

        while (!quit)
        {
            init_settings(videofile);

            int result = stream_open(videofile);
            bool manual_skip = false;

            if (result >= STREAM_OK) {
                /* start menu */
                rb->lcd_clear_display();
                rb->lcd_update();
                result = mpeg_start_menu(stream_get_duration());

                next_action = VIDEO_STOP;
                if (result != MPEG_START_QUIT) {
                    /* Enter button loop and process UI */
                    next_action = button_loop();
                    manual_skip = next_action & VIDEO_ACTION_MANUAL;
                    next_action &= ~VIDEO_ACTION_MANUAL;
                }

                stream_close();

                rb->lcd_clear_display();
                rb->lcd_update();

                save_settings();
            } else {
                /* Problem with file; display message about it - not
                 * considered a plugin error */
                long tick;
                const char *errstring;

                DEBUGF("Could not open %s\n", videofile);
                switch (result)
                {
                case STREAM_UNSUPPORTED:
                    errstring = "Unsupported format";
                    break;
                default:
                    errstring = "Error opening file: %d";
                }

                tick = *rb->current_tick + HZ*2;

                rb->splashf(0, errstring, result);

                /* Be sure it doesn't get stuck in an unbreakable loop of bad
                 * files, just in case! Otherwise, keep searching in the
                 * chosen direction until a good one is found. */
                while (!quit && TIME_BEFORE(*rb->current_tick, tick))
                {
                    int button = mpeg_button_get(HZ*2);

                    switch (button)
                    {
                    case MPEG_STOP:
                    case ACTION_STD_CANCEL:
                        /* Abort the search and exit */
                        next_action = VIDEO_STOP;
                        quit = true;
                        break;

                    case BUTTON_NONE:
                        if (settings.play_mode != 0) {
                            if (next_action == VIDEO_STOP) {
                                /* Default to next file */
                                next_action = VIDEO_NEXT;
                            }
                            else if (next_action == VIDEO_PREV &&
                                     !get_videofile_says) {
                                /* Was first file already; avoid endlessly
                                 * retrying it */
                                next_action = VIDEO_STOP;
                            }
                        }
                        break;

                    default:
                        rb->default_event_handler(button);
                    } /* switch */
                } /* while */
            }

            /* return value of button_loop says, what's next */
            switch (next_action)
            {
            case VIDEO_NEXT:
            {
                get_videofile_says = get_videofile(VIDEO_NEXT, videofile,
                                                   sizeof(videofile));
                /* quit after finished the last videofile */
                quit = !get_videofile_says;

                if (manual_skip)
                {
                    rb->system_sound_play(get_videofile_says ?
                                          SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE);
                }

                break;
                }
            case VIDEO_PREV:
            {
                get_videofile_says = get_videofile(VIDEO_PREV, videofile,
                                                   sizeof(videofile));
                /* if there is no previous file, play the same videofile */

                if (manual_skip)
                {
                    rb->system_sound_play(get_videofile_says ?
                                          SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE);
                }

                break;
                }
            case VIDEO_STOP:
            {
                quit = true;
                break;
                }
            }
        } /* while */
    }

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
    rb->lcd_set_mode(LCD_MODE_RGB565);
#endif

    stream_exit();

#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR)
    if (!iram_saved_copy)
        rb->global_settings->talk_menu = preserved_talk_state;
#endif

    rb->talk_disable(false);

    /* Actually handle delayed processing of system events of interest
     * that were captured in other button loops */
    mpeg_sysevent_handle();

    return status;
}
