/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * mpegplayer main entrypoint and UI implementation * * Copyright (c) 2007 Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * 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 "helper.h" #include "mpeg_settings.h" #include "mpeg2.h" #include "video_out.h" #include "stream_thread.h" #include "stream_mgr.h" PLUGIN_HEADER PLUGIN_IRAM_DECLARE /* 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 #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_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 #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_MENU BUTTON_RC_HEART #define MPEG_STOP BUTTON_POWER #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 #endif struct plugin_api* rb; CACHE_FUNCTION_WRAPPERS(rb); ALIGN_BUFFER_WRAPPER(rb); /* 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 (wvs_play(settings.resume_time) < STREAM_OK) { rb->splash(HZ*2, "Playback failed"); return; } /* Gently poll the video player for EOS and handle UI */ while (stream_status() != STREAM_STOPPED) { 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: #ifdef MPEG_VOLUP2 case MPEG_VOLUP2: case MPEG_VOLUP2|BUTTON_REPEAT: #endif { wvs_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 { wvs_set_volume(-1); break; } /* MPEG_VOLDOWN*: */ case MPEG_MENU: { 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); result = mpeg_menu(); /* The menu can change the font, so restore */ rb->lcd_setfont(FONT_SYSFIXED); switch (result) { case MPEG_MENU_QUIT: wvs_stop(); break; default: /* If not stopped, show video again */ 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); wvs_resume(); } break; } break; } /* MPEG_MENU: */ case MPEG_STOP: { wvs_stop(); break; } /* MPEG_STOP: */ case MPEG_PAUSE: #ifdef MPEG_PAUSE2 case MPEG_PAUSE2: #endif { int status = wvs_status(); if (status == STREAM_PLAYING) { /* Playing => Paused */ wvs_pause(); backlight_use_settings(rb); } else if (status == STREAM_PAUSED) { /* Paused => Playing */ backlight_force_on(rb); 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 */ wvs_stop(); stream_close(); /* Fall-through */ default: rb->default_event_handler(button); break; } rb->yield(); } /* end while */ wvs_stop(); rb->lcd_setfont(FONT_UI); /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(rb); } enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { int status = PLUGIN_ERROR; /* assume failure */ int result; int err; const char *errstring; if (parameter == NULL) { /* No file = GTFO */ api->splash(HZ*2, "No File"); return PLUGIN_ERROR; } /* Disable all talking before initializing IRAM */ api->talk_disable(true); /* Initialize IRAM - stops audio and voice as well */ PLUGIN_IRAM_INIT(api) rb = api; #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(); if (stream_init() < STREAM_OK) { DEBUGF("Could not initialize streams\n"); } else { rb->splash(0, "Loading..."); init_settings((char*)parameter); err = stream_open((char *)parameter); if (err >= STREAM_OK) { /* start menu */ rb->lcd_clear_display(); rb->lcd_update(); result = mpeg_start_menu(stream_get_duration()); if (result != MPEG_START_QUIT) { /* Enter button loop and process UI */ button_loop(); } stream_close(); rb->lcd_clear_display(); rb->lcd_update(); save_settings(); /* Save settings (if they have changed) */ status = PLUGIN_OK; } else { DEBUGF("Could not open %s\n", (char*)parameter); switch (err) { case STREAM_UNSUPPORTED: errstring = "Unsupported format"; break; default: errstring = "Error opening file: %d"; } rb->splash(HZ*2, errstring, err); } } stream_exit(); rb->talk_disable(false); return status; }