source: other-projects/tipple-android/trunk/src/org/greenstone/android/tipple/AudioPlayerView.java@ 23977

Last change on this file since 23977 was 23977, checked in by davidb, 13 years ago

Initial set of files for the Tipple project, to be run on an Android device

File size: 17.0 KB
Line 
1package org.greenstone.android.tipple;
2
3import java.util.LinkedList;
4import java.util.List;
5
6import android.content.Context;
7import android.media.MediaPlayer;
8import android.view.View;
9import android.view.ViewGroup;
10import android.widget.Button;
11import android.widget.LinearLayout;
12import android.widget.TextView;
13
14public class AudioPlayerView extends LinearLayout
15{
16 TextView text_view_;
17
18 protected Button buttonPlayPause_;
19 protected Button buttonNext_;
20 protected Button buttonShowHideText_;
21
22 protected String stopLabel_;
23
24 protected boolean hasCompletionHandler_;
25
26 protected TippleActivity.TextAudioModeEnum current_text_audio_mode_;
27
28 protected List<MediaPlayer> audioPlayerQueue_;
29 protected List<String> idPlayerQueue_;
30 protected List<String> textPlayerQueue_;
31 protected List<Boolean> visitedPlayerQueue_;
32 protected int absolute_head_pos_;
33 protected int absolute_tail_pos_;
34
35
36 AudioPlayerView(Context context, TextView text_view)
37 {
38 super(context);
39
40 text_view_ = text_view;
41
42 audioPlayerQueue_ = new LinkedList<MediaPlayer>();
43 idPlayerQueue_ = new LinkedList<String>();
44 textPlayerQueue_ = new LinkedList<String>();
45 visitedPlayerQueue_ = new LinkedList<Boolean>();
46
47 absolute_head_pos_ = 0;
48 absolute_tail_pos_ = -1;
49
50 // media buttons - play/pause, and next song/audio as horizontal layout, 50% each
51
52 stopLabel_ = "Stop";
53
54 LayoutParams layout_params
55 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
56 ViewGroup.LayoutParams.MATCH_PARENT,1);
57
58 buttonPlayPause_ = new Button(context);
59 buttonPlayPause_.setLayoutParams(layout_params);
60 buttonPlayPause_.setText("Play");
61 buttonPlayPause_.setEnabled(false);
62
63 buttonNext_ = new Button(context);
64 buttonNext_.setLayoutParams(layout_params);
65 buttonNext_.setText(stopLabel_);
66 buttonNext_.setEnabled(false);
67
68 hasCompletionHandler_ = false;
69
70
71 buttonPlayPause_.setOnClickListener(new View.OnClickListener() {
72 public void onClick(View v) {
73
74 if (isPlaying()) {
75 // Change to pause
76
77 TippleActivity.log.optMessage("Manual Pause Audio", textPlayerQueue_.get(0));
78
79 pauseAudio();
80 }
81 else {
82 // Must be paused => resume playing
83 TippleActivity.log.optMessage("Manual Play/Resume Audio", textPlayerQueue_.get(0));
84
85 playResumeAudio();
86 }
87 }
88 });
89
90 buttonNext_.setOnClickListener(new View.OnClickListener() {
91 @Override
92 public void onClick(View v) {
93 TippleActivity.log.optMessage("Manual Next Audio Location");
94 nextAudioLocation();
95 }
96 });
97
98 // Optional show text button
99
100 buttonShowHideText_ = new Button(context);
101 buttonShowHideText_.setLayoutParams(layout_params);
102 buttonShowHideText_.setText("Show Text");
103 buttonShowHideText_.setEnabled(false);
104
105 buttonShowHideText_.setOnClickListener(new View.OnClickListener() {
106 public void onClick(View v) {
107 TippleActivity.log.optMessage("Manual Show/Hide Text");
108
109 if (text_view_.getVisibility()==View.INVISIBLE) {
110 TippleActivity.log.optMessage("Manual Show Text");
111 text_view_.setVisibility(View.VISIBLE);
112 buttonShowHideText_.setText("Hide Text");
113 }
114 else {
115 TippleActivity.log.optMessage("Manual Hide Text");
116 text_view_.setVisibility(View.INVISIBLE);
117 buttonShowHideText_.setText("Show Text");
118 }
119 }
120 });
121
122
123
124
125 // Horozontal layout is:
126 // Play | OptText | Next
127
128 addView(buttonPlayPause_);
129
130 /*
131 current_text_audio_mode_ = TippleActivity.textAudioMode;
132 if (current_text_audio_mode_ == TippleActivity.TextAudioModeEnum.AUDIO_PLUS_TEXT) {
133 addView(buttonShowHideText_);
134 }
135 */
136 addView(buttonShowHideText_);
137
138 addView(buttonNext_);
139 }
140
141 protected boolean audioViewIsActive()
142 {
143 return ((current_text_audio_mode_==TippleActivity.TextAudioModeEnum.AUDIO_ONLY)
144 || (current_text_audio_mode_==TippleActivity.TextAudioModeEnum.AUDIO_PLUS_TEXT)
145 || (current_text_audio_mode_==TippleActivity.TextAudioModeEnum.TEXT_PLUS_AUDIO));
146
147 }
148
149 protected boolean textViewIsActive()
150 {
151 return ((current_text_audio_mode_==TippleActivity.TextAudioModeEnum.TEXT_ONLY)
152 || (current_text_audio_mode_==TippleActivity.TextAudioModeEnum.TEXT_PLUS_AUDIO)
153 || (current_text_audio_mode_==TippleActivity.TextAudioModeEnum.AUDIO_PLUS_TEXT));
154
155 }
156
157 public void refreshView()
158 {
159 if (current_text_audio_mode_ != TippleActivity.textAudioMode) {
160 // There has been a mode change
161
162 if (TippleActivity.textAudioMode == TippleActivity.TextAudioModeEnum.AUDIO_PLUS_TEXT) {
163 // ensure both play/pause and show/hide removed, then add in order needed
164
165 if (this.indexOfChild(buttonPlayPause_)>=0) {
166 this.removeView(buttonPlayPause_);
167 }
168 if (this.indexOfChild(buttonShowHideText_)>=0) {
169 this.removeView(buttonShowHideText_);
170 }
171 this.addView(buttonPlayPause_,0);
172 this.addView(buttonShowHideText_,1);
173
174 stopLabel_ = "Stop";
175 }
176
177 else if (TippleActivity.textAudioMode == TippleActivity.TextAudioModeEnum.AUDIO_ONLY) {
178 // ensure show/hide is gone, and only add play/pause if needed
179 if (this.indexOfChild(buttonShowHideText_)>=0) {
180 // Don't want to see a show/hide text button
181 this.removeView(buttonShowHideText_);
182 }
183 if (this.indexOfChild(buttonPlayPause_)<0) {
184 this.addView(buttonPlayPause_);
185 }
186 stopLabel_ = "Stop";
187 }
188
189 else if (TippleActivity.textAudioMode == TippleActivity.TextAudioModeEnum.TEXT_PLUS_AUDIO) {
190 // ensure both play/pause and show/hide removed, then add in order needed
191 if (this.indexOfChild(buttonPlayPause_)>=0) {
192 this.removeView(buttonPlayPause_);
193 }
194 if (this.indexOfChild(buttonShowHideText_)>=0) {
195 this.removeView(buttonShowHideText_);
196 }
197
198 this.addView(buttonShowHideText_,0);
199 this.addView(buttonPlayPause_,1);
200
201 stopLabel_ = "Stop";
202 }
203
204 else if (TippleActivity.textAudioMode == TippleActivity.TextAudioModeEnum.TEXT_ONLY) {
205 // ensure play/pause is gone, and only add show/hide if needed
206
207 if (this.indexOfChild(buttonPlayPause_)>=0) {
208 // Don't want to see a play button
209 this.removeView(buttonPlayPause_);
210 }
211
212 if (this.indexOfChild(buttonShowHideText_)<0) {
213 this.addView(buttonShowHideText_,0);
214 }
215 stopLabel_ = "End";
216 }
217
218 current_text_audio_mode_ = TippleActivity.textAudioMode;
219 }
220
221 }
222 synchronized public boolean audioQueueEmpty()
223 {
224 return (audioPlayerQueue_.size()==0);
225 }
226
227 synchronized public boolean outstandingAudioPending()
228 {
229 return (audioPlayerQueue_.size()>1);
230 }
231
232 synchronized public int audioQueueLength()
233 {
234 return audioPlayerQueue_.size();
235 }
236
237 synchronized public boolean addAudio(String new_id)
238 {
239 // Assume audio should be added, unless an existing match is found
240 boolean add_audio = true;
241
242 int rev_pos = audioPlayerQueue_.size()-1;
243 for (int i=0; i<3; i++) {
244 if (rev_pos<0) {
245 break;
246 }
247
248 String id = idPlayerQueue_.get(rev_pos);
249
250 if (new_id.equals(id)) {
251 add_audio=false;
252 }
253
254 rev_pos--;
255 }
256
257 return add_audio;
258 }
259 synchronized public void queueAudio(String audio_filename, String id, String text,
260 boolean previously_visited)
261 {
262 MediaPlayer mp = new MediaPlayer();
263 try {
264
265 mp.setDataSource(audio_filename);
266 mp.prepare();
267 audioPlayerQueue_.add(mp);
268 idPlayerQueue_.add(id);
269 textPlayerQueue_.add(text);
270 visitedPlayerQueue_.add(previously_visited);
271
272 absolute_tail_pos_++;
273
274 int audio_queue_len = audioPlayerQueue_.size();
275 int mod_head_pos = absolute_head_pos_ % 3;
276
277 if (audio_queue_len==1) {
278 // Nothing to do, not enough data to allow the user to play anything
279 return;
280 }
281 else if (audio_queue_len==2) {
282
283 // mod_head_pos==0 => title followed by text => ready to play
284 // mod_head_pos==1 => text followed by beep => "stop"
285 // mod_head_pos==2 => beep followed title => "skip to next"
286
287 if (mod_head_pos==0) {
288 // just queued up a title and text (at the head of the queue)
289 // => make play active if not already so
290 if (!buttonPlayPause_.isEnabled()) {
291 //buttonPlayPause_.setText("Play");
292 buttonPlayPause_.setEnabled(true);
293 }
294 if (!buttonShowHideText_.isEnabled()) {
295 buttonShowHideText_.setEnabled(true);
296
297 }
298 buttonNext_.setText(stopLabel_);
299 buttonNext_.setEnabled(true);
300 }
301 else if (mod_head_pos==1) {
302
303 buttonNext_.setText(stopLabel_);
304 buttonNext_.setEnabled(true);
305 }
306 else {
307 // mod_head_pos==2
308 buttonNext_.setText("Skip to Next (1)");
309 buttonNext_.setEnabled(true);
310 }
311 }
312 else {
313 // length >= 3
314
315 // mod_head_pos==0 => title at head, spans loc if len >3
316 // mod_head_pos==1 => text at head, guaranteed to span loc => "skip to next"
317 // mod_head_pos==2 => beep at head, guaranteed to span loc => "skip to next"
318
319 if ((mod_head_pos==0) && (audio_queue_len==3)) {
320 // stop
321 buttonNext_.setText(stopLabel_);
322 buttonNext_.setEnabled(true);
323 }
324 else {
325 // for everything else, the queue spans to another location
326
327
328 int relative_adjustment_pos = mod_head_pos -1;
329 int num_remaining_locations = (audio_queue_len+relative_adjustment_pos)/3;
330
331 buttonNext_.setText("Skip to Next (" + num_remaining_locations + ")");
332 buttonNext_.setEnabled(true);
333 }
334 }
335 }
336 catch (Exception e) {
337 e.printStackTrace();
338 }
339 }
340
341 synchronized public void queueAudio(String audio_filename, String id, String text)
342 {
343 queueAudio(audio_filename,id,text,false);
344 }
345
346 synchronized public void removeAudioTrack()
347 {
348 if (audioPlayerQueue_.size()>0) {
349
350 TippleActivity.log.optMessage("Remove Audio Track","Queue size was: " + audioPlayerQueue_.size());
351
352 MediaPlayer mp = audioPlayerQueue_.remove(0);
353 idPlayerQueue_.remove(0);
354 textPlayerQueue_.remove(0);
355 visitedPlayerQueue_.remove(0);
356
357 absolute_head_pos_++;
358 mp.stop();
359 mp.release();
360
361 hasCompletionHandler_ = false;
362 }
363 else {
364 System.err.println("Error: No audio in queue to remove");
365 }
366 }
367
368 synchronized public void removeAudioLocation()
369 {
370 int audio_queue_len = audioPlayerQueue_.size();
371
372 if (audio_queue_len>0) {
373
374 TippleActivity.log.optMessage("Remove Audio Location","Queue size was: " + audioPlayerQueue_.size());
375
376 int mod_head_pos = absolute_head_pos_%3;
377 int need_to_skip = 3-mod_head_pos;
378
379 // remove up to the next absolute multiple of 3
380 for (int i=0; i<need_to_skip; i++) {
381 MediaPlayer mp = audioPlayerQueue_.remove(0);
382 idPlayerQueue_.remove(0);
383 textPlayerQueue_.remove(0);
384 visitedPlayerQueue_.remove(0);
385 absolute_head_pos_++;
386
387 mp.stop();
388 mp.release();
389 if (audioPlayerQueue_.size()==0) {
390 // happens if there is no beep on the last location
391 break;
392 }
393 }
394
395 hasCompletionHandler_ = false;
396 }
397 else {
398 System.err.println("Error: No audio in queue to remove");
399 }
400 }
401
402
403 synchronized public void onAudioCompletion(MediaPlayer mp)
404 {
405 mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
406
407 @Override
408 public void onCompletion(MediaPlayer mp) {
409 nextAudioTrack();
410 }
411 });
412 }
413
414 synchronized public void onVisitedAudioCompletion(MediaPlayer mp)
415 {
416 mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
417
418 @Override
419 public void onCompletion(MediaPlayer mp) {
420 playResumeAudioInternal();
421 }
422 });
423 }
424
425 public boolean updateTextViewIfHeadIsNewLocation()
426 {
427 boolean updated = false;
428
429 if ((absolute_head_pos_%3)==0) {
430 // start of a new location
431 String text_desc = textPlayerQueue_.get(0);
432 if (visitedPlayerQueue_.get(0)) {
433 text_desc += "\n(Previously Visited)";
434 }
435 text_desc += "\n------";
436
437 if (textPlayerQueue_.size()>1) {
438 text_desc += "\n" + textPlayerQueue_.get(1);
439 }
440
441 text_view_.setText(text_desc);
442 updated = true;
443 }
444
445 return updated;
446 }
447
448
449 synchronized public void playResumeAudio()
450 {
451 if (audioPlayerQueue_.size()>0) {
452
453 if ((audioViewIsActive()) && (visitedPlayerQueue_.get(0))) {
454 // Play (synchronously) the "previously visited" message before moving on to
455 // describing the location itself
456 MediaPlayer mp = new MediaPlayer();
457
458 try {
459 mp.setDataSource(TipLocationManager.prevVisitedFilename);
460 mp.prepare();
461 onVisitedAudioCompletion(mp);
462 mp.start();
463 }
464 catch (Exception e) {
465 e.printStackTrace();
466 }
467 }
468 else {
469 // go straight on and play the queued audio
470 playResumeAudioInternal();
471 }
472 }
473 else {
474 System.err.println("Error: No audio in queue to start playing");
475
476 }
477
478 }
479
480
481 synchronized public void playResumeAudioInternal()
482 {
483 if (audioPlayerQueue_.size()>0) {
484 TippleActivity.log.optMessage("Play/Resume Audio","Queue size was: " + audioPlayerQueue_.size());
485
486 MediaPlayer mp = audioPlayerQueue_.get(0);
487
488 if (!hasCompletionHandler_) {
489
490 updateTextViewIfHeadIsNewLocation();
491
492 onAudioCompletion(mp);
493 hasCompletionHandler_ = true;
494 }
495
496 if (audioViewIsActive() || ((absolute_head_pos_%3)==2)) {
497 // allow it to play a "ding" even in text only mode
498 mp.start();
499 }
500 buttonPlayPause_.setText("Pause"); // next operation
501 }
502 else {
503 System.err.println("Error: No audio in queue to start playing");
504 }
505
506 }
507
508 synchronized public void pauseAudio()
509 {
510 if (audioPlayerQueue_.size()>0) {
511 MediaPlayer mp = audioPlayerQueue_.get(0);
512 mp.pause();
513 buttonPlayPause_.setText("Play/Resume"); // next operation
514 }
515 else {
516 System.err.println("Error: No audio in queue to pause");
517 }
518 }
519
520 synchronized public void nextAudioLocation()
521 {
522 removeAudioLocation(); // stops it if currently playing
523
524 int remaining_audio_len = audioQueueLength();
525 if (remaining_audio_len>0) {
526 playResumeAudio();
527
528 int mod_head_pos = absolute_head_pos_ % 3;
529 /*
530 int relative_adjustment_pos = 1 - mod_head_pos;
531 int num_remaining_locations = (remaining_audio_len+relative_adjustment_pos)/3;
532
533 if (num_remaining_locations>=1) {
534
535 buttonNext_.setText("Skip to Next (" + num_remaining_locations + ")");
536 buttonNext_.setEnabled(true);
537 }
538 */
539
540 int num_remaining_locations = (remaining_audio_len+mod_head_pos)/3;
541
542 if (num_remaining_locations>1) {
543
544 buttonNext_.setText("Skip to Next (" + (num_remaining_locations-1) + ")");
545 buttonNext_.setEnabled(true);
546 }
547
548 else {
549 buttonNext_.setText(stopLabel_);
550 buttonNext_.setEnabled(true);
551 buttonPlayPause_.setText("Play");
552 buttonPlayPause_.setEnabled(true);
553 buttonShowHideText_.setText("Show Text");
554 buttonShowHideText_.setEnabled(true);
555 }
556 }
557 else {
558 buttonNext_.setText(stopLabel_);
559 buttonNext_.setEnabled(false);
560 buttonPlayPause_.setText("Play");
561 buttonPlayPause_.setEnabled(false);
562
563 if (text_view_.getVisibility() == View.VISIBLE) {
564 TippleActivity.log.optMessage("Audio Hide Text");
565 text_view_.setVisibility(View.INVISIBLE);
566 }
567 buttonShowHideText_.setText("Show Text");
568 buttonShowHideText_.setEnabled(false);
569 }
570 }
571
572 synchronized public void nextAudioTrack()
573 {
574 removeAudioTrack(); // stops it if currently playing
575
576 int remaining_audio_len = audioQueueLength();
577 if (remaining_audio_len>0) {
578 playResumeAudio();
579
580 int num_remaining_locations = ((remaining_audio_len-1)/3);
581
582 if (num_remaining_locations>=1) {
583
584 buttonNext_.setText("Skip to Next (" + num_remaining_locations + ")");
585 buttonNext_.setEnabled(true);
586 }
587 else {
588 buttonNext_.setText(stopLabel_);
589 buttonNext_.setEnabled(true);
590 //buttonPlayPause_.setText("Play");
591 buttonPlayPause_.setEnabled(true);
592 buttonShowHideText_.setEnabled(true);
593 }
594 }
595 else {
596 buttonNext_.setText(stopLabel_);
597 buttonNext_.setEnabled(false);
598 buttonPlayPause_.setText("Play");
599 buttonPlayPause_.setEnabled(false);
600
601 if (text_view_.getVisibility() == View.VISIBLE) {
602 TippleActivity.log.optMessage("Audio Hide Text");
603 text_view_.setVisibility(View.INVISIBLE);
604 }
605 buttonShowHideText_.setText("Show Text");
606 buttonShowHideText_.setEnabled(false);
607
608
609 }
610 }
611
612
613 synchronized public boolean isPlaying()
614 {
615 boolean is_playing = false;
616 if (audioPlayerQueue_.size()>0) {
617 is_playing = audioPlayerQueue_.get(0).isPlaying();
618 }
619 return is_playing;
620 }
621}
Note: See TracBrowser for help on using the repository browser.