/** * 11/19/04 1.0 moved to LGPL. *----------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *---------------------------------------------------------------------- */ import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import javazoom.jl.decoder.Bitstream; import javazoom.jl.decoder.BitstreamException; import javazoom.jl.decoder.Decoder; import javazoom.jl.decoder.Header; import javazoom.jl.decoder.JavaLayerException; import javazoom.jl.decoder.SampleBuffer; import javazoom.jl.player.AudioDevice; import javazoom.jl.player.FactoryRegistry; /** * Play music files. * This class is a modified version of javazoom.jl.player.advanced.AdvancedPlayer, * which is part of the javazoom JLayer library. * The main modifications consist of: * + Restriction to playing files rather than streams. * + Pre-reading of the audio file to determine its length in frames. * These modifications permit arbitrary seek operations. * * Modifications by David J. Barnes and Michael Kölling. * @version 2011.07.31 * This class is not suitable for playing streams as a file is read * completely before playing. */ public class MusicFilePlayer { // The MPEG audio bitstream. private Bitstream bitstream; // The MPEG audio decoder. private Decoder decoder; // The AudioDevice the audio samples are written to. private AudioDevice audio; // Whether currently playing. private boolean playing = false; // The file being played. private String filename; // The number of frames. private int frameCount; // The current frame number. private int frameNumber; // The position to resume, if any. private int resumePosition; /** * Creates a new MusicFilePlayer instance. * @param filename The file to be played. */ public MusicFilePlayer(String filename) throws JavaLayerException { this.filename = filename; openAudio(); frameCount = getFrameCount(filename); // Open a fresh bitstream following the frame count. openBitstream(filename); frameNumber = 0; resumePosition = -1; } /** * Play the whole file. */ public void play() throws JavaLayerException { playFrames(0, frameCount); } /** * Plays a number of MPEG audio frames. * * @param frames The number of frames to play. * @return true if the last frame was played, or false if there are * more frames. */ public boolean play(int frames) throws JavaLayerException { return playFrames(frameNumber, frameNumber + frames); } /** * Plays a range of MPEG audio frames * @param start The first frame to play * @param end The last frame to play * @return true if the last frame was played, or false if there are more frames. */ public boolean play(int start, int end) throws JavaLayerException { return playFrames(start, start + end); } /** * Play from the given position to the end. * @param start The first frame to play. * @return true if the last frame was played, or false if there are more frames. */ public boolean playFrom(int start) throws JavaLayerException { return playFrames(start, frameCount); } /** * Get the length of the file (in frames). * @return The file length, in frames. */ public int getLength() { return frameCount; } /** * Get the current playing position (in frames). * @return The current frame number. */ public int getPosition() { return frameNumber; } /** * Set the playing position (in frames). * Playing does not start until resume() is called. * @param position The playing position. */ public void setPosition(int position) throws JavaLayerException { pause(); resumePosition = position; } /** * Pause the playing. */ public void pause() throws JavaLayerException { synchronized(this) { playing = false; resumePosition = frameNumber; } } /** * Resume the playing. */ public void resume() throws JavaLayerException { if(!playing) { int start; if(resumePosition >= 0) { start = resumePosition; } else { start = frameNumber; } resumePosition = -1; playFrames(start, frameCount); } } /** * Return the current frame number. * @return The number of the last frame played, or -1 if nothing played yet. */ public int getFrameNumber() { return frameNumber; } /** * Play the number of frames left. * @return true If finished for any reason, false if paused. */ private boolean playFrames(int start, int end) throws JavaLayerException { // Clear any resumption position. resumePosition = -1; if(end > frameCount) { end = frameCount; } // Make sure the player is in the correct position in the input. synchronized(this) { moveTo(start); playing = true; } // Play until finished, paused, or a problem. boolean ok = true; while (frameNumber < end && playing && ok) { ok = decodeFrame(); if(ok) { frameNumber++; } } // Stopped for some reason. synchronized(this) { playing = false; // last frame, ensure all data flushed to the audio device. AudioDevice out = audio; if (out != null) { out.flush(); } } return ok; } /** * Set the playing position. * @param position (in frames) */ private void moveTo(int position) throws JavaLayerException { if(position < frameNumber) { synchronized(this) { // Already played too far. if(bitstream != null) { try { bitstream.close(); } catch (BitstreamException ex) { } } if(audio != null) { audio.close(); } openAudio(); openBitstream(filename); frameNumber = 0; } } while(frameNumber < position) { skipFrame(); frameNumber++; } } /** * Cloases this player. Any audio currently playing is stopped * immediately. */ public void close() { synchronized(this) { if (audio != null) { AudioDevice out = audio; audio = null; // this may fail, so ensure object state is set up before // calling this method. out.close(); try { bitstream.close(); } catch (BitstreamException ex) { } bitstream = null; decoder = null; } } } /** * Decodes a single frame. * * @return true if there are no more frames to decode, false otherwise. */ protected boolean decodeFrame() throws JavaLayerException { try { synchronized (this) { if (audio == null) { return false; } Header h = readFrame(); if (h == null) { return false; } // sample buffer set when decoder constructed SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream); if(audio != null) { audio.write(output.getBuffer(), 0, output.getBufferLength()); } } bitstream.closeFrame(); } catch (RuntimeException ex) { ex.printStackTrace(); throw new JavaLayerException("Exception decoding audio frame", ex); } return true; } /** * skips over a single frame * @return false if there are no more frames to decode, true otherwise. */ protected boolean skipFrame() throws JavaLayerException { Header h = readFrame(); if (h == null) { return false; } frameNumber++; bitstream.closeFrame(); return true; } /** * closes the player and notifies PlaybackListener */ public void stop() { close(); } /** * Count the number of frames in the file. * This can be used for positioning. * @param filename The file to be measured. * @return The number of frames. */ protected int getFrameCount(String filename) throws JavaLayerException { openBitstream(filename); int count = 0; while(skipFrame()) { count++; } bitstream.close(); return count; } /** * Read a frame. * @return The frame read. */ protected Header readFrame() throws JavaLayerException { if(audio != null) { return bitstream.readFrame(); } else { return null; } } /** * Open an audio device. */ protected void openAudio() throws JavaLayerException { audio = FactoryRegistry.systemRegistry().createAudioDevice(); decoder = new Decoder(); audio.open(decoder); } /** * Open a BitStream for the given file. * @param filename The file to be opened. * @throws IOException If the file cannot be opened. */ protected void openBitstream(String filename) throws JavaLayerException { try { bitstream = new Bitstream(new BufferedInputStream( new FileInputStream(filename))); } catch(java.io.IOException ex) { throw new JavaLayerException(ex.getMessage(), ex); } } }