package org.limewire.ui.swing.player; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.api.library.LocalFileItem; import org.limewire.player.api.AudioPlayer; import org.limewire.player.api.AudioPlayerEvent; import org.limewire.player.api.AudioPlayerListener; import org.limewire.player.api.AudioSource; import org.limewire.player.api.PlayerState; import org.limewire.ui.swing.library.LibraryMediator; import org.limewire.ui.swing.util.I18n; import org.limewire.ui.swing.util.NativeLaunchUtils; import org.limewire.util.FileUtils; import ca.odell.glazedlists.EventList; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; /** * Mediator that controls the interaction between the player view, the current * playlist, and the audio player. */ @Singleton class AudioPlayerMediator implements PlayerMediator { public static final String AUDIO_LENGTH_BYTES = "audio.length.bytes"; public static final String AUDIO_TYPE = "audio.type"; private static final String MP3 = "mp3"; private static final String WAVE = "wave"; private final Provider<AudioPlayer> audioPlayerProvider; private final LibraryMediator libraryMediator; private final List<PlayerMediatorListener> listenerList; private final CategoryManager categoryManager; private final Playlist playlist; /** Audio player component. */ private AudioPlayer audioPlayer; /** File item for the last opened song. */ private LocalFileItem fileItem = null; /** Map containing properties for the last opened song. */ private Map audioProperties = null; /** Progress of current song from 0.0 to 1.0. */ private float progress; private long playingWindowStartTime = -1; private int playingSwitches = -1; /** * Constructs a PlayerMediator using the specified services. */ @Inject public AudioPlayerMediator(Provider<AudioPlayer> audioPlayerProvider, LibraryMediator libraryMediator, CategoryManager categoryManager) { this.audioPlayerProvider = audioPlayerProvider; this.libraryMediator = libraryMediator; this.categoryManager = categoryManager; this.listenerList = new ArrayList<PlayerMediatorListener>(); this.playlist = new Playlist(); } /** * Returns the audio player component. When first called, this method * creates the component and registers this mediator as a listener. */ private AudioPlayer getPlayer() { if (audioPlayer == null) { audioPlayer = audioPlayerProvider.get(); audioPlayer.addAudioPlayerListener(new PlayerListener()); } return audioPlayer; } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#addMediatorListener(org.limewire.ui.swing.player.PlayerMediatorListener) */ public void addMediatorListener(PlayerMediatorListener listener) { listenerList.add(listener); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#removeMediatorListener(org.limewire.ui.swing.player.PlayerMediatorListener) */ public void removeMediatorListener(PlayerMediatorListener listener) { listenerList.remove(listener); } /** * Notifies registered listeners that the progress is updated to the * specified value. */ private void fireProgressUpdated(float progress) { for (int i = 0, size = listenerList.size(); i < size; i++) { listenerList.get(i).progressUpdated(progress); } } /** * Notifies registered listeners that the song is changed to the specified * song name. */ private void fireSongChanged(String name) { for (int i = 0, size = listenerList.size(); i < size; i++) { listenerList.get(i).mediaChanged(name); } } /** * Notifies registered listeners that the player state is changed to the * specified state. */ private void fireStateChanged(PlayerState state) { for (int i = 0, size = listenerList.size(); i < size; i++) { listenerList.get(i).stateChanged(state); } } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#getStatus() */ @Override public PlayerState getStatus() { return getPlayer().getStatus(); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#isShuffle() */ @Override public boolean isShuffle() { return playlist.isShuffle(); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#setShuffle(boolean) */ @Override public void setShuffle(boolean shuffle) { playlist.setShuffle(shuffle); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#setVolume(double) */ @Override public void setVolume(double value) { getPlayer().setVolume((float)value); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#pause() */ @Override public void pause() { getPlayer().pause(); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#resume() */ @Override public void resume() { PlayerState status = getPlayer().getStatus(); if ((status == PlayerState.STOPPED || status == PlayerState.UNKNOWN) && fileItem != null) { //resume the file that was stopped play(fileItem); } else if ((status == PlayerState.STOPPED) || (status == PlayerState.UNKNOWN)) { // Get first selected item. List<LocalFileItem> selectedItems = libraryMediator.getSelectedItems(); if (selectedItems.size() > 0) { LocalFileItem selectedItem = selectedItems.get(0); if (isPlayable(selectedItem.getFile())) { // Set active playlist and play file item. setActivePlaylist(libraryMediator.getPlayableList()); play(selectedItem); } } } else { getPlayer().unpause(); } } @Override public void setActivePlaylist(EventList<LocalFileItem> fileList) { playlist.setActivePlaylist(fileList); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#play(java.io.File) */ public void play(File file) { // Stop current song. stop(); // Play new song. this.fileItem = null; playlist.setActivePlaylist(null); loadAndPlay(file); } @Override public void playOrLaunchNatively(File file) { if(isPlayable(file)) { play(file); } else { NativeLaunchUtils.safeLaunchFile(file, categoryManager); } } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#play(org.limewire.core.api.library.LocalFileItem) */ @Override public void play(LocalFileItem localFileItem) { // Stop current song. stop(); // Play new song. this.fileItem = localFileItem; playlist.setCurrentItem(localFileItem); loadAndPlay(localFileItem.getFile()); } private void loadAndPlay(File fileToPlay) { AudioPlayer player = getPlayer(); player.loadSong(fileToPlay); player.playSong(); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#skip(double) */ @Override public void seek(double percent) { // need to know something about the audio type to be able to skip if (audioProperties != null && audioProperties.containsKey(AUDIO_TYPE)) { String songType = (String) audioProperties.get(AUDIO_TYPE); // currently, only mp3 and wav files can be seeked upon if (isSeekable(songType) && audioProperties.containsKey(AUDIO_LENGTH_BYTES)) { final long skipBytes = Math.round((Integer) audioProperties.get(AUDIO_LENGTH_BYTES)* percent); getPlayer().seekLocation(skipBytes); } } } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#stop() */ @Override public void stop() { getPlayer().stop(); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#nextSong() */ @Override public void nextSong() { // Stop current song. stop(); // Get next file item. fileItem = playlist.getNextFileItem(); // Play song. if (fileItem != null) { loadAndPlay(fileItem.getFile()); } } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#prevSong() */ @Override public void prevSong() { // Stop current song. stop(); fileItem = playlist.getPrevFileItem(); // Play song. if (fileItem != null) { loadAndPlay(fileItem.getFile()); } } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#getCurrentSongFile() */ @Override public File getCurrentMediaFile() { AudioSource source = getPlayer().getCurrentSong(); return (source != null) ? source.getFile() : null; } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#isPlaying(java.io.File) */ @Override public boolean isPlaying(File file) { return getPlayer().isPlaying(file); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#isPaused(java.io.File) */ @Override public boolean isPaused(File file) { return getPlayer().isPaused(file); } /* (non-Javadoc) * @see org.limewire.ui.swing.player.IPlayerMediator#isSeekable() */ @Override public boolean isSeekable() { if (audioProperties != null) { return isSeekable((String) audioProperties.get(AUDIO_TYPE)); } return false; } @Override public boolean isPlayable(File file) { String name = file.getName().toLowerCase(Locale.US); return name.endsWith(".mp3") || name.endsWith(".ogg") || name.endsWith(".wav"); } @Override public boolean hasVolumeControl() { return true; } /** * Returns true if the specified song type is seekable, which means that * the progress position can be set. At present, only MP3 and Wave files * are seekable. */ private boolean isSeekable(String songType) { if (songType == null) { return false; } return songType.equalsIgnoreCase(MP3) || songType.equalsIgnoreCase(WAVE); } /** * Returns the name of the current song. */ private String getSongName() { // Use audio properties if available. if (audioProperties != null) { Object author = audioProperties.get("author"); Object title = audioProperties.get("title"); if ((author != null) && (title != null)) { return author + " - " + title; } } // Use file item if available. if (fileItem != null) { return fileItem.getFile().getName(); } else if(getCurrentMediaFile() != null) { return FileUtils.getFilenameNoExtension(getCurrentMediaFile().getName()); } else { return I18n.tr("Unknown"); } } /** * Listener to handle audio player events. */ private class PlayerListener implements AudioPlayerListener { @Override public void progressChange(int bytesread) { // If we know the length of the song, update progress value. if ((audioProperties != null) && audioProperties.containsKey(AUDIO_LENGTH_BYTES)) { float byteslength = ((Integer) audioProperties.get(AUDIO_LENGTH_BYTES)).floatValue(); progress = bytesread / byteslength; // Notify UI about progress. fireProgressUpdated(progress); } } @Override public void songOpened(Map<String, Object> properties) { // Save properties. audioProperties = properties; // Notify UI about new song. fireSongChanged(getSongName()); } @Override public void stateChange(AudioPlayerEvent event) { // Go to next song when finished. if (event.getState() == PlayerState.EOM) { // Sanity check before switching to the next song, // the last 10 songs that switched must have taken over // 5 seconds to play. playingSwitches = (playingSwitches+1); if (playingSwitches % 10 == 0) { if(playingSwitches == 0) { playingWindowStartTime = System.currentTimeMillis(); nextSong(); } else { long currentTime = System.currentTimeMillis(); if(currentTime - playingWindowStartTime < 5000) { playingSwitches = -1; playingWindowStartTime = -1; } else { playingSwitches = -1; playingWindowStartTime = currentTime; nextSong(); } } } else { nextSong(); } } // Notify UI about state change. fireStateChanged(event.getState()); } } }