mpegplayer: Fix a bitflag value. Add some commenting to the WVS code to help readability.
[Rockbox.git] / apps / plugins / mpegplayer / mpegplayer.c
blobb228a88dda983a9b355c66cfb8d12d3118396920
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * mpegplayer main entrypoint and UI implementation
12 * Copyright (c) 2007 Michael Sevakis
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /****************************************************************************
23 * NOTES:
25 * mpegplayer is structured as follows:
27 * +-->Video Thread-->Video Output-->LCD
28 * |
29 * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device
30 * | | | | (ref. clock)
31 * | | +-->Buffer Thread |
32 * Stream Data | | (clock intf./
33 * Requests | File Cache drift adj.)
34 * | Disk I/O
35 * Stream services
36 * (timing, etc.)
38 * Thread list:
39 * 1) The main thread - Handles user input, settings, basic playback control
40 * and USB connect.
42 * 2) Stream Manager thread - Handles playback state, events from streams
43 * such as when a stream is finished, stream commands, PCM state. The
44 * layer in which this thread run also handles arbitration of data
45 * requests between the streams and the disk buffer. The actual specific
46 * transport layer code may get moved out to support multiple container
47 * formats.
49 * 3) Buffer thread - Buffers data in the background, generates notifications
50 * to streams when their data has been buffered, and watches streams'
51 * progress to keep data available during playback. Handles synchronous
52 * random access requests when the file cache is missed.
54 * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes
55 * the video stream and renders video frames to the LCD. Handles
56 * miscellaneous video tasks like frame and thumbnail printing.
58 * 5) Audio thread (running on the main CPU to maintain consistency with the
59 * audio FIQ hander on PP) - Decodes audio frames and places them into
60 * the PCM buffer for rendering by the audio device.
62 * Streams are neither aware of one another nor care about one another. All
63 * streams shall have their own thread (unless it is _really_ efficient to
64 * have a single thread handle a couple minor streams). All coordination of
65 * the streams is done through the stream manager. The clocking is controlled
66 * by and exposed by the stream manager to other streams and implemented at
67 * the PCM level.
69 * Notes about MPEG files:
71 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
73 * FPS is represented in terms of a frame period - this is always an
74 * integer number of 27MHz ticks.
76 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
77 * 900900 27MHz ticks.
79 * In libmpeg2, info->sequence->frame_period contains the frame_period.
81 * Working with Rockbox's 100Hz tick, the common frame rates would need
82 * to be as follows (1):
84 * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz
85 * --------|-----------------------------------------------------------
86 * 10* | 2700000 | 10 | 4410 | 4800
87 * 12* | 2250000 | 8.3333 | 3675 | 4000
88 * 15* | 1800000 | 6.6667 | 2940 | 3200
89 * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002
90 * 24 | 1125000 | 4.166667 | 1837.5 | 2000
91 * 25 | 1080000 | 4 | 1764 | 1920
92 * 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6
93 * 30 | 900000 | 3.333333 | 1470 | 1600
95 * *Unofficial framerates
97 * (1) But we don't really care since the audio clock is used anyway and has
98 * very fine resolution ;-)
99 *****************************************************************************/
100 #include "plugin.h"
101 #include "mpegplayer.h"
102 #include "helper.h"
103 #include "mpeg_settings.h"
104 #include "mpeg2.h"
105 #include "video_out.h"
106 #include "stream_thread.h"
107 #include "stream_mgr.h"
109 PLUGIN_HEADER
110 PLUGIN_IRAM_DECLARE
112 /* button definitions */
113 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
114 #define MPEG_MENU BUTTON_MODE
115 #define MPEG_STOP BUTTON_OFF
116 #define MPEG_PAUSE BUTTON_ON
117 #define MPEG_VOLDOWN BUTTON_DOWN
118 #define MPEG_VOLUP BUTTON_UP
119 #define MPEG_RW BUTTON_LEFT
120 #define MPEG_FF BUTTON_RIGHT
122 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
123 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
124 #define MPEG_MENU BUTTON_MENU
125 #define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
126 #define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
127 #define MPEG_VOLDOWN BUTTON_SCROLL_BACK
128 #define MPEG_VOLUP BUTTON_SCROLL_FWD
129 #define MPEG_RW BUTTON_LEFT
130 #define MPEG_FF BUTTON_RIGHT
132 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
133 #define MPEG_MENU (BUTTON_REC | BUTTON_REL)
134 #define MPEG_STOP BUTTON_POWER
135 #define MPEG_PAUSE BUTTON_PLAY
136 #define MPEG_VOLDOWN BUTTON_DOWN
137 #define MPEG_VOLUP BUTTON_UP
138 #define MPEG_RW BUTTON_LEFT
139 #define MPEG_FF BUTTON_RIGHT
141 #elif CONFIG_KEYPAD == GIGABEAT_PAD
142 #define MPEG_MENU BUTTON_MENU
143 #define MPEG_STOP BUTTON_POWER
144 #define MPEG_PAUSE BUTTON_SELECT
145 #define MPEG_PAUSE2 BUTTON_A
146 #define MPEG_VOLDOWN BUTTON_LEFT
147 #define MPEG_VOLUP BUTTON_RIGHT
148 #define MPEG_VOLDOWN2 BUTTON_VOL_DOWN
149 #define MPEG_VOLUP2 BUTTON_VOL_UP
150 #define MPEG_RW BUTTON_UP
151 #define MPEG_FF BUTTON_DOWN
153 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
154 #define MPEG_MENU BUTTON_LEFT
155 #define MPEG_STOP BUTTON_POWER
156 #define MPEG_PAUSE BUTTON_PLAY
157 #define MPEG_VOLDOWN BUTTON_SCROLL_DOWN
158 #define MPEG_VOLUP BUTTON_SCROLL_UP
159 #define MPEG_RW BUTTON_REW
160 #define MPEG_FF BUTTON_FF
162 #elif CONFIG_KEYPAD == SANSA_E200_PAD
163 #define MPEG_MENU BUTTON_SELECT
164 #define MPEG_STOP BUTTON_POWER
165 #define MPEG_PAUSE BUTTON_UP
166 #define MPEG_VOLDOWN BUTTON_SCROLL_UP
167 #define MPEG_VOLUP BUTTON_SCROLL_DOWN
168 #define MPEG_RW BUTTON_LEFT
169 #define MPEG_FF BUTTON_RIGHT
171 #elif CONFIG_KEYPAD == SANSA_C200_PAD
172 #define MPEG_MENU BUTTON_SELECT
173 #define MPEG_STOP BUTTON_POWER
174 #define MPEG_PAUSE BUTTON_UP
175 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
176 #define MPEG_VOLUP BUTTON_VOL_UP
177 #define MPEG_RW BUTTON_LEFT
178 #define MPEG_FF BUTTON_RIGHT
180 #elif CONFIG_KEYPAD == MROBE500_PAD
181 #define MPEG_MENU BUTTON_RC_HEART
182 #define MPEG_STOP BUTTON_POWER
183 #define MPEG_PAUSE BUTTON_TOUCHPAD
184 #define MPEG_VOLDOWN BUTTON_RC_VOL_DOWN
185 #define MPEG_VOLUP BUTTON_RC_VOL_UP
186 #define MPEG_RW BUTTON_RC_REW
187 #define MPEG_FF BUTTON_RC_FF
189 #else
190 #error MPEGPLAYER: Unsupported keypad
191 #endif
193 struct plugin_api* rb;
195 CACHE_FUNCTION_WRAPPERS(rb);
196 ALIGN_BUFFER_WRAPPER(rb);
198 /* One thing we can do here for targets with remotes is having a display
199 * always on the remote instead of always forcing a popup on the main display */
201 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
202 /* 3% of 30min file == 54s step size */
203 #define MIN_FF_REWIND_STEP (TS_SECOND/2)
204 #define WVS_MIN_UPDATE_INTERVAL (HZ/2)
206 /* WVS status - same order as icon array */
207 enum wvs_status_enum
209 WVS_STATUS_STOPPED = 0,
210 WVS_STATUS_PAUSED,
211 WVS_STATUS_PLAYING,
212 WVS_STATUS_FF,
213 WVS_STATUS_RW,
214 WVS_STATUS_COUNT,
215 WVS_STATUS_MASK = 0x7
218 enum wvs_bits
220 WVS_REFRESH_DEFAULT = 0x0000, /* Only refresh elements when due */
221 /* Refresh the... */
222 WVS_REFRESH_VOLUME = 0x0001, /* ...volume display */
223 WVS_REFRESH_TIME = 0x0002, /* ...time display+progress */
224 WVS_REFRESH_STATUS = 0x0004, /* ...playback status icon */
225 WVS_REFRESH_BACKGROUND = 0x0008, /* ...background (implies ALL) */
226 WVS_REFRESH_VIDEO = 0x0010, /* ...video image upon timeout */
227 WVS_REFRESH_RESUME = 0x0020, /* Resume playback upon timeout */
228 WVS_NODRAW = 0x8000, /* OR bitflag - don't draw anything */
229 WVS_SHOW = 0x4000, /* OR bitflag - show the WVS */
230 WVS_HIDE = 0x0000, /* hide the WVS (aid readability) */
231 WVS_REFRESH_ALL = 0x000f, /* Only immediate graphical elements */
234 /* Status icons selected according to font height */
235 extern const unsigned char mpegplayer_status_icons_8x8x1[];
236 extern const unsigned char mpegplayer_status_icons_12x12x1[];
237 extern const unsigned char mpegplayer_status_icons_16x16x1[];
239 /* Main border areas that contain WVS elements */
240 #define WVS_BDR_L 2
241 #define WVS_BDR_T 2
242 #define WVS_BDR_R 2
243 #define WVS_BDR_B 2
245 struct wvs
247 long hide_tick;
248 long show_for;
249 long print_tick;
250 long print_delay;
251 long resume_tick;
252 long resume_delay;
253 long next_auto_refresh;
254 int x;
255 int y;
256 int width;
257 int height;
258 unsigned fgcolor;
259 unsigned bgcolor;
260 #ifdef HAVE_LCD_COLOR
261 unsigned prog_fillcolor;
262 struct vo_rect update_rect;
263 #endif
264 struct vo_rect prog_rect;
265 struct vo_rect time_rect;
266 struct vo_rect dur_rect;
267 struct vo_rect vol_rect;
268 const unsigned char *icons;
269 struct vo_rect stat_rect;
270 int status;
271 uint32_t curr_time;
272 unsigned auto_refresh;
273 unsigned flags;
276 static struct wvs wvs;
278 static void wvs_show(unsigned show);
280 #ifdef LCD_LANDSCAPE
281 #define _X (x + wvs.x)
282 #define _Y (y + wvs.y)
283 #define _W width
284 #define _H height
285 #else
286 #define _X (LCD_WIDTH - (y + wvs.y) - height)
287 #define _Y (x + wvs.x)
288 #define _W height
289 #define _H width
290 #endif
292 #ifdef HAVE_LCD_COLOR
293 /* Blend two colors in 0-100% (0-255) mix of c2 into c1 */
294 static unsigned draw_blendcolor(unsigned c1, unsigned c2, unsigned char amount)
296 int r1 = RGB_UNPACK_RED(c1);
297 int g1 = RGB_UNPACK_GREEN(c1);
298 int b1 = RGB_UNPACK_BLUE(c1);
300 int r2 = RGB_UNPACK_RED(c2);
301 int g2 = RGB_UNPACK_GREEN(c2);
302 int b2 = RGB_UNPACK_BLUE(c2);
304 return LCD_RGBPACK(amount*(r2 - r1) / 255 + r1,
305 amount*(g2 - g1) / 255 + g1,
306 amount*(b2 - b1) / 255 + b1);
308 #endif
310 /* Drawing functions that operate rotated on LCD_PORTRAIT displays -
311 * most are just wrappers of lcd_* functions with transforms applied.
312 * The origin is the upper-left corner of the WVS area */
313 #ifdef HAVE_LCD_COLOR
314 static void draw_update_rect(int x, int y, int width, int height)
316 rb->lcd_update_rect(_X, _Y, _W, _H);
318 #endif
320 static void draw_clear_area(int x, int y, int width, int height)
322 rb->screen_clear_area(rb->screens[SCREEN_MAIN], _X, _Y, _W, _H);
325 static void draw_clear_area_rect(const struct vo_rect *rc)
327 int x = rc->l;
328 int y = rc->t;
329 int width = rc->r - rc->l;
330 int height = rc->b - rc->t;
331 rb->screen_clear_area(rb->screens[SCREEN_MAIN], _X, _Y, _W, _H);
334 static void draw_scrollbar_draw(int x, int y, int width, int height,
335 int items, int min_shown, int max_shown)
337 #ifdef HAVE_LCD_COLOR
338 int oldbg = rb->lcd_get_background();
339 rb->lcd_set_background(wvs.prog_fillcolor);
340 #endif
342 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], _X, _Y,
343 _W, _H, items, min_shown, max_shown,
345 #ifdef LCD_LANDSCAPE
346 | HORIZONTAL
347 #endif
348 #ifdef HAVE_LCD_COLOR
349 | INNER_BGFILL | FOREGROUND
350 #endif
353 #ifdef HAVE_LCD_COLOR
354 rb->lcd_set_background(oldbg);
355 #endif
358 static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int items,
359 int min_shown, int max_shown)
361 draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t,
362 items, min_shown, max_shown);
365 static void draw_hline(int x1, int x2, int y)
367 #ifdef LCD_LANDSCAPE
368 rb->lcd_hline(x1 + wvs.x, x2 + wvs.x, y + wvs.y);
369 #else
370 y = LCD_WIDTH - (y + wvs.y) - 1;
371 rb->lcd_vline(y, x1 + wvs.x, x2 + wvs.x);
372 #endif
375 #ifdef LCD_PORTRAIT
376 /* Portrait displays need rotated text rendering */
378 /* Limited function that only renders in DRMODE_FG and uses absolute screen
379 * coordinates */
380 static void draw_oriented_mono_bitmap_part(const unsigned char *src,
381 int src_x, int src_y,
382 int stride, int x, int y,
383 int width, int height)
385 const unsigned char *src_end;
386 fb_data *dst, *dst_end;
387 unsigned fg_pattern, bg_pattern;
389 if (x + width > SCREEN_WIDTH)
390 width = SCREEN_WIDTH - x; /* Clip right */
391 if (x < 0)
392 width += x, x = 0; /* Clip left */
393 if (width <= 0)
394 return; /* nothing left to do */
396 if (y + height > SCREEN_HEIGHT)
397 height = SCREEN_HEIGHT - y; /* Clip bottom */
398 if (y < 0)
399 height += y, y = 0; /* Clip top */
400 if (height <= 0)
401 return; /* nothing left to do */
403 fg_pattern = rb->lcd_get_foreground();
404 bg_pattern = rb->lcd_get_background();
406 src += stride * (src_y >> 3) + src_x; /* move starting point */
407 src_y &= 7;
408 src_end = src + width;
410 dst = rb->lcd_framebuffer + (LCD_WIDTH - y) + x*LCD_WIDTH;
413 const unsigned char *src_col = src++;
414 unsigned data = *src_col >> src_y;
415 int numbits = 8 - src_y;
417 fb_data *dst_col = dst;
418 dst_end = dst_col - height;
419 dst += LCD_WIDTH;
423 dst_col--;
425 if (data & 1)
426 *dst_col = fg_pattern;
427 #if 0
428 else
429 *dst_col = bg_pattern;
430 #endif
431 data >>= 1;
432 if (--numbits == 0) {
433 src_col += stride;
434 data = *src_col;
435 numbits = 8;
438 while (dst_col > dst_end);
440 while (src < src_end);
443 static void draw_putsxy_oriented(int x, int y, const char *str)
445 unsigned short ch;
446 unsigned short *ucs;
447 int ofs = MIN(x, 0);
448 struct font* pf = rb->font_get(FONT_UI);
450 ucs = rb->bidi_l2v(str, 1);
452 x += wvs.x;
453 y += wvs.y;
455 while ((ch = *ucs++) != 0 && x < SCREEN_WIDTH)
457 int width;
458 const unsigned char *bits;
460 /* get proportional width and glyph bits */
461 width = rb->font_get_width(pf, ch);
463 if (ofs > width) {
464 ofs -= width;
465 continue;
468 bits = rb->font_get_bits(pf, ch);
470 draw_oriented_mono_bitmap_part(bits, ofs, 0, width, x, y,
471 width - ofs, pf->height);
473 x += width - ofs;
474 ofs = 0;
477 #else
478 static void draw_oriented_mono_bitmap_part(const unsigned char *src,
479 int src_x, int src_y,
480 int stride, int x, int y,
481 int width, int height)
483 int mode = rb->lcd_get_drawmode();
484 rb->lcd_set_drawmode(DRMODE_FG);
485 rb->lcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height);
486 rb->lcd_set_drawmode(mode);
489 static void draw_putsxy_oriented(int x, int y, const char *str)
491 int mode = rb->lcd_get_drawmode();
492 rb->lcd_set_drawmode(DRMODE_FG);
493 rb->lcd_putsxy(x + wvs.x, y + wvs.y, str);
494 rb->lcd_set_drawmode(mode);
496 #endif /* LCD_PORTRAIT */
499 static void wvs_text_init(void)
501 struct hms hms;
502 char buf[32];
503 int phys;
504 int spc_width;
506 rb->lcd_setfont(FONT_UI);
508 wvs.x = 0;
509 wvs.width = SCREEN_WIDTH;
511 vo_rect_clear(&wvs.time_rect);
512 vo_rect_clear(&wvs.stat_rect);
513 vo_rect_clear(&wvs.prog_rect);
514 vo_rect_clear(&wvs.vol_rect);
516 ts_to_hms(stream_get_duration(), &hms);
517 hms_format(buf, sizeof (buf), &hms);
518 rb->lcd_getstringsize(buf, &wvs.time_rect.r,
519 &wvs.time_rect.b);
521 /* Choose well-sized bitmap images relative to font height */
522 if (wvs.time_rect.b < 12) {
523 wvs.icons = mpegplayer_status_icons_8x8x1;
524 wvs.stat_rect.r = wvs.stat_rect.b = 8;
525 } else if (wvs.time_rect.b < 16) {
526 wvs.icons = mpegplayer_status_icons_12x12x1;
527 wvs.stat_rect.r = wvs.stat_rect.b = 12;
528 } else {
529 wvs.icons = mpegplayer_status_icons_16x16x1;
530 wvs.stat_rect.r = wvs.stat_rect.b = 16;
533 if (wvs.stat_rect.b < wvs.time_rect.b) {
534 vo_rect_offset(&wvs.stat_rect, 0,
535 (wvs.time_rect.b - wvs.stat_rect.b) / 2 + WVS_BDR_T);
536 vo_rect_offset(&wvs.time_rect, WVS_BDR_L, WVS_BDR_T);
537 } else {
538 vo_rect_offset(&wvs.time_rect, WVS_BDR_L,
539 wvs.stat_rect.b - wvs.time_rect.b + WVS_BDR_T);
540 vo_rect_offset(&wvs.stat_rect, 0, WVS_BDR_T);
543 wvs.dur_rect = wvs.time_rect;
545 phys = rb->sound_val2phys(SOUND_VOLUME, rb->sound_min(SOUND_VOLUME));
546 rb->snprintf(buf, sizeof(buf), "%d%s", phys,
547 rb->sound_unit(SOUND_VOLUME));
549 rb->lcd_getstringsize(" ", &spc_width, NULL);
550 rb->lcd_getstringsize(buf, &wvs.vol_rect.r, &wvs.vol_rect.b);
552 wvs.prog_rect.r = SCREEN_WIDTH - WVS_BDR_L - spc_width -
553 wvs.vol_rect.r - WVS_BDR_R;
554 wvs.prog_rect.b = 3*wvs.stat_rect.b / 4;
555 vo_rect_offset(&wvs.prog_rect, wvs.time_rect.l,
556 wvs.time_rect.b);
558 vo_rect_offset(&wvs.stat_rect,
559 (wvs.prog_rect.r + wvs.prog_rect.l - wvs.stat_rect.r) / 2,
562 vo_rect_offset(&wvs.dur_rect,
563 wvs.prog_rect.r - wvs.dur_rect.r, 0);
565 vo_rect_offset(&wvs.vol_rect, wvs.prog_rect.r + spc_width,
566 (wvs.prog_rect.b + wvs.prog_rect.t - wvs.vol_rect.b) / 2);
568 wvs.height = WVS_BDR_T + MAX(wvs.prog_rect.b, wvs.vol_rect.b) -
569 MIN(wvs.time_rect.t, wvs.stat_rect.t) + WVS_BDR_B;
571 #if LCD_PIXELFORMAT == VERTICAL_PACKING
572 wvs.height = ALIGN_UP(wvs.height, 8);
573 #else
574 wvs.height = ALIGN_UP(wvs.height, 2);
575 #endif
576 wvs.y = SCREEN_HEIGHT - wvs.height;
578 rb->lcd_setfont(FONT_SYSFIXED);
581 static void wvs_init(void)
583 wvs.flags = 0;
584 wvs.show_for = HZ*4;
585 wvs.print_delay = 75*HZ/100;
586 wvs.resume_delay = HZ/2;
587 #ifdef HAVE_LCD_COLOR
588 wvs.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd);
589 wvs.fgcolor = LCD_WHITE;
590 wvs.prog_fillcolor = LCD_BLACK;
591 #else
592 wvs.bgcolor = LCD_LIGHTGRAY;
593 wvs.fgcolor = LCD_BLACK;
594 #endif
595 wvs.curr_time = 0;
596 wvs.status = WVS_STATUS_STOPPED;
597 wvs.auto_refresh = WVS_REFRESH_TIME;
598 wvs.next_auto_refresh = *rb->current_tick;
599 wvs_text_init();
602 static void wvs_schedule_refresh(unsigned refresh)
604 long tick = *rb->current_tick;
606 if (refresh & WVS_REFRESH_VIDEO)
607 wvs.print_tick = tick + wvs.print_delay;
609 if (refresh & WVS_REFRESH_RESUME)
610 wvs.resume_tick = tick + wvs.resume_delay;
612 wvs.auto_refresh |= refresh;
615 static void wvs_cancel_refresh(unsigned refresh)
617 wvs.auto_refresh &= ~refresh;
620 /* Refresh the background area */
621 static void wvs_refresh_background(void)
623 char buf[32];
624 struct hms hms;
626 int bg = rb->lcd_get_background();
627 rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
629 #ifdef HAVE_LCD_COLOR
630 /* Draw a "raised" area for our graphics */
631 rb->lcd_set_background(draw_blendcolor(bg, LCD_WHITE, 192));
632 draw_hline(0, wvs.width, 0);
634 rb->lcd_set_background(draw_blendcolor(bg, LCD_WHITE, 80));
635 draw_hline(0, wvs.width, 1);
637 rb->lcd_set_background(draw_blendcolor(bg, LCD_BLACK, 48));
638 draw_hline(0, wvs.width, wvs.height-2);
640 rb->lcd_set_background(draw_blendcolor(bg, LCD_BLACK, 128));
641 draw_hline(0, wvs.width, wvs.height-1);
643 rb->lcd_set_background(bg);
644 draw_clear_area(0, 2, wvs.width, wvs.height - 4);
646 vo_rect_set_ext(&wvs.update_rect, 0, 0, wvs.width, wvs.height);
647 #else
648 /* Give contrast with the main background */
649 rb->lcd_set_background(LCD_DARKGRAY);
650 draw_hline(0, wvs.width, 0);
652 rb->lcd_set_background(bg);
653 draw_clear_area(0, 1, wvs.width, wvs.height - 1);
654 #endif
656 rb->lcd_set_drawmode(DRMODE_SOLID);
658 if (stream_get_duration() != INVALID_TIMESTAMP) {
659 /* Draw the movie duration */
660 ts_to_hms(stream_get_duration(), &hms);
661 hms_format(buf, sizeof (buf), &hms);
662 draw_putsxy_oriented(wvs.dur_rect.l, wvs.dur_rect.t, buf);
664 /* else don't know the duration */
667 /* Refresh the current time display + the progress bar */
668 static void wvs_refresh_time(void)
670 char buf[32];
671 struct hms hms;
673 uint32_t duration = stream_get_duration();
675 draw_scrollbar_draw_rect(&wvs.prog_rect, duration, 0,
676 wvs.curr_time);
678 ts_to_hms(wvs.curr_time, &hms);
679 hms_format(buf, sizeof (buf), &hms);
681 draw_clear_area_rect(&wvs.time_rect);
682 draw_putsxy_oriented(wvs.time_rect.l, wvs.time_rect.t, buf);
684 #ifdef HAVE_LCD_COLOR
685 vo_rect_union(&wvs.update_rect, &wvs.update_rect,
686 &wvs.prog_rect);
687 vo_rect_union(&wvs.update_rect, &wvs.update_rect,
688 &wvs.time_rect);
689 #endif
692 /* Refresh the volume display area */
693 static void wvs_refresh_volume(void)
695 char buf[32];
696 int width;
698 int volume = rb->global_settings->volume;
699 rb->snprintf(buf, sizeof (buf), "%d%s",
700 rb->sound_val2phys(SOUND_VOLUME, volume),
701 rb->sound_unit(SOUND_VOLUME));
702 rb->lcd_getstringsize(buf, &width, NULL);
704 /* Right-justified */
705 draw_clear_area_rect(&wvs.vol_rect);
706 draw_putsxy_oriented(wvs.vol_rect.r - width, wvs.vol_rect.t, buf);
708 #ifdef HAVE_LCD_COLOR
709 vo_rect_union(&wvs.update_rect, &wvs.update_rect, &wvs.vol_rect);
710 #endif
713 /* Refresh the status icon */
714 static void wvs_refresh_status(void)
716 int icon_size = wvs.stat_rect.r - wvs.stat_rect.l;
718 draw_clear_area_rect(&wvs.stat_rect);
720 #ifdef HAVE_LCD_COLOR
721 /* Draw status icon with a drop shadow */
722 unsigned oldfg = rb->lcd_get_foreground();
723 int i = 1;
725 rb->lcd_set_foreground(draw_blendcolor(rb->lcd_get_background(),
726 LCD_BLACK, 96));
728 while (1)
730 draw_oriented_mono_bitmap_part(wvs.icons,
731 icon_size*wvs.status,
733 icon_size*WVS_STATUS_COUNT,
734 wvs.stat_rect.l + wvs.x + i,
735 wvs.stat_rect.t + wvs.y + i,
736 icon_size, icon_size);
738 if (--i < 0)
739 break;
741 rb->lcd_set_foreground(oldfg);
744 vo_rect_union(&wvs.update_rect, &wvs.update_rect, &wvs.stat_rect);
745 #else
746 draw_oriented_mono_bitmap_part(wvs.icons,
747 icon_size*wvs.status,
749 icon_size*WVS_STATUS_COUNT,
750 wvs.stat_rect.l + wvs.x,
751 wvs.stat_rect.t + wvs.y,
752 icon_size, icon_size);
753 #endif
756 /* Update the current status which determines which icon is displayed */
757 static bool wvs_update_status(void)
759 int status;
761 switch (stream_status())
763 default:
764 status = WVS_STATUS_STOPPED;
765 break;
766 case STREAM_PAUSED:
767 /* If paused with a pending resume, coerce it to WVS_STATUS_PLAYING */
768 status = (wvs.auto_refresh & WVS_REFRESH_RESUME) ?
769 WVS_STATUS_PLAYING : WVS_STATUS_PAUSED;
770 break;
771 case STREAM_PLAYING:
772 status = WVS_STATUS_PLAYING;
773 break;
776 if (status != wvs.status) {
777 /* A refresh is needed */
778 wvs.status = status;
779 return true;
782 return false;
785 /* Update the current time that will be displayed */
786 static void wvs_update_time(void)
788 uint32_t start;
789 wvs.curr_time = stream_get_seek_time(&start);
790 wvs.curr_time -= start;
793 /* Refresh various parts of the WVS - showing it if it is hidden */
794 static void wvs_refresh(int hint)
796 long tick;
797 unsigned oldbg, oldfg;
799 tick = *rb->current_tick;
801 if (hint == WVS_REFRESH_DEFAULT) {
802 /* The default which forces no updates */
804 /* Redraw the current or possibly extract a new video frame */
805 if ((wvs.auto_refresh & WVS_REFRESH_VIDEO) &&
806 TIME_AFTER(tick, wvs.print_tick)) {
807 wvs.auto_refresh &= ~WVS_REFRESH_VIDEO;
808 stream_draw_frame(false);
811 /* Restart playback if the timout was reached */
812 if ((wvs.auto_refresh & WVS_REFRESH_RESUME) &&
813 TIME_AFTER(tick, wvs.resume_tick)) {
814 wvs.auto_refresh &= ~(WVS_REFRESH_RESUME | WVS_REFRESH_VIDEO);
815 stream_resume();
818 /* If not visible, return */
819 if (!(wvs.flags & WVS_SHOW))
820 return;
822 /* Hide if the visibility duration was reached */
823 if (TIME_AFTER(tick, wvs.hide_tick)) {
824 wvs_show(WVS_HIDE);
825 return;
827 } else {
828 /* A forced update of some region */
830 /* Show if currently invisible */
831 if (!(wvs.flags & WVS_SHOW)) {
832 /* Avoid call back into this function - it will be drawn */
833 wvs_show(WVS_SHOW | WVS_NODRAW);
834 hint = WVS_REFRESH_ALL;
837 /* Move back timeouts for frame print and hide */
838 wvs.print_tick = tick + wvs.print_delay;
839 wvs.hide_tick = tick + wvs.show_for;
842 if (TIME_AFTER(tick, wvs.next_auto_refresh)) {
843 /* Refresh whatever graphical elements are due automatically */
844 wvs.next_auto_refresh = tick + WVS_MIN_UPDATE_INTERVAL;
846 if (wvs.auto_refresh & WVS_REFRESH_STATUS) {
847 if (wvs_update_status())
848 hint |= WVS_REFRESH_STATUS;
851 if (wvs.auto_refresh & WVS_REFRESH_TIME) {
852 wvs_update_time();
853 hint |= WVS_REFRESH_TIME;
857 if (hint == 0)
858 return; /* No drawing needed */
860 /* Set basic drawing params that are used. Elements that perform variations
861 * will restore them. */
862 oldfg = rb->lcd_get_foreground();
863 oldbg = rb->lcd_get_background();
865 rb->lcd_setfont(FONT_UI);
866 rb->lcd_set_foreground(wvs.fgcolor);
867 rb->lcd_set_background(wvs.bgcolor);
869 #ifdef HAVE_LCD_COLOR
870 vo_rect_clear(&wvs.update_rect);
871 #endif
873 if (hint & WVS_REFRESH_BACKGROUND) {
874 wvs_refresh_background();
875 hint |= WVS_REFRESH_ALL; /* Requires a redraw of everything */
878 if (hint & WVS_REFRESH_TIME) {
879 wvs_refresh_time();
882 if (hint & WVS_REFRESH_VOLUME) {
883 wvs_refresh_volume();
886 if (hint & WVS_REFRESH_STATUS) {
887 wvs_refresh_status();
890 /* Go back to defaults */
891 rb->lcd_setfont(FONT_SYSFIXED);
892 rb->lcd_set_foreground(oldfg);
893 rb->lcd_set_background(oldbg);
895 #ifdef HAVE_LCD_COLOR
896 /* Update the dirty rectangle */
897 vo_lock();
899 draw_update_rect(wvs.update_rect.l,
900 wvs.update_rect.t,
901 wvs.update_rect.r - wvs.update_rect.l,
902 wvs.update_rect.b - wvs.update_rect.t);
904 vo_unlock();
905 #else
906 /* Defer update to greylib */
907 grey_deferred_lcd_update();
908 #endif
911 /* Show/Hide the WVS */
912 static void wvs_show(unsigned show)
914 if (((show ^ wvs.flags) & WVS_SHOW) == 0)
915 return;
917 if (show & WVS_SHOW) {
918 /* Clip away the part of video that is covered */
919 struct vo_rect rc = { 0, 0, SCREEN_WIDTH, wvs.y };
921 wvs.flags |= WVS_SHOW;
923 stream_vo_set_clip(&rc);
925 if (!(show & WVS_NODRAW))
926 wvs_refresh(WVS_REFRESH_ALL);
927 } else {
928 /* Uncover clipped video area and redraw it */
929 wvs.flags &= ~WVS_SHOW;
931 stream_vo_set_clip(NULL);
933 draw_clear_area(0, 0, wvs.width, wvs.height);
935 if (!(show & WVS_NODRAW)) {
936 #ifdef HAVE_LCD_COLOR
937 vo_lock();
938 draw_update_rect(0, 0, wvs.width, wvs.height);
939 vo_unlock();
940 #endif
941 stream_draw_frame(false);
946 /* Set the current status - update screen if specified */
947 static void wvs_set_status(int status)
949 bool draw = (status & WVS_NODRAW) == 0;
951 status &= WVS_STATUS_MASK;
953 if (wvs.status != status) {
955 wvs.status = status;
957 if (draw)
958 wvs_refresh(WVS_REFRESH_STATUS);
962 /* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;) */
963 static uint32_t wvs_ff_rw(int btn, unsigned refresh)
965 unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step;
966 const long ff_rw_accel = rb->global_settings->ff_rewind_accel;
967 long accel_tick = *rb->current_tick + ff_rw_accel*HZ;
968 uint32_t start;
969 uint32_t time = stream_get_seek_time(&start);
970 const uint32_t duration = stream_get_duration();
971 unsigned int max_step = 0;
972 uint32_t ff_rw_count = 0;
973 unsigned status = wvs.status;
975 wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME |
976 WVS_REFRESH_TIME);
978 time -= start; /* Absolute clock => stream-relative */
980 switch (btn)
982 case MPEG_FF:
983 wvs_set_status(WVS_STATUS_FF);
984 break;
985 case MPEG_RW:
986 wvs_set_status(WVS_STATUS_RW);
987 break;
988 default:
989 btn = -1;
992 btn |= BUTTON_REPEAT;
994 while (1)
996 long tick = *rb->current_tick;
997 stream_keep_disk_active();
999 switch (btn)
1001 case BUTTON_NONE:
1002 wvs_refresh(WVS_REFRESH_DEFAULT);
1003 break;
1005 case MPEG_FF | BUTTON_REPEAT:
1006 case MPEG_RW | BUTTON_REPEAT:
1007 break;
1009 case MPEG_FF | BUTTON_REL:
1010 case MPEG_RW | BUTTON_REL:
1011 if (wvs.status == WVS_STATUS_FF)
1012 time += ff_rw_count;
1013 else if (wvs.status == WVS_STATUS_RW)
1014 time -= ff_rw_count;
1016 /* Fall-through */
1017 case -1:
1018 default:
1019 wvs_schedule_refresh(refresh);
1020 wvs_set_status(status);
1021 wvs_schedule_refresh(WVS_REFRESH_TIME);
1022 return time;
1025 if (wvs.status == WVS_STATUS_FF) {
1026 /* fast forwarding, calc max step relative to end */
1027 max_step = muldiv_uint32(duration - (time + ff_rw_count),
1028 FF_REWIND_MAX_PERCENT, 100);
1029 } else {
1030 /* rewinding, calc max step relative to start */
1031 max_step = muldiv_uint32(time - ff_rw_count,
1032 FF_REWIND_MAX_PERCENT, 100);
1035 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
1037 if (step > max_step)
1038 step = max_step;
1040 ff_rw_count += step;
1042 if (ff_rw_accel != 0 && TIME_AFTER(tick, accel_tick)) {
1043 step *= 2;
1044 accel_tick = tick + ff_rw_accel*HZ;
1047 if (wvs.status == WVS_STATUS_FF) {
1048 if (duration - time <= ff_rw_count)
1049 ff_rw_count = duration - time;
1051 wvs.curr_time = time + ff_rw_count;
1052 } else {
1053 if (time <= ff_rw_count)
1054 ff_rw_count = time;
1056 wvs.curr_time = time - ff_rw_count;
1059 wvs_refresh(WVS_REFRESH_TIME);
1061 btn = rb->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL);
1065 static int wvs_status(void)
1067 int status = stream_status();
1069 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1070 if (status == STREAM_PAUSED) {
1071 if (wvs.auto_refresh & WVS_REFRESH_RESUME)
1072 status = STREAM_PLAYING;
1075 return status;
1078 /* Change the current audio volume by a specified amount */
1079 static void wvs_set_volume(int delta)
1081 int vol = rb->global_settings->volume;
1082 int limit;
1084 vol += delta;
1086 if (delta < 0) {
1087 /* Volume down - clip to lower limit */
1088 limit = rb->sound_min(SOUND_VOLUME);
1089 if (vol < limit)
1090 vol = limit;
1091 } else {
1092 /* Volume up - clip to upper limit */
1093 limit = rb->sound_max(SOUND_VOLUME);
1094 if (vol > limit)
1095 vol = limit;
1098 /* Sync the global settings */
1099 if (vol != rb->global_settings->volume) {
1100 rb->sound_set(SOUND_VOLUME, vol);
1101 rb->global_settings->volume = vol;
1104 /* Update the volume display */
1105 wvs_refresh(WVS_REFRESH_VOLUME);
1108 static int wvs_play(uint32_t time)
1110 int retval;
1112 wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
1114 retval = stream_seek(time, SEEK_SET);
1116 if (retval >= STREAM_OK) {
1117 stream_show_vo(true);
1118 retval = stream_play();
1120 if (retval >= STREAM_OK)
1121 wvs_set_status(WVS_STATUS_PLAYING | WVS_NODRAW);
1124 return retval;
1127 /* Halt playback - pause engine and return logical state */
1128 static int wvs_halt(void)
1130 int status = stream_pause();
1132 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1133 if (status == STREAM_PAUSED) {
1134 if (wvs.auto_refresh & WVS_REFRESH_RESUME)
1135 status = STREAM_PLAYING;
1138 /* Cancel some auto refreshes - caller will restart them if desired */
1139 wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
1141 return status;
1144 /* Pause playback if playing */
1145 static int wvs_pause(void)
1147 unsigned refresh = wvs.auto_refresh;
1148 int status = wvs_halt();
1150 if (status == STREAM_PLAYING && (refresh & WVS_REFRESH_RESUME)) {
1151 /* Resume pending - change to a still video frame update */
1152 wvs_schedule_refresh(WVS_REFRESH_VIDEO);
1155 wvs_set_status(WVS_STATUS_PAUSED);
1157 return status;
1160 /* Resume playback if halted or paused */
1161 static void wvs_resume(void)
1163 /* Cancel video and resume auto refresh - the resyc when starting playback
1164 * will perform those tasks */
1165 wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
1166 wvs_set_status(WVS_STATUS_PLAYING);
1167 stream_resume();
1170 /* Stop playback - remember the resume point if not already stopped */
1171 static void wvs_stop(void)
1173 wvs_cancel_refresh(WVS_REFRESH_VIDEO | WVS_REFRESH_RESUME);
1174 wvs_show(WVS_HIDE | WVS_NODRAW);
1176 if (stream_stop() != STREAM_STOPPED)
1177 settings.resume_time = stream_get_resume_time();
1180 /* Perform a seek if seeking is possible for this stream - if playing, a delay
1181 * will be inserted before restarting in case the user decides to seek again */
1182 static void wvs_seek(int btn)
1184 int status;
1185 unsigned refresh;
1186 uint32_t time;
1188 if (!stream_can_seek())
1189 return;
1191 /* Halt playback - not strictly nescessary but nice */
1192 status = wvs_halt();
1194 if (status == STREAM_STOPPED)
1195 return;
1197 wvs_show(WVS_SHOW);
1199 if (status == STREAM_PLAYING)
1200 refresh = WVS_REFRESH_RESUME; /* delay resume if playing */
1201 else
1202 refresh = WVS_REFRESH_VIDEO; /* refresh if paused */
1204 /* Obtain a new playback point */
1205 time = wvs_ff_rw(btn, refresh);
1207 /* Tell engine to resume at that time */
1208 stream_seek(time, SEEK_SET);
1211 static void button_loop(void)
1213 rb->lcd_setfont(FONT_SYSFIXED);
1214 rb->lcd_clear_display();
1215 rb->lcd_update();
1217 /* Turn off backlight timeout */
1218 /* backlight control in lib/helper.c */
1219 backlight_force_on(rb);
1221 wvs_init();
1223 /* Start playback at the specified starting time */
1224 if (wvs_play(settings.resume_time) < STREAM_OK) {
1225 rb->splash(HZ*2, "Playback failed");
1226 return;
1229 /* Gently poll the video player for EOS and handle UI */
1230 while (stream_status() != STREAM_STOPPED)
1232 int button = rb->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL);
1234 switch (button)
1236 case BUTTON_NONE:
1238 wvs_refresh(WVS_REFRESH_DEFAULT);
1239 continue;
1240 } /* BUTTON_NONE: */
1242 case MPEG_VOLUP:
1243 case MPEG_VOLUP|BUTTON_REPEAT:
1244 #ifdef MPEG_VOLUP2
1245 case MPEG_VOLUP2:
1246 case MPEG_VOLUP2|BUTTON_REPEAT:
1247 #endif
1249 wvs_set_volume(+1);
1250 break;
1251 } /* MPEG_VOLUP*: */
1253 case MPEG_VOLDOWN:
1254 case MPEG_VOLDOWN|BUTTON_REPEAT:
1255 #ifdef MPEG_VOLDOWN2
1256 case MPEG_VOLDOWN2:
1257 case MPEG_VOLDOWN2|BUTTON_REPEAT:
1258 #endif
1260 wvs_set_volume(-1);
1261 break;
1262 } /* MPEG_VOLDOWN*: */
1264 case MPEG_MENU:
1266 int state = wvs_halt(); /* save previous state */
1267 int result;
1269 /* Hide video output */
1270 wvs_show(WVS_HIDE | WVS_NODRAW);
1271 stream_show_vo(false);
1272 backlight_use_settings(rb);
1274 result = mpeg_menu();
1276 /* The menu can change the font, so restore */
1277 rb->lcd_setfont(FONT_SYSFIXED);
1279 switch (result)
1281 case MPEG_MENU_QUIT:
1282 wvs_stop();
1283 break;
1284 default:
1285 /* If not stopped, show video again */
1286 if (state != STREAM_STOPPED) {
1287 wvs_show(WVS_SHOW);
1288 stream_show_vo(true);
1291 /* If stream was playing, restart it */
1292 if (state == STREAM_PLAYING) {
1293 backlight_force_on(rb);
1294 wvs_resume();
1296 break;
1298 break;
1299 } /* MPEG_MENU: */
1301 case MPEG_STOP:
1303 wvs_stop();
1304 break;
1305 } /* MPEG_STOP: */
1307 case MPEG_PAUSE:
1308 #ifdef MPEG_PAUSE2
1309 case MPEG_PAUSE2:
1310 #endif
1312 int status = wvs_status();
1314 if (status == STREAM_PLAYING) {
1315 /* Playing => Paused */
1316 wvs_pause();
1317 backlight_use_settings(rb);
1319 else if (status == STREAM_PAUSED) {
1320 /* Paused => Playing */
1321 backlight_force_on(rb);
1322 wvs_resume();
1325 break;
1326 } /* MPEG_PAUSE*: */
1328 case MPEG_RW:
1329 case MPEG_FF:
1331 wvs_seek(button);
1332 break;
1333 } /* MPEG_RW: MPEG_FF: */
1335 case SYS_POWEROFF:
1336 case SYS_USB_CONNECTED:
1337 /* Stop and get the resume time before closing the file early */
1338 wvs_stop();
1339 stream_close();
1340 /* Fall-through */
1341 default:
1342 rb->default_event_handler(button);
1343 break;
1346 rb->yield();
1347 } /* end while */
1349 wvs_stop();
1351 rb->lcd_setfont(FONT_UI);
1353 /* Turn on backlight timeout (revert to settings) */
1354 backlight_use_settings(rb);
1357 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1359 int status = PLUGIN_ERROR; /* assume failure */
1360 int result;
1361 int err;
1362 const char *errstring;
1364 if (parameter == NULL) {
1365 /* No file = GTFO */
1366 api->splash(HZ*2, "No File");
1367 return PLUGIN_ERROR;
1370 /* Disable all talking before initializing IRAM */
1371 api->talk_disable(true);
1373 /* Initialize IRAM - stops audio and voice as well */
1374 PLUGIN_IRAM_INIT(api)
1376 rb = api;
1378 #ifdef HAVE_LCD_COLOR
1379 rb->lcd_set_backdrop(NULL);
1380 rb->lcd_set_foreground(LCD_WHITE);
1381 rb->lcd_set_background(LCD_BLACK);
1382 #endif
1384 rb->lcd_clear_display();
1385 rb->lcd_update();
1387 if (stream_init() < STREAM_OK) {
1388 DEBUGF("Could not initialize streams\n");
1389 } else {
1390 rb->splash(0, "Loading...");
1392 init_settings((char*)parameter);
1394 err = stream_open((char *)parameter);
1396 if (err >= STREAM_OK) {
1397 /* start menu */
1398 rb->lcd_clear_display();
1399 rb->lcd_update();
1400 result = mpeg_start_menu(stream_get_duration());
1402 if (result != MPEG_START_QUIT) {
1403 /* Enter button loop and process UI */
1404 button_loop();
1407 stream_close();
1409 rb->lcd_clear_display();
1410 rb->lcd_update();
1412 save_settings(); /* Save settings (if they have changed) */
1413 status = PLUGIN_OK;
1414 } else {
1415 DEBUGF("Could not open %s\n", (char*)parameter);
1416 switch (err)
1418 case STREAM_UNSUPPORTED:
1419 errstring = "Unsupported format";
1420 break;
1421 default:
1422 errstring = "Error opening file: %d";
1425 rb->splash(HZ*2, errstring, err);
1429 stream_exit();
1431 rb->talk_disable(false);
1432 return status;