/** * ----------------------------------------------------------------------- * File: $HeadURL: http://keith-laptop/svn/krs/LanguageTest/trunk/org.thanlwinsoft.languagetest/src/org/thanlwinsoft/eclipse/widgets/SoundPlayer.java $ * Revision $LastChangedRevision: 1388 $ * Last Modified: $LastChangedDate: 2009-01-31 19:32:00 +0700 (Sat, 31 Jan 2009) $ * Last Change by: $LastChangedBy: keith $ * ----------------------------------------------------------------------- * Copyright (C) 2007 Keith Stribley <devel@thanlwinsoft.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * ----------------------------------------------------------------------- * This code is heavily based around the JavaZOOM player. * * JavaZOOM : jlgui@javazoom.net * http://www.javazoom.net */ package org.thanlwinsoft.eclipse.widgets; import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Scale; import org.thanlwinsoft.languagetest.MessageUtil; import org.thanlwinsoft.languagetest.eclipse.LanguageTestPlugin; import org.thanlwinsoft.languagetest.sound.AudioPlayListener; import javazoom.jlgui.basicplayer.BasicController; import javazoom.jlgui.basicplayer.BasicPlayer; import javazoom.jlgui.basicplayer.BasicPlayerEvent; import javazoom.jlgui.basicplayer.BasicPlayerException; import javazoom.jlgui.basicplayer.BasicPlayerListener; /** * @author keith * */ public class SoundPlayer extends Composite implements BasicPlayerListener { /** Fractional position to seek to */ private double posValue = 0.0; /*-- JavaSound Members --*/ public static final int INIT = 0; public static final int OPEN = 1; public static final int PLAY = 2; public static final int PAUSE = 3; public static final int STOP = 4; private int playerState = INIT; private long secondsAmount = 0; private long msAmount = 0; private long msStopValue = -1; private BasicController theSoundPlayer = null; private Map<?,?> audioInfo = null; private boolean posValueJump = false; private String currentFileOrURL = null; private String titleText = ""; private boolean currentIsFile; private long lengthInSecond = -1l; private long lengthInMs = -1; private boolean isRealLength = false; //private long lastBytesRead = 0l; // gui private Scale slider = null; private Display display = null; private Set<AudioPlayListener> audioPlayListenerSet = new HashSet<AudioPlayListener>(); public SoundPlayer(Composite parent) { super(parent, SWT.NONE); display = parent.getShell().getDisplay(); RowLayout layout = new RowLayout(); layout.type = SWT.VERTICAL; layout.spacing = 0; layout.fill = true; setLayout(layout); Composite buttonRow = new Composite(this, SWT.NONE); RowLayout buttonLayout = new RowLayout(); buttonLayout.type = SWT.HORIZONTAL; buttonLayout.spacing = 0; buttonLayout.fill = true; buttonRow.setLayout(buttonLayout); Button play = new Button(buttonRow, SWT.NONE); Button pause = new Button(buttonRow, SWT.NONE); Button stop = new Button(buttonRow, SWT.NONE); //play.setImage(idPlay.createImage()); ImageRegistry ir = LanguageTestPlugin.getDefault().getImageRegistry(); play.setImage(ir.get("Play")); play.setToolTipText(MessageUtil.getString("SoundPlayer_Play")); pause.setImage(ir.get("Pause")); pause.setToolTipText(MessageUtil.getString("SoundPlayer_Pause")); stop.setImage(ir.get("Stop")); stop.setToolTipText(MessageUtil.getString("SoundPlayer_Stop")); slider = new Scale(this, SWT.HORIZONTAL); slider.setMinimum(0); slider.setMaximum(600); slider.setSelection(0); slider.setSize(50, SWT.DEFAULT); slider.addSelectionListener( new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e){} public void widgetSelected(SelectionEvent e) { seek(); } }); play.addSelectionListener(new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) {} public void widgetSelected(SelectionEvent e) { play(); }}); pause.addSelectionListener(new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) {} public void widgetSelected(SelectionEvent e) { pause(); }}); stop.addSelectionListener(new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) {} public void widgetSelected(SelectionEvent e) { stop(); }}); slider.addTraverseListener(new TraverseListener() { public void keyTraversed(TraverseEvent e) { seek(); }}); // sound intialisation BasicPlayer bplayer = new BasicPlayer(); // Register the front-end to low-level player events. bplayer.addBasicPlayerListener(this); // Adds controls for front-end to low-level player. setController(bplayer); playerState = STOP; // debug testing only //setFile("/home/keith/ogg/Fierce/Simply Worship 1/01 - Track 1.ogg"); } /** * */ protected void stop() { if ((playerState == PLAY) || (playerState == PAUSE)) { try { theSoundPlayer.stop(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.INFO, "Cannot stop", e1); } playerState = STOP; } } /** * */ protected void pause() { if (playerState == PLAY) { try { theSoundPlayer.pause(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.ERROR,"Cannot pause",e1); } playerState = PAUSE; } else if (playerState == PAUSE) { try { theSoundPlayer.resume(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.ERROR, "Cannot resume",e1); } playerState = PLAY; } } /** * */ public void play() { Job job = new Job("Play") { protected IStatus run(IProgressMonitor monitor) { runPlay(); return Status.OK_STATUS; } }; job.schedule(); } protected void runPlay() { try { // Resume is paused. if (playerState == PAUSE) { try { theSoundPlayer.resume(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.ERROR,"Cannot resume",e1); } playerState = PLAY; } // Stop if playing. else if (playerState == PLAY) { try { theSoundPlayer.stop(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.ERROR,"Cannot stop",e1); } playerState = PLAY; secondsAmount = 0; if (currentFileOrURL != null) { try { if (currentIsFile == true) theSoundPlayer.open(openFile(currentFileOrURL)); else theSoundPlayer.open(new URL(currentFileOrURL)); processSeek(); theSoundPlayer.play(); } catch (Exception ex) { LanguageTestPlugin.log(IStatus.ERROR,"Cannot read file : " + currentFileOrURL,ex); } } } else if ((playerState == STOP) || (playerState == OPEN)) { try { theSoundPlayer.stop(); } catch (BasicPlayerException e1) { LanguageTestPlugin.log(IStatus.ERROR,"Stop failed",e1); } if (currentFileOrURL != null) { try { if (currentIsFile == true) theSoundPlayer.open(openFile(currentFileOrURL)); else theSoundPlayer.open(new URL(currentFileOrURL)); processSeek(); theSoundPlayer.play(); //titleText = currentSongName.toUpperCase(); // Get bitrate, samplingrate, channels, time in the following order : // PlaylistItem, BasicPlayer (JavaSound SPI), Manual computation. int bitRate = -1; //if (currentPlaylistItem != null) bitRate = currentPlaylistItem.getBitrate(); if ((bitRate <= 0) && (audioInfo.containsKey("bitrate"))) bitRate = ((Integer) audioInfo.get("bitrate")).intValue(); if ((bitRate <= 0) && (audioInfo.containsKey("audio.framerate.fps")) && (audioInfo.containsKey("audio.framesize.bytes"))) { float FR = ((Float)audioInfo.get("audio.framerate.fps")).floatValue(); int FS = ((Integer)audioInfo.get("audio.framesize.bytes")).intValue(); bitRate = Math.round(FS * FR * 8); } int channels = -1; //if (currentPlaylistItem != null) channels = currentPlaylistItem.getChannels(); if ((channels <= 0) &&(audioInfo.containsKey("audio.channels"))) channels = ((Integer) audioInfo.get("audio.channels")).intValue(); float sampleRate = -1.0f; //if (currentPlaylistItem != null) sampleRate = currentPlaylistItem.getSamplerate(); if ((sampleRate <= 0) && (audioInfo.containsKey("audio.samplerate.hz"))) sampleRate = ((Float) audioInfo.get("audio.samplerate.hz")).floatValue(); lengthInSecond = -1L; //if (currentPlaylistItem != null) lenghtInSecond = currentPlaylistItem.getLength(); if ((lengthInSecond <= 0) && (audioInfo.containsKey("duration"))) lengthInSecond = ((Long) audioInfo.get("duration")).longValue()/1000000; if ((lengthInSecond <= 0) && (audioInfo.containsKey("audio.length.bytes"))) { // Try to compute time length. lengthInSecond = (long) Math.round(getTimeLengthEstimation(audioInfo)/1000); } if (lengthInSecond < 0) { lengthInSecond = 120; // 2min isRealLength = false; } else isRealLength = true; final int lengthMax = (int)lengthInSecond; display.asyncExec (new Runnable () { public void run () { slider.setMaximum(lengthMax); } }); bitRate = Math.round(( bitRate/ 1000)); if (bitRate > 999) { bitRate = (int) (bitRate/100); } } catch (BasicPlayerException bpe) { LanguageTestPlugin.log(IStatus.INFO,"Stream error :" + currentFileOrURL,bpe); } catch (MalformedURLException mue) { LanguageTestPlugin.log(IStatus.INFO,"Stream error :" + currentFileOrURL,mue); } // Set pan/gain. // try // { // theSoundPlayer.setGain(((double) gainValue / (double) maxGain)); // theSoundPlayer.setPan((float) balanceValue); // } // catch (BasicPlayerException e2) // { // LanguageTestPlugin.log(IStatus.INFO,"Cannot set control",e2); // } playerState = PLAY; LanguageTestPlugin.log(IStatus.INFO,titleText); } } else { LanguageTestPlugin.log(IStatus.INFO,"Play ignored " + playerState); } } catch (Exception e) { e.printStackTrace(); LanguageTestPlugin.log(IStatus.ERROR,"play error " + e.getLocalizedMessage(),e); } } /** * */ protected void seek() { if (playerState == PAUSE || playerState == STOP) { posValue = (double) slider.getSelection() / (double) slider.getMaximum(); // processSeek(); } } /** * Seek to the specified offset in ms * @param msInterval */ public void seek(long msInterval) { if (playerState == PAUSE || playerState == STOP) { if (lengthInMs < 0) { lengthInMs = getTimeLengthEstimation(audioInfo); } // the actual seeking is done in the play thread if (msInterval > 0 && lengthInMs > 0) { posValue = (double)msInterval / (double)lengthInMs; } else { posValue = 0; } } } /** * Stop after playing msInterval Miliseconds. This is relative to the seek * position, NOT the start of the file. * @param msInterval */ public void stopAfter(long msInterval) { msStopValue = msInterval; } public void setFile(String file) { stop(); currentIsFile = true; currentFileOrURL = file; lengthInMs = -1; lengthInSecond = -1; msAmount = 0; msStopValue = -1; secondsAmount = 0; if (file != null) LanguageTestPlugin.log(IStatus.INFO, "SoundPlayer setFile " + file); slider.setSelection(0); } // methods from JLPlayer /*-----------------------------------------*/ /*-- BasicPlayerListener Interface --*/ /*-----------------------------------------*/ /** * Open callback, stream is ready to play. */ @SuppressWarnings("unchecked") public void opened(Object stream, Map properties) { audioInfo = properties; LanguageTestPlugin.log(IStatus.INFO, properties.toString()); } /** * Progress callback while playing. */ @SuppressWarnings("unchecked") public void progress(int bytesread, long microseconds, byte[] pcmdata, Map properties) { int byteslength = -1; long total = -1; msAmount = microseconds / 1000; if (lengthInMs <= 0) lengthInMs = getTimeLengthEstimation(audioInfo); // If it fails then try again with JavaSound SPI. if (total <=0) total = (long) Math.round(lengthInMs/1000); // If it fails again then it might be stream => Total = -1 if (total <=0) total = -1; if (audioInfo.containsKey("audio.length.bytes")) { byteslength = ((Integer) audioInfo.get("audio.length.bytes")).intValue(); } float progress = -1.0f; if ((bytesread > 0) && ((byteslength > 0))) progress = bytesread*1.0f/byteslength*1.0f; if (audioInfo.containsKey("audio.type")) { String audioformat = (String) audioInfo.get("audio.type"); if (audioformat.equalsIgnoreCase("mp3")) { // Shoutcast stream title. if (properties.containsKey("mp3.shoutcast.metadata.StreamTitle")) { String shoutTitle = ((String) properties.get("mp3.shoutcast.metadata.StreamTitle")).trim(); if (shoutTitle.length()>0) { titleText = shoutTitle; } } // Equalizer if (total > 0) secondsAmount = (long) (total*progress); else secondsAmount = -1; } else if (audioformat.equalsIgnoreCase("wave")) { secondsAmount = (long) (total*progress); } else { secondsAmount = (long) Math.round(microseconds/1000000); } } else { secondsAmount = (long) Math.round(microseconds/1000000); } if (secondsAmount < 0) secondsAmount = (long) Math.round(microseconds/1000000); //lastBytesRead = bytesread; final int sliderPos = (int)secondsAmount; // Update PosBar location. try { //slider.setSelection(sliderPos); Iterator<AudioPlayListener> i = audioPlayListenerSet.iterator(); long msPosition = secondsAmount * 1000; long msTotalLength = getLengthInSeconds() * 1000; while (i.hasNext()) { i.next().playPosition(msPosition, msTotalLength); } display.asyncExec (new Runnable () { public void run () { int oldMax = slider.getMaximum(); if (sliderPos > oldMax) slider.setMaximum(oldMax + 60); // add 1 minute slider.setSelection(sliderPos); } }); } catch (Exception e) { LanguageTestPlugin.log(IStatus.ERROR, "slider update failed " + sliderPos,e); } if (msStopValue >= 0 && msAmount >= msStopValue) { try { theSoundPlayer.stop(); } catch (BasicPlayerException e) { LanguageTestPlugin.log(IStatus.WARNING, "SoundPlayer failed to stop", e); } } } /** * Notification callback. */ public void stateUpdated(BasicPlayerEvent event) { LanguageTestPlugin.log(IStatus.INFO, "Event:" + event); /*-- End Of Media reached --*/ int state = event.getCode(); Object obj = event.getDescription(); if (state == BasicPlayerEvent.EOM) { if ((playerState == PAUSE) || (playerState == PLAY)) { } } else if (state == BasicPlayerEvent.PLAYING) { //lastScrollTime = System.currentTimeMillis(); posValueJump = false; } else if (state == BasicPlayerEvent.SEEKING) { posValueJump = true; } else if (state == BasicPlayerEvent.OPENING) { if ((obj instanceof URL) || (obj instanceof InputStream)) { LanguageTestPlugin.log(IStatus.INFO, "PLEASE WAIT ... BUFFERING ..."); } } } /** * A handle to the BasicPlayer, plugins may control the player through * the controller (play, stop, ...) */ public void setController(BasicController controller) { theSoundPlayer = controller; } /** * Process seek feature. */ protected void processSeek() { try { if (posValue >= 1.0) posValue = 0; if (audioInfo.containsKey("audio.type")) { String type = (String) audioInfo.get("audio.type"); // Seek support for MP3. if ((type.equalsIgnoreCase("mp3")) && (audioInfo.containsKey("audio.length.bytes"))) { long skipBytes = (long) Math.round(((Integer) audioInfo.get("audio.length.bytes")).intValue()*posValue); LanguageTestPlugin.log(IStatus.INFO, "Seek value (MP3) : "+skipBytes); theSoundPlayer.seek(skipBytes); } // Seek support for WAV. else if ((type.equalsIgnoreCase("wave")) && (audioInfo.containsKey("audio.length.bytes"))) { long skipBytes = (long) Math.round(((Integer) audioInfo.get("audio.length.bytes")).intValue()*posValue); LanguageTestPlugin.log(IStatus.INFO, "Seek value (WAVE) : "+skipBytes); theSoundPlayer.seek(skipBytes); } else posValueJump = false; } else posValueJump = false; } catch (BasicPlayerException ioe) { LanguageTestPlugin.log(IStatus.ERROR, "Cannot skip",ioe); posValueJump = false; } } /** * Try to compute time length in milliseconds. */ @SuppressWarnings("unchecked") public long getTimeLengthEstimation(Map properties) { long milliseconds = -1; int byteslength = -1; if (properties != null) { if (properties.containsKey("audio.length.bytes")) { byteslength = ((Integer) properties.get("audio.length.bytes")).intValue(); } if (properties.containsKey("duration")) { milliseconds = (int) (((Long) properties.get("duration")).longValue())/1000; } else { // Try to compute duration int bitspersample = -1; int channels = -1; float samplerate = -1.0f; int framesize = -1; if (properties.containsKey("audio.samplesize.bits")) { bitspersample = ((Integer) properties.get("audio.samplesize.bits")).intValue(); } if (properties.containsKey("audio.channels")) { channels = ((Integer) properties.get("audio.channels")).intValue(); } if (properties.containsKey("audio.samplerate.hz")) { samplerate = ((Float) properties.get("audio.samplerate.hz")).floatValue(); } if (properties.containsKey("audio.framesize.bytes")) { framesize = ((Integer) properties.get("audio.framesize.bytes")).intValue(); } if (bitspersample > 0) { milliseconds = (int) (1000.0f*byteslength/(samplerate * channels * (bitspersample/8))); } else { milliseconds = (int)(1000.0f*byteslength/(samplerate*framesize)); } } } //LanguageTestPlugin.log(IStatus.INFO, "Bytes: " + byteslength + " ms: " + milliseconds); return milliseconds; } /** * Returns a File from a filename. */ protected File openFile(String file) { return new File(file); } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Widget#dispose() */ public void dispose() { stop(); super.dispose(); } // KRS: The next 2 methods have not been tested yet public long getPosition() { if (posValueJump) return -1l; return secondsAmount; } public long getLengthInSeconds() { if (isRealLength) return lengthInSecond; return -1l; } public void addPlayListener(AudioPlayListener listener) { audioPlayListenerSet.add(listener); } public void removePlayListener(AudioPlayListener listener) { audioPlayListenerSet.remove(listener); } }