1 | package org.apollo.widgets;
|
---|
2 |
|
---|
3 | import java.awt.Dimension;
|
---|
4 | import java.awt.GridBagConstraints;
|
---|
5 | import java.awt.GridBagLayout;
|
---|
6 | import java.awt.Insets;
|
---|
7 | import java.awt.event.ActionEvent;
|
---|
8 | import java.awt.event.ActionListener;
|
---|
9 | import java.io.BufferedReader;
|
---|
10 | import java.io.File;
|
---|
11 | import java.io.FileReader;
|
---|
12 | import java.io.FileWriter;
|
---|
13 | import java.io.IOException;
|
---|
14 | import java.util.Deque;
|
---|
15 | import java.util.HashSet;
|
---|
16 | import java.util.LinkedList;
|
---|
17 | import java.util.List;
|
---|
18 | import java.util.Set;
|
---|
19 |
|
---|
20 | import javax.swing.ComboBoxModel;
|
---|
21 | import javax.swing.DefaultComboBoxModel;
|
---|
22 | import javax.swing.JButton;
|
---|
23 | import javax.swing.JComboBox;
|
---|
24 | import javax.swing.JPanel;
|
---|
25 | import javax.swing.JSlider;
|
---|
26 | import javax.swing.JToggleButton;
|
---|
27 | import javax.swing.event.ChangeEvent;
|
---|
28 | import javax.swing.event.ChangeListener;
|
---|
29 |
|
---|
30 | import org.apollo.audio.ApolloSubjectChangedEvent;
|
---|
31 | import org.apollo.audio.SampledAudioManager;
|
---|
32 | import org.apollo.audio.util.MultiTrackPlaybackController;
|
---|
33 | import org.apollo.audio.util.SoundDesk;
|
---|
34 | import org.apollo.audio.util.TrackMixSubject;
|
---|
35 | import org.apollo.audio.util.MultiTrackPlaybackController.MultitrackLoadListener;
|
---|
36 | import org.apollo.gui.FrameLayoutDaemon;
|
---|
37 | import org.apollo.io.AudioPathManager;
|
---|
38 | import org.apollo.io.IconRepository;
|
---|
39 | import org.apollo.items.FramePlaybackLauncher;
|
---|
40 | import org.apollo.mvc.Observer;
|
---|
41 | import org.apollo.mvc.Subject;
|
---|
42 | import org.apollo.mvc.SubjectChangedEvent;
|
---|
43 | import org.apollo.util.ApolloSystemLog;
|
---|
44 | import org.apollo.util.AudioMath;
|
---|
45 | import org.expeditee.core.Colour;
|
---|
46 | import org.expeditee.core.Image;
|
---|
47 | import org.expeditee.gio.gesture.StandardGestureActions;
|
---|
48 | import org.expeditee.gio.swing.SwingMiscManager;
|
---|
49 | import org.expeditee.gui.DisplayController;
|
---|
50 | import org.expeditee.gui.DisplayObserver;
|
---|
51 | import org.expeditee.gui.FrameIO;
|
---|
52 | import org.expeditee.gui.MessageBay;
|
---|
53 | import org.expeditee.items.ItemParentStateChangedEvent;
|
---|
54 | import org.expeditee.items.Text;
|
---|
55 | import org.expeditee.items.widgets.SwingWidget;
|
---|
56 |
|
---|
57 | /**
|
---|
58 | * This a last minute hack ... should be revised
|
---|
59 | *
|
---|
60 | * @author Brook Novak
|
---|
61 | *
|
---|
62 | */
|
---|
63 | public class FramePlayer extends SwingWidget
|
---|
64 | implements Observer, MultitrackLoadListener, ActionListener, DisplayObserver {
|
---|
65 |
|
---|
66 | private static final int BUTTON_SIZE = 40;
|
---|
67 |
|
---|
68 | private static TrackMixSubject masterMix = null;
|
---|
69 | private static String currentPlayingFrame = null;
|
---|
70 |
|
---|
71 | private JButton playPauseButton;
|
---|
72 | private JButton stopButton;
|
---|
73 | private JButton rewindButton;
|
---|
74 | private JButton playLauncherButton;
|
---|
75 | private JComboBox<String> frameSelection;
|
---|
76 | private JToggleButton muteButton;
|
---|
77 | private JSlider volumeSlider;
|
---|
78 |
|
---|
79 | private int state = READY;
|
---|
80 | private String abortMessage = null;
|
---|
81 |
|
---|
82 | private boolean isUpdatingGUI = false;
|
---|
83 |
|
---|
84 | private static final String CURRENT_FRAME_SPECIFIER = "Current Frame";
|
---|
85 |
|
---|
86 | public static String FRAME_PLAYERMASTER_CHANNEL_ID = "#$frameplayer#master$";
|
---|
87 |
|
---|
88 | private static final Colour LOADING_BORDER_COLOR = Colour.FromRGB255(22, 205, 5);
|
---|
89 | //private static final Color FAILED_MESSAGE_COLOR = Color.RED;
|
---|
90 | //private static final Font MESSAGE_FONT = TrackWidgetCommons.FREESPACE_TRACKNAME_FONT;
|
---|
91 |
|
---|
92 | /** States - mutex in this widget but not really .. for example since can be loading tracks and the graph at the same time... */
|
---|
93 | private static final int READY = 1; // Waiting for user interaction
|
---|
94 | private static final int PLAYBACK_LOADING = 2; // Loading tracks from file/cache to play/resume
|
---|
95 | private static final int PLAYING = 3; // Playing audio.
|
---|
96 |
|
---|
97 | private static final int TYPED_FRAME_CAPACITY = 10;
|
---|
98 | private static final int FRAME_HISTORY_CAPACITY = 5;
|
---|
99 |
|
---|
100 | private static final String TYPED_FRAMED_HISTORY_FILE = ".typedframes";
|
---|
101 |
|
---|
102 | private static Deque<String> typedFrameNames = new LinkedList<String>();
|
---|
103 | private Set<String> frameComboModelData = new HashSet<String>();
|
---|
104 |
|
---|
105 | static {
|
---|
106 | masterMix = SoundDesk.getInstance().getOrCreateMix(FRAME_PLAYERMASTER_CHANNEL_ID);
|
---|
107 |
|
---|
108 | // Read typed frames
|
---|
109 | File f = new File(AudioPathManager.AUDIO_HOME_DIRECTORY + TYPED_FRAMED_HISTORY_FILE);
|
---|
110 | if (f.exists()) {
|
---|
111 | BufferedReader in = null;
|
---|
112 | String line = null;
|
---|
113 | try {
|
---|
114 |
|
---|
115 | // Open the vbase for reading
|
---|
116 | in = new BufferedReader(new FileReader(f));
|
---|
117 |
|
---|
118 | // Read the sbase file and check all names
|
---|
119 | while ((line = in.readLine()) != null && typedFrameNames.size() < TYPED_FRAME_CAPACITY) {
|
---|
120 | line = line.trim();
|
---|
121 | if (!line.isEmpty() && !typedFrameNames.contains(line))
|
---|
122 | typedFrameNames.add(line);
|
---|
123 | }
|
---|
124 |
|
---|
125 | } catch (Exception e) {
|
---|
126 | e.printStackTrace();
|
---|
127 | // Clean up
|
---|
128 | } finally {
|
---|
129 | if (in != null) {
|
---|
130 | try {
|
---|
131 | in.close();
|
---|
132 | } catch (IOException e) {
|
---|
133 | e.printStackTrace();
|
---|
134 | }
|
---|
135 | }
|
---|
136 | }
|
---|
137 | }
|
---|
138 | }
|
---|
139 |
|
---|
140 | /**
|
---|
141 | * Saves typed frames to file
|
---|
142 | */
|
---|
143 | public static void saveTypedFrames() {
|
---|
144 |
|
---|
145 | FileWriter out = null;
|
---|
146 |
|
---|
147 | try {
|
---|
148 | // Open the vbase for appending
|
---|
149 | out = new FileWriter(AudioPathManager.AUDIO_HOME_DIRECTORY + TYPED_FRAMED_HISTORY_FILE, false);
|
---|
150 | for (String str : typedFrameNames) {
|
---|
151 | out.write(str + "\n");
|
---|
152 | }
|
---|
153 | } catch (IOException e) {
|
---|
154 | e.printStackTrace();
|
---|
155 |
|
---|
156 | // Clean up
|
---|
157 | } finally {
|
---|
158 | if (out != null) {
|
---|
159 | try {
|
---|
160 | out.close();
|
---|
161 | } catch (IOException e) {
|
---|
162 | e.printStackTrace();
|
---|
163 | }
|
---|
164 | }
|
---|
165 | }
|
---|
166 | }
|
---|
167 |
|
---|
168 |
|
---|
169 | /**
|
---|
170 | * Constructor called by Expeditee.
|
---|
171 | *
|
---|
172 | * @param source
|
---|
173 | * @param args
|
---|
174 | */
|
---|
175 | public FramePlayer(Text source, String[] args) {
|
---|
176 | super(source, new JPanel(new GridBagLayout()),
|
---|
177 | BUTTON_SIZE * 12, BUTTON_SIZE * 12,
|
---|
178 | BUTTON_SIZE, BUTTON_SIZE);
|
---|
179 |
|
---|
180 | playPauseButton = new JButton();
|
---|
181 | playPauseButton.addActionListener(this);
|
---|
182 | SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("play.png"));
|
---|
183 | playPauseButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
|
---|
184 | playPauseButton.setToolTipText("Play selection / Pause");
|
---|
185 |
|
---|
186 | stopButton = new JButton();
|
---|
187 | stopButton.setEnabled(false);
|
---|
188 | stopButton.addActionListener(this);
|
---|
189 | SwingMiscManager.setJButtonIcon(stopButton, IconRepository.getIcon("stop.png"));
|
---|
190 | stopButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
|
---|
191 | stopButton.setToolTipText("Stop playback");
|
---|
192 |
|
---|
193 | rewindButton = new JButton();
|
---|
194 | rewindButton.addActionListener(this);
|
---|
195 | SwingMiscManager.setJButtonIcon(rewindButton, IconRepository.getIcon("rewind.png"));
|
---|
196 | rewindButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
|
---|
197 | rewindButton.setToolTipText("Rewind to start");
|
---|
198 |
|
---|
199 | // Icon changes
|
---|
200 | muteButton = new JToggleButton();
|
---|
201 | SwingMiscManager.setJButtonIcon(muteButton, IconRepository.getIcon("volmute.png"));
|
---|
202 | muteButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
|
---|
203 | muteButton.setToolTipText("Toggle mute");
|
---|
204 | muteButton.addChangeListener(new ChangeListener() {
|
---|
205 | public void stateChanged(ChangeEvent e) {
|
---|
206 | if (!FramePlayer.this.isUpdatingGUI) {
|
---|
207 | muteChanged();
|
---|
208 | }
|
---|
209 | }
|
---|
210 | });
|
---|
211 |
|
---|
212 | playLauncherButton = new JButton();
|
---|
213 | playLauncherButton.addActionListener(this);
|
---|
214 | SwingMiscManager.setJButtonIcon(playLauncherButton, IconRepository.getIcon("frameplay.png"));
|
---|
215 | playLauncherButton.setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
|
---|
216 | playLauncherButton.setToolTipText("Play from a specific position");
|
---|
217 |
|
---|
218 | final int VOLUME_SPACING = 6;
|
---|
219 | volumeSlider = new JSlider(JSlider.HORIZONTAL);
|
---|
220 | volumeSlider.setMinimum(0);
|
---|
221 | volumeSlider.setMaximum(100);
|
---|
222 | volumeSlider.addChangeListener(new ChangeListener() {
|
---|
223 | public void stateChanged(ChangeEvent e) {
|
---|
224 | if (!FramePlayer.this.isUpdatingGUI) {
|
---|
225 | volumeChanged();
|
---|
226 | }
|
---|
227 | // Update the icons
|
---|
228 | updateButtonGUI();
|
---|
229 | }
|
---|
230 | });
|
---|
231 | volumeSlider.setPreferredSize(new Dimension((3 * BUTTON_SIZE) - (2 * VOLUME_SPACING), BUTTON_SIZE));
|
---|
232 |
|
---|
233 | frameSelection = new JComboBox<String>();
|
---|
234 | frameSelection.setEditable(true);
|
---|
235 | frameSelection.setPreferredSize(new Dimension(4 * BUTTON_SIZE, BUTTON_SIZE));
|
---|
236 |
|
---|
237 | GridBagConstraints c = new GridBagConstraints();
|
---|
238 | c.gridx = 0;
|
---|
239 | c.gridy = 0;
|
---|
240 | c.fill = GridBagConstraints.BOTH;
|
---|
241 | _swingComponent.add(playPauseButton, c);
|
---|
242 |
|
---|
243 | c = new GridBagConstraints();
|
---|
244 | c.gridx = 1;
|
---|
245 | c.gridy = 0;
|
---|
246 | c.fill = GridBagConstraints.BOTH;
|
---|
247 | _swingComponent.add(playLauncherButton, c);
|
---|
248 |
|
---|
249 | c = new GridBagConstraints();
|
---|
250 | c.gridx = 2;
|
---|
251 | c.gridy = 0;
|
---|
252 | c.fill = GridBagConstraints.BOTH;
|
---|
253 | _swingComponent.add(stopButton, c);
|
---|
254 |
|
---|
255 | c = new GridBagConstraints();
|
---|
256 | c.gridx = 3;
|
---|
257 | c.gridy = 0;
|
---|
258 | c.fill = GridBagConstraints.BOTH;
|
---|
259 | _swingComponent.add(rewindButton, c);
|
---|
260 |
|
---|
261 | c = new GridBagConstraints();
|
---|
262 | c.gridx = 4;
|
---|
263 | c.gridy = 0;
|
---|
264 | c.fill = GridBagConstraints.BOTH;
|
---|
265 | _swingComponent.add(muteButton, c);
|
---|
266 |
|
---|
267 | c = new GridBagConstraints();
|
---|
268 | c.gridx = 5;
|
---|
269 | c.gridy = 0;
|
---|
270 | c.fill = GridBagConstraints.BOTH;
|
---|
271 | c.insets = new Insets(0,VOLUME_SPACING,0,VOLUME_SPACING);
|
---|
272 | _swingComponent.add(volumeSlider, c);
|
---|
273 |
|
---|
274 | c = new GridBagConstraints();
|
---|
275 | c.gridx = 6;
|
---|
276 | c.gridy = 0;
|
---|
277 | c.fill = GridBagConstraints.BOTH;
|
---|
278 | _swingComponent.add(frameSelection, c);
|
---|
279 |
|
---|
280 | updateButtonGUI();
|
---|
281 | updateBorderColor();
|
---|
282 | updateFrameSelection();
|
---|
283 |
|
---|
284 | setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
|
---|
285 |
|
---|
286 | }
|
---|
287 |
|
---|
288 |
|
---|
289 | /**
|
---|
290 | * Sets the GUI and all the popups to reflect the current state.
|
---|
291 | * Must be on the swing thread.
|
---|
292 | *
|
---|
293 | * @param newState
|
---|
294 | * The new state. The same state then it is ignored, unless
|
---|
295 | * its LOADING_TRACK_GRAPH - in that case the graph is reloaded.
|
---|
296 | */
|
---|
297 | private void setState(int newState) {
|
---|
298 | if (this.state == newState) return; // no need to process request.
|
---|
299 |
|
---|
300 | switch(newState) {
|
---|
301 | case READY:
|
---|
302 |
|
---|
303 | rewindButton.setEnabled(true);
|
---|
304 | stopButton.setEnabled(false);
|
---|
305 | playPauseButton.setEnabled(true);
|
---|
306 | SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("play.png"));
|
---|
307 | playLauncherButton.setEnabled(true);
|
---|
308 |
|
---|
309 | setWidgetEdgeThickness(TrackWidgetCommons.STOPPED_TRACK_EDGE_THICKNESS);
|
---|
310 |
|
---|
311 | break;
|
---|
312 |
|
---|
313 | case PLAYBACK_LOADING:
|
---|
314 | playLauncherButton.setEnabled(false);
|
---|
315 | stopButton.setEnabled(false);
|
---|
316 | rewindButton.setEnabled(false);
|
---|
317 | playPauseButton.setEnabled(false);
|
---|
318 | break;
|
---|
319 |
|
---|
320 | case PLAYING:
|
---|
321 | stopButton.setEnabled(true);
|
---|
322 | rewindButton.setEnabled(false);
|
---|
323 | playLauncherButton.setEnabled(true);
|
---|
324 | playPauseButton.setEnabled(true);
|
---|
325 | SwingMiscManager.setJButtonIcon(playPauseButton, IconRepository.getIcon("pause.png"));
|
---|
326 |
|
---|
327 | setWidgetEdgeThickness(TrackWidgetCommons.PLAYING_TRACK_EDGE_THICKNESS);
|
---|
328 |
|
---|
329 | break;
|
---|
330 |
|
---|
331 | }
|
---|
332 |
|
---|
333 | state = newState;
|
---|
334 |
|
---|
335 | updateBorderColor();
|
---|
336 |
|
---|
337 | invalidateSelf();
|
---|
338 | DisplayController.requestRefresh(true);
|
---|
339 | }
|
---|
340 |
|
---|
341 | /**
|
---|
342 | * {@inheritDoc}
|
---|
343 | */
|
---|
344 | @Override
|
---|
345 | protected void onParentStateChanged(int eventType) {
|
---|
346 | super.onParentStateChanged(eventType);
|
---|
347 |
|
---|
348 |
|
---|
349 | //Frame currentFrame = DisplayIO.getCurrentFrame();
|
---|
350 | // String currentFrameName = (currentFrame != null) ? currentFrame.getName() : null;
|
---|
351 |
|
---|
352 | switch (eventType) {
|
---|
353 |
|
---|
354 | case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
|
---|
355 | case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
|
---|
356 | case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
|
---|
357 | case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
|
---|
358 |
|
---|
359 | // Setup observers
|
---|
360 | SoundDesk.getInstance().addObserver(this); // for solo
|
---|
361 | MultiTrackPlaybackController.getInstance().addObserver(this); // the core!
|
---|
362 | masterMix.addObserver(this);
|
---|
363 | DisplayController.addDisplayObserver(this);
|
---|
364 |
|
---|
365 | // Evaluate the state of this add set the state accordingly
|
---|
366 | if (currentPlayingFrame != null
|
---|
367 | && MultiTrackPlaybackController.getInstance().isLoading(
|
---|
368 | currentPlayingFrame,
|
---|
369 | masterMix.getChannelID())) {
|
---|
370 |
|
---|
371 | // Ensure that am receiving notifiactions:
|
---|
372 | List<String> loaded = MultiTrackPlaybackController.getInstance().attachLoadListener(this);
|
---|
373 | assert(loaded != null);
|
---|
374 |
|
---|
375 | setState(PLAYBACK_LOADING);
|
---|
376 |
|
---|
377 | } else if (currentPlayingFrame != null
|
---|
378 | && MultiTrackPlaybackController.getInstance().isPlaying(
|
---|
379 | currentPlayingFrame,
|
---|
380 | masterMix.getChannelID())) {
|
---|
381 |
|
---|
382 | setState(PLAYING);
|
---|
383 |
|
---|
384 | } else {
|
---|
385 | setState(READY);
|
---|
386 | }
|
---|
387 |
|
---|
388 | updateFrameSelection();
|
---|
389 |
|
---|
390 | break;
|
---|
391 |
|
---|
392 | case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
|
---|
393 |
|
---|
394 | // Cancel loading of audio
|
---|
395 | if (state == PLAYBACK_LOADING) {
|
---|
396 | assert(currentPlayingFrame != null);
|
---|
397 |
|
---|
398 | MultiTrackPlaybackController.getInstance().cancelLoad(
|
---|
399 | currentPlayingFrame,
|
---|
400 | masterMix.getChannelID());
|
---|
401 | }
|
---|
402 |
|
---|
403 | case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY: // TODO revise - and in sampled track widget
|
---|
404 | case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
|
---|
405 |
|
---|
406 | // Remove observers
|
---|
407 | DisplayController.removeDisplayObserver(this);
|
---|
408 | SoundDesk.getInstance().removeObserver(this);
|
---|
409 | MultiTrackPlaybackController.getInstance().removeObserver(this);
|
---|
410 | masterMix.removeObserver(this);
|
---|
411 |
|
---|
412 | setState(READY);
|
---|
413 | break;
|
---|
414 |
|
---|
415 | }
|
---|
416 | }
|
---|
417 |
|
---|
418 |
|
---|
419 |
|
---|
420 | /**
|
---|
421 | * {@inheritDoc}
|
---|
422 | */
|
---|
423 | public void multiplaybackLoadStatusUpdate(int id, Object state) {
|
---|
424 |
|
---|
425 | // NOTE: Could switch to an unloaded state
|
---|
426 | if (this.state != PLAYBACK_LOADING) return;
|
---|
427 |
|
---|
428 | switch(id) {
|
---|
429 | case MultitrackLoadListener.LOAD_CANCELLED:
|
---|
430 | setState(READY);
|
---|
431 | break;
|
---|
432 | case MultitrackLoadListener.LOAD_COMPLETE:
|
---|
433 | break;
|
---|
434 | case MultitrackLoadListener.LOAD_FAILED_BAD_GRAPH:
|
---|
435 | abortMessage = "Graph contains loops";
|
---|
436 | ((Exception)state).printStackTrace();
|
---|
437 | break;
|
---|
438 | case MultitrackLoadListener.LOAD_FAILED_GENERIC:
|
---|
439 | abortMessage = "Unexpected error";
|
---|
440 | ((Exception)state).printStackTrace();
|
---|
441 | break;
|
---|
442 | case MultitrackLoadListener.LOAD_FAILED_PLAYBACK:
|
---|
443 | abortMessage = "Unable to aquire sound device";
|
---|
444 | break;
|
---|
445 | case MultitrackLoadListener.NOTHING_TO_PLAY:
|
---|
446 | abortMessage = "Nothing to play"; // could be due to user slecting empty space
|
---|
447 | break;
|
---|
448 | case MultitrackLoadListener.TRACK_LOAD_FAILED_IO:
|
---|
449 | // This is special... the loader does not abort... and it tries to load more.
|
---|
450 | ((Exception)state).printStackTrace();
|
---|
451 | break;
|
---|
452 | case MultitrackLoadListener.TRACK_LOADED:
|
---|
453 | break;
|
---|
454 |
|
---|
455 | }
|
---|
456 |
|
---|
457 | if (abortMessage != null) {
|
---|
458 | ApolloSystemLog.println("Aborted playback - " + abortMessage);
|
---|
459 | setState(READY);
|
---|
460 | }
|
---|
461 |
|
---|
462 | }
|
---|
463 |
|
---|
464 | /**
|
---|
465 | * {@inheritDoc}
|
---|
466 | */
|
---|
467 | public Subject getObservedSubject() {
|
---|
468 | return null;
|
---|
469 | }
|
---|
470 |
|
---|
471 | /**
|
---|
472 | * {@inheritDoc}
|
---|
473 | */
|
---|
474 | public void modelChanged(Subject source, SubjectChangedEvent event) {
|
---|
475 |
|
---|
476 | // Synch GUI with track state
|
---|
477 | switch (event.getID()) {
|
---|
478 |
|
---|
479 | case ApolloSubjectChangedEvent.PLAYBACK_STARTED:
|
---|
480 |
|
---|
481 | if (currentPlayingFrame != null &&
|
---|
482 | MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
|
---|
483 | currentPlayingFrame, masterMix.getChannelID())) {
|
---|
484 | setState(PLAYING);
|
---|
485 | }
|
---|
486 |
|
---|
487 | break;
|
---|
488 |
|
---|
489 | case ApolloSubjectChangedEvent.PLAYBACK_STOPPED:
|
---|
490 |
|
---|
491 | if (state == PLAYING) {
|
---|
492 | //assert(currentPlayingFrame != null);
|
---|
493 |
|
---|
494 | // Transition into new state
|
---|
495 | setState(READY);
|
---|
496 |
|
---|
497 | }
|
---|
498 | break;
|
---|
499 |
|
---|
500 | case ApolloSubjectChangedEvent.MULTIPLAYBACK_LOADING:
|
---|
501 |
|
---|
502 | // Adjust state accordingly
|
---|
503 | if (currentPlayingFrame != null &&
|
---|
504 | MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
|
---|
505 | currentPlayingFrame, masterMix.getChannelID()) && state != PLAYBACK_LOADING) {
|
---|
506 | MultiTrackPlaybackController.getInstance().attachLoadListener(this);
|
---|
507 | setState(PLAYBACK_LOADING);
|
---|
508 | }
|
---|
509 | break;
|
---|
510 |
|
---|
511 | case ApolloSubjectChangedEvent.VOLUME: // From obseved track mix
|
---|
512 | updateVolume();
|
---|
513 | break;
|
---|
514 |
|
---|
515 | case ApolloSubjectChangedEvent.MUTE: // From obseved track mix
|
---|
516 | updateMute();
|
---|
517 | updateBorderColor();
|
---|
518 | break;
|
---|
519 |
|
---|
520 | }
|
---|
521 |
|
---|
522 | }
|
---|
523 |
|
---|
524 | /**
|
---|
525 | * {@inheritDoc}
|
---|
526 | */
|
---|
527 | public void setObservedSubject(Subject parent) {
|
---|
528 | }
|
---|
529 |
|
---|
530 | /**
|
---|
531 | * {@inheritDoc}
|
---|
532 | */
|
---|
533 | @Override
|
---|
534 | protected String[] getArgs() {
|
---|
535 | return null;
|
---|
536 | }
|
---|
537 |
|
---|
538 | /**
|
---|
539 | * @return
|
---|
540 | * The selected frame name. Null if not valid.
|
---|
541 | */
|
---|
542 | private String getSelectedFrameName() {
|
---|
543 |
|
---|
544 | if ((frameSelection.getSelectedItem() != null && frameSelection.getSelectedItem().equals(CURRENT_FRAME_SPECIFIER))
|
---|
545 | || frameSelection.getSelectedItem() == null)
|
---|
546 | return DisplayController.getCurrentFrame().getName();
|
---|
547 |
|
---|
548 | String frameSpecifier = (String)frameSelection.getSelectedItem();
|
---|
549 |
|
---|
550 | if (frameSpecifier.isEmpty()) {
|
---|
551 | return DisplayController.getCurrentFrame().getName();
|
---|
552 | }
|
---|
553 |
|
---|
554 | frameSpecifier = frameSpecifier.trim();
|
---|
555 | int metaIndex = frameSpecifier.indexOf('<');
|
---|
556 | if (metaIndex > 0) {
|
---|
557 | frameSpecifier = frameSpecifier.substring(0, metaIndex).trim();
|
---|
558 | }
|
---|
559 |
|
---|
560 | if (!FrameIO.isValidFrameName(frameSpecifier)) return null;
|
---|
561 |
|
---|
562 | return frameSpecifier;
|
---|
563 | }
|
---|
564 |
|
---|
565 | /**
|
---|
566 | * {@inheritDoc}
|
---|
567 | */
|
---|
568 | public void actionPerformed(ActionEvent e) {
|
---|
569 |
|
---|
570 | if (!(state == PLAYING || state == READY)) return; // safety
|
---|
571 |
|
---|
572 | String selectedFrameName = getSelectedFrameName();
|
---|
573 |
|
---|
574 | if (e.getSource() == playPauseButton) {
|
---|
575 |
|
---|
576 | if (state == READY) { // comence playback
|
---|
577 |
|
---|
578 | if (selectedFrameName == null) {
|
---|
579 |
|
---|
580 | // Bad frame specifier
|
---|
581 | MessageBay.displayMessage("Cannot play frame \"" +
|
---|
582 | frameSelection.getSelectedItem() +
|
---|
583 | "\" - bad name");
|
---|
584 |
|
---|
585 | } else {
|
---|
586 |
|
---|
587 | int startFrame = -1, endFrame = -1;
|
---|
588 |
|
---|
589 | // Remember typed frames
|
---|
590 | if (frameSelection.getSelectedItem() != null && !frameSelection.getSelectedItem().equals(CURRENT_FRAME_SPECIFIER)) {
|
---|
591 |
|
---|
592 | String typedFrame = (String)frameSelection.getSelectedItem();
|
---|
593 |
|
---|
594 | if (!frameComboModelData.contains(typedFrame.toLowerCase())) {
|
---|
595 |
|
---|
596 | typedFrameNames.addFirst(selectedFrameName);
|
---|
597 | if (typedFrameNames.size() >= TYPED_FRAME_CAPACITY)
|
---|
598 | typedFrameNames.removeLast();
|
---|
599 | updateFrameSelection();
|
---|
600 |
|
---|
601 | }
|
---|
602 |
|
---|
603 | }
|
---|
604 |
|
---|
605 | // Resume playback?
|
---|
606 | if (currentPlayingFrame != null &&
|
---|
607 | MultiTrackPlaybackController.getInstance().isMarkedAsPaused(
|
---|
608 | currentPlayingFrame, masterMix.getChannelID())) {
|
---|
609 |
|
---|
610 | long runningTime = FrameLayoutDaemon.inferCurrentTotalMSTime();
|
---|
611 | long inferredTotalFrameLength = -1;
|
---|
612 | if (runningTime > 0) {
|
---|
613 | inferredTotalFrameLength = AudioMath.millisecondsToFrames(
|
---|
614 | runningTime,
|
---|
615 | SampledAudioManager.getInstance().getDefaultPlaybackFormat());
|
---|
616 | }
|
---|
617 |
|
---|
618 |
|
---|
619 | startFrame = MultiTrackPlaybackController.getInstance().getLastSuspendedFrame();
|
---|
620 |
|
---|
621 | if (inferredTotalFrameLength > 0 &&
|
---|
622 | startFrame >= 0 && startFrame < inferredTotalFrameLength) {
|
---|
623 | // Work around for playing to end of frame: give biggest value
|
---|
624 | // since it is eventually clamped:
|
---|
625 | endFrame = Integer.MAX_VALUE;
|
---|
626 | }
|
---|
627 | }
|
---|
628 |
|
---|
629 | // Play from beginning of selection to end of selection
|
---|
630 | if (startFrame < 0) {
|
---|
631 | startFrame = 0;
|
---|
632 | endFrame = Integer.MAX_VALUE; // see notes about for workaround hack
|
---|
633 | }
|
---|
634 |
|
---|
635 | if (startFrame < endFrame) {
|
---|
636 |
|
---|
637 | playFrame(this,
|
---|
638 | selectedFrameName,
|
---|
639 | false, // TODO: Set appropriatly
|
---|
640 | startFrame,
|
---|
641 | endFrame);
|
---|
642 |
|
---|
643 | setState(PLAYBACK_LOADING);
|
---|
644 |
|
---|
645 | }
|
---|
646 | }
|
---|
647 |
|
---|
648 | } else { // pause
|
---|
649 |
|
---|
650 | MultiTrackPlaybackController.getInstance().setPauseMark(true);
|
---|
651 | MultiTrackPlaybackController.getInstance().stopPlayback();
|
---|
652 |
|
---|
653 | }
|
---|
654 |
|
---|
655 |
|
---|
656 | } else if (e.getSource() == stopButton) {
|
---|
657 | assert(currentPlayingFrame != null);
|
---|
658 |
|
---|
659 | if (MultiTrackPlaybackController.getInstance().isCurrentPlaybackSubject(
|
---|
660 | currentPlayingFrame, masterMix.getChannelID())) {
|
---|
661 | MultiTrackPlaybackController.getInstance().setPauseMark(false);
|
---|
662 | MultiTrackPlaybackController.getInstance().stopPlayback();
|
---|
663 | }
|
---|
664 |
|
---|
665 |
|
---|
666 | } else if (e.getSource() == rewindButton) {
|
---|
667 | assert(state != PLAYING);
|
---|
668 | MultiTrackPlaybackController.getInstance().setPauseMark(false);
|
---|
669 |
|
---|
670 | } else if (e.getSource() == playLauncherButton) {
|
---|
671 |
|
---|
672 | String target = getSelectedFrameName();
|
---|
673 | if (target == DisplayController.getCurrentFrame().getName()) target = null;
|
---|
674 |
|
---|
675 | // Create the launcher
|
---|
676 | FramePlaybackLauncher launcher = new FramePlaybackLauncher(target);
|
---|
677 | launcher.setPosition(DisplayController.getMousePosition());
|
---|
678 |
|
---|
679 | // Pick it up
|
---|
680 | StandardGestureActions.pickup(launcher);
|
---|
681 | }
|
---|
682 |
|
---|
683 | }
|
---|
684 |
|
---|
685 |
|
---|
686 | private void updateBorderColor() {
|
---|
687 |
|
---|
688 | // Get border color currently used
|
---|
689 | Colour oldC = getSource().getBorderColor();
|
---|
690 | Colour newC = null;
|
---|
691 |
|
---|
692 | if (this.state == PLAYBACK_LOADING) {
|
---|
693 |
|
---|
694 | newC = LOADING_BORDER_COLOR;
|
---|
695 |
|
---|
696 | } else {
|
---|
697 |
|
---|
698 | newC = TrackWidgetCommons.getBorderColor(
|
---|
699 | SoundDesk.getInstance().isSolo(masterMix.getChannelID()),
|
---|
700 | masterMix.isMuted());
|
---|
701 |
|
---|
702 | }
|
---|
703 |
|
---|
704 | // Update the color
|
---|
705 | if (!newC.equals(oldC)) {
|
---|
706 | setWidgetEdgeColor(newC);
|
---|
707 | }
|
---|
708 | }
|
---|
709 |
|
---|
710 | /**
|
---|
711 | * Sets the mute icon to represent the current volume value in the slider.
|
---|
712 | * Note: this is not the icon if mute is on.
|
---|
713 | */
|
---|
714 | private void updateButtonGUI() {
|
---|
715 |
|
---|
716 | Image newIcon = null;
|
---|
717 | if (volumeSlider.getValue() <= 25)
|
---|
718 | newIcon = IconRepository.getIcon("vol25.png");
|
---|
719 | else if (volumeSlider.getValue() <= 50)
|
---|
720 | newIcon = IconRepository.getIcon("vol50.png");
|
---|
721 | else if (volumeSlider.getValue() <= 75)
|
---|
722 | newIcon = IconRepository.getIcon("vol75.png");
|
---|
723 | else // maxing
|
---|
724 | newIcon = IconRepository.getIcon("vol100.png");
|
---|
725 |
|
---|
726 | SwingMiscManager.setJButtonIcon(muteButton, newIcon);
|
---|
727 | }
|
---|
728 |
|
---|
729 |
|
---|
730 | public void volumeChanged() {
|
---|
731 | masterMix.setVolume(((float)volumeSlider.getValue()) / 100.0f);
|
---|
732 | }
|
---|
733 |
|
---|
734 | public void muteChanged() {
|
---|
735 | masterMix.setMuted(muteButton.isSelected());
|
---|
736 | }
|
---|
737 |
|
---|
738 |
|
---|
739 |
|
---|
740 | /**
|
---|
741 | * Updates the volume GUI for all views
|
---|
742 | */
|
---|
743 | public void updateVolume() {
|
---|
744 | int volume = (int)(100 * masterMix.getVolume());
|
---|
745 |
|
---|
746 | if (volumeSlider.getValue() == volume) return;
|
---|
747 |
|
---|
748 | isUpdatingGUI = true;
|
---|
749 |
|
---|
750 | volumeSlider.setValue(volume);
|
---|
751 |
|
---|
752 | isUpdatingGUI = false;
|
---|
753 | }
|
---|
754 |
|
---|
755 |
|
---|
756 | /**
|
---|
757 | * Updates the mute button GUI for all views.
|
---|
758 | */
|
---|
759 | public void updateMute() {
|
---|
760 | if (muteButton.isSelected() == masterMix.isMuted()) return;
|
---|
761 |
|
---|
762 | isUpdatingGUI = true;
|
---|
763 |
|
---|
764 | muteButton.setSelected(masterMix.isMuted());
|
---|
765 |
|
---|
766 | isUpdatingGUI = false;
|
---|
767 | }
|
---|
768 |
|
---|
769 | /**
|
---|
770 | * Updates the combo box on frame changes
|
---|
771 | */
|
---|
772 | public void frameChanged() {
|
---|
773 | updateFrameSelection();
|
---|
774 | }
|
---|
775 |
|
---|
776 | private void updateFrameSelection() {
|
---|
777 |
|
---|
778 |
|
---|
779 | frameComboModelData.clear();
|
---|
780 | List<String> orderedModelData = new LinkedList<String>();
|
---|
781 |
|
---|
782 | orderedModelData.add(CURRENT_FRAME_SPECIFIER);
|
---|
783 |
|
---|
784 | // Place last visited frames
|
---|
785 | List<String> history = DisplayController.getUnmodifiableVisitedList();
|
---|
786 |
|
---|
787 | for (int i = history.size() - 1; i >= 0; i--) {
|
---|
788 | String str = history.get(i);
|
---|
789 |
|
---|
790 | if ((history.size() - i) >= FRAME_HISTORY_CAPACITY) break;
|
---|
791 |
|
---|
792 | String frameName = str;
|
---|
793 | for (int j = 0; j <= (history.size() - i); j++) frameName += "<";
|
---|
794 |
|
---|
795 | orderedModelData.add(frameName);
|
---|
796 | }
|
---|
797 |
|
---|
798 | // Append last typed frames
|
---|
799 | for (String str : typedFrameNames) {
|
---|
800 | if (!history.contains(str))
|
---|
801 | orderedModelData.add(str);
|
---|
802 | }
|
---|
803 |
|
---|
804 | ComboBoxModel<String> model = new DefaultComboBoxModel<String>((String[]) orderedModelData.toArray());
|
---|
805 |
|
---|
806 | // Remove helper tags from model data
|
---|
807 |
|
---|
808 | frameComboModelData = new HashSet<String>();
|
---|
809 | for (String str : orderedModelData) {
|
---|
810 | int index = str.indexOf('<');
|
---|
811 | assert(index != 0);
|
---|
812 | if (index > 1) {
|
---|
813 | frameComboModelData.add(str.substring(0, index).trim().toLowerCase());
|
---|
814 | } else {
|
---|
815 | frameComboModelData.add(str.trim().toLowerCase());
|
---|
816 | }
|
---|
817 | }
|
---|
818 |
|
---|
819 | Object prevSelected = frameSelection.getSelectedItem();
|
---|
820 |
|
---|
821 | frameSelection.setModel(model);
|
---|
822 |
|
---|
823 | if (prevSelected == null)
|
---|
824 | frameSelection.setSelectedIndex(0);
|
---|
825 | else
|
---|
826 | frameSelection.setSelectedItem(prevSelected);
|
---|
827 |
|
---|
828 |
|
---|
829 |
|
---|
830 | }
|
---|
831 |
|
---|
832 | /**
|
---|
833 | * Plays a frame with the FramePlayer mix.
|
---|
834 | *
|
---|
835 | * @see MultiTrackPlaybackController#playFrame(MultitrackLoadListener, String, String, boolean, int, int, int)
|
---|
836 | *
|
---|
837 | */
|
---|
838 | public static void playFrame(
|
---|
839 | MultitrackLoadListener loadListener,
|
---|
840 | String rootFrameName,
|
---|
841 | boolean resume,
|
---|
842 | int startFrame,
|
---|
843 | int endFrame) {
|
---|
844 |
|
---|
845 | if (loadListener == null) throw new NullPointerException("loadListener");
|
---|
846 | if (rootFrameName == null) throw new NullPointerException("rootFrameName");
|
---|
847 |
|
---|
848 |
|
---|
849 | currentPlayingFrame = rootFrameName;
|
---|
850 |
|
---|
851 | MultiTrackPlaybackController.getInstance().playFrame(
|
---|
852 | loadListener,
|
---|
853 | rootFrameName,
|
---|
854 | masterMix.getChannelID(),
|
---|
855 | resume,
|
---|
856 | startFrame,
|
---|
857 | endFrame);
|
---|
858 | }
|
---|
859 |
|
---|
860 | }
|
---|