summaryrefslogtreecommitdiff
path: root/apps/plugins/playing_time.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/playing_time.c')
-rw-r--r--apps/plugins/playing_time.c376
1 files changed, 376 insertions, 0 deletions
diff --git a/apps/plugins/playing_time.c b/apps/plugins/playing_time.c
new file mode 100644
index 0000000000..148da67df8
--- /dev/null
+++ b/apps/plugins/playing_time.c
@@ -0,0 +1,376 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include "plugin.h"
22
23#define rb_talk_ids(enqueue, ids...) rb->talk_idarray(TALK_IDARRAY(ids), enqueue)
24
25/* units used with output_dyn_value */
26const unsigned char * const byte_units[] =
27{
28 ID2P(LANG_BYTE),
29 ID2P(LANG_KIBIBYTE),
30 ID2P(LANG_MEBIBYTE),
31 ID2P(LANG_GIBIBYTE)
32};
33
34const unsigned char * const * const kibyte_units = &byte_units[1];
35
36enum ePT_SECS {
37 ePT_SECS_TTL = 0,
38 ePT_SECS_BEF,
39 ePT_SECS_AFT,
40 ePT_SECS_COUNT
41};
42
43enum ePT_KBS {
44 /* Note: Order matters (voicing order of LANG_PLAYTIME_STORAGE) */
45 ePT_KBS_TTL = 0,
46 ePT_KBS_BEF,
47 ePT_KBS_AFT,
48 ePT_KBS_COUNT
49};
50
51/* playing_time screen context */
52struct playing_time_info {
53 int curr_playing; /* index of currently playing track in playlist */
54 int nb_tracks; /* how many tracks in playlist */
55 /* seconds total, before, and after current position. Datatype
56 allows for values up to 68years. If I had kept it in ms
57 though, it would have overflowed at 24days, which takes
58 something like 8.5GB at 32kbps, and so we could conceivably
59 have playlists lasting longer than that. */
60 long secs[ePT_SECS_COUNT];
61 long trk_secs[ePT_SECS_COUNT];
62 /* kilobytes played total, before, and after current pos.
63 Kilobytes because bytes would overflow. Data type range is up
64 to 2TB. */
65 long kbs[ePT_KBS_COUNT];
66};
67
68/* list callback for playing_time screen */
69static const char * playing_time_get_or_speak_info(int selected_item, void * data,
70 char *buf, size_t buffer_len,
71 bool say_it)
72{
73 long elapsed_pct; /* percentage of duration elapsed */
74 struct playing_time_info *pti = (struct playing_time_info *)data;
75 switch(selected_item) {
76 case 0: { /* elapsed and total time */
77 char timestr1[25], timestr2[25];
78 rb->format_time_auto(timestr1, sizeof(timestr1),
79 pti->secs[ePT_SECS_BEF], UNIT_SEC, false);
80
81 rb->format_time_auto(timestr2, sizeof(timestr2),
82 pti->secs[ePT_SECS_TTL], UNIT_SEC, false);
83
84 if (pti->secs[ePT_SECS_TTL] == 0)
85 elapsed_pct = 0;
86 else if (pti->secs[ePT_SECS_TTL] <= 0xFFFFFF)
87 {
88 elapsed_pct = (pti->secs[ePT_SECS_BEF] * 100
89 / pti->secs[ePT_SECS_TTL]);
90 }
91 else /* sacrifice some precision to avoid overflow */
92 {
93 elapsed_pct = (pti->secs[ePT_SECS_BEF] >> 7) * 100
94 / (pti->secs[ePT_SECS_TTL] >> 7);
95 }
96 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_ELAPSED),
97 timestr1, timestr2, elapsed_pct);
98
99 if (say_it)
100 rb_talk_ids(false, LANG_PLAYTIME_ELAPSED,
101 TALK_ID(pti->secs[ePT_SECS_BEF], UNIT_TIME),
102 VOICE_OF,
103 TALK_ID(pti->secs[ePT_SECS_TTL], UNIT_TIME),
104 VOICE_PAUSE,
105 TALK_ID(elapsed_pct, UNIT_PERCENT));
106 break;
107 }
108 case 1: { /* playlist remaining time */
109 char timestr[25];
110 rb->format_time_auto(timestr, sizeof(timestr), pti->secs[ePT_SECS_AFT],
111 UNIT_SEC, false);
112 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_REMAINING), timestr);
113
114 if (say_it)
115 rb_talk_ids(false, LANG_PLAYTIME_REMAINING,
116 TALK_ID(pti->secs[ePT_SECS_AFT], UNIT_TIME));
117 break;
118 }
119 case 2: { /* track elapsed and duration */
120 char timestr1[25], timestr2[25];
121
122 rb->format_time_auto(timestr1, sizeof(timestr1), pti->trk_secs[ePT_SECS_BEF],
123 UNIT_SEC, false);
124 rb->format_time_auto(timestr2, sizeof(timestr2), pti->trk_secs[ePT_SECS_TTL],
125 UNIT_SEC, false);
126
127 if (pti->trk_secs[ePT_SECS_TTL] == 0)
128 elapsed_pct = 0;
129 else if (pti->trk_secs[ePT_SECS_TTL] <= 0xFFFFFF)
130 {
131 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] * 100
132 / pti->trk_secs[ePT_SECS_TTL]);
133 }
134 else /* sacrifice some precision to avoid overflow */
135 {
136 elapsed_pct = (pti->trk_secs[ePT_SECS_BEF] >> 7) * 100
137 / (pti->trk_secs[ePT_SECS_TTL] >> 7);
138 }
139 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_ELAPSED),
140 timestr1, timestr2, elapsed_pct);
141
142 if (say_it)
143 rb_talk_ids(false, LANG_PLAYTIME_TRK_ELAPSED,
144 TALK_ID(pti->trk_secs[ePT_SECS_BEF], UNIT_TIME),
145 VOICE_OF,
146 TALK_ID(pti->trk_secs[ePT_SECS_TTL], UNIT_TIME),
147 VOICE_PAUSE,
148 TALK_ID(elapsed_pct, UNIT_PERCENT));
149 break;
150 }
151 case 3: { /* track remaining time */
152 char timestr[25];
153 rb->format_time_auto(timestr, sizeof(timestr), pti->trk_secs[ePT_SECS_AFT],
154 UNIT_SEC, false);
155 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRK_REMAINING), timestr);
156
157 if (say_it)
158 rb_talk_ids(false, LANG_PLAYTIME_TRK_REMAINING,
159 TALK_ID(pti->trk_secs[ePT_SECS_AFT], UNIT_TIME));
160 break;
161 }
162 case 4: { /* track index */
163 int track_pct = (pti->curr_playing + 1) * 100 / pti->nb_tracks;
164 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_TRACK),
165 pti->curr_playing + 1, pti->nb_tracks, track_pct);
166
167 if (say_it)
168 rb_talk_ids(false, LANG_PLAYTIME_TRACK,
169 TALK_ID(pti->curr_playing + 1, UNIT_INT),
170 VOICE_OF,
171 TALK_ID(pti->nb_tracks, UNIT_INT),
172 VOICE_PAUSE,
173 TALK_ID(track_pct, UNIT_PERCENT));
174 break;
175 }
176 case 5: { /* storage size */
177 int i;
178 char kbstr[ePT_KBS_COUNT][10];
179
180 for (i = 0; i < ePT_KBS_COUNT; i++) {
181 rb->output_dyn_value(kbstr[i], sizeof(kbstr[i]),
182 pti->kbs[i], kibyte_units, 3, true);
183 }
184 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_STORAGE),
185 kbstr[ePT_KBS_TTL], kbstr[ePT_KBS_BEF],kbstr[ePT_KBS_AFT]);
186
187 if (say_it) {
188 int32_t voice_ids[ePT_KBS_COUNT];
189 voice_ids[ePT_KBS_TTL] = LANG_PLAYTIME_STORAGE;
190 voice_ids[ePT_KBS_BEF] = VOICE_PLAYTIME_DONE;
191 voice_ids[ePT_KBS_AFT] = LANG_PLAYTIME_REMAINING;
192
193 for (i = 0; i < ePT_KBS_COUNT; i++) {
194 rb_talk_ids(i > 0, VOICE_PAUSE, voice_ids[i]);
195 rb->output_dyn_value(NULL, 0, pti->kbs[i], kibyte_units, 3, true);
196 }
197 }
198 break;
199 }
200 case 6: { /* Average track file size */
201 char str[10];
202 long avg_track_size = pti->kbs[ePT_KBS_TTL] / pti->nb_tracks;
203 rb->output_dyn_value(str, sizeof(str), avg_track_size, kibyte_units, 3, true);
204 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_TRACK_SIZE), str);
205
206 if (say_it) {
207 rb->talk_id(LANG_PLAYTIME_AVG_TRACK_SIZE, false);
208 rb->output_dyn_value(NULL, 0, avg_track_size, kibyte_units, 3, true);
209 }
210 break;
211 }
212 case 7: { /* Average bitrate */
213 /* Convert power of 2 kilobytes to power of 10 kilobits */
214 long avg_bitrate = (pti->kbs[ePT_KBS_TTL] / pti->secs[ePT_SECS_TTL]
215 * 1024 * 8 / 1000);
216 rb->snprintf(buf, buffer_len, rb->str(LANG_PLAYTIME_AVG_BITRATE), avg_bitrate);
217
218 if (say_it)
219 rb_talk_ids(false, LANG_PLAYTIME_AVG_BITRATE,
220 TALK_ID(avg_bitrate, UNIT_KBIT));
221 break;
222 }
223 }
224 return buf;
225}
226
227static const char * playing_time_get_info(int selected_item, void * data,
228 char *buffer, size_t buffer_len)
229{
230 return playing_time_get_or_speak_info(selected_item, data,
231 buffer, buffer_len, false);
232}
233
234static int playing_time_speak_info(int selected_item, void * data)
235{
236 static char buffer[MAX_PATH];
237 playing_time_get_or_speak_info(selected_item, data,
238 buffer, MAX_PATH, true);
239 return 0;
240}
241
242/* playing time screen: shows total and elapsed playlist duration and
243 other stats */
244static bool playing_time(void)
245{
246 int error_count = 0;
247 unsigned long talked_tick = *rb->current_tick;
248 struct playing_time_info pti;
249 struct playlist_track_info pltrack;
250 struct mp3entry id3;
251 int i, fd;
252
253 pti.nb_tracks = rb->playlist_amount();
254 rb->playlist_get_resume_info(&pti.curr_playing);
255 struct mp3entry *curr_id3 = rb->audio_current_track();
256 if (pti.curr_playing == -1 || !curr_id3)
257 return false;
258 pti.secs[ePT_SECS_BEF] = pti.trk_secs[ePT_SECS_BEF] = curr_id3->elapsed / 1000;
259 pti.secs[ePT_SECS_AFT] = pti.trk_secs[ePT_SECS_AFT]
260 = (curr_id3->length -curr_id3->elapsed) / 1000;
261 pti.kbs[ePT_KBS_BEF] = curr_id3->offset / 1024;
262 pti.kbs[ePT_KBS_AFT] = (curr_id3->filesize -curr_id3->offset) / 1024;
263
264 rb->splash(0, ID2P(LANG_WAIT));
265 rb->splash_progress_set_delay(5 * HZ);
266 /* Go through each file in the playlist and get its stats. For
267 huge playlists this can take a while... The reference position
268 is the position at the moment this function was invoked,
269 although playback continues forward. */
270 for (i = 0; i < pti.nb_tracks; i++) {
271 /* Show a splash while we are loading. */
272 rb->splash_progress(i, pti.nb_tracks,
273 "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT));
274
275 /* Voice equivalent */
276 if (TIME_AFTER(*rb->current_tick, talked_tick + 5 * HZ)) {
277 talked_tick = *rb->current_tick;
278 rb_talk_ids(false, LANG_LOADING_PERCENT,
279 TALK_ID(i * 100 / pti.nb_tracks, UNIT_PERCENT));
280 }
281 if (rb->action_userabort(TIMEOUT_NOBLOCK))
282 goto exit;
283
284 if (i == pti.curr_playing)
285 continue;
286
287 if (rb->playlist_get_track_info(NULL, i, &pltrack) >= 0)
288 {
289 bool ret = false;
290 if ((fd = rb->open(pltrack.filename, O_RDONLY)) >= 0)
291 {
292 ret = rb->get_metadata(&id3, fd, pltrack.filename);
293 rb->close(fd);
294 if (ret)
295 {
296 if (i < pti.curr_playing) {
297 pti.secs[ePT_SECS_BEF] += id3.length / 1000;
298 pti.kbs[ePT_KBS_BEF] += id3.filesize / 1024;
299 } else {
300 pti.secs[ePT_SECS_AFT] += id3.length / 1000;
301 pti.kbs[ePT_KBS_AFT] += id3.filesize / 1024;
302 }
303 }
304 }
305
306 if (!ret)
307 {
308 error_count++;
309 continue;
310 }
311 }
312 else
313 {
314 error_count++;
315 break;
316 }
317 }
318
319 if (error_count > 0)
320 {
321 rb->splash(HZ, ID2P(LANG_PLAYTIME_ERROR));
322 }
323
324 pti.nb_tracks -= error_count;
325 pti.secs[ePT_SECS_TTL] = pti.secs[ePT_SECS_BEF] + pti.secs[ePT_SECS_AFT];
326 pti.trk_secs[ePT_SECS_TTL] = pti.trk_secs[ePT_SECS_BEF] + pti.trk_secs[ePT_SECS_AFT];
327 pti.kbs[ePT_KBS_TTL] = pti.kbs[ePT_KBS_BEF] + pti.kbs[ePT_KBS_AFT];
328
329 struct gui_synclist pt_lists;
330 int key;
331
332 rb->gui_synclist_init(&pt_lists, &playing_time_get_info, &pti, true, 1, NULL);
333 if (rb->global_settings->talk_menu)
334 rb->gui_synclist_set_voice_callback(&pt_lists, playing_time_speak_info);
335 rb->gui_synclist_set_nb_items(&pt_lists, 8);
336 rb->gui_synclist_set_title(&pt_lists, rb->str(LANG_PLAYING_TIME), NOICON);
337 rb->gui_synclist_draw(&pt_lists);
338 rb->gui_synclist_speak_item(&pt_lists);
339 while (true) {
340 if (rb->list_do_action(CONTEXT_LIST, HZ/2, &pt_lists, &key) == 0
341 && key!=ACTION_NONE && key!=ACTION_UNKNOWN)
342 {
343 rb->talk_force_shutup();
344 return(rb->default_event_handler(key) == SYS_USB_CONNECTED);
345 }
346
347 }
348
349 exit:
350 return false;
351}
352
353/* this is the plugin entry point */
354enum plugin_status plugin_start(const void* parameter)
355{
356 enum plugin_status status = PLUGIN_OK;
357
358 (void)parameter;
359
360 if (!rb->audio_status())
361 {
362 rb->splash(HZ*2, "Nothing Playing");
363 return status;
364 }
365
366 FOR_NB_SCREENS(i)
367 rb->viewportmanager_theme_enable(i, true, NULL);
368
369 if (playing_time())
370 status = PLUGIN_USB_CONNECTED;
371
372 FOR_NB_SCREENS(i)
373 rb->viewportmanager_theme_undo(i, false);
374
375 return status;
376}