/********************************************************************************** * $URL$ * $Id$ *********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ /* AudioRecorder.java * Originally based on code from CapturePlayback.java * @(#)CapturePlayback.java 1.11 99/12/03 * * portions Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved. * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear * facility. Licensee represents and warrants that it will not use or * redistribute the Software for such purposes. */ package org.sakaiproject.tool.assessment.audio; import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Line2D; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.text.DecimalFormat; import java.util.ResourceBundle; import java.util.Vector; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SpringLayout; import javax.swing.SwingConstants; import javax.swing.Timer; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.SoftBevelBorder; import netscape.javascript.*; /** * Record audio in different formats and then playback the recorded audio. The * captured audio can be saved either as a WAVE, AU or AIFF. Or load an audio * file for streaming playback. * * @version * @(#)CapturePlayback.java 1.11 99/12/03 * @author Brian Lichtenwalter * @author Ed Smiley numerous modifications */ public class AudioRecorder extends JPanel implements ActionListener, AudioControlContext { private static final long serialVersionUID = 0L; static ResourceBundle res; final static int bufSize = 16384; FormatControls formatControls = null; Capture capture = new Capture(); Playback playback = new Playback(); AudioInputStream audioInputStream; SamplingGraph samplingGraph; AudioMeter audioMeter; Timer timer; JButton playB, captB, stopB, saveButton; JTextField textField; DecimalFormat NoDecimalPlaces = new DecimalFormat("#0"); JTextField rtextField; JLabel finishedLabel; JPanel samplingPanelContainer; JLabel statusLabel = new JLabel("", SwingConstants.LEFT); String fileName; String agentId; String errStr; String mediaId; double duration, seconds; int attempts; File file; Vector<Line2D> lines = new Vector<Line2D>(); AudioRecorderParams params; String imageUrl; ImageIcon recordIcon = null; ImageIcon playIcon = null; ImageIcon stopIcon = null; Applet containingApplet = null; String lastButtonClicked = ""; // record or play final String RECORD = "record"; final String PLAY = "play"; public AudioRecorder(AudioRecorderParams params) { res = AudioUtil.getInstance().getResourceBundle(); fileName = res.getString("default_file_name"); this.params = params; if (params.getAttemptsRemaining() == -1) { params.setAttemptsRemaining(params.getAttemptsAllowed()); } // load images imageUrl = params.getImageUrl(); // System.out.println("**** imageUrl="+imageUrl); try { recordIcon = new ImageIcon(new URL(imageUrl + "/audio_record.gif")); playIcon = new ImageIcon(new URL(imageUrl + "/audio_play.gif")); stopIcon = new ImageIcon(new URL(imageUrl + "/audio_stop.gif")); } catch (Exception ex) { reportStatus("**** cannot create image icons for applet:" + ex.toString()); } EmptyBorder eb = new EmptyBorder(5, 5, 5, 5); SoftBevelBorder sbb = new SoftBevelBorder(SoftBevelBorder.LOWERED); Border b = new CompoundBorder(eb, sbb); formatControls = new FormatControls(params); /* * Samigo 2.2 does not required Advanced Settings, so comment it out * setLayout(new BorderLayout()); setBorder(new EmptyBorder(5, 5, 5, * 5)); JPanel p1 = new JPanel(); p1.setLayout(new BoxLayout(p1, * BoxLayout.X_AXIS)); p1.add(formatControls); */ JPanel p2 = new JPanel(); p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS)); p2.add(BorderLayout.WEST, makeAttemptsAllowedLabel()); p2.add(makeAudioButtonsPanel()); samplingGraph = new SamplingGraph(); audioMeter = new AudioMeter(params.getImageUrl()); samplingPanelContainer = makeAudioSamplingPanelContainer(b); samplingPanelContainer.add(audioMeter); p2.add(samplingPanelContainer); JPanel savePanel = new JPanel(); savePanel.setLayout(new BoxLayout(savePanel, BoxLayout.Y_AXIS)); JPanel saveTFpanel = makeSaveTFPanel(); savePanel.add(saveTFpanel); p2.add(savePanel); add(p2); // p1.add(p2); // add(p1); } private JPanel makeSaveTFPanel() { FlowLayout flow = new FlowLayout(); SpringLayout spring = new SpringLayout(); JPanel saveTFpanel = new JPanel(spring); JLabel flabel = new JLabel(res.getString("current_recordig_length"), SwingConstants.LEFT); JLabel rlabel = new JLabel(res.getString("attempts_remaining"), SwingConstants.LEFT); saveTFpanel.add(flabel); saveTFpanel.add(textField = new JTextField("" + params.getCurrentRecordingLength())); saveTFpanel.add(rlabel); if (params.getAttemptsRemaining() >= 9999) { saveTFpanel.add(rtextField = new JTextField("Unlimited")); } else if (params.getAttemptsRemaining() > 0 && params.getAttemptsRemaining() < 9999) saveTFpanel.add(rtextField = new JTextField("" + params.getAttemptsRemaining())); else saveTFpanel.add(rtextField = new JTextField("0")); textField.setEditable(false); rtextField.setEditable(false); Font font = new Font("Ariel", Font.PLAIN, 14); flabel.setFont(font); rlabel.setFont(font); textField.setFont(font); rtextField.setFont(font); SpringUtilities.makeCompactGrid(saveTFpanel, 2, 2, 5, 5, 5, 5); return saveTFpanel; } private JPanel makeAudioButtonsPanel() { JPanel buttonsPanel = new JPanel(new GridLayout(2,4)); // JPanel buttonsPanel = new JPanel(new SpringLayout()); // the two leftmost buttons are unlabled buttonsPanel.add(new JLabel("")); buttonsPanel.add(new JLabel("")); buttonsPanel.add(new JLabel("")); finishedLabel = new JLabel(res.getString("Finished")); finishedLabel.setForeground(Color.red); Font f = finishedLabel.getFont(); finishedLabel.setFont(f.deriveFont(f.getStyle() ^ Font.BOLD)); buttonsPanel.add(finishedLabel); if (params.getAttemptsRemaining() > 0 || params.getAttemptsRemaining() == -1) { captB = addButton(res.getString("Record"), buttonsPanel, true, params.isEnableRecord()); } else { captB = addButton(res.getString("Record"), buttonsPanel, false, params.isEnableRecord()); } if (recordIcon != null) captB.setIcon(recordIcon); stopB = addButton(res.getString("Stop"), buttonsPanel, false, params.isEnableStop()); if (stopIcon != null) stopB.setIcon(stopIcon); playB = addButton(res.getString("Play"), buttonsPanel, false, params .isEnablePlay()); if (playIcon != null) playB.setIcon(playIcon); saveButton = addButton(res.getString("Save_and_close"), Font.BOLD, buttonsPanel, false, params.isEnableSave()); return buttonsPanel; } private JButton addButton(String name, int style, JPanel p, boolean state, boolean visible) { JButton b = new JButton(name); Font f = b.getFont(); b.setFont(f.deriveFont(f.getStyle() ^ style)); b.addActionListener(this); b.setEnabled(state); b.setVisible(visible); p.add(b); return b; } private JPanel makeAudioSamplingPanelContainer(Border b) { JPanel samplingPanel = new JPanel(new BorderLayout()); samplingPanel.setBorder(b); samplingPanel.setPreferredSize(new Dimension(225, 150)); return samplingPanel; } public void open() { /* * daisy test code - pls do not delete String mediaUrl = * "http://sakai-l.stanford.edu:8080/samigo-app/servlet/ShowMedia?mediaId=107"; * //String mediaUrl = * "http://sakai-l.stanford.edu:8080/samigo-app/spacemusic.au"; * System.out.println("*****open applet="+mediaUrl); * createAudioInputStream(mediaUrl, true); */ } public void close() { if (playback.thread != null) { playB.doClick(0); } if (capture.thread != null) { captB.doClick(0); } } /** * Add a button to a panel, enabled or disabled, visible or invisible. We * create invisible buttons if their function is turned off in our * configuration, so that the UI logic always works the same way. * * @param name * @param p * the panel * @param state * enabled/disabled state * @param visible * visible/invisible state * @return */ private JButton addButton(String name, JPanel p, boolean state, boolean visible) { return addButton(name, Font.PLAIN, p, state, visible); } public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); statusLabel.setVisible(false); statusLabel.setText(""); if (obj.equals(playB)) { // System.out.println("playB"); lastButtonClicked = PLAY; playback.start(); samplingGraph.start(); captB.setEnabled(false); stopB.setEnabled(true); playB.setEnabled(false); } else if (obj.equals(captB)) { // System.out.println("captB"); lastButtonClicked = RECORD; if (containingApplet != null) { JSObject openingWindow = (JSObject)((JSObject)JSObject.getWindow(containingApplet).getMember("opener")); openingWindow.call("disableSubmitForGrade", null); openingWindow.call("disableSave", null); // openingWindow.call("hide", new Object[]{"question" + params.getQuestionId()}); } file = null; capture.start(); fileName = res.getString("default_file_name"); samplingGraph.start(); audioMeter.start(); captB.setEnabled(false); playB.setEnabled(false); saveButton.setEnabled(false); samplingPanelContainer.remove(samplingGraph); samplingPanelContainer.add(audioMeter); stopB.setEnabled(true); startTimer(); } else if (obj.equals(stopB)) { // System.out.println("stopB"); if (RECORD.equals(lastButtonClicked)) { // System.out.println("captB is enabled"); statusLabel.setText(res.getString("processing")); statusLabel.setVisible(true); playB.setEnabled(false); captB.setEnabled(false); saveButton.setEnabled(false); stopB.setEnabled(false); audioMeter.stop(); samplingPanelContainer.remove(audioMeter); samplingPanelContainer.add(samplingGraph); captureAudio(); } else if (PLAY.equals(lastButtonClicked)) { // System.out.println("playB is enabled"); playback.stop(); samplingGraph.stop(); if (params.getAttemptsAllowed() > 0) { if (attempts == 0) captB.setEnabled(false); else captB.setEnabled(true); stopB.setEnabled(false); playB.setEnabled(true); } } } else if (obj.equals(saveButton)) { saveButton.setEnabled(false); captB.setEnabled(false); playB.setEnabled(false); statusLabel.setText(res.getString("processing")); statusLabel.setVisible(true); saveMedia(); } } /* private void resetAttempts(int attempts0) { if (attempts0 > 0) { rtextField.setText("" + attempts0); } } */ public JPanel getFormatControlsPanel() { return formatControls; } public void createAudioInputStream(File file, boolean updateComponents) { if (file != null && file.isFile()) { try { this.file = file; errStr = null; audioInputStream = AudioSystem.getAudioInputStream(file); // playB.setEnabled(true); fileName = file.getName(); long milliseconds = (long) ((audioInputStream.getFrameLength() * 1000) / audioInputStream .getFormat().getFrameRate()); duration = milliseconds / 1000.0; if (updateComponents) { formatControls.setFormat(audioInputStream.getFormat()); samplingGraph.createWaveForm(null, lines, audioInputStream); } } catch (Exception ex) { reportStatus(ex.toString()); } } else { reportStatus(res.getString("Audio_file_required_")); } } /** * Post audio data directly. * * @param audioType * the audio type string * @param urlString * the url (in applets must use getCodeBase().toString() + * same-host relative url) * @param inputStream * the input stream * @param attemptsLeft * attempts left */ public void saveAndPost(InputStream inputStream, final AudioFileFormat.Type audioType, final String urlString, int attemptsLeft, final boolean post) { Thread saveAndPostThread = new Thread() { public void run() { while (audioInputStream == null) { try { // politely waiting for capture Thread to finish with audioInputStream. Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // reset to the beginnning of the captured data try { audioInputStream.reset(); } catch (Exception ex) { reportStatus(res.getString("Unable_to_reset") + ex); return; } if (post) postAudio(audioType, urlString); if (containingApplet != null) { JSObject window = (JSObject)JSObject.getWindow(containingApplet); JSObject opener = (JSObject)window.getMember("opener"); opener.call("clickReloadLink", new Object[]{window}); window.call("close", null); } } // end of run }; // end of saveAndPostThread saveAndPostThread.start(); } public void postAudio(AudioFileFormat.Type audioType, String urlString) { String suffix = getAudioSuffix(); URL url; URLConnection urlConn; try { // URL of audio processing servlet // note for applet security, this must be on same host agentId = params.getAgentId(); String queryString = "&agent=" + agentId + "&lastDuration=" + duration + "&suffix=" + suffix + "&attempts=" + attempts; url = new URL(urlString + queryString); urlConn = url.openConnection(); urlConn.setDoInput(true); urlConn.setDoOutput(true); // No caching, we want the real thing. urlConn.setUseCaches(false); urlConn.setRequestProperty("CONTENT-TYPE", getMimeType(audioType)); // Send binary POST output. OutputStream outputStream = urlConn.getOutputStream(); BufferedReader input = null; try { // System.out.println("**** no. of bytes // recorded="+audioInputStream.available()); int c = AudioSystem.write(audioInputStream, audioType, outputStream); if (c <= 0) { throw new IOException(res.getString("Problems_writing_to")); } outputStream.flush(); outputStream.close(); input = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); // Get response data. //String reportStr = res.getString("contentlenw") + ": " + c + " " + res.getString("bytes") + ".\n "; // need to check that acknowlegement from server matches or // display //StringBuilder reportStrBuf = new StringBuilder(reportStr); StringBuilder responseBuf = new StringBuilder(); String str; while (null != ((str = input.readLine()))) { // reportStrBuf.append(str); responseBuf.append(str); } //reportStr = reportStrBuf.toString(); String response = responseBuf.toString(); input.close(); mediaId = response; // mock up doesn't require report, let's comment it out // reportStatus(reportStr + "\n"); } catch (Exception ex) { reportStatus(ex.toString()); } finally{ if (input !=null){ try { input.close(); } catch (Exception e1) { e1.printStackTrace(); } } } } catch (IOException ex) { reportStatus(ex.toString()); } } private void reportStatus(String msg) { if ((errStr = msg) != null) { System.out.println(errStr); samplingGraph.repaint(); } } /** * Write data to the OutputChannel. */ public class Playback implements Runnable { SourceDataLine line; Thread thread; public void start() { errStr = null; thread = new Thread(this); thread.setName(res.getString("thread_Name")); thread.start(); } public void stop() { thread = null; } private void shutDown(String message) { if ((errStr = message) != null) { System.err.println(errStr); samplingGraph.repaint(); } if (thread != null) { thread = null; samplingGraph.stop(); if (params.getAttemptsAllowed() > 0) { if (attempts == 0) captB.setEnabled(false); else captB.setEnabled(true); } playB.setEnabled(true); stopB.setEnabled(false); } } public void run() { // reload the file if loaded by file if (file != null) { createAudioInputStream(file, false); } // make sure we have something to play if (audioInputStream == null) { shutDown(res.getString("No_loaded_audio_to1")); return; } // reset to the beginnning of the stream try { audioInputStream.reset(); } catch (Exception e) { shutDown(res.getString("Unable_to_reset_the") + e); return; } // get an AudioInputStream of the desired format for playback AudioFormat format = formatControls.getFormat(); AudioInputStream playbackInputStream = AudioSystem .getAudioInputStream(format, audioInputStream); if (playbackInputStream == null) { shutDown(res.getString("Unable_to_convert") + audioInputStream + res.getString("to_format") + format); return; } // define the required attributes for our line, // and make sure a compatible line is supported. DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); if (!AudioSystem.isLineSupported(info)) { shutDown(res.getString("Line_matching") + info + res.getString("not_supported_")); return; } // get and open the source data line for playback. try { line = (SourceDataLine) AudioSystem.getLine(info); line.open(format, bufSize); } catch (LineUnavailableException ex) { shutDown(res.getString("Unable_to_open_the") + ex); return; } // play back the captured audio data int frameSizeInBytes = format.getFrameSize(); int bufferLengthInFrames = line.getBufferSize() / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte[] data = new byte[bufferLengthInBytes]; int numBytesRead = 0; // start the source data line line.start(); while (thread != null) { try { if ((numBytesRead = playbackInputStream.read(data)) == -1) { break; } int numBytesRemaining = numBytesRead; while (numBytesRemaining > 0) { numBytesRemaining -= line.write(data, 0, numBytesRemaining); } } catch (Exception e) { shutDown(res.getString("Error_during_playback") + e); break; } } // we reached the end of the stream. let the data play out, then // stop and close the line. if (thread != null) { line.drain(); } line.stop(); line.close(); line = null; shutDown(null); } } // End class Playback /** * Reads data from the input channel and writes to the output stream */ class Capture implements Runnable { TargetDataLine line; Thread thread; public void start() { errStr = null; thread = new Thread(this); thread.setName(res.getString("thread_Name1")); thread.start(); } public void stop() { thread = null; } private void shutDown(String message) { if ((errStr = message) != null && thread != null) { thread = null; samplingGraph.stop(); playB.setEnabled(true); saveButton.setEnabled(true); System.err.println(errStr); samplingGraph.repaint(); } } public void run() { duration = 0; audioInputStream = null; // define the required attributes for our line, // and make sure a compatible line is supported. AudioFormat format = formatControls.getFormat(); DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); if (!AudioSystem.isLineSupported(info)) { shutDown(res.getString("Line_matching") + info + res.getString("not_supported_")); return; } // get and open the target data line for capture. try { line = (TargetDataLine) AudioSystem.getLine(info); line.open(format, line.getBufferSize()); } catch (LineUnavailableException ex) { shutDown(res.getString("Unable_to_open_the") + ex); return; } catch (SecurityException ex) { shutDown(ex.toString()); AudioConfigHelp help = new AudioConfigHelp(true, false, true, true); help.configHelp(); return; } catch (Exception ex) { shutDown(ex.toString()); return; } // play back the captured audio data ByteArrayOutputStream out = new ByteArrayOutputStream(); int frameSizeInBytes = format.getFrameSize(); int bufferLengthInFrames = line.getBufferSize() / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte[] data = new byte[bufferLengthInBytes]; int numBytesRead; line.start(); audioMeter.start(); while (thread != null) { if ((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1) { break; } // we want this to sample in a separate thread, methinks for (int i=0; i < data.length; i++) { if (Math.abs(data[i]) > 0) { audioMeter.setLevel(Math.abs(data[i])); audioMeter.setSeconds(seconds); } } out.write(data, 0, numBytesRead); } // we reached the end of the stream. stop and close the line. line.stop(); line.close(); line = null; // stop and close the output stream try { out.flush(); out.close(); } catch (IOException ex) { ex.printStackTrace(); } // load bytes into the audio input stream for playback byte audioBytes[] = out.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes); audioInputStream = new AudioInputStream(bais, format, audioBytes.length / frameSizeInBytes); long milliseconds = (long) ((audioInputStream.getFrameLength() * 1000) / format .getFrameRate()); duration = milliseconds / 1000.0; try { audioInputStream.reset(); } catch (Exception ex) { ex.printStackTrace(); return; } samplingGraph.createWaveForm(audioBytes, lines, audioInputStream); } } // End class Capture /** * Controls for the AudioFormat. */ class FormatControls extends AudioFormatPanel implements AudioControlContext { private static final long serialVersionUID = 0L; FormatControls(AudioRecorderParams params) { super(params); } public void open() { } public void close() { if (playback.thread != null) { playB.doClick(0); } if (capture.thread != null) { captB.doClick(0); } } } // End class FormatControls /** * Render a WaveForm. */ class SamplingGraph extends AudioSampleGraphPanel implements Runnable { private static final long serialVersionUID = 0L; private Thread thread; public void reportGraphStatus(String message) { reportStatus(message); } public void paint(Graphics g) { AudioSamplingData data = new AudioSamplingData(); data.setAudioInputStream(audioInputStream); data.setCapture(capture); data.setCaptureThread(capture.thread); data.setDuration(duration); data.setErrStr(errStr); data.setFileName(fileName); data.setLine(lines); data.setSeconds(seconds); data.setMaxSeconds(params.getMaxSeconds()); paintData(g, data); } public void start() { thread = new Thread(this); thread.setName(AudioSampleGraphPanel.res.getString("thread_Name2")); thread.start(); seconds = 0; } public void stop() { if (thread != null) { thread.interrupt(); } thread = null; } public void run() { seconds = 0; while (thread != null) { if ((playback.line != null) && (playback.line.isOpen())) { long milliseconds = (long) (playback.line .getMicrosecondPosition() / 1000); seconds = milliseconds / 1000.0; } else if ((capture.line != null) && (capture.line.isActive())) { long milliseconds = (long) (capture.line .getMicrosecondPosition() / 1000); seconds = milliseconds / 1000.0; } try { Thread.sleep(100); } catch (Exception e) { break; } repaint(); while ((capture.line != null && !capture.line.isActive()) || (playback.line != null && !playback.line.isOpen())) { try { Thread.sleep(10); } catch (Exception e) { break; } } } seconds = 0; repaint(); } } // End class SamplingGraph class AudioMeter extends AudioMeterPanel implements Runnable { private static final long serialVersionUID = 0L; public AudioMeter(String imageUrl) { super(imageUrl); } public void start() { animator = new Thread(this); animator.start(); } public void stop() { animator = null; offImage = null; offGraphics = null; } /** * This method is called by the thread that was created in * the start method. It does the main animation. */ public void run() { // Remember the starting time long tm = System.currentTimeMillis(); while (Thread.currentThread() == animator) { // Display the next frame of animation. repaint(); // Delay depending on how far we are behind. try { tm += delay; Thread.sleep(Math.max(0, tm - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } } public static void main(String s[]) { AudioRecorderParams params = new AudioRecorderParams(); AudioRecorder capturePlayback = new AudioRecorder(params); capturePlayback.open(); JFrame f = new JFrame(res.getString("Capture_Playback")); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.getContentPane().add("Center", capturePlayback); f.pack(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int w = 600; int h = 250; f.setLocation(screenSize.width / 2 - w / 2, screenSize.height / 2 - h / 2); f.setSize(w, h); f.setVisible(true); } private JPanel makeAttemptsAllowedLabel() { GridLayout grid = new GridLayout(2, 3); JPanel panel = new JPanel(grid); JLabel label1 = new JLabel(res.getString("attempts_allowed") + " " + params.getAttemptsAllowed(), SwingConstants.LEFT); if (params.getAttemptsAllowed() >= 9999) { label1 = new JLabel(res.getString("attempts_allowed") + " " + res.getString("unlimited"), SwingConstants.LEFT); } JLabel label2 = new JLabel(res.getString("time_limit") + " " + params.getMaxSeconds() + " sec", SwingConstants.LEFT); Font font = new Font("Ariel", Font.PLAIN, 14); label1.setFont(font); label2.setFont(font); Font statusFont = new Font("Ariel", Font.BOLD, 12); statusLabel = new JLabel("", SwingConstants.CENTER); statusLabel.setFont(statusFont); statusLabel.setForeground(Color.red); panel.add(label2); panel.add(statusLabel); panel.add(label1); return panel; } private String getAudioSuffix() { String suffix = "au"; if (params.isSaveWave()) suffix = "wav"; if (params.isSaveAiff()) suffix = "aif"; return suffix; } private AudioFileFormat.Type getAudioType() { AudioFileFormat.Type type = AudioFileFormat.Type.AU; if (params.isSaveWave()) type = AudioFileFormat.Type.WAVE; if (params.isSaveAiff()) type = AudioFileFormat.Type.AIFF; return type; } private void saveMedia() { AudioFileFormat.Type type = getAudioType(); if (params.isSaveToUrl()) { saveAndPost(audioInputStream, type, params.getUrl(), attempts, true); } else { // this is for preview when u just wnat to keep the recording at // client saveAndPost(audioInputStream, type, params.getUrl(), attempts, false); } } private String getMimeType(AudioFileFormat.Type audioType) { String mimeType = "audio/basic"; if (audioType.equals(AudioFileFormat.Type.AIFF)) { mimeType = "audio/x-aiff"; } else if (audioType.equals(AudioFileFormat.Type.WAVE)) { mimeType = "audio/x-wav"; } return mimeType; } private void captureAudio() { // timer was started by clicking Record to enforce time limit if (timer != null) { timer.stop(); } lines.removeAllElements(); capture.stop(); samplingGraph.stop(); int retry = 1; while (audioInputStream == null) { try { // politely waiting for capture Thread to finish with audioInputStream. Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // reset to the beginnning of the captured data try { audioInputStream.reset(); } catch (Exception ex) { reportStatus(res.getString("Unable_to_reset") + ex); return; } statusLabel.setVisible(false); statusLabel.setText(""); playB.setEnabled(true); saveButton.setEnabled(true); attempts = params.getAttemptsRemaining(); if (attempts > 0) { if (attempts < 9999) { params.setAttemptsRemaining(--attempts); rtextField.setText("" + attempts); } } if (attempts == 0) captB.setEnabled(false); else captB.setEnabled(true); // earlier we add 1sec leeway for the applet to load after the timer // start. to avoid // alarm user, the duration value shown would not be over max seconds // allowed. // However, for record keeping, the actual duration is saved. if (duration > params.getMaxSeconds()) textField.setText(NoDecimalPlaces.format(params.getMaxSeconds())); else textField.setText(NoDecimalPlaces.format(duration)); samplingGraph.repaint(); if (containingApplet != null) { JSObject openingWindow = (JSObject)((JSObject)JSObject.getWindow(containingApplet).getMember("opener")); openingWindow.call("enableSubmitForGrade", null); openingWindow.call("enableSave", null); } } private void startTimer() { Action stopRecordingAction = new AbstractAction() { private static final long serialVersionUID = 0L; public void actionPerformed(ActionEvent e) { statusLabel.setText(res.getString("time_expired")); statusLabel.setVisible(true); captB.setEnabled(false); stopB.setEnabled(false); playB.setEnabled(false); saveButton.setEnabled(false); audioMeter.stop(); samplingPanelContainer.remove(audioMeter); samplingPanelContainer.add(samplingGraph); captureAudio(); } }; // allow 1sec for leeway in case the page loads slow timer = new Timer(params.getMaxSeconds() * 1000 + 1000, stopRecordingAction); timer.start(); } /* Daisy test code for loading media back to applet - pls do not delete */ public void createAudioInputStream(String mediaUrl, boolean updateComponents) { try { URL url = new URL(mediaUrl); audioInputStream = AudioSystem.getAudioInputStream(url); // playB.setEnabled(true); long milliseconds = (long) ((audioInputStream.getFrameLength() * 1000) / audioInputStream .getFormat().getFrameRate()); duration = milliseconds / 1000.0; System.out.println("*****createAudioInpuStream= " + audioInputStream); System.out.println("*** duration= " + duration); if (updateComponents) { formatControls.setFormat(audioInputStream.getFormat()); System.out.println("*** calling samplingGraph.createWaveForm"); samplingGraph.createWaveForm(null, lines, audioInputStream); } } catch (Exception ex) { // doesn't matter; treat it as no input stream System.out.println(ex.getMessage()); } } public Applet getContainingApplet() { return containingApplet; } public void setContainingApplet(Applet containingApplet) { this.containingApplet = containingApplet; } }