package tk.baumi.jaraco;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import javax.swing.DefaultListModel;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.AudioDevice;
import javazoom.jl.player.advanced.AdvancedPlayer;
import javazoom.jl.player.advanced.PlaybackEvent;
import javazoom.jl.player.advanced.PlaybackListener;
import javazoom.jl.player.advanced.AdvancedPlayer.AmplitudeListener;
/**
* <p>
* The player is the complete class that provides all methods to play mp3-files.<br/>
* It also hosts the playlist, which is edited by the gui and the network.<br/>
* Moreover the player can exactly show and edit the current position.</p>
* @author Manuel Baumgartner
*
*/
public class JaracoPlayer{
private AdvancedPlayer p1;
private DefaultListModel<MusicFile> files = new DefaultListModel<MusicFile>();
private int currFile = 0, currPosition = 0;
private boolean repeat = false, playing = false;
private AudioDevice audio;
private File fCurrent;
private FileInputStream fisAudioFile;
private PlayingChangeListener playChange;
private PlayingChangeNetworkListener playChangeNetwork;
private OnPlayingListener playingListener;
private PlaylistChange playlistChange;
private OnAmplitudeChangeListener ampChange;
/**
* The complete length of the mp3-file.
* The maximum is given for supporing VBR.
*/
private int max_frames;
private float vol1 = 1;//, vol2 = 1;
/**
* The player runs in its own thread, also the slider.
*/
private Thread thPlay, thSlide;
private byte memAmp = -1;
public boolean isPlaying() {
return playing;
}
public void setVolume(float vol) {
vol1 = vol;
p1.setVolume(vol);
}
public interface PlayingChangeListener
{
/**
* Sends the current file to the GUI.
* @param currFile The current-file number of the list.
* @param filename The file-path.
* @param playing true if playback is running.
*/
public void changePlay(int currFile, String filename, boolean playing);
}
public interface PlayingChangeNetworkListener
{
public void changePlay(int currFile, String filename, boolean playing, int pos, int max);
}
public interface OnPlayingListener
{
/**
* Notifies the GUI when the position of the file has been changed.
* @param position The current position in frames
* @param max The maximum-amount of frames.
*/
public void onPlaying(int position, int max);
}
public interface PlaylistChange {
/**
* Gets the current playlist in a string-arraylist.
* @param playlist The playlist.
*/
public void getPlaylist(ArrayList<String> playlist);
}
public interface OnAmplitudeChangeListener{
/**
* Gets the amplitude of the current frame
* @param value The value between 0 and 5
*/
public void amplitudeChange(byte value);
}
/**
* The method for running playback on the sound-card.
*/
Runnable player = new Runnable() {
@Override
public void run() {
try {
p1.setVolume(vol1);
p1.play(currPosition, Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* The method for setting the current position each second.
*/
Runnable slide = new Runnable() {
@Override
public void run() {
try {
while(playing && playingListener != null) {
playingListener.onPlaying(p1.getFramePosition(), max_frames);
Thread.sleep(1000);
}
} catch(Exception e) {
}
}
};
/**
* The playback-listener tells the player if the current song has been
* finished and also if the playback has
*/
PlaybackListener playback = new PlaybackListener() {
/**
* When the playback starts it calculates the maximum number of frames.
*/
@Override
public void playbackStarted(PlaybackEvent evt){
////System.out.println("START");
Header h;
try {
h = p1.bitstream.readFrame();
max_frames = (int)h.max_number_of_frames((int)fCurrent.length());
//System.out.println(max_frames);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* When the playback has been finished it jumps to the next songs.
*/
@Override
public void playbackFinished(PlaybackEvent evt) {
try {
if(playing) {
currPosition = 0;
playNext();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
public void addFile(String file) {
addMusicFile(new MusicFile(file));
}
/**
* Adds a music file to the playlist.
* @param file The file must be an Music-file with an .mp3-ending.
*/
public void addMusicFile(MusicFile file) {
if(file.getExtension().equals("mp3")) {
files.addElement(file);
if(playlistChange != null) {
playlistChange.getPlaylist(playListToString());
}
} else {
/*
try {
String cmd = "ffmpeg -i \"" + file.getFilename() + "\" \"" + file.getFilename() + ".mp3\" -y";
System.out.println(cmd);
Process p = Runtime.getRuntime().exec(cmd);
p.waitFor();
if(p.exitValue() == 1) {
System.out.println(p.exitValue());
files.addElement(new MusicFile(file.getFilename() + ".mp3"));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
}
}
/**
* Removes a file from the playlist.
* @param index of the file to delete.
*/
public void removeFile(int index) {
if(index > currFile) {
files.remove(index);
} else if(index < currFile) {
files.remove(index);
currFile--;
} else if(index == currFile) {
stop();
files.remove(index);
}
if(files.size() > 0) {
playChange.changePlay(currFile, files.get(currFile).getFilename(),playing);
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(), playing, currPosition, 0);
}
}
if(playlistChange != null) {
playlistChange.getPlaylist(playListToString());
}
}
/**
* Swaps two files in the playlist.
* @param index1 The index of the first file.
* @param index2 The index of the second file.
*/
public void changePosition(int index1, int index2) {
MusicFile help = files.get(index1);
files.set(index1, files.get(index2));
files.set(index2, help);
if(index1 == currFile) {
currFile = index2;
}else if(index2 == currFile) {
currFile = index1;
}
playChange.changePlay(currFile, files.get(currFile).getFilename(),playing);
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(), playing, currPosition, max_frames);
}
if(playlistChange != null) {
playlistChange.getPlaylist(playListToString());
}
}
public void resetPosition() {
currPosition = 0;
}
/**
* Gets an the values of the current file.
* @return <p>
* An string-array of the following elements:
* <ul>
* <li>The index in the playlist of the current file.</li>
* <li>The complete path to the file</li>
* <li>t if playing, f if playing</li>
* </ul>
* </p>
*/
public String[] getCurrentFile() {
if(files.size() == 0) {
return null;
} else {
return new String[] {
String.valueOf(currFile),
files.get(currFile).getFilename(),
playing ? "t" : "f"
};
}
}
/**
* Starts the playback of the playerInstance.
* @return true if the playback has stopped, false if playing again.
* @throws IOException File can't be read.
* @throws JavaLayerException Music is e.g. not an valid mp3-file.
*/
public boolean play() throws IOException, JavaLayerException{
if(playing) {
playing = false;
currPosition = p1.getFramePosition();
p1.stop();
thPlay.interrupt();
thPlay = null;
thSlide.interrupt();
thSlide = null;
playChange.changePlay(currFile,files.get(currFile).getFilename() ,playing);
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(),
playing, currPosition, max_frames);
}
return true;
} else {
if(currFile < files.size() && currFile >= 0) {
playFile(currFile);
return false;
} else {
return true;
}
}
}
/**
* Plays the next file in the ArrayList file
* @return if the playlist is finished, true will be returned.
* @throws IOException
* @throws JavaLayerException
*/
public boolean playNext() throws IOException, JavaLayerException{
if(currFile >= files.size() - 1 && repeat) {
currFile = 0;
currPosition = 0;
playFile(currFile);
return false;
} else if(currFile >= files.size() - 1) {
playing = false;
currPosition = 0;
playChange.changePlay(currFile,files.get(currFile).getFilename(),playing);
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(),
playing, currPosition, max_frames);
}
return true;
} else {
currPosition = 0;
currFile++;
playFile(currFile);
return false;
}
}
/**
* Plays the song before the current in the list.
* @return if you have reached the upper end, true will be returned.
* @throws IOException
* @throws JavaLayerException
*/
public boolean playBefore() throws IOException, JavaLayerException{
if(currFile > 0) {
currFile--;
playFile(currFile);
return false;
} else {
return true;
}
}
/**
* Plays a specific File from the playlist.
* @param i The number of the playlist
* @return The name of the played file
* @throws IOException
* @throws JavaLayerException
*/
public String playFile(int i) throws IOException, JavaLayerException{
if(p1 != null) p1.close();
if(fisAudioFile != null) {
try {
fisAudioFile.close();
} catch (Exception ex) {}
fisAudioFile = null;
}
if(i < files.size()) {
fCurrent = new File(files.get(i).getFilename());
fisAudioFile = new FileInputStream(fCurrent);
playing = true;
p1 = new AdvancedPlayer(fisAudioFile);
p1.setVolume(1);
p1.setPlayBackListener(playback);
p1.setAmplitudeListener(new AmplitudeListener() {
@Override
public void getAmplitude(short amplitude) {
byte bamp = 0;
if(amplitude < 10 && amplitude >= 0) {
bamp = 0;
} else if(amplitude < 6400) {
bamp = 1;
} else if(amplitude < 12800) {
bamp = 2;
} else if(amplitude < 19200) {
bamp = 3;
} else if(amplitude < 25600) {
bamp = 4;
} else if(amplitude < 32000) {
bamp = 5;
} else {
bamp = 6;
}
if(bamp != memAmp) {
if(ampChange != null) {
ampChange.amplitudeChange(bamp);
}
memAmp = bamp;
}
}
});
thPlay = new Thread(player);
thPlay.start();
thSlide = new Thread(slide);
thSlide.start();
if(i != currFile) {
currFile = i;
}
if(playChange != null) {
playChange.changePlay(i, files.get(currFile).getFilename() ,true);
}
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(), true,
currPosition, max_frames);
}
return files.get(i).getFilename();
} else if(repeat == true) {
currFile = 0;
fCurrent = new File(files.get(i).getFilename());
FileInputStream fis = new FileInputStream(fCurrent);
playing = true;
p1 = new AdvancedPlayer(fis, audio);
p1.setPlayBackListener(playback);
thPlay = new Thread(player);
thPlay.start();
thSlide = new Thread(slide);
thSlide.start();
if(playChange != null) {
playChange.changePlay(currFile, files.get(currFile).getFilename() ,true);
}
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(), true,
currPosition, max_frames);
}
return files.get(currFile).getFilename();
} else {
return null;
}
}
/**
* Sets the position of playback<br/>
* normally called from the JSlider of the GUI.
* @param pos The position, normally the frame.
* @throws IOException IO-errors from playback.
* @throws JavaLayerException mp3-errors from playback.
*/
public void setPosition(int pos) throws IOException, JavaLayerException {
boolean cont = false;
if(playing) {
play();
cont = true;
}
currPosition = pos;
if(cont) {
try {
play();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JavaLayerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Stops the playback and sets the position back to zero.
*/
public void stop() {
if(playing) {
playing = false;
currPosition = 0;
////System.out.println(currPosition);
p1.stop();
thPlay.interrupt();
thPlay = null;
thSlide.interrupt();
thSlide = null;
playChange.changePlay(currFile, files.get(currFile).getFilename() ,playing);
if(playChangeNetwork != null) {
playChangeNetwork.changePlay(currFile, files.get(currFile).getFilename(), playing,
currPosition, max_frames);
}
}
}
public boolean isInitialized() {
if(p1 == null) {
return false;
} else {
return true;
}
}
public void setRepeat(boolean repeat) {
this.repeat = repeat;
}
public DefaultListModel<MusicFile> getList() {
return files;
}
public void setPlayingChangeNetworkListener(PlayingChangeNetworkListener playChangeNetwork) {
this.playChangeNetwork = playChangeNetwork;
}
public void setPlayingChangeListener(PlayingChangeListener playChange) {
this.playChange = playChange;
}
public void setOnPlayingListener(OnPlayingListener playingListener) {
this.playingListener = playingListener;
}
public void setPlaylistchange(PlaylistChange playlistchange) {
this.playlistChange = playlistchange;
}
/**
* Adds a vote to an song.
* @param songId The index in the playlist.
* @param client The IP-address of the client.
*/
public void voteSong(int songId, InetAddress client) {
if(songId < files.size()) {
files.get(songId).addVote(client);
voteInvalidate();
}
}
/**
* Calls the sort for the playlist and sends the new playlist to each user.
*/
public void voteInvalidate() {
int currSong = currFile;
if(currSong < files.size() - 1) {
sort(files, currSong + 1, files.size() - 1);
if(playlistChange != null) {
playlistChange.getPlaylist(playListToString());
}
}
}
/**
* A merge-sort implementation for sorting the playlist.
* @param songs The DefaultListModel for the playlist.
* @param start The starting-point (important for mergesort)
* @param end The ending-point (important for mergesort)
*/
public void sort(DefaultListModel<MusicFile> songs, int start, int end) {
if(start < end) {
int middle = (end + start) / 2;
sort(songs, start, middle);
sort(songs, middle + 1, end);
int i = start, j = middle + 1, k = start;
MusicFile[] help = new MusicFile[songs.size()];
for(; i <= end; i++) {
help[i] = songs.get(i);
}
i = start;
while(i <= middle && j <= end) {
if(help[i].compareTo(help[j]) >= 0) {
songs.set(k++, help[i++]);
} else {
songs.set(k++, help[j++]);
}
}
while(i <= middle) {
songs.set(k++, help[i++]);
}
}
}
/**
* Gets an array-list of string-representations of the playlist.
* @return ArrayList of the whole playlist.
*/
public ArrayList<String> playListToString() {
ArrayList<String> arPlaylist = new ArrayList<String>();
for(int i = 0; i < files.size(); i++) {
arPlaylist.add(files.get(i).getName() + "(" + files.get(i).getVotes() +")");
}
return arPlaylist;
}
public void setAmplitudeChangeListener(OnAmplitudeChangeListener ampChange) {
this.ampChange = ampChange;
}
}