/*
* The GPLv3 licence :
* -----------------
* Copyright (c) 2009 Ricardo Dias
*
* This file is part of MuVis.
*
* MuVis is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MuVis 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MuVis. If not, see <http://www.gnu.org/licenses/>.
*/
package muvis.audio;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Map;
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;
import muvis.util.Observable;
import muvis.util.Observer;
/**
* This class implements the basic mechanism of a simple audio player
* It has the main functions of play, pause and stop, as functions for controlling the volume
* and get some information about the player and the track being played
* @author Ricardo
* @version 1.0
*/
public class MuVisAudioPlayer implements BasicPlayerListener, Observable {
/**
* This enum represents the state of the player or the operation accomplished
* by the muvis audio player
*/
public enum Event {
PAUSED, STOPPED, OPENED, VOLUME_CHANGED, NOTHING, SEEKED,
NEW_TRACK_PLAYING, RESUMED
}
/**
* The current implementation of the player - using Basic Player
*/
private BasicPlayer player;
/**
* The basicplayer controler
*/
private BasicController control;
/**
* This field holds the properties of the file being played
*/
private Map fileProperties;
/**
* This field holds the properties of the stream being played
*/
private Map playingProperties;
/**
* This field holds the bytes that were readed from the current file
*/
private int bytesReaded;
/**
* Boolean that indicates if the player is currently playing
*/
private boolean isPlaying;
/**
* Boolean that indicates if the player is currently paused
*/
private boolean isPaused;
/**
* The current value of the volume.
* starts the volume at average
*/
private float volume = 50;
/**
* Observers of the MuVisPlayer
*/
private ArrayList<Observer> observers;
/**
* String that represents the file currently being played
*/
private String filePlayling;
/**
* This field is used to be passed to the observers, so they could know
* what's going on in the player
*/
private Event event;
public MuVisAudioPlayer() {
player = new BasicPlayer();
control = (BasicController) player;
//for receiving updates
player.addBasicPlayerListener(this);
isPlaying = false;
isPaused = false;
filePlayling = "";
event = Event.NOTHING;
//creating the list for the observers
observers = new ArrayList<Observer>();
}
/**
* Method that returns a boolean indicating if the track is being played
* @return
*/
public boolean isPlaying() {
return isPlaying;
}
/**
* Method that returns a boolean indicating if the player is paused
* @return
*/
public boolean isPaused() {
return isPaused;
}
/**
* This method plays the music indicated by the parameter, that should include the absolute path
* in the filename
* @param filename - the string that represents the file
* @throws BasicPlayerException
*/
public void play(String filename) throws BasicPlayerException {
if (!isPlaying && !isPaused) {
control.open(new File(filename));
// Start playback in a thread.
control.play();
filePlayling = filename;
//setting the volume
if (volume > 1) {
volume /= 100;
}
control.setGain(volume);
isPlaying = true;
isPaused = false;
event = Event.NEW_TRACK_PLAYING;
updateObservers();
} else if (isPaused) {
resume();
event = Event.RESUMED;
updateObservers();
} else {
pause();
event = Event.PAUSED;
updateObservers();
}
}
public void play(byte[] fileBytes) throws BasicPlayerException {
if (!isPlaying && !isPaused) {
control.open(new ByteArrayInputStream(fileBytes));
// Start playback in a thread.
control.play();
//setting the volume
if (volume > 1) {
volume /= 100;
}
control.setGain(volume);
isPlaying = true;
isPaused = false;
event = Event.NEW_TRACK_PLAYING;
updateObservers();
} else if (isPaused) {
resume();
event = Event.RESUMED;
updateObservers();
} else {
pause();
event = Event.PAUSED;
updateObservers();
}
}
/**
* Method for pausing the player
* @throws BasicPlayerException
*/
public void pause() throws BasicPlayerException {
if (isPlaying) {
control.pause();
isPaused = true;
isPlaying = false;
event = Event.PAUSED;
updateObservers();
}
}
/**
* Stops the player if it's playing, otherwise don't execute any action
* @throws BasicPlayerException
*/
public void stop() throws BasicPlayerException {
control.stop();
isPlaying = false;
isPaused = false;
filePlayling = "";
event = Event.STOPPED;
updateObservers();
}
/**
* Open callback, stream is ready to play.
*
* properties map includes audio format dependant features such as
* bitrate, duration, frequency, channels, number of frames, vbr flag,
* id3v2/id3v1 (for MP3 only), comments (for Ogg Vorbis), ...
*
* @param stream could be File, URL or InputStream
* @param properties audio stream properties.
*/
@Override
public void opened(Object stream, Map properties) {
// Pay attention to properties. It's useful to get duration,
// bitrate, channels, even tag such as ID3v2.
System.out.println("opened : " + properties.toString());
//for now just saving the properties
fileProperties = properties;
event = Event.OPENED;
}
/**
* Progress callback while playing.
*
* This method is called severals time per seconds while playing.
* properties map includes audio format features such as
* instant bitrate, microseconds position, current frame number, ...
*
* @param bytesread from encoded stream.
* @param microseconds elapsed (<b>reseted after a seek !</b>).
* @param pcmdata PCM samples.
* @param properties audio stream parameters.
*/
@Override
public void progress(int bytesread, long microseconds, byte[] pcmdata, Map properties) {
bytesReaded = bytesread;
playingProperties = properties;
}
/**
* Notification callback for basicplayer events such as opened, eom ...
*
* @param event
*/
@Override
public void stateUpdated(BasicPlayerEvent event) {
//nothing here to do
if (event.getCode() == BasicPlayerEvent.STOPPED) {
isPlaying = false;
isPaused = false;
filePlayling = "";
this.event = Event.STOPPED;
updateObservers();
}
}
/**
* A handle to the BasicPlayer, plugins may control the player through
* the controller (play, stop, ...)
* @param controller : a handle to the player
*/
@Override
public void setController(BasicController controller) {
control = controller;
}
/**
* This allow the user to skip a part of the music and place it wherever he
* wants
* @param millis
* @throws BasicPlayerException
*/
public void seek(int millis) throws BasicPlayerException {
long trackDuration = Long.parseLong(fileProperties.get("duration").toString()) / 1000;
trackDuration /= 1000;
long mp3LenghtBytes = Long.parseLong(fileProperties.get("mp3.length.bytes").toString());
long value = millis * mp3LenghtBytes;
value = value / trackDuration;
player.seek(value);
event = Event.SEEKED;
updateObservers();
}
/**
* This method retrieves the total time of the track (the length of the track)
* @return the length of the track
*/
public int getTrackTotalTime() {
return (Integer) fileProperties.get("duration");
}
/**
* This method retrieves the position of the track is being played -
* the time passed since the beginning of the track
* @return the time position of the track is being played
*/
public int getPlayerTime() {
return (Integer) playingProperties.get("mp3.position.byte");
}
/**
* This method sets the actual volume of the player, based on a scale, useful for conversions between
* different ranges.
* @param value The new value of the volume
*/
public void setVolume(float value) throws BasicPlayerException {
if (player != null) {
if (player.hasGainControl()) {
//map the entrance volume to the volume range
volume = value / 100;
player.setGain(volume);
event = Event.VOLUME_CHANGED;
updateObservers();
}
}
}
/**
* This method allows to inspect the value of the volume of the player.
* If the volume is not available it return Float.MAX_VALUE
* @return The current value of the volume, or Float.MAX_VALUE if not available
*/
public float getVolume() {
if (player != null) {
if (player.hasGainControl()) {
//map the entrance volume to the volume range
volume = player.getGainValue();
}
}
return volume;
}
private void resume() throws BasicPlayerException {
control.resume();
isPlaying = true;
isPaused = false;
event = Event.RESUMED;
updateObservers();
}
/**
* Method that returns the string that represents the file being played
* @return the filePlayling
*/
public String getFilePlayling() {
return filePlayling;
}
/* Methods for the observer pattern */
/**
* Register an observer to listen to this player
* @param obs
*/
@Override
public void registerObserver(Observer obs) {
observers.add(obs);
}
/**
* Unregister observer to stop listening to this player
* @param obs
*/
@Override
public void unregisterObserver(Observer obs) {
if (observers.contains(obs)) {
observers.remove(obs);
}
}
/**
* Notify all the observers that this player has changed
*/
@Override
public void updateObservers() {
for (Observer obs : observers) {
obs.update(this, event);
}
}
}