A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mpegplayer main entrypoint and UI implementation
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
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.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23
24/****************************************************************************
25 * NOTES:
26 *
27 * mpegplayer is structured as follows:
28 *
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.)
39 *
40 * Thread list:
41 * 1) The main thread - Handles user input, settings, basic playback control
42 * and USB connect.
43 *
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.
50 *
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.
55 *
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.
59 *
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.
63 *
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.
70 *
71 * Notes about MPEG files:
72 *
73 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
74 *
75 * FPS is represented in terms of a frame period - this is always an
76 * integer number of 27MHz ticks.
77 *
78 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
79 * 900900 27MHz ticks.
80 *
81 * In libmpeg2, info->sequence->frame_period contains the frame_period.
82 *
83 * Working with Rockbox's 100Hz tick, the common frame rates would need
84 * to be as follows (1):
85 *
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
96 *
97 * *Unofficial framerates
98 *
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 "video_out.h"
107#include "stream_thread.h"
108#include "stream_mgr.h"
109
110
111/* button definitions */
112#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
113#define MPEG_MENU BUTTON_MODE
114#define MPEG_STOP BUTTON_OFF
115#define MPEG_PAUSE BUTTON_ON
116#define MPEG_VOLDOWN BUTTON_DOWN
117#define MPEG_VOLUP BUTTON_UP
118#define MPEG_RW BUTTON_LEFT
119#define MPEG_FF BUTTON_RIGHT
120
121#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
122 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
123#define MPEG_MENU BUTTON_MENU
124#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
125#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
126#define MPEG_VOLDOWN BUTTON_SCROLL_BACK
127#define MPEG_VOLUP BUTTON_SCROLL_FWD
128#define MPEG_RW BUTTON_LEFT
129#define MPEG_FF BUTTON_RIGHT
130
131#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
132#define MPEG_MENU (BUTTON_REC | BUTTON_REL)
133#define MPEG_STOP BUTTON_POWER
134#define MPEG_PAUSE BUTTON_PLAY
135#define MPEG_VOLDOWN BUTTON_DOWN
136#define MPEG_VOLUP BUTTON_UP
137#define MPEG_RW BUTTON_LEFT
138#define MPEG_FF BUTTON_RIGHT
139
140#elif CONFIG_KEYPAD == GIGABEAT_PAD
141#define MPEG_MENU BUTTON_MENU
142#define MPEG_STOP BUTTON_POWER
143#define MPEG_PAUSE BUTTON_SELECT
144#define MPEG_PAUSE2 BUTTON_A
145#define MPEG_VOLDOWN BUTTON_LEFT
146#define MPEG_VOLUP BUTTON_RIGHT
147#define MPEG_VOLDOWN2 BUTTON_VOL_DOWN
148#define MPEG_VOLUP2 BUTTON_VOL_UP
149#define MPEG_RW BUTTON_UP
150#define MPEG_FF BUTTON_DOWN
151
152#define MPEG_RC_MENU BUTTON_RC_DSP
153#define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT)
154#define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL)
155#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
156#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
157#define MPEG_RC_RW BUTTON_RC_REW
158#define MPEG_RC_FF BUTTON_RC_FF
159
160#elif CONFIG_KEYPAD == GIGABEAT_S_PAD
161#define MPEG_MENU BUTTON_MENU
162#define MPEG_STOP BUTTON_POWER
163#define MPEG_PAUSE BUTTON_SELECT
164#define MPEG_PAUSE2 BUTTON_PLAY
165#define MPEG_VOLDOWN BUTTON_LEFT
166#define MPEG_VOLUP BUTTON_RIGHT
167#define MPEG_VOLDOWN2 BUTTON_VOL_DOWN
168#define MPEG_VOLUP2 BUTTON_VOL_UP
169#define MPEG_RW BUTTON_UP
170#define MPEG_RW2 BUTTON_PREV
171#define MPEG_FF BUTTON_DOWN
172#define MPEG_FF2 BUTTON_NEXT
173#define MPEG_SHOW_OSD BUTTON_BACK
174
175#define MPEG_RC_MENU BUTTON_RC_DSP
176#define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT)
177#define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL)
178#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
179#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
180#define MPEG_RC_RW BUTTON_RC_REW
181#define MPEG_RC_FF BUTTON_RC_FF
182
183#elif CONFIG_KEYPAD == IRIVER_H10_PAD
184#define MPEG_MENU BUTTON_LEFT
185#define MPEG_STOP BUTTON_POWER
186#define MPEG_PAUSE BUTTON_PLAY
187#define MPEG_VOLDOWN BUTTON_SCROLL_DOWN
188#define MPEG_VOLUP BUTTON_SCROLL_UP
189#define MPEG_RW BUTTON_REW
190#define MPEG_FF BUTTON_FF
191
192#elif CONFIG_KEYPAD == SANSA_E200_PAD
193#define MPEG_MENU BUTTON_SELECT
194#define MPEG_STOP BUTTON_POWER
195#define MPEG_PAUSE BUTTON_RIGHT
196#define MPEG_VOLDOWN BUTTON_SCROLL_BACK
197#define MPEG_VOLUP BUTTON_SCROLL_FWD
198#define MPEG_RW BUTTON_UP
199#define MPEG_FF BUTTON_DOWN
200
201#elif CONFIG_KEYPAD == SANSA_FUZE_PAD
202#define MPEG_MENU BUTTON_SELECT
203#define MPEG_STOP (BUTTON_HOME|BUTTON_REPEAT)
204#define MPEG_PAUSE BUTTON_UP
205#define MPEG_VOLDOWN BUTTON_SCROLL_BACK
206#define MPEG_VOLUP BUTTON_SCROLL_FWD
207#define MPEG_RW BUTTON_LEFT
208#define MPEG_FF BUTTON_RIGHT
209
210
211#elif CONFIG_KEYPAD == SANSA_C200_PAD || \
212CONFIG_KEYPAD == SANSA_CLIP_PAD || \
213CONFIG_KEYPAD == SANSA_M200_PAD
214#define MPEG_MENU BUTTON_SELECT
215#define MPEG_STOP BUTTON_POWER
216#define MPEG_PAUSE BUTTON_UP
217#define MPEG_VOLDOWN BUTTON_VOL_DOWN
218#define MPEG_VOLUP BUTTON_VOL_UP
219#define MPEG_RW BUTTON_LEFT
220#define MPEG_FF BUTTON_RIGHT
221
222#elif CONFIG_KEYPAD == MROBE500_PAD
223#define MPEG_STOP BUTTON_POWER
224
225#define MPEG_RC_MENU BUTTON_RC_HEART
226#define MPEG_RC_STOP BUTTON_RC_DOWN
227#define MPEG_RC_PAUSE BUTTON_RC_PLAY
228#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
229#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
230#define MPEG_RC_RW BUTTON_RC_REW
231#define MPEG_RC_FF BUTTON_RC_FF
232
233#elif CONFIG_KEYPAD == MROBE100_PAD
234#define MPEG_MENU BUTTON_MENU
235#define MPEG_STOP BUTTON_POWER
236#define MPEG_PAUSE BUTTON_PLAY
237#define MPEG_VOLDOWN BUTTON_DOWN
238#define MPEG_VOLUP BUTTON_UP
239#define MPEG_RW BUTTON_LEFT
240#define MPEG_FF BUTTON_RIGHT
241
242#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
243#define MPEG_MENU BUTTON_RC_MENU
244#define MPEG_STOP BUTTON_RC_REC
245#define MPEG_PAUSE BUTTON_RC_PLAY
246#define MPEG_VOLDOWN BUTTON_RC_VOL_DOWN
247#define MPEG_VOLUP BUTTON_RC_VOL_UP
248#define MPEG_RW BUTTON_RC_REW
249#define MPEG_FF BUTTON_RC_FF
250
251#elif CONFIG_KEYPAD == COWON_D2_PAD
252#define MPEG_MENU (BUTTON_MENU|BUTTON_REL)
253//#define MPEG_STOP BUTTON_POWER
254#define MPEG_VOLDOWN BUTTON_MINUS
255#define MPEG_VOLUP BUTTON_PLUS
256
257#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
258#define MPEG_MENU BUTTON_MENU
259#define MPEG_STOP BUTTON_BACK
260#define MPEG_PAUSE BUTTON_PLAY
261#define MPEG_VOLDOWN BUTTON_UP
262#define MPEG_VOLUP BUTTON_DOWN
263#define MPEG_RW BUTTON_LEFT
264#define MPEG_FF BUTTON_RIGHT
265
266#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD
267#define MPEG_MENU BUTTON_MENU
268#define MPEG_STOP (BUTTON_PLAY|BUTTON_REPEAT)
269#define MPEG_PAUSE (BUTTON_PLAY|BUTTON_REL)
270#define MPEG_VOLDOWN BUTTON_VOL_DOWN
271#define MPEG_VOLUP BUTTON_VOL_UP
272#define MPEG_RW BUTTON_DOWN
273#define MPEG_FF BUTTON_UP
274
275#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
276#define MPEG_MENU BUTTON_MENU
277#define MPEG_STOP BUTTON_POWER
278#define MPEG_PAUSE BUTTON_SELECT
279#define MPEG_VOLDOWN BUTTON_VOL_DOWN
280#define MPEG_VOLUP BUTTON_VOL_UP
281#define MPEG_RW BUTTON_LEFT
282#define MPEG_FF BUTTON_RIGHT
283
284#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
285#define MPEG_MENU BUTTON_MENU
286#define MPEG_STOP BUTTON_POWER
287#define MPEG_PAUSE BUTTON_PLAY
288#define MPEG_VOLDOWN BUTTON_VOL_DOWN
289#define MPEG_VOLUP BUTTON_VOL_UP
290#define MPEG_RW BUTTON_PREV
291#define MPEG_FF BUTTON_NEXT
292
293#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
294#define MPEG_MENU BUTTON_MENU
295#define MPEG_STOP BUTTON_POWER
296#define MPEG_PAUSE BUTTON_PLAY
297#define MPEG_VOLDOWN BUTTON_VOL_DOWN
298#define MPEG_VOLUP BUTTON_VOL_UP
299#define MPEG_RW BUTTON_UP
300#define MPEG_FF BUTTON_DOWN
301
302#elif CONFIG_KEYPAD == ONDAVX747_PAD
303#define MPEG_MENU (BUTTON_MENU|BUTTON_REL)
304//#define MPEG_STOP BUTTON_POWER
305#define MPEG_VOLDOWN BUTTON_VOL_DOWN
306#define MPEG_VOLUP BUTTON_VOL_UP
307
308#elif CONFIG_KEYPAD == ONDAVX777_PAD
309#define MPEG_MENU BUTTON_POWER
310
311#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
312 (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)
313#define MPEG_MENU BUTTON_REW
314#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
315#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
316#define MPEG_VOLDOWN BUTTON_DOWN
317#define MPEG_VOLUP BUTTON_UP
318#define MPEG_RW BUTTON_LEFT
319#define MPEG_FF BUTTON_RIGHT
320#define MPEG_SHOW_OSD BUTTON_FFWD
321
322#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
323#define MPEG_MENU BUTTON_MENU
324#define MPEG_STOP BUTTON_REC
325#define MPEG_PAUSE BUTTON_PLAY
326#define MPEG_VOLDOWN BUTTON_DOWN
327#define MPEG_VOLUP BUTTON_UP
328#define MPEG_RW BUTTON_PREV
329#define MPEG_FF BUTTON_NEXT
330
331#elif CONFIG_KEYPAD == MPIO_HD200_PAD
332#define MPEG_MENU BUTTON_FUNC
333#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
334#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
335#define MPEG_VOLDOWN BUTTON_VOL_DOWN
336#define MPEG_VOLUP BUTTON_VOL_UP
337#define MPEG_RW BUTTON_REW
338#define MPEG_FF BUTTON_FF
339
340#elif CONFIG_KEYPAD == MPIO_HD300_PAD
341#define MPEG_MENU BUTTON_MENU
342#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
343#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
344#define MPEG_VOLDOWN BUTTON_DOWN
345#define MPEG_VOLUP BUTTON_UP
346#define MPEG_RW BUTTON_REW
347#define MPEG_FF BUTTON_FF
348
349#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
350#define MPEG_MENU BUTTON_POWER
351#define MPEG_PAUSE (BUTTON_PLAYPAUSE | BUTTON_REL)
352#define MPEG_STOP (BUTTON_PLAYPAUSE | BUTTON_REPEAT)
353#define MPEG_VOLDOWN BUTTON_VOL_DOWN
354#define MPEG_VOLUP BUTTON_VOL_UP
355#define MPEG_RW BUTTON_LEFT
356#define MPEG_FF BUTTON_RIGHT
357
358#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD
359#define MPEG_MENU BUTTON_POWER
360#define MPEG_PAUSE (BUTTON_SELECT | BUTTON_REL)
361#define MPEG_STOP (BUTTON_SELECT | BUTTON_REPEAT)
362#define MPEG_VOLDOWN BUTTON_VOL_DOWN
363#define MPEG_VOLUP BUTTON_VOL_UP
364#define MPEG_RW BUTTON_LEFT
365#define MPEG_FF BUTTON_RIGHT
366
367#elif CONFIG_KEYPAD == SAMSUNG_YPR0_PAD
368#define MPEG_MENU BUTTON_MENU
369#define MPEG_PAUSE BUTTON_SELECT
370#define MPEG_STOP BUTTON_POWER
371#define MPEG_VOLDOWN BUTTON_DOWN
372#define MPEG_VOLUP BUTTON_UP
373#define MPEG_RW BUTTON_LEFT
374#define MPEG_FF BUTTON_RIGHT
375
376#elif CONFIG_KEYPAD == HM60X_PAD
377#define MPEG_MENU BUTTON_POWER
378#define MPEG_PAUSE BUTTON_SELECT
379#define MPEG_STOP (BUTTON_SELECT | BUTTON_POWER)
380#define MPEG_VOLDOWN (BUTTON_POWER | BUTTON_DOWN)
381#define MPEG_VOLUP (BUTTON_POWER | BUTTON_UP)
382#define MPEG_RW BUTTON_LEFT
383#define MPEG_FF BUTTON_RIGHT
384
385#elif CONFIG_KEYPAD == HM801_PAD
386#define MPEG_MENU BUTTON_POWER
387#define MPEG_PAUSE BUTTON_PLAY
388#define MPEG_STOP (BUTTON_POWER | BUTTON_PLAY)
389#define MPEG_VOLDOWN (BUTTON_POWER | BUTTON_DOWN)
390#define MPEG_VOLUP (BUTTON_POWER | BUTTON_UP)
391#define MPEG_RW BUTTON_PREV
392#define MPEG_FF BUTTON_NEXT
393
394#elif CONFIG_KEYPAD == SONY_NWZ_PAD
395#define MPEG_MENU BUTTON_BACK
396#define MPEG_PAUSE BUTTON_PLAY
397#define MPEG_STOP BUTTON_POWER
398#define MPEG_VOLDOWN BUTTON_UP
399#define MPEG_VOLUP BUTTON_DOWN
400#define MPEG_RW BUTTON_LEFT
401#define MPEG_FF BUTTON_RIGHT
402
403#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
404#define MPEG_MENU BUTTON_MENU
405#define MPEG_PAUSE BUTTON_PLAYPAUSE
406#define MPEG_STOP BUTTON_BACK
407#define MPEG_VOLDOWN BUTTON_DOWN
408#define MPEG_VOLUP BUTTON_UP
409#define MPEG_RW BUTTON_LEFT
410#define MPEG_FF BUTTON_RIGHT
411
412#elif CONFIG_KEYPAD == DX50_PAD
413#define MPEG_MENU BUTTON_POWER
414#define MPEG_VOLDOWN BUTTON_VOL_DOWN
415#define MPEG_VOLUP BUTTON_VOL_UP
416#define MPEG_RW BUTTON_LEFT
417#define MPEG_FF BUTTON_RIGHT
418#define MPEG_PAUSE BUTTON_PLAY
419#define MPEG_STOP (BUTTON_PLAY|BUTTON_REPEAT)
420
421#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD
422#define MPEG_MENU BUTTON_POWER
423#define MPEG_PAUSE BUTTON_MENU
424#define MPEG_STOP (BUTTON_MENU|BUTTON_REPEAT)
425
426#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
427#define MPEG_MENU BUTTON_POWER
428#define MPEG_PAUSE BUTTON_SELECT
429#define MPEG_STOP BUTTON_DOWN
430#define MPEG_VOLDOWN BUTTON_VOLDOWN
431#define MPEG_VOLUP BUTTON_VOLUP
432#define MPEG_RW BUTTON_LEFT
433#define MPEG_FF BUTTON_RIGHT
434
435#elif CONFIG_KEYPAD == XDUOO_X3_PAD
436#define MPEG_MENU BUTTON_PLAY
437#define MPEG_STOP BUTTON_POWER
438#define MPEG_PAUSE BUTTON_HOME
439#define MPEG_VOLDOWN BUTTON_VOL_DOWN
440#define MPEG_VOLUP BUTTON_VOL_UP
441#define MPEG_RW BUTTON_PREV
442#define MPEG_FF BUTTON_NEXT
443
444#elif CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD
445#define MPEG_MENU BUTTON_PLAY
446#define MPEG_STOP BUTTON_POWER
447#define MPEG_PAUSE BUTTON_HOME
448#define MPEG_VOLDOWN BUTTON_VOL_DOWN
449#define MPEG_VOLUP BUTTON_VOL_UP
450#define MPEG_RW BUTTON_PREV
451#define MPEG_FF BUTTON_NEXT
452
453#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD
454#define MPEG_MENU BUTTON_PLAY
455#define MPEG_STOP BUTTON_POWER
456#define MPEG_PAUSE BUTTON_HOME
457#define MPEG_VOLDOWN BUTTON_VOL_DOWN
458#define MPEG_VOLUP BUTTON_VOL_UP
459#define MPEG_RW BUTTON_PREV
460#define MPEG_FF BUTTON_NEXT
461
462#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD
463#define MPEG_MENU BUTTON_PLAY
464#define MPEG_STOP BUTTON_POWER
465#define MPEG_PAUSE BUTTON_HOME
466#define MPEG_VOLDOWN BUTTON_VOL_DOWN
467#define MPEG_VOLUP BUTTON_VOL_UP
468#define MPEG_RW BUTTON_PREV
469#define MPEG_FF BUTTON_NEXT
470
471#elif CONFIG_KEYPAD == EROSQ_PAD
472#define MPEG_MENU BUTTON_MENU
473#define MPEG_STOP BUTTON_POWER
474#define MPEG_PAUSE BUTTON_PLAY
475#define MPEG_VOLDOWN BUTTON_VOL_DOWN
476#define MPEG_VOLUP BUTTON_VOL_UP
477#define MPEG_RW BUTTON_PREV
478#define MPEG_FF BUTTON_NEXT
479
480#elif CONFIG_KEYPAD == FIIO_M3K_PAD
481#define MPEG_MENU BUTTON_MENU
482#define MPEG_STOP BUTTON_POWER
483#define MPEG_PAUSE BUTTON_PLAY
484#define MPEG_VOLDOWN BUTTON_VOL_DOWN
485#define MPEG_VOLUP BUTTON_VOL_UP
486#define MPEG_RW BUTTON_LEFT
487#define MPEG_FF BUTTON_RIGHT
488
489#elif CONFIG_KEYPAD == MA_PAD
490#define MPEG_MENU BUTTON_MENU
491#define MPEG_STOP BUTTON_BACK
492#define MPEG_PAUSE BUTTON_PLAY
493#define MPEG_VOLDOWN BUTTON_DOWN
494#define MPEG_VOLUP BUTTON_UP
495#define MPEG_RW BUTTON_LEFT
496#define MPEG_FF BUTTON_RIGHT
497
498#elif CONFIG_KEYPAD == SHANLING_Q1_PAD
499/* use touchscreen */
500
501#elif CONFIG_KEYPAD == RG_NANO_PAD
502#define MPEG_MENU BUTTON_START
503#define MPEG_STOP BUTTON_X
504#define MPEG_PAUSE BUTTON_A
505#define MPEG_VOLDOWN BUTTON_DOWN
506#define MPEG_VOLUP BUTTON_UP
507#define MPEG_RW BUTTON_LEFT
508#define MPEG_FF BUTTON_RIGHT
509
510#else
511#error No keymap defined!
512#endif
513
514#ifdef HAVE_TOUCHSCREEN
515#ifndef MPEG_MENU
516#define MPEG_MENU (BUTTON_TOPRIGHT|BUTTON_REL)
517#endif
518#ifndef MPEG_STOP
519#define MPEG_STOP BUTTON_TOPLEFT
520#endif
521#ifndef MPEG_PAUSE
522#define MPEG_PAUSE BUTTON_CENTER
523#endif
524#ifndef MPEG_VOLDOWN
525#define MPEG_VOLDOWN BUTTON_BOTTOMMIDDLE
526#endif
527#ifndef MPEG_VOLUP
528#define MPEG_VOLUP BUTTON_TOPMIDDLE
529#endif
530#ifndef MPEG_RW
531#define MPEG_RW BUTTON_MIDLEFT
532#endif
533#ifndef MPEG_FF
534#define MPEG_FF BUTTON_MIDRIGHT
535#endif
536#endif
537
538/* One thing we can do here for targets with remotes is having a display
539 * always on the remote instead of always forcing a popup on the main display */
540
541#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
542 /* 3% of 30min file == 54s step size */
543#define MIN_FF_REWIND_STEP (TS_SECOND/2)
544#define OSD_MIN_UPDATE_INTERVAL (HZ/2)
545#define FPS_UPDATE_INTERVAL (HZ) /* Get new FPS reading each second */
546
547enum video_action
548{
549 VIDEO_STOP = 0,
550 VIDEO_PREV,
551 VIDEO_NEXT,
552 VIDEO_ACTION_MANUAL = 0x8000, /* Flag that says user did it */
553};
554
555/* OSD status - same order as icon array */
556enum osd_status_enum
557{
558 OSD_STATUS_STOPPED = 0,
559 OSD_STATUS_PAUSED,
560 OSD_STATUS_PLAYING,
561 OSD_STATUS_FF,
562 OSD_STATUS_RW,
563 OSD_STATUS_COUNT,
564 OSD_STATUS_MASK = 0x7
565};
566
567enum osd_bits
568{
569 OSD_REFRESH_DEFAULT = 0x0000, /* Only refresh elements when due */
570 /* Refresh the... */
571 OSD_REFRESH_VOLUME = 0x0001, /* ...volume display */
572 OSD_REFRESH_TIME = 0x0002, /* ...time display+progress */
573 OSD_REFRESH_STATUS = 0x0004, /* ...playback status icon */
574 OSD_REFRESH_BACKGROUND = 0x0008, /* ...background (implies ALL) */
575 OSD_REFRESH_VIDEO = 0x0010, /* ...video image upon timeout */
576 OSD_REFRESH_RESUME = 0x0020, /* Resume playback upon timeout */
577 OSD_NODRAW = 0x8000, /* OR bitflag - don't draw anything */
578 OSD_SHOW = 0x4000, /* OR bitflag - show the OSD */
579#ifdef HAVE_HEADPHONE_DETECTION
580 OSD_HP_PAUSE = 0x2000, /* OR bitflag - headphones caused pause */
581#endif
582 OSD_HIDE = 0x0000, /* hide the OSD (aid readability) */
583 OSD_REFRESH_ALL = 0x000f, /* Only immediate graphical elements */
584};
585
586/* Status icons selected according to font height */
587extern const unsigned char mpegplayer_status_icons_8x8x1[];
588extern const unsigned char mpegplayer_status_icons_12x12x1[];
589extern const unsigned char mpegplayer_status_icons_16x16x1[];
590
591/* Main border areas that contain OSD elements */
592#define OSD_BDR_L 2
593#define OSD_BDR_T 2
594#define OSD_BDR_R 2
595#define OSD_BDR_B 2
596
597struct osd
598{
599 long hide_tick;
600 long show_for;
601 long print_tick;
602 long print_delay;
603 long resume_tick;
604 long resume_delay;
605 long next_auto_refresh;
606 int x;
607 int y;
608 int width;
609 int height;
610 unsigned fgcolor;
611 unsigned bgcolor;
612 unsigned prog_fillcolor;
613 struct vo_rect update_rect;
614 struct vo_rect prog_rect;
615 struct vo_rect time_rect;
616 struct vo_rect dur_rect;
617 struct vo_rect vol_rect;
618 const unsigned char *icons;
619 struct vo_rect stat_rect;
620 int status;
621 uint32_t curr_time;
622 unsigned auto_refresh;
623 unsigned flags;
624 int font;
625};
626
627struct fps
628{
629 /* FPS Display */
630 struct vo_rect rect; /* OSD coordinates */
631 int pf_x; /* Screen coordinates */
632 int pf_y;
633 int pf_width;
634 int pf_height;
635 long update_tick; /* When to next update FPS reading */
636 #define FPS_FORMAT "%d.%02d"
637 #define FPS_DIMSTR "999.99" /* For establishing rect size */
638 #define FPS_BUFSIZE sizeof("999.99")
639};
640
641static struct osd osd;
642static struct fps fps NOCACHEBSS_ATTR; /* Accessed on other processor */
643
644#ifdef LCD_PORTRAIT
645static fb_data* get_framebuffer(void)
646{
647 struct viewport *vp_main = *(rb->screens[SCREEN_MAIN]->current_viewport);
648 return vp_main->buffer->fb_ptr;
649}
650#endif
651
652static void osd_show(unsigned show);
653
654#ifdef LCD_LANDSCAPE
655 #define __X (x + osd.x)
656 #define __Y (y + osd.y)
657 #define __W width
658 #define __H height
659#else
660 #define __X (LCD_WIDTH - (y + osd.y) - height)
661 #define __Y (x + osd.x)
662 #define __W height
663 #define __H width
664#endif
665
666#ifdef HAVE_LCD_COLOR
667/* Blend two colors in 0-100% (0-255) mix of c2 into c1 */
668static unsigned draw_blendcolor(unsigned c1, unsigned c2, unsigned char amount)
669{
670 int r1 = RGB_UNPACK_RED(c1);
671 int g1 = RGB_UNPACK_GREEN(c1);
672 int b1 = RGB_UNPACK_BLUE(c1);
673
674 int r2 = RGB_UNPACK_RED(c2);
675 int g2 = RGB_UNPACK_GREEN(c2);
676 int b2 = RGB_UNPACK_BLUE(c2);
677
678 return LCD_RGBPACK(amount*(r2 - r1) / 255 + r1,
679 amount*(g2 - g1) / 255 + g1,
680 amount*(b2 - b1) / 255 + b1);
681}
682#endif
683
684#ifdef PLUGIN_USE_IRAM
685/* IRAM preserving mechanism to enable talking menus */
686static char *iram_saved_copy;
687extern char iramstart[], iramend[];
688
689static void iram_saving_init(void)
690{
691#ifndef SIMULATOR
692 size_t size;
693 iram_saved_copy = (char *)rb->plugin_get_buffer(&size);
694
695 if (size >= (size_t)(iramend-iramstart))
696 iram_saved_copy += size - (size_t)(iramend - iramstart);
697 else
698#endif
699 iram_saved_copy = NULL;
700
701 return;
702}
703
704void mpegplayer_iram_preserve(void)
705{
706 if (iram_saved_copy)
707 {
708 rb->memcpy(iram_saved_copy, iramstart, iramend-iramstart);
709#ifdef HAVE_CPUCACHE_INVALIDATE
710 /* make the icache (if it exists) up to date with the new code */
711 rb->cpucache_invalidate();
712#endif /* HAVE_CPUCACHE_INVALIDATE */
713 }
714 return;
715}
716
717void mpegplayer_iram_restore(void)
718{
719 if (iram_saved_copy)
720 {
721 rb->audio_hard_stop();
722 rb->memcpy(iramstart, iram_saved_copy, iramend-iramstart);
723#ifdef HAVE_CPUCACHE_INVALIDATE
724 /* make the icache (if it exists) up to date with the new code */
725 rb->cpucache_invalidate();
726#endif /* HAVE_CPUCACHE_INVALIDATE */
727 }
728 return;
729}
730#endif
731
732/* Drawing functions that operate rotated on LCD_PORTRAIT displays -
733 * most are just wrappers of lcd_* functions with transforms applied.
734 * The origin is the upper-left corner of the OSD area */
735static void draw_update_rect(int x, int y, int width, int height)
736{
737 mylcd_update_rect(__X, __Y, __W, __H);
738}
739
740static void draw_clear_area(int x, int y, int width, int height)
741{
742#ifdef HAVE_LCD_COLOR
743 rb->screen_clear_area(rb->screens[SCREEN_MAIN], __X, __Y, __W, __H);
744#else
745 int oldmode = grey_get_drawmode();
746 grey_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
747 grey_fillrect(__X, __Y, __W, __H);
748 grey_set_drawmode(oldmode);
749#endif
750}
751
752static void draw_clear_area_rect(const struct vo_rect *rc)
753{
754 draw_clear_area(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t);
755}
756
757static void draw_fillrect(int x, int y, int width, int height)
758{
759 mylcd_fillrect(__X, __Y, __W, __H);
760}
761
762static void draw_hline(int x1, int x2, int y)
763{
764#ifdef LCD_LANDSCAPE
765 mylcd_hline(x1 + osd.x, x2 + osd.x, y + osd.y);
766#else
767 y = LCD_WIDTH - (y + osd.y) - 1;
768 mylcd_vline(y, x1 + osd.x, x2 + osd.x);
769#endif
770}
771
772static void draw_vline(int x, int y1, int y2)
773{
774#ifdef LCD_LANDSCAPE
775 mylcd_vline(x + osd.x, y1 + osd.y, y2 + osd.y);
776#else
777 y1 = LCD_WIDTH - (y1 + osd.y) - 1;
778 y2 = LCD_WIDTH - (y2 + osd.y) - 1;
779 mylcd_hline(y1, y2, x + osd.x);
780#endif
781}
782
783static void draw_scrollbar_draw(int x, int y, int width, int height,
784 uint32_t min, uint32_t max, uint32_t val)
785{
786 unsigned oldfg = mylcd_get_foreground();
787
788 draw_hline(x + 1, x + width - 2, y);
789 draw_hline(x + 1, x + width - 2, y + height - 1);
790 draw_vline(x, y + 1, y + height - 2);
791 draw_vline(x + width - 1, y + 1, y + height - 2);
792
793 val = muldiv_uint32(width - 2, val, max - min);
794 val = MIN(val, (uint32_t)(width - 2));
795
796 draw_fillrect(x + 1, y + 1, val, height - 2);
797
798 mylcd_set_foreground(osd.prog_fillcolor);
799
800 draw_fillrect(x + 1 + val, y + 1, width - 2 - val, height - 2);
801
802 mylcd_set_foreground(oldfg);
803}
804
805static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int min,
806 int max, int val)
807{
808 draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t,
809 min, max, val);
810}
811
812static void draw_setfont(int font)
813{
814 osd.font = font;
815 mylcd_setfont(font);
816}
817
818#ifdef LCD_PORTRAIT
819/* Portrait displays need rotated text rendering */
820
821/* Limited function that only renders in DRMODE_FG and uses absolute screen
822 * coordinates */
823static void draw_oriented_mono_bitmap_part(const unsigned char *src,
824 int src_x, int src_y,
825 int stride, int x, int y,
826 int width, int height)
827{
828 const unsigned char *src_end;
829 fb_data *dst, *dst_end;
830 unsigned fg_pattern;
831
832 if (x + width > SCREEN_WIDTH)
833 width = SCREEN_WIDTH - x; /* Clip right */
834 if (x < 0)
835 width += x, x = 0; /* Clip left */
836 if (width <= 0)
837 return; /* nothing left to do */
838
839 if (y + height > SCREEN_HEIGHT)
840 height = SCREEN_HEIGHT - y; /* Clip bottom */
841 if (y < 0)
842 height += y, y = 0; /* Clip top */
843 if (height <= 0)
844 return; /* nothing left to do */
845
846 fg_pattern = rb->lcd_get_foreground();
847 /*bg_pattern =*/ rb->lcd_get_background();
848
849 src += stride * (src_y >> 3) + src_x; /* move starting point */
850 src_y &= 7;
851 src_end = src + width;
852
853 dst = get_framebuffer() + (LCD_WIDTH - y) + x*LCD_WIDTH;
854 do
855 {
856 const unsigned char *src_col = src++;
857 unsigned data = *src_col >> src_y;
858 int numbits = 8 - src_y;
859
860 fb_data *dst_col = dst;
861 dst_end = dst_col - height;
862 dst += LCD_WIDTH;
863
864 do
865 {
866 dst_col--;
867
868 if (data & 1)
869 *dst_col = FB_SCALARPACK(fg_pattern);
870#if 0
871 else
872 *dst_col = bg_pattern;
873#endif
874 data >>= 1;
875 if (--numbits == 0) {
876 src_col += stride;
877 data = *src_col;
878 numbits = 8;
879 }
880 }
881 while (dst_col > dst_end);
882 }
883 while (src < src_end);
884}
885
886
887#define ALPHA_COLOR_FONT_DEPTH 2
888#define ALPHA_COLOR_LOOKUP_SHIFT (1 << ALPHA_COLOR_FONT_DEPTH)
889#define ALPHA_COLOR_LOOKUP_SIZE ((1 << ALPHA_COLOR_LOOKUP_SHIFT) - 1)
890#define ALPHA_COLOR_PIXEL_PER_BYTE (8 >> ALPHA_COLOR_FONT_DEPTH)
891#define ALPHA_COLOR_PIXEL_PER_WORD (32 >> ALPHA_COLOR_FONT_DEPTH)
892#ifdef CPU_ARM
893#define BLEND_INIT do {} while (0)
894#define BLEND_FINISH do {} while(0)
895#define BLEND_START(acc, color, alpha) \
896 asm volatile("mul %0, %1, %2" : "=&r" (acc) : "r" (color), "r" (alpha))
897#define BLEND_CONT(acc, color, alpha) \
898 asm volatile("mla %0, %1, %2, %0" : "+&r" (acc) : "r" (color), "r" (alpha))
899#define BLEND_OUT(acc) do {} while (0)
900#elif defined(CPU_COLDFIRE)
901#define ALPHA_BITMAP_READ_WORDS
902#define BLEND_INIT \
903 unsigned long _macsr = coldfire_get_macsr(); \
904 coldfire_set_macsr(EMAC_UNSIGNED)
905#define BLEND_FINISH \
906 coldfire_set_macsr(_macsr)
907#define BLEND_START(acc, color, alpha) \
908 asm volatile("mac.l %0, %1, %%acc0" :: "%d" (color), "d" (alpha))
909#define BLEND_CONT BLEND_START
910#define BLEND_OUT(acc) asm volatile("movclr.l %%acc0, %0" : "=d" (acc))
911#else
912#define BLEND_INIT do {} while (0)
913#define BLEND_FINISH do {} while(0)
914#define BLEND_START(acc, color, alpha) ((acc) = (color) * (alpha))
915#define BLEND_CONT(acc, color, alpha) ((acc) += (color) * (alpha))
916#define BLEND_OUT(acc) do {} while (0)
917#endif
918
919/* Blend the given two colors */
920static inline unsigned blend_two_colors(unsigned c1, unsigned c2, unsigned a)
921{
922#if LCD_DEPTH == 16
923 a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1);
924#if (LCD_PIXELFORMAT == RGB565SWAPPED)
925 c1 = swap16(c1);
926 c2 = swap16(c2);
927#endif
928 unsigned c1l = (c1 | (c1 << 16)) & 0x07e0f81f;
929 unsigned c2l = (c2 | (c2 << 16)) & 0x07e0f81f;
930 unsigned p;
931 BLEND_START(p, c1l, a);
932 BLEND_CONT(p, c2l, ALPHA_COLOR_LOOKUP_SIZE + 1 - a);
933 BLEND_OUT(p);
934 p = (p >> ALPHA_COLOR_LOOKUP_SHIFT) & 0x07e0f81f;
935 p |= (p >> 16);
936#if (LCD_PIXELFORMAT == RGB565SWAPPED)
937 return swap16(p);
938#else
939 return p;
940#endif
941
942#else /* LCD_DEPTH == 24 */
943 unsigned s = c1;
944 unsigned d = c2;
945 unsigned s1 = s & 0xff00ff;
946 unsigned d1 = d & 0xff00ff;
947 a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1);
948 d1 = (d1 + ((s1 - d1) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00ff;
949 s &= 0xff00;
950 d &= 0xff00;
951 d = (d + ((s - d) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00;
952
953 return d1 | d;
954#endif
955}
956
957static void draw_oriented_alpha_bitmap_part(const unsigned char *src,
958 int src_x, int src_y,
959 int stride, int x, int y,
960 int width, int height)
961{
962 fb_data *dst, *dst_start;
963 unsigned fg_pattern;
964
965 if (x + width > SCREEN_WIDTH)
966 width = SCREEN_WIDTH - x; /* Clip right */
967 if (x < 0)
968 width += x, x = 0; /* Clip left */
969 if (width <= 0)
970 return; /* nothing left to do */
971
972 if (y + height > SCREEN_HEIGHT)
973 height = SCREEN_HEIGHT - y; /* Clip bottom */
974 if (y < 0)
975 height += y, y = 0; /* Clip top */
976 if (height <= 0)
977 return; /* nothing left to do */
978
979 /* initialize blending */
980 BLEND_INIT;
981
982 fg_pattern = rb->lcd_get_foreground();
983 /*bg_pattern=*/ rb->lcd_get_background();
984
985 dst_start = get_framebuffer() + (LCD_WIDTH - y - 1) + x*LCD_WIDTH;
986 int col, row = height;
987 unsigned data, pixels;
988 unsigned skip_end = (stride - width);
989 unsigned skip_start = src_y * stride + src_x;
990
991#ifdef ALPHA_BITMAP_READ_WORDS
992 uint32_t *src_w = (uint32_t *)((uintptr_t)src & ~3);
993 skip_start += ALPHA_COLOR_PIXEL_PER_BYTE * ((uintptr_t)src & 3);
994 src_w += skip_start / ALPHA_COLOR_PIXEL_PER_WORD;
995 data = letoh32(*src_w++);
996#else
997 src += skip_start / ALPHA_COLOR_PIXEL_PER_BYTE;
998 data = *src;
999#endif
1000 pixels = skip_start % ALPHA_COLOR_PIXEL_PER_WORD;
1001 data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
1002#ifdef ALPHA_BITMAP_READ_WORDS
1003 pixels = 8 - pixels;
1004#endif
1005
1006 do
1007 {
1008 col = width;
1009 dst = dst_start--;
1010#ifdef ALPHA_BITMAP_READ_WORDS
1011#define UPDATE_SRC_ALPHA do { \
1012 if (--pixels) \
1013 data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
1014 else \
1015 { \
1016 data = letoh32(*src_w++); \
1017 pixels = ALPHA_COLOR_PIXEL_PER_WORD; \
1018 } \
1019 } while (0)
1020#elif ALPHA_COLOR_PIXEL_PER_BYTE == 2
1021#define UPDATE_SRC_ALPHA do { \
1022 if (pixels ^= 1) \
1023 data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
1024 else \
1025 data = *(++src); \
1026 } while (0)
1027#else
1028#define UPDATE_SRC_ALPHA do { \
1029 if (pixels = (++pixels % ALPHA_COLOR_PIXEL_PER_BYTE)) \
1030 data >>= ALPHA_COLOR_LOOKUP_SHIFT; \
1031 else \
1032 data = *(++src); \
1033 } while (0)
1034#endif
1035 do
1036 {
1037 unsigned color = blend_two_colors(FB_UNPACK_SCALAR_LCD(*dst), fg_pattern,
1038 data & ALPHA_COLOR_LOOKUP_SIZE );
1039 *dst= FB_SCALARPACK(color);
1040 dst += LCD_WIDTH;
1041 UPDATE_SRC_ALPHA;
1042 }
1043 while (--col);
1044#ifdef ALPHA_BITMAP_READ_WORDS
1045 if (skip_end < pixels)
1046 {
1047 pixels -= skip_end;
1048 data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT;
1049 } else {
1050 pixels = skip_end - pixels;
1051 src_w += pixels / ALPHA_COLOR_PIXEL_PER_WORD;
1052 pixels %= ALPHA_COLOR_PIXEL_PER_WORD;
1053 data = letoh32(*src_w++);
1054 data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
1055 pixels = 8 - pixels;
1056 }
1057#else
1058 if (skip_end)
1059 {
1060 pixels += skip_end;
1061 if (pixels >= ALPHA_COLOR_PIXEL_PER_BYTE)
1062 {
1063 src += pixels / ALPHA_COLOR_PIXEL_PER_BYTE;
1064 pixels %= ALPHA_COLOR_PIXEL_PER_BYTE;
1065 data = *src;
1066 data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT;
1067 } else
1068 data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT;
1069 }
1070#endif
1071 } while (--row);
1072}
1073
1074static void draw_putsxy_oriented(int x, int y, const char *str)
1075{
1076 ucschar_t ch;
1077 ucschar_t *ucs;
1078 int ofs = MIN(x, 0);
1079 struct font* pf = rb->font_get(osd.font);
1080
1081 ucs = rb->bidi_l2v(str, 1);
1082
1083 x += osd.x;
1084 y += osd.y;
1085
1086 while ((ch = *ucs++) != 0 && x < SCREEN_WIDTH)
1087 {
1088 int width;
1089 const unsigned char *bits;
1090
1091 /* get proportional width and glyph bits */
1092 width = rb->font_get_width(pf, ch);
1093
1094 if (ofs > width) {
1095 ofs -= width;
1096 continue;
1097 }
1098
1099 bits = rb->font_get_bits(pf, ch);
1100
1101 if (pf->depth)
1102 draw_oriented_alpha_bitmap_part(bits, ofs, 0, width, x, y,
1103 width - ofs, pf->height);
1104 else
1105 draw_oriented_mono_bitmap_part(bits, ofs, 0, width, x, y,
1106 width - ofs, pf->height);
1107
1108 x += width - ofs;
1109 ofs = 0;
1110 }
1111}
1112#else
1113static void draw_oriented_mono_bitmap_part(const unsigned char *src,
1114 int src_x, int src_y,
1115 int stride, int x, int y,
1116 int width, int height)
1117{
1118 int mode = mylcd_get_drawmode();
1119 mylcd_set_drawmode(DRMODE_FG);
1120 mylcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height);
1121 mylcd_set_drawmode(mode);
1122}
1123
1124static void draw_putsxy_oriented(int x, int y, const char *str)
1125{
1126 int mode = mylcd_get_drawmode();
1127 mylcd_set_drawmode(DRMODE_FG);
1128 mylcd_putsxy(x + osd.x, y + osd.y, str);
1129 mylcd_set_drawmode(mode);
1130}
1131#endif /* LCD_PORTRAIT */
1132
1133/** FPS Display **/
1134
1135/* Post-frame callback (on video thread) - update the FPS rectangle from the
1136 * framebuffer */
1137static void fps_post_frame_callback(void)
1138{
1139 vo_lock();
1140 mylcd_update_rect(fps.pf_x, fps.pf_y,
1141 fps.pf_width, fps.pf_height);
1142 vo_unlock();
1143}
1144
1145/* Set up to have the callback only update the intersection of the video
1146 * rectangle and the FPS text rectangle - if they don't intersect, then
1147 * the callback is set to NULL */
1148static void fps_update_post_frame_callback(void)
1149{
1150 void (*cb)(void) = NULL;
1151
1152 if (settings.showfps) {
1153 struct vo_rect cliprect;
1154
1155 if (stream_vo_get_clip(&cliprect)) {
1156 /* Oriented screen coordinates -> OSD coordinates */
1157 vo_rect_offset(&cliprect, -osd.x, -osd.y);
1158
1159 if (vo_rect_intersect(&cliprect, &cliprect, &fps.rect)) {
1160 int x = cliprect.l;
1161 int y = cliprect.t;
1162 int width = cliprect.r - cliprect.l;
1163 int height = cliprect.b - cliprect.t;
1164
1165 /* OSD coordinates -> framebuffer coordinates */
1166 fps.pf_x = __X;
1167 fps.pf_y = __Y;
1168 fps.pf_width = __W;
1169 fps.pf_height = __H;
1170
1171 cb = fps_post_frame_callback;
1172 }
1173 }
1174 }
1175
1176 stream_set_callback(VIDEO_SET_POST_FRAME_CALLBACK, cb);
1177}
1178
1179/* Refresh the FPS display */
1180static void fps_refresh(void)
1181{
1182 char str[FPS_BUFSIZE];
1183 struct video_output_stats stats;
1184 int w, h, sw;
1185 long tick;
1186
1187 tick = *rb->current_tick;
1188
1189 if (TIME_BEFORE(tick, fps.update_tick))
1190 return;
1191
1192 fps.update_tick = tick + FPS_UPDATE_INTERVAL;
1193
1194 stream_video_stats(&stats);
1195
1196 rb->snprintf(str, FPS_BUFSIZE, FPS_FORMAT,
1197 stats.fps / 100, stats.fps % 100);
1198
1199 w = fps.rect.r - fps.rect.l;
1200 h = fps.rect.b - fps.rect.t;
1201
1202 draw_clear_area(fps.rect.l, fps.rect.t, w, h);
1203 mylcd_getstringsize(str, &sw, NULL);
1204 draw_putsxy_oriented(fps.rect.r - sw, fps.rect.t, str);
1205
1206 vo_lock();
1207 draw_update_rect(fps.rect.l, fps.rect.t, w, h);
1208 vo_unlock();
1209}
1210
1211/* Initialize the FPS display */
1212static void fps_init(void)
1213{
1214 fps.update_tick = *rb->current_tick;
1215 fps.rect.l = fps.rect.t = 0;
1216 mylcd_getstringsize(FPS_DIMSTR, &fps.rect.r, &fps.rect.b);
1217 vo_rect_offset(&fps.rect, -osd.x, -osd.y);
1218 fps_update_post_frame_callback();
1219}
1220
1221/** OSD **/
1222
1223#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
1224/* So we can refresh the overlay */
1225static void osd_lcd_enable_hook(unsigned short id, void* param)
1226{
1227 (void)id;
1228 (void)param;
1229 rb->button_queue_post(LCD_ENABLE_EVENT_1, 0);
1230}
1231#endif
1232
1233static void osdbacklight_hw_on_video_mode(bool video_on)
1234{
1235 if (video_on) {
1236 /* Turn off backlight timeout */
1237 backlight_ignore_timeout();
1238
1239#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
1240 rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
1241#endif
1242 } else {
1243#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
1244 rb->add_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
1245#endif
1246 /* Revert to user's backlight settings */
1247 backlight_use_settings();
1248 }
1249}
1250
1251#ifdef HAVE_BACKLIGHT_BRIGHTNESS
1252static void osd_backlight_brightness_video_mode(bool video_on)
1253{
1254 if (settings.backlight_brightness < 0)
1255 return;
1256
1257 mpeg_backlight_update_brightness(
1258 video_on ? settings.backlight_brightness : -1);
1259}
1260#else
1261#define osd_backlight_brightness_video_mode(video_on)
1262#endif /* HAVE_BACKLIGHT_BRIGHTNESS */
1263
1264static void osd_text_init(void)
1265{
1266 struct hms hms;
1267 char buf[32];
1268 int phys;
1269 int spc_width;
1270
1271 draw_setfont(FONT_UI);
1272
1273 osd.x = 0;
1274 osd.width = SCREEN_WIDTH;
1275
1276 vo_rect_clear(&osd.time_rect);
1277 vo_rect_clear(&osd.stat_rect);
1278 vo_rect_clear(&osd.prog_rect);
1279 vo_rect_clear(&osd.vol_rect);
1280
1281 ts_to_hms(stream_get_duration(), &hms);
1282 hms_format(buf, sizeof (buf), &hms);
1283 mylcd_getstringsize(buf, &osd.time_rect.r, &osd.time_rect.b);
1284
1285 /* Choose well-sized bitmap images relative to font height */
1286 if (osd.time_rect.b < 12) {
1287 osd.icons = mpegplayer_status_icons_8x8x1;
1288 osd.stat_rect.r = osd.stat_rect.b = 8;
1289 } else if (osd.time_rect.b < 16) {
1290 osd.icons = mpegplayer_status_icons_12x12x1;
1291 osd.stat_rect.r = osd.stat_rect.b = 12;
1292 } else {
1293 osd.icons = mpegplayer_status_icons_16x16x1;
1294 osd.stat_rect.r = osd.stat_rect.b = 16;
1295 }
1296
1297 if (osd.stat_rect.b < osd.time_rect.b) {
1298 vo_rect_offset(&osd.stat_rect, 0,
1299 (osd.time_rect.b - osd.stat_rect.b) / 2 + OSD_BDR_T);
1300 vo_rect_offset(&osd.time_rect, OSD_BDR_L, OSD_BDR_T);
1301 } else {
1302 vo_rect_offset(&osd.time_rect, OSD_BDR_L,
1303 osd.stat_rect.b - osd.time_rect.b + OSD_BDR_T);
1304 vo_rect_offset(&osd.stat_rect, 0, OSD_BDR_T);
1305 }
1306
1307 osd.dur_rect = osd.time_rect;
1308
1309 phys = rb->sound_val2phys(SOUND_VOLUME, rb->sound_min(SOUND_VOLUME));
1310 rb->snprintf(buf, sizeof(buf), "%d%s", phys,
1311 rb->sound_unit(SOUND_VOLUME));
1312
1313 mylcd_getstringsize(" ", &spc_width, NULL);
1314 mylcd_getstringsize(buf, &osd.vol_rect.r, &osd.vol_rect.b);
1315
1316 osd.prog_rect.r = SCREEN_WIDTH - OSD_BDR_L - spc_width -
1317 osd.vol_rect.r - OSD_BDR_R;
1318 osd.prog_rect.b = 3*osd.stat_rect.b / 4;
1319 vo_rect_offset(&osd.prog_rect, osd.time_rect.l,
1320 osd.time_rect.b);
1321
1322 vo_rect_offset(&osd.stat_rect,
1323 (osd.prog_rect.r + osd.prog_rect.l - osd.stat_rect.r) / 2,
1324 0);
1325
1326 vo_rect_offset(&osd.dur_rect,
1327 osd.prog_rect.r - osd.dur_rect.r, 0);
1328
1329 vo_rect_offset(&osd.vol_rect, osd.prog_rect.r + spc_width,
1330 (osd.prog_rect.b + osd.prog_rect.t - osd.vol_rect.b) / 2);
1331
1332 osd.height = OSD_BDR_T + MAX(osd.prog_rect.b, osd.vol_rect.b) -
1333 MIN(osd.time_rect.t, osd.stat_rect.t) + OSD_BDR_B;
1334
1335#ifdef HAVE_LCD_COLOR
1336 osd.height = ALIGN_UP(osd.height, 2);
1337#endif
1338 osd.y = SCREEN_HEIGHT - osd.height;
1339
1340 draw_setfont(FONT_SYSFIXED);
1341}
1342
1343static void osd_init(void)
1344{
1345 osd.flags = 0;
1346 osd.show_for = HZ*4;
1347 osd.print_delay = 75*HZ/100;
1348 osd.resume_delay = HZ/2;
1349#ifdef HAVE_LCD_COLOR
1350 osd.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd);
1351 osd.fgcolor = LCD_WHITE;
1352 osd.prog_fillcolor = LCD_BLACK;
1353#else
1354 osd.bgcolor = GREY_LIGHTGRAY;
1355 osd.fgcolor = GREY_BLACK;
1356 osd.prog_fillcolor = GREY_WHITE;
1357#endif
1358 osd.curr_time = 0;
1359 osd.status = OSD_STATUS_STOPPED;
1360 osd.auto_refresh = OSD_REFRESH_TIME;
1361 osd.next_auto_refresh = *rb->current_tick;
1362 osd_text_init();
1363 fps_init();
1364}
1365
1366#ifdef HAVE_HEADPHONE_DETECTION
1367static void osd_set_hp_pause_flag(bool set)
1368{
1369 if (set)
1370 osd.flags |= OSD_HP_PAUSE;
1371 else
1372 osd.flags &= ~OSD_HP_PAUSE;
1373}
1374#else
1375#define osd_set_hp_pause_flag(set)
1376#endif /* HAVE_HEADPHONE_DETECTION */
1377
1378static void osd_schedule_refresh(unsigned refresh)
1379{
1380 long tick = *rb->current_tick;
1381
1382 if (refresh & OSD_REFRESH_VIDEO)
1383 osd.print_tick = tick + osd.print_delay;
1384
1385 if (refresh & OSD_REFRESH_RESUME)
1386 osd.resume_tick = tick + osd.resume_delay;
1387
1388 osd.auto_refresh |= refresh;
1389}
1390
1391static void osd_cancel_refresh(unsigned refresh)
1392{
1393 osd.auto_refresh &= ~refresh;
1394}
1395
1396/* Refresh the background area */
1397static void osd_refresh_background(void)
1398{
1399 char buf[32];
1400 struct hms hms;
1401
1402 unsigned bg = mylcd_get_background();
1403 mylcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
1404
1405#ifdef HAVE_LCD_COLOR
1406 /* Draw a "raised" area for our graphics */
1407 mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 192));
1408 draw_hline(0, osd.width, 0);
1409
1410 mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 80));
1411 draw_hline(0, osd.width, 1);
1412
1413 mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 48));
1414 draw_hline(0, osd.width, osd.height-2);
1415
1416 mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 128));
1417 draw_hline(0, osd.width, osd.height-1);
1418
1419 mylcd_set_background(bg);
1420 draw_clear_area(0, 2, osd.width, osd.height - 4);
1421#else
1422 /* Give contrast with the main background */
1423 mylcd_set_background(MYLCD_WHITE);
1424 draw_hline(0, osd.width, 0);
1425
1426 mylcd_set_background(MYLCD_DARKGRAY);
1427 draw_hline(0, osd.width, osd.height-1);
1428
1429 mylcd_set_background(bg);
1430 draw_clear_area(0, 1, osd.width, osd.height - 2);
1431#endif
1432
1433 vo_rect_set_ext(&osd.update_rect, 0, 0, osd.width, osd.height);
1434 mylcd_set_drawmode(DRMODE_SOLID);
1435
1436 if (stream_get_duration() != INVALID_TIMESTAMP) {
1437 /* Draw the movie duration */
1438 ts_to_hms(stream_get_duration(), &hms);
1439 hms_format(buf, sizeof (buf), &hms);
1440 draw_putsxy_oriented(osd.dur_rect.l, osd.dur_rect.t, buf);
1441 }
1442 /* else don't know the duration */
1443}
1444
1445/* Refresh the current time display + the progress bar */
1446static void osd_refresh_time(void)
1447{
1448 char buf[32];
1449 struct hms hms;
1450
1451 uint32_t duration = stream_get_duration();
1452
1453 draw_scrollbar_draw_rect(&osd.prog_rect, 0, duration,
1454 osd.curr_time);
1455
1456 ts_to_hms(osd.curr_time, &hms);
1457 hms_format(buf, sizeof (buf), &hms);
1458
1459 draw_clear_area_rect(&osd.time_rect);
1460 draw_putsxy_oriented(osd.time_rect.l, osd.time_rect.t, buf);
1461
1462 vo_rect_union(&osd.update_rect, &osd.update_rect,
1463 &osd.prog_rect);
1464 vo_rect_union(&osd.update_rect, &osd.update_rect,
1465 &osd.time_rect);
1466}
1467
1468/* Refresh the volume display area */
1469static void osd_refresh_volume(void)
1470{
1471 char buf[32];
1472 int width;
1473
1474 int volume = rb->global_status->volume;
1475 rb->snprintf(buf, sizeof (buf), "%d%s",
1476 rb->sound_val2phys(SOUND_VOLUME, volume),
1477 rb->sound_unit(SOUND_VOLUME));
1478 mylcd_getstringsize(buf, &width, NULL);
1479
1480 /* Right-justified */
1481 draw_clear_area_rect(&osd.vol_rect);
1482 draw_putsxy_oriented(osd.vol_rect.r - width, osd.vol_rect.t, buf);
1483
1484 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.vol_rect);
1485}
1486
1487/* Refresh the status icon */
1488static void osd_refresh_status(void)
1489{
1490 int icon_size = osd.stat_rect.r - osd.stat_rect.l;
1491
1492 draw_clear_area_rect(&osd.stat_rect);
1493
1494#ifdef HAVE_LCD_COLOR
1495 /* Draw status icon with a drop shadow */
1496 unsigned oldfg = mylcd_get_foreground();
1497 int i = 1;
1498
1499 mylcd_set_foreground(draw_blendcolor(mylcd_get_background(),
1500 MYLCD_BLACK, 96));
1501
1502 while (1)
1503 {
1504 draw_oriented_mono_bitmap_part(osd.icons,
1505 icon_size*osd.status,
1506 0,
1507 icon_size*OSD_STATUS_COUNT,
1508 osd.stat_rect.l + osd.x + i,
1509 osd.stat_rect.t + osd.y + i,
1510 icon_size, icon_size);
1511
1512 if (--i < 0)
1513 break;
1514
1515 mylcd_set_foreground(oldfg);
1516 }
1517
1518 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
1519#else
1520 draw_oriented_mono_bitmap_part(osd.icons,
1521 icon_size*osd.status,
1522 0,
1523 icon_size*OSD_STATUS_COUNT,
1524 osd.stat_rect.l + osd.x,
1525 osd.stat_rect.t + osd.y,
1526 icon_size, icon_size);
1527 vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect);
1528#endif
1529}
1530
1531/* Update the current status which determines which icon is displayed */
1532static bool osd_update_status(void)
1533{
1534 int status;
1535
1536 switch (stream_status())
1537 {
1538 default:
1539 status = OSD_STATUS_STOPPED;
1540 break;
1541 case STREAM_PAUSED:
1542 /* If paused with a pending resume, coerce it to OSD_STATUS_PLAYING */
1543 status = (osd.auto_refresh & OSD_REFRESH_RESUME) ?
1544 OSD_STATUS_PLAYING : OSD_STATUS_PAUSED;
1545 break;
1546 case STREAM_PLAYING:
1547 status = OSD_STATUS_PLAYING;
1548 break;
1549 }
1550
1551 if (status != osd.status) {
1552 /* A refresh is needed */
1553 osd.status = status;
1554 return true;
1555 }
1556
1557 return false;
1558}
1559
1560/* Update the current time that will be displayed */
1561static void osd_update_time(void)
1562{
1563 uint32_t start;
1564 osd.curr_time = stream_get_seek_time(&start);
1565 osd.curr_time -= start;
1566}
1567
1568/* Refresh various parts of the OSD - showing it if it is hidden */
1569static void osd_refresh(int hint)
1570{
1571 long tick;
1572 unsigned oldbg, oldfg;
1573
1574 tick = *rb->current_tick;
1575
1576 if (settings.showfps)
1577 fps_refresh();
1578
1579 if (hint == OSD_REFRESH_DEFAULT) {
1580 /* The default which forces no updates */
1581
1582 /* Make sure Rockbox doesn't turn off the player because of
1583 too little activity */
1584 if (osd.status == OSD_STATUS_PLAYING)
1585 rb->reset_poweroff_timer();
1586
1587 /* Redraw the current or possibly extract a new video frame */
1588 if ((osd.auto_refresh & OSD_REFRESH_VIDEO) &&
1589 TIME_AFTER(tick, osd.print_tick)) {
1590 osd.auto_refresh &= ~OSD_REFRESH_VIDEO;
1591 stream_draw_frame(false);
1592 }
1593
1594 /* Restart playback if the timout was reached */
1595 if ((osd.auto_refresh & OSD_REFRESH_RESUME) &&
1596 TIME_AFTER(tick, osd.resume_tick)) {
1597 osd.auto_refresh &= ~(OSD_REFRESH_RESUME | OSD_REFRESH_VIDEO);
1598 stream_resume();
1599 }
1600
1601 /* If not visible, return */
1602 if (!(osd.flags & OSD_SHOW))
1603 return;
1604
1605 /* Hide if the visibility duration was reached */
1606 if (TIME_AFTER(tick, osd.hide_tick)) {
1607 osd_show(OSD_HIDE);
1608 return;
1609 }
1610 } else {
1611 /* A forced update of some region */
1612
1613 /* Show if currently invisible */
1614 if (!(osd.flags & OSD_SHOW)) {
1615 /* Avoid call back into this function - it will be drawn */
1616 osd_show(OSD_SHOW | OSD_NODRAW);
1617 hint = OSD_REFRESH_ALL;
1618 }
1619
1620 /* Move back timeouts for frame print and hide */
1621 osd.print_tick = tick + osd.print_delay;
1622 osd.hide_tick = tick + osd.show_for;
1623 }
1624
1625 if (TIME_AFTER(tick, osd.next_auto_refresh)) {
1626 /* Refresh whatever graphical elements are due automatically */
1627 osd.next_auto_refresh = tick + OSD_MIN_UPDATE_INTERVAL;
1628
1629 if (osd.auto_refresh & OSD_REFRESH_STATUS) {
1630 if (osd_update_status())
1631 hint |= OSD_REFRESH_STATUS;
1632 }
1633
1634 if (osd.auto_refresh & OSD_REFRESH_TIME) {
1635 osd_update_time();
1636 hint |= OSD_REFRESH_TIME;
1637 }
1638 }
1639
1640 if (hint == 0)
1641 return; /* No drawing needed */
1642
1643 /* Set basic drawing params that are used. Elements that perform variations
1644 * will restore them. */
1645 oldfg = mylcd_get_foreground();
1646 oldbg = mylcd_get_background();
1647
1648 draw_setfont(FONT_UI);
1649 mylcd_set_foreground(osd.fgcolor);
1650 mylcd_set_background(osd.bgcolor);
1651
1652 vo_rect_clear(&osd.update_rect);
1653
1654 if (hint & OSD_REFRESH_BACKGROUND) {
1655 osd_refresh_background();
1656 hint |= OSD_REFRESH_ALL; /* Requires a redraw of everything */
1657 }
1658
1659 if (hint & OSD_REFRESH_TIME) {
1660 osd_refresh_time();
1661 }
1662
1663 if (hint & OSD_REFRESH_VOLUME) {
1664 osd_refresh_volume();
1665 }
1666
1667 if (hint & OSD_REFRESH_STATUS) {
1668 osd_refresh_status();
1669 }
1670
1671 /* Go back to defaults */
1672 draw_setfont(FONT_SYSFIXED);
1673 mylcd_set_foreground(oldfg);
1674 mylcd_set_background(oldbg);
1675
1676 /* Update the dirty rectangle */
1677 vo_lock();
1678
1679 draw_update_rect(osd.update_rect.l,
1680 osd.update_rect.t,
1681 osd.update_rect.r - osd.update_rect.l,
1682 osd.update_rect.b - osd.update_rect.t);
1683
1684 vo_unlock();
1685}
1686
1687/* Show/Hide the OSD */
1688static void osd_show(unsigned show)
1689{
1690 if (((show ^ osd.flags) & OSD_SHOW) == 0)
1691 {
1692 if (show & OSD_SHOW) {
1693 osd.hide_tick = *rb->current_tick + osd.show_for;
1694 }
1695 return;
1696 }
1697
1698 if (show & OSD_SHOW) {
1699 /* Clip away the part of video that is covered */
1700 struct vo_rect rc = { 0, 0, SCREEN_WIDTH, osd.y };
1701
1702 osd.flags |= OSD_SHOW;
1703
1704 if (osd.status != OSD_STATUS_PLAYING) {
1705 /* Not playing - set brightness to mpegplayer setting */
1706 osd_backlight_brightness_video_mode(true);
1707 }
1708
1709 stream_vo_set_clip(&rc);
1710
1711 if (!(show & OSD_NODRAW))
1712 osd_refresh(OSD_REFRESH_ALL);
1713 } else {
1714 /* Uncover clipped video area and redraw it */
1715 osd.flags &= ~OSD_SHOW;
1716
1717 draw_clear_area(0, 0, osd.width, osd.height);
1718
1719 if (!(show & OSD_NODRAW)) {
1720 vo_lock();
1721 draw_update_rect(0, 0, osd.width, osd.height);
1722 vo_unlock();
1723
1724 stream_vo_set_clip(NULL);
1725 stream_draw_frame(false);
1726 } else {
1727 stream_vo_set_clip(NULL);
1728 }
1729
1730 if (osd.status != OSD_STATUS_PLAYING) {
1731 /* Not playing - restore backlight brightness */
1732 osd_backlight_brightness_video_mode(false);
1733 }
1734 }
1735}
1736
1737/* Set the current status - update screen if specified */
1738static void osd_set_status(int status)
1739{
1740 bool draw = (status & OSD_NODRAW) == 0;
1741
1742 status &= OSD_STATUS_MASK;
1743
1744 if (osd.status != status) {
1745
1746 osd.status = status;
1747
1748 if (draw)
1749 osd_refresh(OSD_REFRESH_STATUS);
1750 }
1751}
1752
1753/* Get the current status value */
1754static int osd_get_status(void)
1755{
1756 return osd.status & OSD_STATUS_MASK;
1757}
1758
1759/* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;)
1760 * Returns last button code
1761 */
1762static int osd_ff_rw(int btn, unsigned refresh, uint32_t *new_time)
1763{
1764 unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step;
1765 const long ff_rw_accel = (rb->global_settings->ff_rewind_accel + 3);
1766 uint32_t start;
1767 uint32_t time = stream_get_seek_time(&start);
1768 const uint32_t duration = stream_get_duration();
1769 unsigned int max_step = 0;
1770 uint32_t ff_rw_count = 0;
1771 unsigned status = osd.status;
1772 int new_btn;
1773
1774 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME |
1775 OSD_REFRESH_TIME);
1776
1777 time -= start; /* Absolute clock => stream-relative */
1778
1779 switch (btn)
1780 {
1781 case MPEG_FF:
1782#ifdef MPEG_FF2
1783 case MPEG_FF2:
1784#endif
1785#ifdef MPEG_RC_FF
1786 case MPEG_RC_FF:
1787#endif
1788 osd_set_status(OSD_STATUS_FF);
1789 new_btn = btn | BUTTON_REPEAT; /* simplify code below */
1790 break;
1791 case MPEG_RW:
1792#ifdef MPEG_RW2
1793 case MPEG_RW2:
1794#endif
1795#ifdef MPEG_RC_RW
1796 case MPEG_RC_RW:
1797#endif
1798 osd_set_status(OSD_STATUS_RW);
1799 new_btn = btn | BUTTON_REPEAT; /* simplify code below */
1800 break;
1801 default:
1802 new_btn = BUTTON_NONE; /* Fail tests below but still do proper exit */
1803 }
1804
1805 while (1)
1806 {
1807 stream_keep_disk_active();
1808
1809 if (new_btn == (btn | BUTTON_REPEAT)) {
1810 if (osd.status == OSD_STATUS_FF) {
1811 /* fast forwarding, calc max step relative to end */
1812 max_step = muldiv_uint32(duration - (time + ff_rw_count),
1813 FF_REWIND_MAX_PERCENT, 100);
1814 } else {
1815 /* rewinding, calc max step relative to start */
1816 max_step = muldiv_uint32(time - ff_rw_count,
1817 FF_REWIND_MAX_PERCENT, 100);
1818 }
1819
1820 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
1821
1822 if (step > max_step)
1823 step = max_step;
1824
1825 ff_rw_count += step;
1826
1827 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
1828 step += step >> ff_rw_accel;
1829
1830 if (osd.status == OSD_STATUS_FF) {
1831 if (duration - time <= ff_rw_count)
1832 ff_rw_count = duration - time;
1833
1834 osd.curr_time = time + ff_rw_count;
1835 } else {
1836 if (time <= ff_rw_count)
1837 ff_rw_count = time;
1838
1839 osd.curr_time = time - ff_rw_count;
1840 }
1841
1842 osd_refresh(OSD_REFRESH_TIME);
1843
1844 new_btn = mpeg_button_get(TIMEOUT_BLOCK);
1845 }
1846 else {
1847 if (new_btn == (btn | BUTTON_REL)) {
1848 if (osd.status == OSD_STATUS_FF)
1849 time += ff_rw_count;
1850 else if (osd.status == OSD_STATUS_RW)
1851 time -= ff_rw_count;
1852 }
1853
1854 *new_time = time;
1855
1856 osd_schedule_refresh(refresh);
1857 osd_set_status(status);
1858 osd_schedule_refresh(OSD_REFRESH_TIME);
1859
1860 return new_btn;
1861 }
1862 }
1863}
1864
1865/* Return adjusted STREAM_* status */
1866static int osd_stream_status(void)
1867{
1868 int status = stream_status();
1869
1870 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1871 if (status == STREAM_PAUSED) {
1872 if (osd.auto_refresh & OSD_REFRESH_RESUME)
1873 status = STREAM_PLAYING;
1874 }
1875
1876 return status;
1877}
1878
1879/* Change the current audio volume by a specified amount */
1880static void osd_set_volume(int delta)
1881{
1882 int vol = rb->global_status->volume;
1883 int limit;
1884
1885 vol += delta;
1886
1887 if (delta < 0) {
1888 /* Volume down - clip to lower limit */
1889 limit = rb->sound_min(SOUND_VOLUME);
1890 if (vol < limit)
1891 vol = limit;
1892 } else {
1893 /* Volume up - clip to upper limit */
1894 limit = rb->sound_max(SOUND_VOLUME);
1895 if (vol > limit)
1896 vol = limit;
1897 }
1898
1899 /* Sync the global settings */
1900 if (vol != rb->global_status->volume)
1901 rb->sound_set(SOUND_VOLUME, vol);
1902
1903 /* Update the volume display */
1904 osd_refresh(OSD_REFRESH_VOLUME);
1905}
1906
1907/* Begin playback at the specified time */
1908static int osd_play(uint32_t time)
1909{
1910 int retval;
1911
1912 osd_set_hp_pause_flag(false);
1913 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1914
1915 retval = stream_seek(time, SEEK_SET);
1916
1917 if (retval >= STREAM_OK) {
1918 osdbacklight_hw_on_video_mode(true);
1919 osd_backlight_brightness_video_mode(true);
1920 stream_show_vo(true);
1921
1922 retval = stream_play();
1923
1924 if (retval >= STREAM_OK)
1925 osd_set_status(OSD_STATUS_PLAYING | OSD_NODRAW);
1926 }
1927
1928 return retval;
1929}
1930
1931/* Halt playback - pause engine and return logical state */
1932static int osd_halt(void)
1933{
1934 int status = stream_pause();
1935
1936 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1937 if (status == STREAM_PAUSED) {
1938 if (osd_get_status() == OSD_STATUS_PLAYING)
1939 status = STREAM_PLAYING;
1940 }
1941
1942 /* Cancel some auto refreshes - caller will restart them if desired */
1943 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1944
1945 /* No backlight fiddling here - callers does the right thing */
1946
1947 return status;
1948}
1949
1950/* Pause playback if playing */
1951static int osd_pause(void)
1952{
1953 unsigned refresh = osd.auto_refresh;
1954 int status = osd_halt();
1955
1956 osd_set_hp_pause_flag(false);
1957
1958 if (status == STREAM_PLAYING && (refresh & OSD_REFRESH_RESUME)) {
1959 /* Resume pending - change to a still video frame update */
1960 osd_schedule_refresh(OSD_REFRESH_VIDEO);
1961 }
1962
1963 osd_set_status(OSD_STATUS_PAUSED);
1964
1965 osdbacklight_hw_on_video_mode(false);
1966 /* Leave brightness alone and restore it when OSD is hidden */
1967
1968 if (stream_can_seek() && rb->global_settings->pause_rewind) {
1969 stream_seek(-rb->global_settings->pause_rewind*TS_SECOND,
1970 SEEK_CUR);
1971 osd_schedule_refresh(OSD_REFRESH_VIDEO);
1972 /* Update time display now */
1973 osd_update_time();
1974 osd_refresh(OSD_REFRESH_TIME);
1975 }
1976
1977 return status;
1978}
1979
1980/* Resume playback if halted or paused */
1981static void osd_resume(void)
1982{
1983 /* Cancel video and resume auto refresh - the resyc when starting
1984 * playback will perform those tasks */
1985 osd_set_hp_pause_flag(false);
1986 osdbacklight_hw_on_video_mode(true);
1987 osd_backlight_brightness_video_mode(true);
1988 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
1989 osd_set_status(OSD_STATUS_PLAYING);
1990 stream_resume();
1991}
1992
1993/* Stop playback - remember the resume point if not closed */
1994static void osd_stop(void)
1995{
1996 uint32_t resume_time;
1997
1998 osd_set_hp_pause_flag(false);
1999 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
2000 osd_set_status(OSD_STATUS_STOPPED | OSD_NODRAW);
2001 osd_show(OSD_HIDE);
2002
2003 stream_stop();
2004
2005 resume_time = stream_get_resume_time();
2006
2007 if (resume_time != INVALID_TIMESTAMP)
2008 settings.resume_time = resume_time;
2009
2010 osdbacklight_hw_on_video_mode(false);
2011 osd_backlight_brightness_video_mode(false);
2012}
2013
2014/* Perform a seek by button if seeking is possible for this stream.
2015 *
2016 * A delay will be inserted before restarting in case the user decides to
2017 * seek again soon after.
2018 *
2019 * Returns last button code
2020 */
2021static int osd_seek_btn(int btn)
2022{
2023 int status;
2024 unsigned refresh = 0;
2025 uint32_t time;
2026
2027 if (!stream_can_seek())
2028 return true;
2029
2030 /* Halt playback - not strictly necessary but nice when doing
2031 * buttons */
2032 status = osd_halt();
2033
2034 if (status == STREAM_STOPPED)
2035 return true;
2036
2037 osd_show(OSD_SHOW);
2038
2039 /* Obtain a new playback point according to the buttons */
2040 if (status == STREAM_PLAYING)
2041 refresh = OSD_REFRESH_RESUME; /* delay resume if playing */
2042 else
2043 refresh = OSD_REFRESH_VIDEO; /* refresh if paused */
2044
2045 btn = osd_ff_rw(btn, refresh, &time);
2046
2047 /* Tell engine to resume at that time */
2048 stream_seek(time, SEEK_SET);
2049
2050 return btn;
2051}
2052
2053/* Perform a seek by time if seeking is possible for this stream
2054 *
2055 * If playing, the seeking is immediate, otherise a delay is added to showing
2056 * a still if paused in case the user does another seek soon after.
2057 *
2058 * If seeking isn't possible, a time of zero performs a skip to the
2059 * beginning.
2060 */
2061static void osd_seek_time(uint32_t time)
2062{
2063 int status;
2064 unsigned refresh = 0;
2065
2066 if (!stream_can_seek() && time != 0)
2067 return;
2068
2069 stream_wait_status();
2070 status = osd_stream_status();
2071
2072 if (status == STREAM_STOPPED)
2073 return;
2074
2075 if (status == STREAM_PLAYING) /* merely preserve resume */
2076 refresh = osd.auto_refresh & OSD_REFRESH_RESUME;
2077 else
2078 refresh = OSD_REFRESH_VIDEO; /* refresh if paused */
2079
2080 /* Cancel print or resume if pending */
2081 osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME);
2082
2083 /* Tell engine to seek to the given time - no state change */
2084 stream_seek(time, SEEK_SET);
2085
2086 osd_update_time();
2087 osd_refresh(OSD_REFRESH_TIME);
2088 osd_schedule_refresh(refresh);
2089}
2090
2091/* Has this file one of the supported extensions? */
2092static bool is_videofile(const char* file)
2093{
2094 static const char * const extensions[] =
2095 {
2096 /* Should match apps/plugins/viewers.config */
2097 "mpg", "mpeg", "mpv", "m2v"
2098 };
2099
2100 const char* ext = rb->strrchr(file, '.');
2101 int i;
2102
2103 if (!ext)
2104 return false;
2105
2106 for (i = ARRAYLEN(extensions) - 1; i >= 0; i--)
2107 {
2108 if (!rb->strcasecmp(ext + 1, extensions[i]))
2109 break;
2110 }
2111
2112 return i >= 0;
2113}
2114
2115/* deliver the next/previous video file in the current directory.
2116 returns false if there is none. */
2117static bool get_videofile(int direction, char* videofile, size_t bufsize)
2118{
2119 struct tree_context *tree = rb->tree_get_context();
2120 struct entry *dircache = rb->tree_get_entries(tree);
2121 int i, step, end, found = 0;
2122 char *videoname = rb->strrchr(videofile, '/') + 1;
2123 size_t rest = bufsize - (videoname - videofile) - 1;
2124
2125 if (direction == VIDEO_NEXT) {
2126 i = 0;
2127 step = 1;
2128 end = tree->filesindir;
2129 } else {
2130 i = tree->filesindir-1;
2131 step = -1;
2132 end = -1;
2133 }
2134 for (; i != end; i += step)
2135 {
2136 const char* name = dircache[i].name;
2137 if (!rb->strcmp(name, videoname)) {
2138 found = 1;
2139 continue;
2140 }
2141 if (found && rb->strlen(name) <= rest &&
2142 !(dircache[i].attr & ATTR_DIRECTORY) && is_videofile(name))
2143 {
2144 rb->strcpy(videoname, name);
2145 return true;
2146 }
2147 }
2148
2149 return false;
2150}
2151
2152#ifdef HAVE_HEADPHONE_DETECTION
2153/* Handle SYS_PHONE_PLUGGED/UNPLUGGED */
2154static void osd_handle_phone_plug(bool inserted)
2155{
2156 if (rb->global_settings->unplug_mode == 0)
2157 return;
2158
2159 /* Wait for any incomplete state transition to complete first */
2160 stream_wait_status();
2161
2162 int status = osd_stream_status();
2163
2164 if (inserted) {
2165 if (rb->global_settings->unplug_mode > 1) {
2166 if (status == STREAM_PAUSED &&
2167 (osd.flags & OSD_HP_PAUSE)) {
2168 osd_resume();
2169 }
2170 }
2171 } else {
2172 if (status == STREAM_PLAYING) {
2173 osd_pause();
2174
2175 osd_set_hp_pause_flag(true);
2176 }
2177 }
2178}
2179#endif
2180
2181static int button_loop(void)
2182{
2183 int next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;
2184
2185 rb->lcd_setfont(FONT_SYSFIXED);
2186#ifdef HAVE_LCD_COLOR
2187 rb->lcd_set_foreground(LCD_WHITE);
2188 rb->lcd_set_background(LCD_BLACK);
2189#endif
2190 rb->lcd_clear_display();
2191 rb->lcd_update();
2192
2193#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
2194 rb->lcd_set_mode(LCD_MODE_YUV);
2195#endif
2196
2197 osd_init();
2198
2199 /* Start playback at the specified starting time */
2200 if (osd_play(settings.resume_time) < STREAM_OK) {
2201 rb->splash(HZ*2, "Playback failed");
2202 return VIDEO_STOP;
2203 }
2204
2205 /* Gently poll the video player for EOS and handle UI */
2206 while (stream_status() != STREAM_STOPPED)
2207 {
2208 int button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL/2);
2209
2210 switch (button)
2211 {
2212 case BUTTON_NONE:
2213 {
2214 osd_refresh(OSD_REFRESH_DEFAULT);
2215 continue;
2216 } /* BUTTON_NONE: */
2217
2218#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
2219 case LCD_ENABLE_EVENT_1:
2220 {
2221 /* Draw the current frame if prepared already */
2222 stream_draw_frame(true);
2223 break;
2224 } /* LCD_ENABLE_EVENT_1: */
2225#endif
2226
2227 case MPEG_VOLUP:
2228 case MPEG_VOLUP|BUTTON_REPEAT:
2229#ifdef MPEG_VOLUP2
2230 case MPEG_VOLUP2:
2231 case MPEG_VOLUP2|BUTTON_REPEAT:
2232#endif
2233#ifdef MPEG_RC_VOLUP
2234 case MPEG_RC_VOLUP:
2235 case MPEG_RC_VOLUP|BUTTON_REPEAT:
2236#endif
2237 {
2238 osd_set_volume(+1);
2239 break;
2240 } /* MPEG_VOLUP*: */
2241
2242 case MPEG_VOLDOWN:
2243 case MPEG_VOLDOWN|BUTTON_REPEAT:
2244#ifdef MPEG_VOLDOWN2
2245 case MPEG_VOLDOWN2:
2246 case MPEG_VOLDOWN2|BUTTON_REPEAT:
2247#endif
2248#ifdef MPEG_RC_VOLDOWN
2249 case MPEG_RC_VOLDOWN:
2250 case MPEG_RC_VOLDOWN|BUTTON_REPEAT:
2251#endif
2252 {
2253 osd_set_volume(-1);
2254 break;
2255 } /* MPEG_VOLDOWN*: */
2256
2257 case MPEG_MENU:
2258#ifdef MPEG_RC_MENU
2259 case MPEG_RC_MENU:
2260#endif
2261 {
2262 int state = osd_halt(); /* save previous state */
2263 int result;
2264
2265 /* Hide video output */
2266 osd_show(OSD_HIDE | OSD_NODRAW);
2267 stream_show_vo(false);
2268 osd_backlight_brightness_video_mode(false);
2269
2270#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
2271 rb->lcd_set_mode(LCD_MODE_RGB565);
2272#endif
2273
2274 result = mpeg_menu();
2275
2276 next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;
2277
2278 fps_update_post_frame_callback();
2279
2280 /* The menu can change the font, so restore */
2281 rb->lcd_setfont(FONT_SYSFIXED);
2282#ifdef HAVE_LCD_COLOR
2283 rb->lcd_set_foreground(LCD_WHITE);
2284 rb->lcd_set_background(LCD_BLACK);
2285#endif
2286 rb->lcd_clear_display();
2287 rb->lcd_update();
2288
2289 switch (result)
2290 {
2291 case MPEG_MENU_QUIT:
2292 next_action = VIDEO_STOP;
2293 osd_stop();
2294 break;
2295
2296 default:
2297#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
2298 rb->lcd_set_mode(LCD_MODE_YUV);
2299#endif
2300 /* If not stopped, show video again */
2301 if (state != STREAM_STOPPED) {
2302 osd_show(OSD_SHOW);
2303 stream_show_vo(true);
2304 }
2305
2306 /* If stream was playing, restart it */
2307 if (state == STREAM_PLAYING) {
2308 osd_resume();
2309 }
2310 break;
2311 }
2312 break;
2313 } /* MPEG_MENU: */
2314
2315#ifdef MPEG_SHOW_OSD
2316 case MPEG_SHOW_OSD:
2317 case MPEG_SHOW_OSD | BUTTON_REPEAT:
2318 /* Show if not visible */
2319 osd_show(OSD_SHOW);
2320 /* Make sure it refreshes */
2321 osd_refresh(OSD_REFRESH_DEFAULT);
2322 break;
2323#endif
2324
2325 case MPEG_STOP:
2326#ifdef MPEG_RC_STOP
2327 case MPEG_RC_STOP:
2328#endif
2329 case ACTION_STD_CANCEL:
2330 {
2331 cancel_playback:
2332 next_action = VIDEO_STOP;
2333 osd_stop();
2334 break;
2335 } /* MPEG_STOP: */
2336
2337 case MPEG_PAUSE:
2338#ifdef MPEG_PAUSE2
2339 case MPEG_PAUSE2:
2340#endif
2341#ifdef MPEG_RC_PAUSE
2342 case MPEG_RC_PAUSE:
2343#endif
2344 {
2345 int status = osd_stream_status();
2346
2347 if (status == STREAM_PLAYING) {
2348 /* Playing => Paused */
2349 osd_pause();
2350 }
2351 else if (status == STREAM_PAUSED) {
2352 /* Paused => Playing */
2353 osd_resume();
2354 }
2355
2356 break;
2357 } /* MPEG_PAUSE*: */
2358
2359 case MPEG_RW:
2360#ifdef MPEG_RW2
2361 case MPEG_RW2:
2362#endif
2363#ifdef MPEG_RC_RW
2364 case MPEG_RC_RW:
2365#endif
2366 {
2367 int old_button = button;
2368
2369 /* If button has been released: skip to next/previous file */
2370 button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL);
2371
2372 if ((old_button | BUTTON_REL) == button) {
2373 /* Check current playback position */
2374 osd_update_time();
2375
2376 if (settings.play_mode == 0 || osd.curr_time >= 3*TS_SECOND) {
2377 /* Start the current video from the beginning */
2378 osd_seek_time(0*TS_SECOND);
2379 }
2380 else {
2381 /* Release within 3 seconds of start: skip to previous
2382 * file */
2383 osd_stop();
2384 next_action = VIDEO_PREV | VIDEO_ACTION_MANUAL;
2385 }
2386 }
2387 else if ((button & ~BUTTON_REPEAT) == old_button) {
2388 button = osd_seek_btn(old_button);
2389 }
2390
2391 if (button == ACTION_STD_CANCEL)
2392 goto cancel_playback; /* jump to stop handling above */
2393
2394 rb->default_event_handler(button);
2395 break;
2396 } /* MPEG_RW: */
2397
2398 case MPEG_FF:
2399#ifdef MPEG_FF2
2400 case MPEG_FF2:
2401#endif
2402#ifdef MPEG_RC_FF
2403 case MPEG_RC_FF:
2404#endif
2405 {
2406 int old_button = button;
2407
2408 if (settings.play_mode != 0)
2409 button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL);
2410
2411 if ((old_button | BUTTON_REL) == button) {
2412 /* If button has been released: skip to next file */
2413 osd_stop();
2414 next_action = VIDEO_NEXT | VIDEO_ACTION_MANUAL;
2415 }
2416 else if ((button & ~BUTTON_REPEAT) == old_button) {
2417 button = osd_seek_btn(old_button);
2418 }
2419
2420 if (button == ACTION_STD_CANCEL)
2421 goto cancel_playback; /* jump to stop handling above */
2422
2423 rb->default_event_handler(button);
2424 break;
2425 } /* MPEG_FF: */
2426
2427#ifdef HAVE_HEADPHONE_DETECTION
2428 case SYS_PHONE_PLUGGED:
2429 case SYS_PHONE_UNPLUGGED:
2430 {
2431 osd_handle_phone_plug(button == SYS_PHONE_PLUGGED);
2432 break;
2433 } /* SYS_PHONE_*: */
2434#endif
2435
2436 default:
2437 {
2438 osd_refresh(OSD_REFRESH_DEFAULT);
2439 rb->default_event_handler(button);
2440 break;
2441 } /* default: */
2442 }
2443
2444 rb->yield();
2445 } /* end while */
2446
2447 osd_stop();
2448
2449#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
2450 /* Be sure hook is removed before exiting since the stop will put it
2451 * back because of the backlight restore. */
2452 rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook);
2453#endif
2454
2455 rb->lcd_setfont(FONT_UI);
2456
2457 return next_action;
2458}
2459
2460enum plugin_status plugin_start(const void* parameter)
2461{
2462 static char videofile[MAX_PATH];
2463 int status = PLUGIN_OK; /* assume success */
2464 bool quit = false;
2465
2466#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR)
2467 bool preserved_talk_state;
2468#endif
2469
2470 if (parameter == NULL) {
2471 /* No file = GTFO */
2472 rb->splash(HZ*2, "No File");
2473 return PLUGIN_ERROR;
2474 }
2475
2476 /* Disable all talking before initializing IRAM */
2477 rb->talk_disable(true);
2478
2479#ifdef PLUGIN_USE_IRAM
2480 iram_saving_init();
2481
2482#ifndef SIMULATOR
2483 preserved_talk_state = rb->global_settings->talk_menu;
2484 if (!iram_saved_copy)
2485 rb->global_settings->talk_menu = false;
2486#endif
2487#endif
2488
2489#ifdef HAVE_LCD_COLOR
2490 rb->lcd_set_backdrop(NULL);
2491 rb->lcd_set_foreground(LCD_WHITE);
2492 rb->lcd_set_background(LCD_BLACK);
2493#endif
2494
2495 rb->lcd_clear_display();
2496 rb->lcd_update();
2497
2498 rb->strcpy(videofile, (const char*) parameter);
2499
2500 if (stream_init() < STREAM_OK) {
2501 /* Fatal because this should not fail */
2502 DEBUGF("Could not initialize streams\n");
2503 status = PLUGIN_ERROR;
2504 } else {
2505 int next_action = VIDEO_STOP;
2506 bool get_videofile_says = true;
2507
2508 while (!quit)
2509 {
2510 init_settings(videofile);
2511
2512 int result = stream_open(videofile);
2513 bool manual_skip = false;
2514
2515 if (result >= STREAM_OK) {
2516 /* start menu */
2517 rb->lcd_clear_display();
2518 rb->lcd_update();
2519 result = mpeg_start_menu(stream_get_duration());
2520
2521 next_action = VIDEO_STOP;
2522 if (result != MPEG_START_QUIT) {
2523 /* Enter button loop and process UI */
2524 next_action = button_loop();
2525 manual_skip = next_action & VIDEO_ACTION_MANUAL;
2526 next_action &= ~VIDEO_ACTION_MANUAL;
2527 }
2528
2529 stream_close();
2530
2531 rb->lcd_clear_display();
2532 rb->lcd_update();
2533
2534 save_settings();
2535 } else {
2536 /* Problem with file; display message about it - not
2537 * considered a plugin error */
2538 long tick;
2539 const char *errstring;
2540
2541 DEBUGF("Could not open %s\n", videofile);
2542 switch (result)
2543 {
2544 case STREAM_UNSUPPORTED:
2545 errstring = "Unsupported format";
2546 break;
2547 default:
2548 errstring = "Error opening file: %d";
2549 }
2550
2551 tick = *rb->current_tick + HZ*2;
2552
2553 rb->splashf(0, errstring, result);
2554
2555 /* Be sure it doesn't get stuck in an unbreakable loop of bad
2556 * files, just in case! Otherwise, keep searching in the
2557 * chosen direction until a good one is found. */
2558 while (!quit && TIME_BEFORE(*rb->current_tick, tick))
2559 {
2560 int button = mpeg_button_get(HZ*2);
2561
2562 switch (button)
2563 {
2564 case MPEG_STOP:
2565 case ACTION_STD_CANCEL:
2566 /* Abort the search and exit */
2567 next_action = VIDEO_STOP;
2568 quit = true;
2569 break;
2570
2571 case BUTTON_NONE:
2572 if (settings.play_mode != 0) {
2573 if (next_action == VIDEO_STOP) {
2574 /* Default to next file */
2575 next_action = VIDEO_NEXT;
2576 }
2577 else if (next_action == VIDEO_PREV &&
2578 !get_videofile_says) {
2579 /* Was first file already; avoid endlessly
2580 * retrying it */
2581 next_action = VIDEO_STOP;
2582 }
2583 }
2584 break;
2585
2586 default:
2587 rb->default_event_handler(button);
2588 } /* switch */
2589 } /* while */
2590 }
2591
2592 /* return value of button_loop says, what's next */
2593 switch (next_action)
2594 {
2595 case VIDEO_NEXT:
2596 {
2597 get_videofile_says = get_videofile(VIDEO_NEXT, videofile,
2598 sizeof(videofile));
2599 /* quit after finished the last videofile */
2600 quit = !get_videofile_says;
2601
2602 if (manual_skip)
2603 {
2604 rb->system_sound_play(get_videofile_says ?
2605 SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE);
2606 }
2607
2608 break;
2609 }
2610 case VIDEO_PREV:
2611 {
2612 get_videofile_says = get_videofile(VIDEO_PREV, videofile,
2613 sizeof(videofile));
2614 /* if there is no previous file, play the same videofile */
2615
2616 if (manual_skip)
2617 {
2618 rb->system_sound_play(get_videofile_says ?
2619 SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE);
2620 }
2621
2622 break;
2623 }
2624 case VIDEO_STOP:
2625 {
2626 quit = true;
2627 break;
2628 }
2629 }
2630 } /* while */
2631 }
2632
2633#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV)
2634 rb->lcd_set_mode(LCD_MODE_RGB565);
2635#endif
2636
2637 stream_exit();
2638
2639#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR)
2640 if (!iram_saved_copy)
2641 rb->global_settings->talk_menu = preserved_talk_state;
2642#endif
2643
2644 rb->talk_disable(false);
2645
2646 /* Actually handle delayed processing of system events of interest
2647 * that were captured in other button loops */
2648 mpeg_sysevent_handle();
2649
2650 return status;
2651}