Files
G4G0-1/Semester 1/Programming 1/Java/examples/projects/chapter13/musicplayer/MusicFilePlayer.java
2024-01-15 20:14:10 +00:00

414 lines
12 KiB
Java
Executable File

/**
* 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 2016.02.29
* 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 <code>PlaybackListener</code>
*/
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);
}
}
}