24.7. (Optional) Case Study: Clock with Audio |
The example creates an applet that displays a running clock and announces the time at one-minute intervals. For example, if the current time is 6:30:00, the applet announces, "six o'clock thirty minutes A.M ." If the current time is 20:20:00, the applet announces, "eight o'clock twenty minutes P.M ." Also add a label to display the digital time, as shown in Figure 24.7.
To announce the time, the applet plays three audio clips . The first clip announces the hour , the second announces the minute, and the third announces AM or PM. All of the audio files are stored in the directory audio , a subdirectory of the applet's class directory. The twelve audio files that are used to announce the hours are stored in the files hour0.au , hour1.au , and so on, to hour11.au . The sixty audio files that are used to announce the minutes are stored in the files minute0.au , minute1.au , and so on, to minute59.au . The two audio files that are used to announce AM or PM are stored in the file am.au and pm.au .
You need to play three audio clips on a separate thread to avoid animation delays. To illustrate the problem, let us first write a program without playing the audio on a separate thread.
In §13.12, the StillClock class was developed to draw a still clock to show the current time. Create an applet named ClockWithAudio (Listing 24.4) that contains an instance of StillClock to display an analog clock, and an instance of JLabel to display the digit time. Override the init method to load the audio files. Use a Timer object to set and display the current time continuously at a fixed rate. When the second is zero, announce the current time.
1 import java.applet.*; 2 import javax.swing.*; 3 import java.awt.event.*; 4 import java.awt.*; 5 6 public class ClockWithAudio extends JApplet { 7 protected AudioClip[] hourAudio = new AudioClip[ 12 ]; 8 protected AudioClip[] minuteAudio = new AudioClip[ 60 ]; 9 10 // Create audio clips for pronouncing am and pm 11 protected AudioClip amAudio = 12 Applet.newAudioClip( this .getClass().getResource( "audio/am.au" )); 13 protected AudioClip pmAudio = 14 Applet.newAudioClip( this .getClass().getResource( " audio/pm.au " )); 15 16 // Create a clock 17 private StillClock clock = new StillClock(); 18 19 // Create a timer 20 private Timer timer = new Timer( 1000 , new TimerListener()); 21 22 // Create a label to display time 23 private JLabel jlblDigitTime = new JLabel( "" , JLabel.CENTER); 24 25 /** Initialize the applet */ 26 public void init() { 27 // Create audio clips for pronouncing hours 28 for ( int i = ; i < 12 ; i++) 29 hourAudio[i] = Applet.newAudioClip( 30 this .getClass().getResource( "audio/hour" + i + ".au" )); 31 32 // Create audio clips for pronouncing minutes 33 for ( int i = ; i < 60 ; i++) 34 minuteAudio[i] = Applet.newAudioClip( 35 this .getClass().getResource( "audio/minute" + i + ".au" )); 36 37 // Add clock and time label to the content pane of the applet 38 add(clock, BorderLayout.CENTER); 39 add(jlblDigitTime, BorderLayout.SOUTH); 40 } 41 42 /** Override the applet's start method */ 43 public void start() { 44 timer.start(); // Resume clock 45 } 46 47 /** Override the applet's stop method */ 48 public void stop() { 49 timer.stop(); // Suspend clock 50 } 51 52 private class TimerListener implements ActionListener { 53 public void actionPerformed(ActionEvent e) { 54 clock.setCurrentTime(); 55 clock.repaint(); 56 jlblDigitTime.setText(clock.getHour() + ":" + clock.getMinute() 57 + ":" + clock.getSecond()); 58 if (clock.getSecond() == ) 59 announceTime(clock.getHour(), clock.getMinute()); 60 } 61 } 62 63 /** Announce the current time at every minute */ 64 public void announceTime( int hour, int minute) { 65 // Announce hour 66 hourAudio[hour % 12 ].play(); 67 68 try { 69 // Time delay to allow hourAudio play to finish 70 Thread.sleep( 1500 ); 71 72 // Announce minute 73 minuteAudio[minute].play(); 74 75 // Time delay to allow minuteAudio play to finish 76 Thread.sleep( 1500 ); 77 } 78 catch (InterruptedException ex) { 79 } 80 81 // Announce am or pm 82 if (hour < 12 ) 83 amAudio.play(); 84 else 85 pmAudio.play(); 86 } 87 } |
The hourAudio is an array of twelve audio clips that are used to announce the twelve hours of the day (line 7); the minuteAudio is an audio clip that is used to announce the minutes in an hour (line 8). The amAudio announces A.M . (line 11); the pmAudio announces P.M . (line 13).
The init() method creates hour audio clips (lines 29 “30) and minute audio clips (lines 34 “35), and places a clock and a label in the applet (lines 38 “39).
An ActionEvent is fired by the timer every second. In the listener's actionPerformed method (lines 53 “60), the clock is repainted with the new current time, and the digital time is displayed in the label.
In the announceTime method (lines 64 “86), the sleep() method (lines 70, 76) is purposely invoked to ensure that one clip finishes before the next clip starts, so that the clips do not interfere with each other.
The applet's start() and stop() methods (lines 43 “50) are overridden to ensure that the timer starts or stops when the applet is restarted or stopped .
When you run the preceding program, you will notice that the second hand does not display at the first, second, and third seconds of the minute. This is because sleep(1500) is invoked twice in the announceTime() method, which takes three seconds to announce the time at the beginning of each minute. Thus, the next action event is delayed for three seconds during the first three seconds of each minute. As a result of this delay, the time is not updated and the clock was not repainted for these three seconds. To fix this problem, you should announce the time on a separate thread. This can be accomplished by modifying the announceTime method. Listing 24.5 gives the new program.
1 // same import statements as in Listing 24.4, so omitted 2 3 public class ClockWithAudioOnSeparateThread extends JApplet { 4 // same as in lines 7-61, so omitted 5 6 /** Announce the current time at every minute */ 7 public void announceTime( int h, int m) { 8 new Thread( new AnnounceTimeOnSeparateThread(h, m)).start(); 9 } 10 11 /** Inner class for announcing time */ 12 class AnnounceTimeOnSeparateThread implements Runnable { 13 private int hour, minute; 14 15 /** Get Audio clips */ 16 public AnnounceTimeOnSeparateThread( int hour, int minute) { 17 this .hour = hour; 18 this .minute = minute; 19 } 20 21 public void run() { 22 // Announce hour 23 hourAudio[hour % 12 ].play(); 24 25 try { 26 // Time delay to allow hourAudio play to finish 27 Thread.sleep( 1500 ); 28 29 // Announce minute 30 minuteAudio[minute].play(); 31 32 // Time delay to allow minuteAudio play to finish 33 Thread.sleep( 1500 ); 34 } 35 catch (InterruptedException ex) { 36 } 37 38 // Announce am or pm 39 if (hour < 12 ) 40 amAudio.play(); 41 else 42 pmAudio.play(); 43 } 44 } 45 } |
The new class ClockWithAudioOnSeparateThread is the same as ClockWithAudio except that the announceTime method is new. The new announceTime method creates a thread (line 8) for the task of announcing time. The task class is declared an inner class (lines 12 “44). The run method (line 21) announces the time on a separate thread.
When running this program, you will discover that the audio does not interfere with the clock animation because an instance of AnnounceTimeOnSeparateThread starts on a separate thread to announce the current time. This thread is independent of the thread on which the actionPerformed method runs.