MPEGPlayer: Skip to next file when there is a problem with a video file in all-play...
[kugel-rb.git] / apps / plugins / mpegplayer / mpegplayer.c
blob7a2b457aec492da138ccf131949c7ebab0cb61bc
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 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ****************************************************************************/
24 /****************************************************************************
25 * NOTES:
27 * mpegplayer is structured as follows:
29 * +-->Video Thread-->Video Output-->LCD
30 * |
31 * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device
32 * | | | | (ref. clock)
33 * | | +-->Buffer Thread |
34 * Stream Data | | (clock intf./
35 * Requests | File Cache drift adj.)
36 * | Disk I/O
37 * Stream services
38 * (timing, etc.)
40 * Thread list:
41 * 1) The main thread - Handles user input, settings, basic playback control
42 * and USB connect.
44 * 2) Stream Manager thread - Handles playback state, events from streams
45 * such as when a stream is finished, stream commands, PCM state. The
46 * layer in which this thread run also handles arbitration of data
47 * requests between the streams and the disk buffer. The actual specific
48 * transport layer code may get moved out to support multiple container
49 * formats.
51 * 3) Buffer thread - Buffers data in the background, generates notifications
52 * to streams when their data has been buffered, and watches streams'
53 * progress to keep data available during playback. Handles synchronous
54 * random access requests when the file cache is missed.
56 * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes
57 * the video stream and renders video frames to the LCD. Handles
58 * miscellaneous video tasks like frame and thumbnail printing.
60 * 5) Audio thread (running on the main CPU to maintain consistency with the
61 * audio FIQ hander on PP) - Decodes audio frames and places them into
62 * the PCM buffer for rendering by the audio device.
64 * Streams are neither aware of one another nor care about one another. All
65 * streams shall have their own thread (unless it is _really_ efficient to
66 * have a single thread handle a couple minor streams). All coordination of
67 * the streams is done through the stream manager. The clocking is controlled
68 * by and exposed by the stream manager to other streams and implemented at
69 * the PCM level.
71 * Notes about MPEG files:
73 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
75 * FPS is represented in terms of a frame period - this is always an
76 * integer number of 27MHz ticks.
78 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
79 * 900900 27MHz ticks.
81 * In libmpeg2, info->sequence->frame_period contains the frame_period.
83 * Working with Rockbox's 100Hz tick, the common frame rates would need
84 * to be as follows (1):
86 * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz
87 * --------|-----------------------------------------------------------
88 * 10* | 2700000 | 10 | 4410 | 4800
89 * 12* | 2250000 | 8.3333 | 3675 | 4000
90 * 15* | 1800000 | 6.6667 | 2940 | 3200
91 * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002
92 * 24 | 1125000 | 4.166667 | 1837.5 | 2000
93 * 25 | 1080000 | 4 | 1764 | 1920
94 * 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6
95 * 30 | 900000 | 3.333333 | 1470 | 1600
97 * *Unofficial framerates
99 * (1) But we don't really care since the audio clock is used anyway and has
100 * very fine resolution ;-)
101 *****************************************************************************/
102 #include "plugin.h"
103 #include "mpegplayer.h"
104 #include "lib/helper.h"
105 #include "mpeg_settings.h"
106 #include "mpeg2.h"
107 #include "video_out.h"
108 #include "stream_thread.h"
109 #include "stream_mgr.h"
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 #define MPEG_RC_MENU BUTTON_RC_DSP
154 #define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT)
155 #define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL)
156 #define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
157 #define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
158 #define MPEG_RC_RW BUTTON_RC_REW
159 #define MPEG_RC_FF BUTTON_RC_FF
161 #elif CONFIG_KEYPAD == GIGABEAT_S_PAD
162 #define MPEG_MENU BUTTON_MENU
163 #define MPEG_STOP BUTTON_POWER
164 #define MPEG_PAUSE BUTTON_SELECT
165 #define MPEG_PAUSE2 BUTTON_PLAY
166 #define MPEG_VOLDOWN BUTTON_LEFT
167 #define MPEG_VOLUP BUTTON_RIGHT
168 #define MPEG_VOLDOWN2 BUTTON_VOL_DOWN
169 #define MPEG_VOLUP2 BUTTON_VOL_UP
170 #define MPEG_RW BUTTON_UP
171 #define MPEG_RW2 BUTTON_PREV
172 #define MPEG_FF BUTTON_DOWN
173 #define MPEG_FF2 BUTTON_NEXT
174 #define MPEG_SHOW_OSD BUTTON_BACK
176 #define MPEG_RC_MENU BUTTON_RC_DSP
177 #define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT)
178 #define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL)
179 #define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
180 #define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
181 #define MPEG_RC_RW BUTTON_RC_REW
182 #define MPEG_RC_FF BUTTON_RC_FF
184 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
185 #define MPEG_MENU BUTTON_LEFT
186 #define MPEG_STOP BUTTON_POWER
187 #define MPEG_PAUSE BUTTON_PLAY
188 #define MPEG_VOLDOWN BUTTON_SCROLL_DOWN
189 #define MPEG_VOLUP BUTTON_SCROLL_UP
190 #define MPEG_RW BUTTON_REW
191 #define MPEG_FF BUTTON_FF
193 #elif CONFIG_KEYPAD == SANSA_E200_PAD
194 #define MPEG_MENU BUTTON_SELECT
195 #define MPEG_STOP BUTTON_POWER
196 #define MPEG_PAUSE BUTTON_RIGHT
197 #define MPEG_VOLDOWN BUTTON_SCROLL_BACK
198 #define MPEG_VOLUP BUTTON_SCROLL_FWD
199 #define MPEG_RW BUTTON_UP
200 #define MPEG_FF BUTTON_DOWN
202 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
203 #define MPEG_MENU BUTTON_SELECT
204 #define MPEG_STOP (BUTTON_HOME|BUTTON_REPEAT)
205 #define MPEG_PAUSE BUTTON_UP
206 #define MPEG_VOLDOWN BUTTON_SCROLL_BACK
207 #define MPEG_VOLUP BUTTON_SCROLL_FWD
208 #define MPEG_RW BUTTON_LEFT
209 #define MPEG_FF BUTTON_RIGHT
212 #elif CONFIG_KEYPAD == SANSA_C200_PAD || \
213 CONFIG_KEYPAD == SANSA_CLIP_PAD || \
214 CONFIG_KEYPAD == SANSA_M200_PAD
215 #define MPEG_MENU BUTTON_SELECT
216 #define MPEG_STOP BUTTON_POWER
217 #define MPEG_PAUSE BUTTON_UP
218 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
219 #define MPEG_VOLUP BUTTON_VOL_UP
220 #define MPEG_RW BUTTON_LEFT
221 #define MPEG_FF BUTTON_RIGHT
223 #elif CONFIG_KEYPAD == MROBE500_PAD
224 #define MPEG_STOP BUTTON_POWER
226 #define MPEG_RC_MENU BUTTON_RC_HEART
227 #define MPEG_RC_STOP BUTTON_RC_DOWN
228 #define MPEG_RC_PAUSE BUTTON_RC_PLAY
229 #define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
230 #define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
231 #define MPEG_RC_RW BUTTON_RC_REW
232 #define MPEG_RC_FF BUTTON_RC_FF
234 #elif CONFIG_KEYPAD == MROBE100_PAD
235 #define MPEG_MENU BUTTON_MENU
236 #define MPEG_STOP BUTTON_POWER
237 #define MPEG_PAUSE BUTTON_PLAY
238 #define MPEG_VOLDOWN BUTTON_DOWN
239 #define MPEG_VOLUP BUTTON_UP
240 #define MPEG_RW BUTTON_LEFT
241 #define MPEG_FF BUTTON_RIGHT
243 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
244 #define MPEG_MENU BUTTON_RC_MENU
245 #define MPEG_STOP BUTTON_RC_REC
246 #define MPEG_PAUSE BUTTON_RC_PLAY
247 #define MPEG_VOLDOWN BUTTON_RC_VOL_DOWN
248 #define MPEG_VOLUP BUTTON_RC_VOL_UP
249 #define MPEG_RW BUTTON_RC_REW
250 #define MPEG_FF BUTTON_RC_FF
252 #elif CONFIG_KEYPAD == COWON_D2_PAD
253 #define MPEG_MENU (BUTTON_MENU|BUTTON_REL)
254 //#define MPEG_STOP BUTTON_POWER
255 #define MPEG_VOLDOWN BUTTON_MINUS
256 #define MPEG_VOLUP BUTTON_PLUS
258 #elif CONFIG_KEYPAD == IAUDIO67_PAD
259 #define MPEG_MENU BUTTON_MENU
260 #define MPEG_STOP BUTTON_STOP
261 #define MPEG_PAUSE BUTTON_PLAY
262 #define MPEG_VOLDOWN BUTTON_VOLDOWN
263 #define MPEG_VOLUP BUTTON_VOLUP
264 #define MPEG_RW BUTTON_LEFT
265 #define MPEG_FF BUTTON_RIGHT
267 #elif CONFIG_KEYPAD == CREATIVEZVM_PAD
268 #define MPEG_MENU BUTTON_MENU
269 #define MPEG_STOP BUTTON_BACK
270 #define MPEG_PAUSE BUTTON_PLAY
271 #define MPEG_VOLDOWN BUTTON_UP
272 #define MPEG_VOLUP BUTTON_DOWN
273 #define MPEG_RW BUTTON_LEFT
274 #define MPEG_FF BUTTON_RIGHT
276 #elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
277 #define MPEG_MENU BUTTON_MENU
278 #define MPEG_STOP BUTTON_POWER
279 #define MPEG_PAUSE BUTTON_SELECT
280 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
281 #define MPEG_VOLUP BUTTON_VOL_UP
282 #define MPEG_RW BUTTON_LEFT
283 #define MPEG_FF BUTTON_RIGHT
285 #elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
286 #define MPEG_MENU BUTTON_MENU
287 #define MPEG_STOP BUTTON_POWER
288 #define MPEG_PAUSE BUTTON_PLAY
289 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
290 #define MPEG_VOLUP BUTTON_VOL_UP
291 #define MPEG_RW BUTTON_PREV
292 #define MPEG_FF BUTTON_NEXT
294 #elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
295 #define MPEG_MENU BUTTON_MENU
296 #define MPEG_STOP BUTTON_POWER
297 #define MPEG_PAUSE BUTTON_PLAY
298 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
299 #define MPEG_VOLUP BUTTON_VOL_UP
300 #define MPEG_RW BUTTON_UP
301 #define MPEG_FF BUTTON_DOWN
303 #elif CONFIG_KEYPAD == ONDAVX747_PAD
304 #define MPEG_MENU (BUTTON_MENU|BUTTON_REL)
305 //#define MPEG_STOP BUTTON_POWER
306 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
307 #define MPEG_VOLUP BUTTON_VOL_UP
309 #elif CONFIG_KEYPAD == ONDAVX777_PAD
310 #define MPEG_MENU BUTTON_POWER
312 #elif CONFIG_KEYPAD == SAMSUNG_YH_PAD
313 #define MPEG_MENU BUTTON_LEFT
314 #define MPEG_STOP BUTTON_RIGHT
315 #define MPEG_PAUSE BUTTON_PLAY
316 #define MPEG_VOLDOWN BUTTON_DOWN
317 #define MPEG_VOLUP BUTTON_UP
318 #define MPEG_RW BUTTON_REW
319 #define MPEG_FF BUTTON_FFWD
321 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
322 #define MPEG_MENU BUTTON_MENU
323 #define MPEG_STOP BUTTON_REC
324 #define MPEG_PAUSE BUTTON_PLAY
325 #define MPEG_VOLDOWN BUTTON_DOWN
326 #define MPEG_VOLUP BUTTON_UP
327 #define MPEG_RW BUTTON_PREV
328 #define MPEG_FF BUTTON_NEXT
330 #elif CONFIG_KEYPAD == MPIO_HD200_PAD
331 #define MPEG_MENU BUTTON_FUNC
332 #define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
333 #define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
334 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
335 #define MPEG_VOLUP BUTTON_VOL_UP
336 #define MPEG_RW BUTTON_REW
337 #define MPEG_FF BUTTON_FF
339 #elif CONFIG_KEYPAD == MPIO_HD300_PAD
340 #define MPEG_MENU BUTTON_MENU
341 #define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
342 #define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
343 #define MPEG_VOLDOWN BUTTON_DOWN
344 #define MPEG_VOLUP BUTTON_UP
345 #define MPEG_RW BUTTON_REW
346 #define MPEG_FF BUTTON_FF
348 #else
349 #error No keymap defined!
350 #endif
352 #ifdef HAVE_TOUCHSCREEN
353 #ifndef MPEG_MENU
354 #define MPEG_MENU (BUTTON_TOPRIGHT|BUTTON_REL)
355 #endif
356 #ifndef MPEG_STOP
357 #define MPEG_STOP BUTTON_TOPLEFT
358 #endif
359 #ifndef MPEG_PAUSE
360 #define MPEG_PAUSE BUTTON_CENTER
361 #endif
362 #ifndef MPEG_VOLDOWN
363 #define MPEG_VOLDOWN BUTTON_BOTTOMMIDDLE
364 #endif
365 #ifndef MPEG_VOLUP
366 #define MPEG_VOLUP BUTTON_TOPMIDDLE
367 #endif
368 #ifndef MPEG_RW
369 #define MPEG_RW BUTTON_MIDLEFT
370 #endif
371 #ifndef MPEG_FF
372 #define MPEG_FF BUTTON_MIDRIGHT
373 #endif
374 #endif
376 /* One thing we can do here for targets with remotes is having a display
377 * always on the remote instead of always forcing a popup on the main display */
379 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
380 /* 3% of 30min file == 54s step size */
381 #define MIN_FF_REWIND_STEP (TS_SECOND/2)
382 #define OSD_MIN_UPDATE_INTERVAL (HZ/2)
384 enum video_action
386 VIDEO_STOP = 0,
387 VIDEO_PREV,
388 VIDEO_NEXT,
391 /* OSD status - same order as icon array */
392 enum osd_status_enum
394 OSD_STATUS_STOPPED = 0,
395 OSD_STATUS_PAUSED,
396 OSD_STATUS_PLAYING,
397 OSD_STATUS_FF,
398 OSD_STATUS_RW,
399 OSD_STATUS_COUNT,
400 OSD_STATUS_MASK = 0x7
403 enum osd_bits
405 OSD_REFRESH_DEFAULT = 0x0000, /* Only refresh elements when due */
406 /* Refresh the... */
407 OSD_REFRESH_VOLUME = 0x0001, /* ...volume display */
408 OSD_REFRESH_TIME = 0x0002, /* ...time display+progress */
409 OSD_REFRESH_STATUS = 0x0004, /* ...playback status icon */
410 OSD_REFRESH_BACKGROUND = 0x0008, /* ...background (implies ALL) */
411 OSD_REFRESH_VIDEO = 0x0010, /* ...video image upon timeout */
412 OSD_REFRESH_RESUME = 0x0020, /* Resume playback upon timeout */
413 OSD_NODRAW = 0x8000, /* OR bitflag - don't draw anything */
414 OSD_SHOW = 0x4000, /* OR bitflag - show the OSD */
415 OSD_HP_PAUSE = 0x2000,
416 OSD_HIDE = 0x0000, /* hide the OSD (aid readability) */
417 OSD_REFRESH_ALL = 0x000f, /* Only immediate graphical elements */
420 /* Status icons selected according to font height */
421 extern const unsigned char mpegplayer_status_icons_8x8x1[];
422 extern const unsigned char mpegplayer_status_icons_12x12x1[];
423 extern const unsigned char mpegplayer_status_icons_16x16x1[];
425 /* Main border areas that contain OSD elements */
426 #define OSD_BDR_L 2
427 #define OSD_BDR_T 2
428 #define OSD_BDR_R 2
429 #define OSD_BDR_B 2
431 struct osd
433 long hide_tick;
434 long show_for;
435 long print_tick;
436 long print_delay;
437 long resume_tick;
438 long resume_delay;
439 long next_auto_refresh;
440 int x;
441 int y;
442 int width;
443 int height;
444 unsigned fgcolor;
445 unsigned bgcolor;
446 unsigned prog_fillcolor;
447 struct vo_rect update_rect;
448 struct vo_rect prog_rect;
449 struct vo_rect time_rect;
450 struct vo_rect dur_rect;
451 struct vo_rect vol_rect;
452 const unsigned char *icons;
453 struct vo_rect stat_rect;
454 int status;
455 uint32_t curr_time;
456 unsigned auto_refresh;
457 unsigned flags;
460 static struct osd osd;
462 static void osd_show(unsigned show);
464 #ifdef LCD_LANDSCAPE
465 #define _X (x + osd.x)
466 #define _Y (y + osd.y)
467 #define _W width
468 #define _H height
469 #else
470 #define _X (LCD_WIDTH - (y + osd.y) - height)
471 #define _Y (x + osd.x)
472 #define _W height
473 #define _H width
474 #endif
476 #ifdef HAVE_LCD_COLOR
477 /* Blend two colors in 0-100% (0-255) mix of c2 into c1 */
478 static unsigned draw_blendcolor(unsigned c1, unsigned c2, unsigned char amount)
480 int r1 = RGB_UNPACK_RED(c1);
481 int g1 = RGB_UNPACK_GREEN(c1);
482 int b1 = RGB_UNPACK_BLUE(c1);
484 int r2 = RGB_UNPACK_RED(c2);
485 int g2 = RGB_UNPACK_GREEN(c2);
486 int b2 = RGB_UNPACK_BLUE(c2);
488 return LCD_RGBPACK(amount*(r2 - r1) / 255 + r1,
489 amount*(g2 - g1) / 255 + g1,
490 amount*(b2 - b1) / 255 + b1);
492 #endif
494 /* Drawing functions that operate rotated on LCD_PORTRAIT displays -
495 * most are just wrappers of lcd_* functions with transforms applied.
496 * The origin is the upper-left corner of the OSD area */
497 static void draw_update_rect(int x, int y, int width, int height)
499 mylcd_update_rect(_X, _Y, _W, _H);
502 static void draw_clear_area(int x, int y, int width, int height)
504 #ifdef HAVE_LCD_COLOR
505 rb->screen_clear_area(rb->screens[SCREEN_MAIN], _X, _Y, _W, _H);
506 #else
507 int oldmode = grey_get_drawmode();
508 grey_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
509 grey_fillrect(_X, _Y, _W, _H);
510 grey_set_drawmode(oldmode);
511 #endif
514 static void draw_clear_area_rect(const struct vo_rect *rc)
516 draw_clear_area(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t);
519 static void draw_fillrect(int x, int y, int width, int height)
521 mylcd_fillrect(_X, _Y, _W, _H);
524 static void draw_hline(int x1, int x2, int y)
526 #ifdef LCD_LANDSCAPE
527 mylcd_hline(x1 + osd.x, x2 + osd.x, y + osd.y);
528 #else
529 y = LCD_WIDTH - (y + osd.y) - 1;
530 mylcd_vline(y, x1 + osd.x, x2 + osd.x);
531 #endif
534 static void draw_vline(int x, int y1, int y2)
536 #ifdef LCD_LANDSCAPE
537 mylcd_vline(x + osd.x, y1 + osd.y, y2 + osd.y);
538 #else
539 y1 = LCD_WIDTH - (y1 + osd.y) - 1;
540 y2 = LCD_WIDTH - (y2 + osd.y) - 1;
541 mylcd_hline(y1, y2, x + osd.x);
542 #endif
545 static void draw_scrollbar_draw(int x, int y, int width, int height,
546 uint32_t min, uint32_t max, uint32_t val)
548 unsigned oldfg = mylcd_get_foreground();
550 draw_hline(x + 1, x + width - 2, y);
551 draw_hline(x + 1, x + width - 2, y + height - 1);
552 draw_vline(x, y + 1, y + height - 2);
553 draw_vline(x + width - 1, y + 1, y + height - 2);
555 val = muldiv_uint32(width - 2, val, max - min);
556 val = MIN(val, (uint32_t)(width - 2));
558 draw_fillrect(x + 1, y + 1, val, height - 2);
560 mylcd_set_foreground(osd.prog_fillcolor);
562 draw_fillrect(x + 1 + val, y + 1, width - 2 - val, height - 2);
564 mylcd_set_foreground(oldfg);
567 static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int min,
568 int max, int val)
570 draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t,
571 min, max, val);
574 #ifdef LCD_PORTRAIT
575 /* Portrait displays need rotated text rendering */
577 /* Limited function that only renders in DRMODE_FG and uses absolute screen
578 * coordinates */
579 static void draw_oriented_mono_bitmap_part(const unsigned char *src,
580 int src_x, int src_y,
581 int stride, int x, int y,
582 int width, int height)
584 const unsigned char *src_end;
585 fb_data *dst, *dst_end;
586 unsigned fg_pattern, bg_pattern;
588 if (x + width > SCREEN_WIDTH)
589 width = SCREEN_WIDTH - x; /* Clip right */
590 if (x < 0)
591 width += x, x = 0; /* Clip left */
592 if (width <= 0)
593 return; /* nothing left to do */
595 if (y + height > SCREEN_HEIGHT)
596 height = SCREEN_HEIGHT - y; /* Clip bottom */
597 if (y < 0)
598 height += y, y = 0; /* Clip top */
599 if (height <= 0)
600 return; /* nothing left to do */
602 fg_pattern = rb->lcd_get_foreground();
603 bg_pattern = rb->lcd_get_background();
605 src += stride * (src_y >> 3) + src_x; /* move starting point */
606 src_y &= 7;
607 src_end = src + width;
609 dst = rb->lcd_framebuffer + (LCD_WIDTH - y) + x*LCD_WIDTH;
612 const unsigned char *src_col = src++;
613 unsigned data = *src_col >> src_y;
614 int numbits = 8 - src_y;
616 fb_data *dst_col = dst;
617 dst_end = dst_col - height;
618 dst += LCD_WIDTH;
622 dst_col--;
624 if (data & 1)
625 *dst_col = fg_pattern;
626 #if 0
627 else
628 *dst_col = bg_pattern;
629 #endif
630 data >>= 1;
631 if (--numbits == 0) {
632 src_col += stride;
633 data = *src_col;
634 numbits = 8;
637 while (dst_col > dst_end);
639 while (src < src_end);
642 static void draw_putsxy_oriented(int x, int y, const char *str)
644 unsigned short ch;
645 unsigned short *ucs;
646 int ofs = MIN(x, 0);
647 struct font* pf = rb->font_get(FONT_UI);
649 ucs = rb->bidi_l2v(str, 1);
651 x += osd.x;
652 y += osd.y;
654 while ((ch = *ucs++) != 0 && x < SCREEN_WIDTH)
656 int width;
657 const unsigned char *bits;
659 /* get proportional width and glyph bits */
660 width = rb->font_get_width(pf, ch);
662 if (ofs > width) {
663 ofs -= width;
664 continue;
667 bits = rb->font_get_bits(pf, ch);
669 draw_oriented_mono_bitmap_part(bits, ofs, 0, width, x, y,
670 width - ofs, pf->height);
672 x += width - ofs;
673 ofs = 0;
676 #else
677 static void draw_oriented_mono_bitmap_part(const unsigned char *src,
678 int src_x, int src_y,
679 int stride, int x, int y,
680 int width, int height)
682 int mode = mylcd_get_drawmode();
683 mylcd_set_drawmode(DRMODE_FG);
684 mylcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height);
685 mylcd_set_drawmode(mode);
688 static void draw_putsxy_oriented(int x, int y, const char *str)
690 int mode = mylcd_get_drawmode();
691 mylcd_set_drawmode(DRMODE_FG);
692 mylcd_putsxy(x + osd.x, y + osd.y, str);
693 mylcd_set_drawmode(mode);
695 #endif /* LCD_PORTRAIT */
697 #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
698 /* So we can refresh the overlay */
699 static void osd_lcd_enable_hook(void* param)
701 (void)param;
702 rb->queue_post(rb->button_queue, LCD_ENABLE_EVENT_1, 0);
704 #endif
706 static void osd_backlight_on_video_mode(bool video_on)
708 if (video_on) {
709 /* Turn off backlight timeout */
710 /* backlight control in lib/helper.c */
711 backlight_force_on();
712 #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
713 rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
714 #endif
715 } else {
716 #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
717 rb->add_event(LCD_EVENT_ACTIVATION, false, osd_lcd_enable_hook);
718 #endif
719 /* Revert to user's backlight settings */
720 backlight_use_settings();
724 #ifdef HAVE_BACKLIGHT_BRIGHTNESS
725 static void osd_backlight_brightness_video_mode(bool video_on)
727 if (settings.backlight_brightness < 0)
728 return;
730 mpeg_backlight_update_brightness(
731 video_on ? settings.backlight_brightness : -1);
733 #else
734 #define osd_backlight_brightness_video_mode(video_on)
735 #endif /* HAVE_BACKLIGHT_BRIGHTNESS */
737 static void osd_text_init(void)
739 struct hms hms;
740 char buf[32];
741 int phys;
742 int spc_width;
744 mylcd_setfont(FONT_UI);
746 osd.x = 0;
747 osd.width = SCREEN_WIDTH;
749 vo_rect_clear(&osd.time_rect);
750 vo_rect_clear(&osd.stat_rect);
751 vo_rect_clear(&osd.prog_rect);
752 vo_rect_clear(&osd.vol_rect);
754 ts_to_hms(stream_get_duration(), &hms);
755 hms_format(buf, sizeof (buf), &hms);
756 mylcd_getstringsize(buf, &osd.time_rect.r, &osd.time_rect.b);
758 /* Choose well-sized bitmap images relative to font height */
759 if (osd.time_rect.b < 12) {
760 osd.icons = mpegplayer_status_icons_8x8x1;
761 osd.stat_rect.r = osd.stat_rect.b = 8;
762 } else if (osd.time_rect.b < 16) {
763 osd.icons = mpegplayer_status_icons_12x12x1;
764 osd.stat_rect.r = osd.stat_rect.b = 12;
765 } else {
766 osd.icons = mpegplayer_status_icons_16x16x1;
767 osd.stat_rect.r = osd.stat_rect.b = 16;
770 if (osd.stat_rect.b < osd.time_rect.b) {
771 vo_rect_offset(&osd.stat_rect, 0,
772 (osd.time_rect.b - osd.stat_rect.b) / 2 + OSD_BDR_T);
773 vo_rect_offset(&osd.time_rect, OSD_BDR_L, OSD_BDR_T);
774 } else {
775 vo_rect_offset(&osd.time_rect, OSD_BDR_L,
776 osd.stat_rect.b - osd.time_rect.b + OSD_BDR_T);
777 vo_rect_offset(&osd.stat_rect, 0, OSD_BDR_T);
780 osd.dur_rect = osd.time_rect;
782 phys = rb->sound_val2phys(SOUND_VOLUME, rb->sound_min(SOUND_VOLUME));
783 rb->snprintf(buf, sizeof(buf), "%d%s", phys,
784 rb->sound_unit(SOUND_VOLUME));
786 mylcd_getstringsize(" ", &spc_width, NULL);
787 mylcd_getstringsize(buf, &osd.vol_rect.r, &osd.vol_rect.b);
789 osd.prog_rect.r = SCREEN_WIDTH - OSD_BDR_L - spc_width -
790 osd.vol_rect.r - OSD_BDR_R;
791 osd.prog_rect.b = 3*osd.stat_rect.b / 4;
792 vo_rect_offset(&osd.prog_rect, osd.time_rect.l,
793 osd.time_rect.b);
795 vo_rect_offset(&osd.stat_rect,
796 (osd.prog_rect.r + osd.prog_rect.l - osd.stat_rect.r) / 2,
799 vo_rect_offset(&osd.dur_rect,
800 osd.prog_rect.r - osd.dur_rect.r, 0);
802 vo_rect_offset(&osd.vol_rect, osd.prog_rect.r + spc_width,
803 (osd.prog_rect.b + osd.prog_rect.t - osd.vol_rect.b) / 2);
805 osd.height = OSD_BDR_T + MAX(osd.prog_rect.b, osd.vol_rect.b) -
806 MIN(osd.time_rect.t, osd.stat_rect.t) + OSD_BDR_B;
808 #ifdef HAVE_LCD_COLOR
809 osd.height = ALIGN_UP(osd.height, 2);
810 #endif
811 osd.y = SCREEN_HEIGHT - osd.height;
813 mylcd_setfont(FONT_SYSFIXED);
816 static void osd_init(void)
818 osd.flags = 0;
819 osd.show_for = HZ*4;
820 osd.print_delay = 75*HZ/100;
821 osd.resume_delay = HZ/2;
822 #ifdef HAVE_LCD_COLOR
823 osd.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd);
824 osd.fgcolor = LCD_WHITE;
825 osd.prog_fillcolor = LCD_BLACK;
826 #else
827 osd.bgcolor = GREY_LIGHTGRAY;
828 osd.fgcolor = GREY_BLACK;
829 osd.prog_fillcolor = GREY_WHITE;
830 #endif
831 osd.curr_time = 0;
832 osd.status = OSD_STATUS_STOPPED;
833 osd.auto_refresh = OSD_REFRESH_TIME;
834 osd.next_auto_refresh = *rb->current_tick;
835 osd_text_init();
838 static void osd_schedule_refresh(unsigned refresh)
840 long tick = *rb->current_tick;
842 if (refresh & OSD_REFRESH_VIDEO)
843 osd.print_tick = tick + osd.print_delay;
845 if (refresh & OSD_REFRESH_RESUME)
846 osd.resume_tick = tick + osd.resume_delay;
848 osd.auto_refresh |= refresh;
851 static void osd_cancel_refresh(unsigned refresh)
853 osd.auto_refresh &= ~refresh;
856 /* Refresh the background area */
857 static void osd_refresh_background(void)
859 char buf[32];
860 struct hms hms;
862 unsigned bg = mylcd_get_background();
863 mylcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
865 #ifdef HAVE_LCD_COLOR
866 /* Draw a "raised" area for our graphics */
867 mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 192));
868 draw_hline(0, osd.width, 0);
870 mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 80));
871 draw_hline(0, osd.width, 1);
873 mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 48));
874 draw_hline(0, osd.width, osd.height-2);
876 mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 128));
877 draw_hline(0, osd.width, osd.height-1);
879 mylcd_set_background(bg);
880 draw_clear_area(0, 2, osd.width, osd.height - 4);
881 #else
882 /* Give contrast with the main background */
883 mylcd_set_background(MYLCD_WHITE);
884 draw_hline(0, osd.width, 0);
886 mylcd_set_background(MYLCD_DARKGRAY);
887 draw_hline(0, osd.width, osd.height-1);
889 mylcd_set_background(bg);
890 draw_clear_area(0, 1, osd.width, osd.height - 2);
891 #endif
893 vo_rect_set_ext(&osd.update_rect, 0, 0, osd.width, osd.height);
894 mylcd_set_drawmode(DRMODE_SOLID);
896 if (stream_get_duration() != INVALID_TIMESTAMP) {
897 /* Draw the movie duration */
898 ts_to_hms(stream_get_duration(), &hms);
899 hms_format(buf, sizeof (buf), &hms);
900 draw_putsxy_oriented(osd.dur_rect.l, osd.dur_rect.t, buf);
902 /* else don't know the duration */
905 /* Refresh the current time display + the progress bar */
906 static void osd_refresh_time(void)
908 char buf[32];
909 struct hms hms;
911 uint32_t duration = stream_get_duration();
913 draw_scrollbar_draw_rect(&osd.prog_rect, 0, duration,
914 osd.curr_time);
916 ts_to_hms(osd.curr_time, &hms);
917 hms_format(buf, sizeof (buf), &hms);
919 draw_clear_area_rect(&osd.time_rect);
920 draw_putsxy_oriented(osd.time_rect.l, osd.time_rect.t, buf);
922 vo_rect_union(&osd.update_rect, &osd.update_rect,
923 &osd.prog_rect);
924 vo_rect_union(&osd.update_rect, &osd.update_rect,
925 &osd.time_rect);
928 /* Refresh the volume display area */
929 static void osd_refresh_volume(void)
931 char buf[32];
932 int width;
934 int volume = rb->global_settings->volume;
935 rb->snprintf(buf, sizeof (buf), "%d%s",
936 rb->sound_val2phys(SOUND_VOLUME, volume),
937 rb->sound_unit(SOUND_VOLUME));
938 mylcd_getstringsize(buf, &width, NULL);
940 /* Right-justified */
941 draw_clear_area_rect(&osd.vol_rect);
942 draw_putsxy_oriented(osd.vol_rect.r - width, osd.vol_rect.t, buf);
944 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.vol_rect);
947 /* Refresh the status icon */
948 static void osd_refresh_status(void)
950 int icon_size = osd.stat_rect.r - osd.stat_rect.l;
952 draw_clear_area_rect(&osd.stat_rect);
954 #ifdef HAVE_LCD_COLOR
955 /* Draw status icon with a drop shadow */
956 unsigned oldfg = mylcd_get_foreground();
957 int i = 1;
959 mylcd_set_foreground(draw_blendcolor(mylcd_get_background(),
960 MYLCD_BLACK, 96));
962 while (1)
964 draw_oriented_mono_bitmap_part(osd.icons,
965 icon_size*osd.status,
967 icon_size*OSD_STATUS_COUNT,
968 osd.stat_rect.l + osd.x + i,
969 osd.stat_rect.t + osd.y + i,
970 icon_size, icon_size);
972 if (--i < 0)
973 break;
975 mylcd_set_foreground(oldfg);
978 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
979 #else
980 draw_oriented_mono_bitmap_part(osd.icons,
981 icon_size*osd.status,
983 icon_size*OSD_STATUS_COUNT,
984 osd.stat_rect.l + osd.x,
985 osd.stat_rect.t + osd.y,
986 icon_size, icon_size);
987 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
988 #endif
991 /* Update the current status which determines which icon is displayed */
992 static bool osd_update_status(void)
994 int status;
996 switch (stream_status())
998 default:
999 status = OSD_STATUS_STOPPED;
1000 break;
1001 case STREAM_PAUSED:
1002 /* If paused with a pending resume, coerce it to OSD_STATUS_PLAYING */
1003 status = (osd.auto_refresh & OSD_REFRESH_RESUME) ?
1004 OSD_STATUS_PLAYING : OSD_STATUS_PAUSED;
1005 break;
1006 case STREAM_PLAYING:
1007 status = OSD_STATUS_PLAYING;
1008 break;
1011 if (status != osd.status) {
1012 /* A refresh is needed */
1013 osd.status = status;
1014 return true;
1017 return false;
1020 /* Update the current time that will be displayed */
1021 static void osd_update_time(void)
1023 uint32_t start;
1024 osd.curr_time = stream_get_seek_time(&start);
1025 osd.curr_time -= start;
1028 /* Refresh various parts of the OSD - showing it if it is hidden */
1029 static void osd_refresh(int hint)
1031 long tick;
1032 unsigned oldbg, oldfg;
1034 tick = *rb->current_tick;
1036 if (hint == OSD_REFRESH_DEFAULT) {
1037 /* The default which forces no updates */
1039 /* Make sure Rockbox doesn't turn off the player because of
1040 too little activity */
1041 if (osd.status == OSD_STATUS_PLAYING)
1042 rb->reset_poweroff_timer();
1044 /* Redraw the current or possibly extract a new video frame */
1045 if ((osd.auto_refresh & OSD_REFRESH_VIDEO) &&
1046 TIME_AFTER(tick, osd.print_tick)) {
1047 osd.auto_refresh &= ~OSD_REFRESH_VIDEO;
1048 stream_draw_frame(false);
1051 /* Restart playback if the timout was reached */
1052 if ((osd.auto_refresh & OSD_REFRESH_RESUME) &&
1053 TIME_AFTER(tick, osd.resume_tick)) {
1054 osd.auto_refresh &= ~(OSD_REFRESH_RESUME | OSD_REFRESH_VIDEO);
1055 stream_resume();
1058 /* If not visible, return */
1059 if (!(osd.flags & OSD_SHOW))
1060 return;
1062 /* Hide if the visibility duration was reached */
1063 if (TIME_AFTER(tick, osd.hide_tick)) {
1064 osd_show(OSD_HIDE);
1065 return;
1067 } else {
1068 /* A forced update of some region */
1070 /* Show if currently invisible */
1071 if (!(osd.flags & OSD_SHOW)) {
1072 /* Avoid call back into this function - it will be drawn */
1073 osd_show(OSD_SHOW | OSD_NODRAW);
1074 hint = OSD_REFRESH_ALL;
1077 /* Move back timeouts for frame print and hide */
1078 osd.print_tick = tick + osd.print_delay;
1079 osd.hide_tick = tick + osd.show_for;
1082 if (TIME_AFTER(tick, osd.next_auto_refresh)) {
1083 /* Refresh whatever graphical elements are due automatically */
1084 osd.next_auto_refresh = tick + OSD_MIN_UPDATE_INTERVAL;
1086 if (osd.auto_refresh & OSD_REFRESH_STATUS) {
1087 if (osd_update_status())
1088 hint |= OSD_REFRESH_STATUS;
1091 if (osd.auto_refresh & OSD_REFRESH_TIME) {
1092 osd_update_time();
1093 hint |= OSD_REFRESH_TIME;
1097 if (hint == 0)
1098 return; /* No drawing needed */
1100 /* Set basic drawing params that are used. Elements that perform variations
1101 * will restore them. */
1102 oldfg = mylcd_get_foreground();
1103 oldbg = mylcd_get_background();
1105 mylcd_setfont(FONT_UI);
1106 mylcd_set_foreground(osd.fgcolor);
1107 mylcd_set_background(osd.bgcolor);
1109 vo_rect_clear(&osd.update_rect);
1111 if (hint & OSD_REFRESH_BACKGROUND) {
1112 osd_refresh_background();
1113 hint |= OSD_REFRESH_ALL; /* Requires a redraw of everything */
1116 if (hint & OSD_REFRESH_TIME) {
1117 osd_refresh_time();
1120 if (hint & OSD_REFRESH_VOLUME) {
1121 osd_refresh_volume();
1124 if (hint & OSD_REFRESH_STATUS) {
1125 osd_refresh_status();
1128 /* Go back to defaults */
1129 mylcd_setfont(FONT_SYSFIXED);
1130 mylcd_set_foreground(oldfg);
1131 mylcd_set_background(oldbg);
1133 /* Update the dirty rectangle */
1134 vo_lock();
1136 draw_update_rect(osd.update_rect.l,
1137 osd.update_rect.t,
1138 osd.update_rect.r - osd.update_rect.l,
1139 osd.update_rect.b - osd.update_rect.t);
1141 vo_unlock();
1144 /* Show/Hide the OSD */
1145 static void osd_show(unsigned show)
1147 if (((show ^ osd.flags) & OSD_SHOW) == 0)
1149 if (show & OSD_SHOW) {
1150 osd.hide_tick = *rb->current_tick + osd.show_for;
1152 return;
1155 if (show & OSD_SHOW) {
1156 /* Clip away the part of video that is covered */
1157 struct vo_rect rc = { 0, 0, SCREEN_WIDTH, osd.y };
1159 osd.flags |= OSD_SHOW;
1161 if (osd.status != OSD_STATUS_PLAYING) {
1162 /* Not playing - set brightness to mpegplayer setting */
1163 osd_backlight_brightness_video_mode(true);
1166 stream_vo_set_clip(&rc);
1168 if (!(show & OSD_NODRAW))
1169 osd_refresh(OSD_REFRESH_ALL);
1170 } else {
1171 /* Uncover clipped video area and redraw it */
1172 osd.flags &= ~OSD_SHOW;
1174 draw_clear_area(0, 0, osd.width, osd.height);
1176 if (!(show & OSD_NODRAW)) {
1177 vo_lock();
1178 draw_update_rect(0, 0, osd.width, osd.height);
1179 vo_unlock();
1181 stream_vo_set_clip(NULL);
1182 stream_draw_frame(false);
1183 } else {
1184 stream_vo_set_clip(NULL);
1187 if (osd.status != OSD_STATUS_PLAYING) {
1188 /* Not playing - restore backlight brightness */
1189 osd_backlight_brightness_video_mode(false);
1194 /* Set the current status - update screen if specified */
1195 static void osd_set_status(int status)
1197 bool draw = (status & OSD_NODRAW) == 0;
1199 status &= OSD_STATUS_MASK;
1201 if (osd.status != status) {
1203 osd.status = status;
1205 if (draw)
1206 osd_refresh(OSD_REFRESH_STATUS);
1210 /* Get the current status value */
1211 static int osd_get_status(void)
1213 return osd.status & OSD_STATUS_MASK;
1216 /* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;) */
1217 static uint32_t osd_ff_rw(int btn, unsigned refresh)
1219 unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step;
1220 const long ff_rw_accel = (rb->global_settings->ff_rewind_accel + 3);
1221 uint32_t start;
1222 uint32_t time = stream_get_seek_time(&start);
1223 const uint32_t duration = stream_get_duration();
1224 unsigned int max_step = 0;
1225 uint32_t ff_rw_count = 0;
1226 unsigned status = osd.status;
1228 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME |
1229 OSD_REFRESH_TIME);
1231 time -= start; /* Absolute clock => stream-relative */
1233 switch (btn)
1235 case MPEG_FF:
1236 #ifdef MPEG_FF2
1237 case MPEG_FF2:
1238 #endif
1239 #ifdef MPEG_RC_FF
1240 case MPEG_RC_FF:
1241 #endif
1242 if (!(btn & BUTTON_REPEAT))
1243 osd_set_status(OSD_STATUS_FF);
1244 btn = MPEG_FF | BUTTON_REPEAT; /* simplify code below */
1245 break;
1246 case MPEG_RW:
1247 #ifdef MPEG_RW2
1248 case MPEG_RW2:
1249 #endif
1250 #ifdef MPEG_RC_RW
1251 case MPEG_RC_RW:
1252 #endif
1253 if (!(btn & BUTTON_REPEAT))
1254 osd_set_status(OSD_STATUS_RW);
1255 btn = MPEG_RW | BUTTON_REPEAT; /* simplify code below */
1256 break;
1257 default:
1258 btn = -1;
1261 while (1)
1263 stream_keep_disk_active();
1265 switch (btn)
1267 case BUTTON_NONE:
1268 osd_refresh(OSD_REFRESH_DEFAULT);
1269 break;
1271 case MPEG_FF | BUTTON_REPEAT:
1272 case MPEG_RW | BUTTON_REPEAT:
1273 #ifdef MPEG_FF2
1274 case MPEG_FF2 | BUTTON_REPEAT:
1275 #endif
1276 #ifdef MPEG_RW2
1277 case MPEG_RW2 | BUTTON_REPEAT:
1278 #endif
1279 #ifdef MPEG_RC_FF
1280 case MPEG_RC_FF | BUTTON_REPEAT:
1281 case MPEG_RC_RW | BUTTON_REPEAT:
1282 #endif
1283 break;
1285 case MPEG_FF | BUTTON_REL:
1286 case MPEG_RW | BUTTON_REL:
1287 #ifdef MPEG_FF2
1288 case MPEG_FF2 | BUTTON_REL:
1289 #endif
1290 #ifdef MPEG_RW2
1291 case MPEG_RW2 | BUTTON_REL:
1292 #endif
1293 #ifdef MPEG_RC_FF
1294 case MPEG_RC_FF | BUTTON_REL:
1295 case MPEG_RC_RW | BUTTON_REL:
1296 #endif
1297 if (osd.status == OSD_STATUS_FF)
1298 time += ff_rw_count;
1299 else if (osd.status == OSD_STATUS_RW)
1300 time -= ff_rw_count;
1302 /* Fall-through */
1303 case -1:
1304 default:
1305 osd_schedule_refresh(refresh);
1306 osd_set_status(status);
1307 osd_schedule_refresh(OSD_REFRESH_TIME);
1308 return time;
1311 if (osd.status == OSD_STATUS_FF) {
1312 /* fast forwarding, calc max step relative to end */
1313 max_step = muldiv_uint32(duration - (time + ff_rw_count),
1314 FF_REWIND_MAX_PERCENT, 100);
1315 } else {
1316 /* rewinding, calc max step relative to start */
1317 max_step = muldiv_uint32(time - ff_rw_count,
1318 FF_REWIND_MAX_PERCENT, 100);
1321 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
1323 if (step > max_step)
1324 step = max_step;
1326 ff_rw_count += step;
1328 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
1329 step += step >> ff_rw_accel;
1331 if (osd.status == OSD_STATUS_FF) {
1332 if (duration - time <= ff_rw_count)
1333 ff_rw_count = duration - time;
1335 osd.curr_time = time + ff_rw_count;
1336 } else {
1337 if (time <= ff_rw_count)
1338 ff_rw_count = time;
1340 osd.curr_time = time - ff_rw_count;
1343 osd_refresh(OSD_REFRESH_TIME);
1345 btn = rb->button_get_w_tmo(OSD_MIN_UPDATE_INTERVAL);
1349 static int osd_status(void)
1351 int status = stream_status();
1353 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1354 if (status == STREAM_PAUSED) {
1355 if (osd.auto_refresh & OSD_REFRESH_RESUME)
1356 status = STREAM_PLAYING;
1359 return status;
1362 /* Change the current audio volume by a specified amount */
1363 static void osd_set_volume(int delta)
1365 int vol = rb->global_settings->volume;
1366 int limit;
1368 vol += delta;
1370 if (delta < 0) {
1371 /* Volume down - clip to lower limit */
1372 limit = rb->sound_min(SOUND_VOLUME);
1373 if (vol < limit)
1374 vol = limit;
1375 } else {
1376 /* Volume up - clip to upper limit */
1377 limit = rb->sound_max(SOUND_VOLUME);
1378 if (vol > limit)
1379 vol = limit;
1382 /* Sync the global settings */
1383 if (vol != rb->global_settings->volume) {
1384 rb->sound_set(SOUND_VOLUME, vol);
1385 rb->global_settings->volume = vol;
1388 /* Update the volume display */
1389 osd_refresh(OSD_REFRESH_VOLUME);
1392 /* Begin playback at the specified time */
1393 static int osd_play(uint32_t time)
1395 int retval;
1397 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1399 retval = stream_seek(time, SEEK_SET);
1401 if (retval >= STREAM_OK) {
1402 osd_backlight_on_video_mode(true);
1403 osd_backlight_brightness_video_mode(true);
1404 stream_show_vo(true);
1405 retval = stream_play();
1407 if (retval >= STREAM_OK)
1408 osd_set_status(OSD_STATUS_PLAYING | OSD_NODRAW);
1411 return retval;
1414 /* Halt playback - pause engine and return logical state */
1415 static int osd_halt(void)
1417 int status = stream_pause();
1419 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1420 if (status == STREAM_PAUSED) {
1421 if (osd_get_status() == OSD_STATUS_PLAYING)
1422 status = STREAM_PLAYING;
1425 /* Cancel some auto refreshes - caller will restart them if desired */
1426 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1428 /* No backlight fiddling here - callers does the right thing */
1430 return status;
1433 /* Pause playback if playing */
1434 static int osd_pause(void)
1436 unsigned refresh = osd.auto_refresh;
1437 int status = osd_halt();
1439 if (status == STREAM_PLAYING && (refresh & OSD_REFRESH_RESUME)) {
1440 /* Resume pending - change to a still video frame update */
1441 osd_schedule_refresh(OSD_REFRESH_VIDEO);
1444 osd_set_status(OSD_STATUS_PAUSED);
1446 osd_backlight_on_video_mode(false);
1447 /* Leave brightness alone and restore it when OSD is hidden */
1449 return status;
1452 /* Resume playback if halted or paused */
1453 static void osd_resume(void)
1455 /* Cancel video and resume auto refresh - the resyc when starting
1456 * playback will perform those tasks */
1457 osd_backlight_on_video_mode(true);
1458 osd_backlight_brightness_video_mode(true);
1459 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1460 osd_set_status(OSD_STATUS_PLAYING);
1461 stream_resume();
1464 /* Stop playback - remember the resume point if not closed */
1465 static void osd_stop(void)
1467 uint32_t resume_time;
1469 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1470 osd_set_status(OSD_STATUS_STOPPED | OSD_NODRAW);
1471 osd_show(OSD_HIDE);
1473 stream_stop();
1475 resume_time = stream_get_resume_time();
1477 if (resume_time != INVALID_TIMESTAMP)
1478 settings.resume_time = resume_time;
1480 osd_backlight_on_video_mode(false);
1481 osd_backlight_brightness_video_mode(false);
1484 /* Perform a seek if seeking is possible for this stream - if playing, a delay
1485 * will be inserted before restarting in case the user decides to seek again */
1486 static void osd_seek(int btn)
1488 int status;
1489 unsigned refresh;
1490 uint32_t time;
1492 if (!stream_can_seek())
1493 return;
1495 /* Halt playback - not strictly nescessary but nice */
1496 status = osd_halt();
1498 if (status == STREAM_STOPPED)
1499 return;
1501 osd_show(OSD_SHOW);
1503 if (status == STREAM_PLAYING)
1504 refresh = OSD_REFRESH_RESUME; /* delay resume if playing */
1505 else
1506 refresh = OSD_REFRESH_VIDEO; /* refresh if paused */
1508 /* Obtain a new playback point */
1509 time = osd_ff_rw(btn, refresh);
1511 /* Tell engine to resume at that time */
1512 stream_seek(time, SEEK_SET);
1515 /* Has this file one of the supported extensions? */
1516 static bool is_videofile(const char* file)
1518 static const char * const extensions[] =
1520 /* Should match apps/plugins/viewers.config */
1521 "mpg", "mpeg", "mpv", "m2v"
1524 const char* ext = rb->strrchr(file, '.');
1525 int i;
1527 if (!ext)
1528 return false;
1530 for (i = ARRAYLEN(extensions) - 1; i >= 0; i--)
1532 if (!rb->strcasecmp(ext + 1, extensions[i]))
1533 break;
1536 return i >= 0;
1539 /* deliver the next/previous video file in the current directory.
1540 returns 0 if there is none. */
1541 static bool get_videofile(int direction, char* videofile, size_t bufsize)
1543 struct tree_context *tree = rb->tree_get_context();
1544 struct entry *dircache = tree->dircache;
1545 int i, step, end, found = 0;
1546 char *videoname = rb->strrchr(videofile, '/') + 1;
1547 size_t rest = bufsize - (videoname - videofile) - 1;
1549 if (direction == VIDEO_NEXT) {
1550 i = 0;
1551 step = 1;
1552 end = tree->filesindir;
1553 } else {
1554 i = tree->filesindir-1;
1555 step = -1;
1556 end = -1;
1558 for (; i != end; i += step)
1560 const char* name = dircache[i].name;
1561 if (!rb->strcmp(name, videoname)) {
1562 found = 1;
1563 continue;
1565 if (found && rb->strlen(name) <= rest &&
1566 !(dircache[i].attr & ATTR_DIRECTORY) && is_videofile(name))
1568 rb->strcpy(videoname, name);
1569 return true;
1573 return false;
1576 #ifdef HAVE_HEADPHONE_DETECTION
1577 /* Handle SYS_PHONE_PLUGGED/UNPLUGGED */
1578 static void osd_handle_phone_plug(bool inserted)
1580 if (rb->global_settings->unplug_mode == 0)
1581 return;
1583 /* Wait for any incomplete state transition to complete first */
1584 stream_wait_status();
1586 int status = osd_status();
1588 if (inserted) {
1589 if (rb->global_settings->unplug_mode > 1) {
1590 if (status == STREAM_PAUSED) {
1591 osd_resume();
1594 } else {
1595 if (status == STREAM_PLAYING) {
1596 osd_pause();
1598 if (stream_can_seek() && rb->global_settings->unplug_rw) {
1599 stream_seek(-rb->global_settings->unplug_rw*TS_SECOND,
1600 SEEK_CUR);
1601 osd_schedule_refresh(OSD_REFRESH_VIDEO);
1602 /* Update time display now */
1603 osd_update_time();
1604 osd_refresh(OSD_REFRESH_TIME);
1609 #endif
1611 static int button_loop(void)
1613 int next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;
1615 rb->lcd_setfont(FONT_SYSFIXED);
1616 #ifdef HAVE_LCD_COLOR
1617 rb->lcd_set_foreground(LCD_WHITE);
1618 rb->lcd_set_background(LCD_BLACK);
1619 #endif
1620 rb->lcd_clear_display();
1621 rb->lcd_update();
1623 #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
1624 rb->lcd_set_mode(LCD_MODE_YUV);
1625 #endif
1627 osd_init();
1629 /* Start playback at the specified starting time */
1630 if (osd_play(settings.resume_time) < STREAM_OK) {
1631 rb->splash(HZ*2, "Playback failed");
1632 return VIDEO_STOP;
1635 /* Gently poll the video player for EOS and handle UI */
1636 while (stream_status() != STREAM_STOPPED)
1638 int button;
1640 mpeg_menu_sysevent_clear();
1641 button = rb->button_get_w_tmo(OSD_MIN_UPDATE_INTERVAL/2);
1643 button = mpeg_menu_sysevent_callback(button, NULL);
1645 switch (button)
1647 case BUTTON_NONE:
1649 osd_refresh(OSD_REFRESH_DEFAULT);
1650 continue;
1651 } /* BUTTON_NONE: */
1653 #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
1654 case LCD_ENABLE_EVENT_1:
1656 /* Draw the current frame if prepared already */
1657 stream_draw_frame(true);
1658 break;
1659 } /* LCD_ENABLE_EVENT_1: */
1660 #endif
1662 case MPEG_VOLUP:
1663 case MPEG_VOLUP|BUTTON_REPEAT:
1664 #ifdef MPEG_VOLUP2
1665 case MPEG_VOLUP2:
1666 case MPEG_VOLUP2|BUTTON_REPEAT:
1667 #endif
1668 #ifdef MPEG_RC_VOLUP
1669 case MPEG_RC_VOLUP:
1670 case MPEG_RC_VOLUP|BUTTON_REPEAT:
1671 #endif
1673 osd_set_volume(+1);
1674 break;
1675 } /* MPEG_VOLUP*: */
1677 case MPEG_VOLDOWN:
1678 case MPEG_VOLDOWN|BUTTON_REPEAT:
1679 #ifdef MPEG_VOLDOWN2
1680 case MPEG_VOLDOWN2:
1681 case MPEG_VOLDOWN2|BUTTON_REPEAT:
1682 #endif
1683 #ifdef MPEG_RC_VOLDOWN
1684 case MPEG_RC_VOLDOWN:
1685 case MPEG_RC_VOLDOWN|BUTTON_REPEAT:
1686 #endif
1688 osd_set_volume(-1);
1689 break;
1690 } /* MPEG_VOLDOWN*: */
1692 case MPEG_MENU:
1693 #ifdef MPEG_RC_MENU
1694 case MPEG_RC_MENU:
1695 #endif
1697 int state = osd_halt(); /* save previous state */
1698 int result;
1700 /* Hide video output */
1701 osd_show(OSD_HIDE | OSD_NODRAW);
1702 stream_show_vo(false);
1703 osd_backlight_brightness_video_mode(false);
1705 #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
1706 rb->lcd_set_mode(LCD_MODE_RGB565);
1707 #endif
1709 result = mpeg_menu();
1711 next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;
1713 /* The menu can change the font, so restore */
1714 rb->lcd_setfont(FONT_SYSFIXED);
1715 #ifdef HAVE_LCD_COLOR
1716 rb->lcd_set_foreground(LCD_WHITE);
1717 rb->lcd_set_background(LCD_BLACK);
1718 #endif
1719 rb->lcd_clear_display();
1720 rb->lcd_update();
1722 switch (result)
1724 case MPEG_MENU_QUIT:
1725 next_action = VIDEO_STOP;
1726 osd_stop();
1727 break;
1729 default:
1730 #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
1731 rb->lcd_set_mode(LCD_MODE_YUV);
1732 #endif
1733 /* If not stopped, show video again */
1734 if (state != STREAM_STOPPED) {
1735 osd_show(OSD_SHOW);
1736 stream_show_vo(true);
1739 /* If stream was playing, restart it */
1740 if (state == STREAM_PLAYING) {
1741 osd_resume();
1743 break;
1745 break;
1746 } /* MPEG_MENU: */
1748 #ifdef MPEG_SHOW_OSD
1749 case MPEG_SHOW_OSD:
1750 case MPEG_SHOW_OSD | BUTTON_REPEAT:
1751 /* Show if not visible */
1752 osd_show(OSD_SHOW);
1753 /* Make sure it refreshes */
1754 osd_refresh(OSD_REFRESH_DEFAULT);
1755 break;
1756 #endif
1758 case MPEG_STOP:
1759 #ifdef MPEG_RC_STOP
1760 case MPEG_RC_STOP:
1761 #endif
1762 case ACTION_STD_CANCEL:
1764 next_action = VIDEO_STOP;
1765 osd_stop();
1766 break;
1767 } /* MPEG_STOP: */
1769 case MPEG_PAUSE:
1770 #ifdef MPEG_PAUSE2
1771 case MPEG_PAUSE2:
1772 #endif
1773 #ifdef MPEG_RC_PAUSE
1774 case MPEG_RC_PAUSE:
1775 #endif
1777 int status = osd_status();
1779 if (status == STREAM_PLAYING) {
1780 /* Playing => Paused */
1781 osd_pause();
1783 else if (status == STREAM_PAUSED) {
1784 /* Paused => Playing */
1785 osd_resume();
1788 break;
1789 } /* MPEG_PAUSE*: */
1791 case MPEG_RW:
1792 case MPEG_FF:
1793 #ifdef MPEG_RW2
1794 case MPEG_RW2:
1795 #endif
1796 #ifdef MPEG_FF2
1797 case MPEG_FF2:
1798 #endif
1799 #ifdef MPEG_RC_RW
1800 case MPEG_RC_RW:
1801 case MPEG_RC_FF:
1802 #endif
1804 int old_button = button;
1805 if (settings.play_mode != 0)
1807 /* if button has been released: skip to next/previous file */
1808 button = rb->button_get_w_tmo(OSD_MIN_UPDATE_INTERVAL);
1810 switch (button)
1812 case MPEG_RW | BUTTON_REL:
1814 /* release within 3 seconds: skip to previous file, else
1815 start the current video from the beginning */
1816 osd_stop();
1817 if ( stream_get_resume_time() > 3*TS_SECOND ) {
1818 osd_play(0);
1819 osd_show(OSD_SHOW);
1820 } else {
1821 next_action = VIDEO_PREV;
1823 break;
1825 case MPEG_FF | BUTTON_REL:
1827 osd_stop();
1828 next_action = VIDEO_NEXT;
1829 break;
1831 default:
1833 button = old_button;
1834 osd_seek(button);
1835 break;
1838 break;
1839 } /* MPEG_RW: MPEG_FF: */
1841 #ifdef HAVE_HEADPHONE_DETECTION
1842 case SYS_PHONE_PLUGGED:
1843 case SYS_PHONE_UNPLUGGED:
1845 osd_handle_phone_plug(button == SYS_PHONE_PLUGGED);
1846 break;
1847 } /* SYS_PHONE_*: */
1848 #endif
1850 default:
1852 rb->default_event_handler(button);
1853 break;
1854 } /* default: */
1857 rb->yield();
1858 } /* end while */
1860 osd_stop();
1862 #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
1863 /* Be sure hook is removed before exiting since the stop will put it
1864 * back because of the backlight restore. */
1865 rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
1866 #endif
1868 rb->lcd_setfont(FONT_UI);
1870 return next_action;
1873 enum plugin_status plugin_start(const void* parameter)
1875 static char videofile[MAX_PATH];
1876 int status = PLUGIN_OK; /* assume success */
1877 int result;
1878 int err;
1879 bool quit = false;
1880 const char *errstring;
1882 if (parameter == NULL) {
1883 /* No file = GTFO */
1884 rb->splash(HZ*2, "No File");
1885 return PLUGIN_ERROR;
1888 /* Disable all talking before initializing IRAM */
1889 rb->talk_disable(true);
1891 #ifdef HAVE_LCD_COLOR
1892 rb->lcd_set_backdrop(NULL);
1893 rb->lcd_set_foreground(LCD_WHITE);
1894 rb->lcd_set_background(LCD_BLACK);
1895 #endif
1897 rb->lcd_clear_display();
1898 rb->lcd_update();
1900 rb->strcpy(videofile, (const char*) parameter);
1902 if (stream_init() < STREAM_OK) {
1903 /* Fatal because this should not fail */
1904 DEBUGF("Could not initialize streams\n");
1905 status = PLUGIN_ERROR;
1906 } else {
1907 while (!quit)
1909 int next_action = VIDEO_STOP;
1911 init_settings(videofile);
1912 err = stream_open(videofile);
1914 if (err >= STREAM_OK) {
1915 /* start menu */
1916 rb->lcd_clear_display();
1917 rb->lcd_update();
1918 result = mpeg_start_menu(stream_get_duration());
1920 if (result != MPEG_START_QUIT) {
1921 /* Enter button loop and process UI */
1922 next_action = button_loop();
1925 stream_close();
1927 rb->lcd_clear_display();
1928 rb->lcd_update();
1930 save_settings();
1932 mpeg_menu_sysevent_handle();
1933 } else {
1934 /* Problem with file; display message about it - not
1935 * considered a plugin error */
1936 DEBUGF("Could not open %s\n", videofile);
1937 switch (err)
1939 case STREAM_UNSUPPORTED:
1940 errstring = "Unsupported format";
1941 break;
1942 default:
1943 errstring = "Error opening file: %d";
1946 rb->splashf(HZ*2, errstring, err);
1948 if (settings.play_mode != 0) {
1949 /* Try the next file if the play mode is not single play */
1950 next_action = VIDEO_NEXT;
1954 /* return value of button_loop says, what's next */
1955 switch (next_action)
1957 case VIDEO_NEXT:
1959 if (!get_videofile(VIDEO_NEXT, videofile, sizeof(videofile))) {
1960 /* quit after finished the last videofile */
1961 quit = true;
1963 break;
1965 case VIDEO_PREV:
1967 get_videofile(VIDEO_PREV, videofile, sizeof(videofile));
1968 /* if there is no previous file, play the same videofile */
1969 break;
1971 case VIDEO_STOP:
1973 quit = true;
1974 break;
1980 #if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
1981 rb->lcd_set_mode(LCD_MODE_RGB565);
1982 #endif
1984 stream_exit();
1986 rb->talk_disable(false);
1987 return status;