source: trunk/src_apollo/org/apollo/widgets/FramePlayer.java@ 315

Last change on this file since 315 was 315, checked in by bjn8, 16 years ago

Apollo spin-off added

File size: 17.4 KB
Line 
1package org.apollo.widgets;
2
3import java.awt.Color;
4import java.awt.Dimension;
5import java.awt.GridBagConstraints;
6import java.awt.GridBagLayout;
7import java.awt.Insets;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
10import java.util.List;
11
12import javax.swing.Icon;
13import javax.swing.JButton;
14import javax.swing.JPanel;
15import javax.swing.JSlider;
16import javax.swing.JToggleButton;
17import javax.swing.event.ChangeEvent;
18import javax.swing.event.ChangeListener;
19
20import org.apollo.audio.ApolloSubjectChangedEvent;
21import org.apollo.audio.SampledAudioManager;
22import org.apollo.audio.util.FrameLayoutDaemon;
23import org.apollo.audio.util.MultiTrackPlaybackController;
24import org.apollo.audio.util.SoundDesk;
25import org.apollo.audio.util.TrackMixSubject;
26import org.apollo.audio.util.MultiTrackPlaybackController.MultitrackLoadListener;
27import org.apollo.io.IconRepository;
28import org.apollo.mvc.Observer;
29import org.apollo.mvc.Subject;
30import org.apollo.mvc.SubjectChangedEvent;
31import org.apollo.util.AudioMath;
32import org.apollo.util.AudioSystemLog;
33import org.expeditee.gui.DisplayIO;
34import org.expeditee.gui.Frame;
35import org.expeditee.gui.FrameGraphics;
36import org.expeditee.items.ItemParentStateChangedEvent;
37import org.expeditee.items.Text;
38import org.expeditee.items.widgets.InteractiveWidget;
39
40/**
41 * This a last minute hack ... should be revised
42 *
43 * @author Brook Novak
44 *
45 */
46public class FramePlayer extends InteractiveWidget
47 implements Observer, MultitrackLoadListener, ActionListener {
48
49 private static final int BUTTON_SIZE = 40;
50
51 private static TrackMixSubject masterMix = null;
52 private static String currentPlayingFrame = null;
53
54 private JButton playPauseButton;
55 private JButton stopButton;
56 private JButton rewindButton;
57 private JToggleButton muteButton;
58 private JToggleButton soloButton;
59 private JSlider volumeSlider;
60
61 private int state = READY;
62 private String abortMessage = null;
63
64 private boolean isUpdatingGUI = false;
65
66 public static String FRAME_PLAYERMASTER_CHANNEL_ID = "#$frameplayer#master$";
67
68 private static final Color LOADING_BORDER_COLOR = new Color(22, 205, 5);
69 //private static final Color FAILED_MESSAGE_COLOR = Color.RED;
70 //private static final Font MESSAGE_FONT = TrackWidgetCommons.FREESPACE_TRACKNAME_FONT;
71
72
73 /** States - mutex in this widget but not really .. for example since can be loading tracks and the graph at the same time... */
74 private static final int READY = 1; // Waiting for user interaction
75 private static final int PLAYBACK_LOADING = 2; // Loading tracks from file/cache to play/resume
76 private static final int PLAYING = 3; // Playing audio.
77
78
79 static {
80 masterMix = SoundDesk.getInstance().getOrCreateMix(FRAME_PLAYERMASTER_CHANNEL_ID);
81 }
82
83 /**
84 * Constructor called by Expeditee.
85 *
86 * @param source
87 * @param args
88 */
89 public FramePlayer(Text source, String[] args) {
90 super(source, new JPanel(new GridBagLayout()),
91 BUTTON_SIZE * 8, BUTTON_SIZE * 8,
92 BUTTON_SIZE, BUTTON_SIZE);
93
94 playPauseButton = new JButton();
95 playPauseButton.addActionListener(this);
96 playPauseButton.setIcon(IconRepository.getIcon("play.png"));
97 playPauseButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
98 playPauseButton.setToolTipText("Play selection / Pause");
99
100 stopButton = new JButton();
101 stopButton.setEnabled(false);
102 stopButton.addActionListener(this);
103 stopButton.setIcon(IconRepository.getIcon("stop.png"));
104 stopButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
105 stopButton.setToolTipText("Stop playback");
106
107 rewindButton = new JButton();
108 rewindButton.addActionListener(this);
109 rewindButton.setIcon(IconRepository.getIcon("rewind.png"));
110 rewindButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
111 rewindButton.setToolTipText("Rewind to start");
112
113 // Icon changes
114 muteButton = new JToggleButton();
115 muteButton.setSelectedIcon(IconRepository.getIcon("volmute.png"));
116 muteButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
117 muteButton.setToolTipText("Toggle mute");
118 muteButton.addChangeListener(new ChangeListener() {
119 public void stateChanged(ChangeEvent e) {
120 if (!FramePlayer.this.isUpdatingGUI) {
121 muteChanged();
122 }
123 }
124 });
125
126 soloButton = new JToggleButton();
127 soloButton.setIcon(IconRepository.getIcon("solo.png"));
128 soloButton.setSelectedIcon(IconRepository.getIcon("soloon.png"));
129 soloButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
130 soloButton.setToolTipText("Toggle solo");
131 soloButton.addChangeListener(new ChangeListener() {
132 public void stateChanged(ChangeEvent e) {
133 if (!FramePlayer.this.isUpdatingGUI) {
134 soloChanged();
135 }
136 }
137 });
138
139
140 final int VOLUME_SPACING = 6;
141 volumeSlider = new JSlider(JSlider.HORIZONTAL);
142 volumeSlider.setMinimum(0);
143 volumeSlider.setMaximum(100);
144 volumeSlider.addChangeListener(new ChangeListener() {
145 public void stateChanged(ChangeEvent e) {
146 if (!FramePlayer.this.isUpdatingGUI) {
147 volumeChanged();
148 }
149 // Update the icons
150 updateButtonGUI();
151 }
152 });
153
154 volumeSlider.setPreferredSize(new Dimension((3 * BUTTON_SIZE) - (2 * VOLUME_SPACING), BUTTON_SIZE));
155
156 GridBagConstraints c = new GridBagConstraints();
157 c.gridx = 0;
158 c.gridy = 0;
159 c.fill = GridBagConstraints.BOTH;
160 _swingComponent.add(playPauseButton, c);
161
162 c = new GridBagConstraints();
163 c.gridx = 1;
164 c.gridy = 0;
165 c.fill = GridBagConstraints.BOTH;
166 _swingComponent.add(stopButton, c);
167
168 c = new GridBagConstraints();
169 c.gridx = 2;
170 c.gridy = 0;
171 c.fill = GridBagConstraints.BOTH;
172 _swingComponent.add(rewindButton, c);
173
174 c = new GridBagConstraints();
175 c.gridx = 3;
176 c.gridy = 0;
177 c.fill = GridBagConstraints.BOTH;
178 _swingComponent.add(soloButton, c);
179
180 c = new GridBagConstraints();
181 c.gridx = 4;
182 c.gridy = 0;
183 c.fill = GridBagConstraints.BOTH;
184 _swingComponent.add(muteButton, c);
185
186 c = new GridBagConstraints();
187 c.gridx = 5;
188 c.gridy = 0;
189 c.fill = GridBagConstraints.BOTH;
190 c.insets = new Insets(0,VOLUME_SPACING,0,VOLUME_SPACING);
191 _swingComponent.add(volumeSlider, c);
192
193 updateButtonGUI();
194 updateBorderColor();
195
196 }
197
198
199 /**
200 * Sets the GUI and all the popups to reflect the current state.
201 * Must be on the swing thread.
202 *
203 * @param newState
204 * The new state. The same state then it is ignored, unless
205 * its LOADING_TRACK_GRAPH - in that case the graph is reloaded.
206 */
207 private void setState(int newState) {
208 if (this.state == newState) return; // no need to process request.
209
210 switch(newState) {
211 case READY:
212
213 rewindButton.setEnabled(true);
214 stopButton.setEnabled(false);
215 playPauseButton.setEnabled(true);
216 playPauseButton.setIcon(IconRepository.getIcon("play.png"));
217
218 setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
219
220 break;
221
222 case PLAYBACK_LOADING:
223 stopButton.setEnabled(false);
224 rewindButton.setEnabled(false);
225 playPauseButton.setEnabled(false);
226 break;
227
228 case PLAYING:
229 stopButton.setEnabled(true);
230 rewindButton.setEnabled(false);
231 playPauseButton.setEnabled(true);
232 playPauseButton.setIcon(IconRepository.getIcon("pause.png"));
233
234 setWidgetEdgeThickness(TrackWidgetCommons.PLAYING_TRACK_EDGE_THICKNESS);
235
236 break;
237
238 }
239
240 state = newState;
241
242 updateBorderColor();
243
244 invalidateSelf();
245 FrameGraphics.refresh(true);
246 }
247
248 /**
249 * {@inheritDoc}
250 */
251 @Override
252 protected void onParentStateChanged(int eventType) {
253 super.onParentStateChanged(eventType);
254
255
256 //Frame currentFrame = DisplayIO.getCurrentFrame();
257 // String currentFrameName = (currentFrame != null) ? currentFrame.getName() : null;
258
259 switch (eventType) {
260
261 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
262 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
263 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
264 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
265
266 // Setup observers
267 SoundDesk.getInstance().addObserver(this); // for solo
268 MultiTrackPlaybackController.getInstance().addObserver(this); // the core!
269 masterMix.addObserver(this);
270
271 // Evaluate the state of this add set the state accordingly
272 if (currentPlayingFrame != null
273 && MultiTrackPlaybackController.getInstance().isLoading(
274 currentPlayingFrame,
275 masterMix.getChannelID())) {
276
277 // Ensure that am receiving notifiactions:
278 List<String> loaded = MultiTrackPlaybackController.getInstance().attachLoadListener(this);
279 assert(loaded != null);
280
281 setState(PLAYBACK_LOADING);
282
283 } else if (currentPlayingFrame != null
284 && MultiTrackPlaybackController.getInstance().isPlaying(
285 currentPlayingFrame,
286 masterMix.getChannelID())) {
287
288 setState(PLAYING);
289
290 } else {
291 setState(READY);
292 }
293
294 break;
295
296 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
297
298 // Cancel loading of audio
299 if (state == PLAYBACK_LOADING) {
300 assert(currentPlayingFrame != null);
301
302 MultiTrackPlaybackController.getInstance().cancelLoad(
303 currentPlayingFrame,
304 masterMix.getChannelID());
305 }
306
307 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY: // TODO revise - and in sampled track widget
308 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
309
310 // Remove observers
311 SoundDesk.getInstance().removeObserver(this);
312 MultiTrackPlaybackController.getInstance().removeObserver(this);
313 masterMix.removeObserver(this);
314
315 setState(READY);
316 break;
317
318 }
319 }
320
321
322
323 /**
324 * {@inheritDoc}
325 */
326 public void multiplaybackLoadStatusUpdate(int id, Object state) {
327
328 // NOTE: Could switch to an unloaded state
329 if (this.state != PLAYBACK_LOADING) return;
330
331 switch(id) {
332 case MultitrackLoadListener.LOAD_CANCELLED:
333 setState(READY);
334 break;
335 case MultitrackLoadListener.LOAD_COMPLETE:
336 break;
337 case MultitrackLoadListener.LOAD_FAILED_BAD_GRAPH:
338 abortMessage = "Graph contains loops";
339 ((Exception)state).printStackTrace();
340 break;
341 case MultitrackLoadListener.LOAD_FAILED_GENERIC:
342 abortMessage = "Unexpected error";
343 ((Exception)state).printStackTrace();
344 break;
345 case MultitrackLoadListener.LOAD_FAILED_PLAYBACK:
346 abortMessage = "Graph contains loops";
347 break;
348 case MultitrackLoadListener.NOTHING_TO_PLAY:
349 abortMessage = "Nothing to play"; // could be due to user slecting empty space
350 break;
351 case MultitrackLoadListener.TRACK_LOAD_FAILED_IO:
352 // This is special... the loader does not abort... and it tries to load more.
353 ((Exception)state).printStackTrace();
354 break;
355 case MultitrackLoadListener.TRACK_LOADED:
356 break;
357
358 }
359
360 if (abortMessage != null) {
361 AudioSystemLog.println("Aborted playback - " + abortMessage);
362 setState(READY);
363 }
364
365 }
366
367 /**
368 * {@inheritDoc}
369 */
370 public Subject getObservedSubject() {
371 return null;
372 }
373
374 /**
375 * {@inheritDoc}
376 */
377 public void modelChanged(Subject source, SubjectChangedEvent event) {
378
379 // Synch GUI with track state
380 switch (event.getID()) {
381
382 case ApolloSubjectChangedEvent.PLAYBACK_STARTED:
383
384 if (currentPlayingFrame != null &&
385 MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
386 currentPlayingFrame, masterMix.getChannelID())) {
387 setState(PLAYING);
388 }
389
390 break;
391
392 case ApolloSubjectChangedEvent.PLAYBACK_STOPPED:
393
394 if (state == PLAYING) {
395 //assert(currentPlayingFrame != null);
396
397 // Transition into new state
398 setState(READY);
399
400 }
401 break;
402
403 case ApolloSubjectChangedEvent.MULTIPLAYBACK_LOADING:
404
405 // Adjust state accordingly
406 if (currentPlayingFrame != null &&
407 MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
408 currentPlayingFrame, masterMix.getChannelID()) && state != PLAYBACK_LOADING) {
409 MultiTrackPlaybackController.getInstance().attachLoadListener(this);
410 setState(PLAYBACK_LOADING);
411 }
412 break;
413
414 case ApolloSubjectChangedEvent.VOLUME: // From obseved track mix
415 updateVolume();
416 break;
417
418 case ApolloSubjectChangedEvent.MUTE: // From obseved track mix
419 updateMute();
420 updateBorderColor();
421 break;
422
423 case ApolloSubjectChangedEvent.SOLO_PREFIX_CHANGED: // From mix desk
424 updateSolo();
425 updateBorderColor();
426 break;
427
428
429 }
430
431 }
432
433 /**
434 * {@inheritDoc}
435 */
436 public void setObservedSubject(Subject parent) {
437 }
438
439 /**
440 * {@inheritDoc}
441 */
442 @Override
443 protected String[] getArgs() {
444 return null;
445 }
446
447 /**
448 * {@inheritDoc}
449 */
450 public void actionPerformed(ActionEvent e) {
451
452 if (!(state == PLAYING || state == READY)) return; // safety
453
454 Frame currentFrame = DisplayIO.getCurrentFrame();
455 String currentFrameName = (currentFrame != null) ? currentFrame.getName() : null;
456
457 if (e.getSource() == playPauseButton) {
458
459 if (state == READY && currentFrameName != null) { // comence playback
460
461 int startFrame = -1, endFrame = -1;
462
463 // Resume playback?
464 if (currentPlayingFrame != null &&
465 MultiTrackPlaybackController.getInstance().isMarkedAsPaused(
466 currentPlayingFrame, masterMix.getChannelID())) {
467
468 long runningTime = FrameLayoutDaemon.inferCurrentTotalMSTime();
469 long inferredTotalFrameLength = -1;
470 if (runningTime > 0) {
471 inferredTotalFrameLength = AudioMath.millisecondsToFrames(
472 runningTime,
473 SampledAudioManager.getInstance().getDefaultPlaybackFormat());
474 }
475
476
477 startFrame = MultiTrackPlaybackController.getInstance().getLastSuspendedFrame();
478
479 if (inferredTotalFrameLength > 0 &&
480 startFrame >= 0 && startFrame < inferredTotalFrameLength) {
481 // Work around for playing to end of frame: give biggest value
482 // since it is eventually clamped:
483 endFrame = Integer.MAX_VALUE;
484 }
485 }
486
487 // Play from beginning of selection to end of selection
488 if (startFrame < 0) {
489 startFrame = 0;
490 endFrame = Integer.MAX_VALUE; // see notes about for workaround hack
491 }
492
493 if (startFrame < endFrame) {
494
495 playFrame(this,
496 currentFrameName,
497 false, // TODO: Set appropriatly
498 0,
499 startFrame,
500 endFrame);
501
502 setState(PLAYBACK_LOADING);
503
504 }
505
506 } else { // pause
507
508 MultiTrackPlaybackController.getInstance().setPauseMark(true);
509 MultiTrackPlaybackController.getInstance().stopPlayback();
510
511 }
512
513
514 } else if (e.getSource() == stopButton) {
515 assert(currentPlayingFrame != null);
516
517 if (MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
518 currentPlayingFrame, masterMix.getChannelID())) {
519 MultiTrackPlaybackController.getInstance().setPauseMark(false);
520 MultiTrackPlaybackController.getInstance().stopPlayback();
521 }
522
523
524 } else if (e.getSource() == rewindButton) {
525 assert(state != PLAYING);
526 MultiTrackPlaybackController.getInstance().setPauseMark(false);
527
528 }
529
530 }
531
532
533 private void updateBorderColor() {
534
535 // Get border color currently used
536 Color oldC = getSource().getBorderColor();
537 Color newC = null;
538
539 if (this.state == PLAYBACK_LOADING) {
540
541 newC = LOADING_BORDER_COLOR;
542
543 } else {
544
545 newC = TrackWidgetCommons.getBorderColor(
546 SoundDesk.getInstance().isSolo(masterMix.getChannelID()),
547 masterMix.isMuted());
548
549 }
550
551 // Update the color
552 if (!newC.equals(oldC)) {
553 setWidgetEdgeColor(newC);
554 }
555 }
556
557 /**
558 * Sets the mute icon to represent the current volume value in the slider.
559 * Note: this is not the icon if mute is on.
560 */
561 private void updateButtonGUI() {
562
563 Icon newIcon = null;
564 if (volumeSlider.getValue() <= 25)
565 newIcon = IconRepository.getIcon("vol25.png");
566 else if (volumeSlider.getValue() <= 50)
567 newIcon = IconRepository.getIcon("vol50.png");
568 else if (volumeSlider.getValue() <= 75)
569 newIcon = IconRepository.getIcon("vol75.png");
570 else // maxing
571 newIcon = IconRepository.getIcon("vol100.png");
572
573 muteButton.setIcon(newIcon);
574 }
575
576
577 public void volumeChanged() {
578 masterMix.setVolume(((float)volumeSlider.getValue()) / 100.0f);
579 }
580
581 public void muteChanged() {
582 masterMix.setMuted(muteButton.isSelected());
583 }
584
585 public void soloChanged() {
586 SoundDesk.getInstance().setSoloIDPrefix(soloButton.isSelected() ?
587 masterMix.getChannelID() : null
588 );
589 }
590
591
592 /**
593 * Updates the volume GUI for all views
594 */
595 public void updateVolume() {
596 int volume = (int)(100 * masterMix.getVolume());
597
598 if (volumeSlider.getValue() == volume) return;
599
600 isUpdatingGUI = true;
601
602 volumeSlider.setValue(volume);
603
604 isUpdatingGUI = false;
605 }
606
607
608 /**
609 * Updates the mute button GUI for all views.
610 */
611 public void updateMute() {
612 if (muteButton.isSelected() == masterMix.isMuted()) return;
613
614 isUpdatingGUI = true;
615
616 muteButton.setSelected(masterMix.isMuted());
617
618 isUpdatingGUI = false;
619 }
620
621
622 /**
623 * Updates the solo button GUI for all views.
624 */
625 public void updateSolo() {
626 boolean isSolo = SoundDesk.getInstance().isSolo(masterMix.getChannelID());
627
628 if (soloButton.isSelected() == isSolo) return;
629
630 isUpdatingGUI = true;
631
632 soloButton.setSelected(isSolo);
633
634 isUpdatingGUI = false;
635
636 }
637
638
639 /**
640 * Plays a frame with the FramePlayer mix.
641 *
642 * @see MultiTrackPlaybackController#playFrame(MultitrackLoadListener, String, String, boolean, int, int, int)
643 *
644 */
645 public static void playFrame(
646 MultitrackLoadListener loadListener,
647 String rootFrameName,
648 boolean resume,
649 int relativeStartFrame,
650 int startFrame,
651 int endFrame) {
652
653 if (loadListener == null) throw new NullPointerException("loadListener");
654 if (rootFrameName == null) throw new NullPointerException("rootFrameName");
655
656
657 currentPlayingFrame = rootFrameName;
658
659 MultiTrackPlaybackController.getInstance().playFrame(
660 loadListener,
661 rootFrameName,
662 masterMix.getChannelID(),
663 resume,
664 relativeStartFrame,
665 startFrame,
666 endFrame);
667 }
668
669}
Note: See TracBrowser for help on using the repository browser.