com.frostwire.gui.player.MediaPlayer.java Source code

Java tutorial

Introduction

Here is the source code for com.frostwire.gui.player.MediaPlayer.java

Source

/*
 * Created by Angel Leon (@gubatron), Alden Torres (aldenml)
 * Copyright (c) 2011-2014, FrostWire(R). All rights reserved.
 *
 * This program 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.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.frostwire.gui.player;

import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;

import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.limewire.concurrent.ExecutorsHelper;
import org.limewire.util.FileUtils;
import org.limewire.util.OSUtils;

import com.coremedia.iso.BoxParser;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.PropertyBoxParserImpl;
import com.coremedia.iso.boxes.Box;
import com.coremedia.iso.boxes.Container;
import com.frostwire.alexandria.Playlist;
import com.frostwire.alexandria.PlaylistItem;
import com.frostwire.gui.library.LibraryMediator;
import com.frostwire.gui.library.tags.TagsReader;
import com.frostwire.gui.mplayer.MPlayer;
import com.frostwire.mplayer.IcyInfoListener;
import com.frostwire.mplayer.MediaPlaybackState;
import com.frostwire.mplayer.PositionListener;
import com.frostwire.mplayer.StateListener;
import com.googlecode.mp4parser.AbstractBox;
import com.googlecode.mp4parser.DataSource;
import com.googlecode.mp4parser.FileDataSourceImpl;
import com.limegroup.gnutella.MediaType;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.gui.MPlayerMediator;
import com.limegroup.gnutella.gui.RefreshListener;
import com.limegroup.gnutella.settings.PlayerSettings;

/**
 * An media player to play compressed and uncompressed media.
 * 
 * @author gubatron
 * @author aldenml
 * 
 */
public abstract class MediaPlayer implements RefreshListener, MPlayerUIEventListener {

    private static final String[] PLAYABLE_EXTENSIONS = new String[] { "mp3", "ogg", "wav", "wma", "wmv", "m4a",
            "aac", "flac", "mp4", "flv", "avi", "mov", "mkv", "mpg", "mpeg", "3gp", "m4v", "webm" };

    /**
     * Our list of MediaPlayerListeners that are currently listening for events
     * from this player
     */
    private List<MediaPlayerListener> listenerList = new CopyOnWriteArrayList<MediaPlayerListener>();

    private MPlayer mplayer;
    private MediaSource currentMedia;
    private Playlist currentPlaylist;
    private MediaSource[] playlistFilesView;
    private RepeatMode repeatMode;
    private boolean shuffle;
    private boolean playNextMedia;

    private double volume;

    private Queue<MediaSource> lastRandomFiles;

    private final ExecutorService playExecutor;

    private static MediaPlayer instance;

    private long durationInSeconds;
    private boolean isPlayPausedForSliding = false;
    private boolean stateNotificationsEnabled = true;

    public static MediaPlayer instance() {
        if (instance == null) {
            if (OSUtils.isWindows()) {
                instance = new MediaPlayerWindows();
            } else if (OSUtils.isMacOSX()) {
                instance = new MediaPlayerOSX();
            } else if (OSUtils.isLinux()) {
                instance = new MediaPlayerLinux();
            }
        }

        return instance;
    }

    protected MediaPlayer() {
        lastRandomFiles = new LinkedList<MediaSource>();
        playExecutor = ExecutorsHelper.newProcessingQueue("AudioPlayer-PlayExecutor");

        String playerPath;
        playerPath = getPlayerPath();

        MPlayer.initialise(new File(playerPath));
        mplayer = new MPlayer();
        mplayer.addPositionListener(new PositionListener() {
            public void positionChanged(float currentTimeInSecs) {
                notifyProgress(currentTimeInSecs);
            }
        });
        mplayer.addStateListener(new StateListener() {
            public void stateChanged(MediaPlaybackState newState) {
                if (newState == MediaPlaybackState.Closed) { // This is the case
                                                             // mplayer is
                                                             // done with the
                                                             // current file
                    playNextMedia();
                }
            }
        });
        mplayer.addIcyInfoListener(new IcyInfoListener() {
            public void newIcyInfoData(String data) {
                notifyIcyInfo(data);
            }
        });

        repeatMode = RepeatMode.values()[PlayerSettings.LOOP_PLAYLIST.getValue()];
        shuffle = PlayerSettings.SHUFFLE_PLAYLIST.getValue();
        playNextMedia = true;
        volume = PlayerSettings.PLAYER_VOLUME.getValue();
        notifyVolumeChanged();

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {

            @Override
            public boolean dispatchKeyEvent(KeyEvent e) {
                if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_SPACE) {
                    Object s = e.getComponent();
                    if (!(s instanceof JTextField)
                            && !(s instanceof JTable && ((JTable) s).isEditing() && !(s instanceof JCheckBox))) {
                        togglePause();
                        return true;
                    }
                }
                return false;
            }
        });

        // prepare to receive UI events
        MPlayerUIEventHandler.instance().addListener(this);
    }

    protected abstract String getPlayerPath();

    protected float getVolumeGainFactor() {
        return 100.0f;
    }

    public Dimension getCurrentVideoSize() {
        if (mplayer != null) {
            return mplayer.getVideoSize();
        } else {
            return null;
        }
    }

    public MediaSource getCurrentMedia() {
        return currentMedia;
    }

    public Playlist getCurrentPlaylist() {
        return currentPlaylist;
    }

    public MediaSource[] getPlaylistFilesView() {
        return playlistFilesView;
    }

    public RepeatMode getRepeatMode() {
        return repeatMode;
    }

    public void setRepeatMode(RepeatMode repeatMode) {
        this.repeatMode = repeatMode;
        PlayerSettings.LOOP_PLAYLIST.setValue(repeatMode.getValue());
    }

    public boolean isShuffle() {
        return shuffle;
    }

    public void setShuffle(boolean shuffle) {
        this.shuffle = shuffle;
        PlayerSettings.SHUFFLE_PLAYLIST.setValue(shuffle);
    }

    /**
     * Adds the specified MediaPlayer listener to the list
     */
    public void addMediaPlayerListener(MediaPlayerListener listener) {
        listenerList.add(listener);
    }

    /**
     * Removes the specified MediaPlayer listener from the list
     */
    public void removeMediaPlayerListener(MediaPlayerListener listener) {
        listenerList.remove(listener);
    }

    public MediaPlaybackState getState() {
        return mplayer.getCurrentState();
    }

    /**
     * Loads a MediaSource into the player to play next
     */
    public void loadMedia(MediaSource source, boolean play, boolean playNextSong, Playlist currentPlaylist,
            List<MediaSource> playlistFilesView) {
        try {
            if (source == null) {
                return;
            }

            if (PlayerSettings.USE_OS_DEFAULT_PLAYER.getValue()) {
                playInOS(source);
                return;
            }

            currentMedia = source;
            this.playNextMedia = playNextSong;
            this.currentPlaylist = currentPlaylist;

            if (playlistFilesView != null) {
                this.playlistFilesView = playlistFilesView.toArray(new MediaSource[playlistFilesView.size()]);
            } else {
                this.playlistFilesView = null;
            }

            if (play && currentMedia != null) {
                durationInSeconds = -1;

                if (currentMedia.getFile() != null) {
                    LibraryMediator.instance().getLibraryCoverArt().setFile(currentMedia.getFile());
                    calculateDurationInSecs(currentMedia.getFile());
                    playMedia();
                } else if (currentMedia.getPlaylistItem() != null
                        && currentMedia.getPlaylistItem().getFilePath() != null) {
                    LibraryMediator.instance().getLibraryCoverArt()
                            .setFile(new File(currentMedia.getPlaylistItem().getFilePath()));
                    playMedia();
                    durationInSeconds = (long) currentMedia.getPlaylistItem().getTrackDurationInSecs();
                } else if (currentMedia instanceof InternetRadioAudioSource) {
                    LibraryMediator.instance().getLibraryCoverArt().setDefault();
                    playMedia(false);
                } else if (currentMedia instanceof StreamMediaSource) {
                    LibraryMediator.instance().getLibraryCoverArt().setDefault();
                    playMedia(((StreamMediaSource) currentMedia).showPlayerWindow());
                } else if (currentMedia instanceof DeviceMediaSource) {
                    LibraryMediator.instance().getLibraryCoverArt().setDefault();
                    playMedia(((DeviceMediaSource) currentMedia).showPlayerWindow());
                }
                notifyOpened(source);
            }
        } catch (Throwable e) {
            // NPE from bug report
            e.printStackTrace();
        }
    }

    private void calculateDurationInSecs(File f) {
        String ext = FilenameUtils.getExtension(f.getName());
        if (ext == null || !ext.toLowerCase().endsWith("mp3") || !ext.toLowerCase().endsWith("m4a")) {
            durationInSeconds = -1;
            return;
        }

        if (ext.toLowerCase().endsWith("mp3")) {
            durationInSeconds = getDurationFromMP3(f);
        } else if (ext.toLowerCase().endsWith("m4a")) {
            durationInSeconds = getDurationFromM4A(f);
        }
    }

    private long getDurationFromMP3(File f) {
        try {
            return new TagsReader(f).parse().getDuration();
        } catch (Throwable e) {
            return -1;
        }
    }

    private long getDurationFromM4A(File f) {
        try {
            BoxParser parser = new PropertyBoxParserImpl() {
                @Override
                public Box parseBox(DataSource byteChannel, Container parent) throws IOException {
                    Box box = super.parseBox(byteChannel, parent);

                    if (box instanceof AbstractBox) {
                        ((AbstractBox) box).parseDetails();
                    }

                    return box;
                }
            };
            IsoFile isoFile = new IsoFile(new FileDataSourceImpl(f), parser);

            try {
                return isoFile.getMovieBox().getMovieHeaderBox().getDuration()
                        / isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
            } finally {
                IOUtils.closeQuietly(isoFile);
            }
        } catch (Throwable e) {
            return -1;
        }
    }

    public void asyncLoadMedia(final MediaSource source, final boolean play, final boolean playNextSong,
            final Playlist currentPlaylist, final List<MediaSource> playlistFilesView) {
        playExecutor.execute(new Runnable() {
            public void run() {
                loadMedia(source, play, playNextSong, currentPlaylist, playlistFilesView);
            }
        });
    }

    public void loadMedia(MediaSource source, boolean play, boolean playNextSong) {
        loadMedia(source, play, playNextSong, currentPlaylist,
                (playlistFilesView != null) ? Arrays.asList(playlistFilesView) : null);
    }

    public void asyncLoadMedia(final MediaSource source, final boolean play, final boolean playNextSong) {
        playExecutor.execute(new Runnable() {
            public void run() {
                loadMedia(source, play, playNextSong);
            }
        });
    }

    private String stopAndPrepareFilename() {
        String filename = "";

        try {
            mplayer.stop();
            setVolume(volume);

            if (currentMedia != null) {
                if (currentMedia.getFile() != null) {
                    filename = currentMedia.getFile().getAbsolutePath();
                } else if (currentMedia.getURL() != null) {
                    filename = currentMedia.getURL().toString();
                } else if (currentMedia.getPlaylistItem() != null) {
                    filename = currentMedia.getPlaylistItem().getFilePath();
                }
            }
        } catch (Throwable e) {
            e.printStackTrace(); // one more NPE
        }

        return filename;
    }

    /** Force showing or not the media player window */
    public void playMedia(boolean showPlayerWindow) {
        String filename = stopAndPrepareFilename();

        if (filename.length() > 0) {
            MPlayerMediator mplayerMediator = MPlayerMediator.instance();

            if (mplayerMediator != null) {
                mplayerMediator.showPlayerWindow(showPlayerWindow);
            }

            mplayer.open(filename, getAdjustedVolume());
        }

        notifyState(getState());
    }

    /**
     * Plays a file and determines whether or not to show the player window based on the MediaType of the file.
     */
    public void playMedia() {

        String filename = stopAndPrepareFilename();

        if (filename.length() > 0) {
            boolean isVideoFile = MediaType.getVideoMediaType().matches(filename);
            MPlayerMediator mplayerMediator = MPlayerMediator.instance();

            if (mplayerMediator != null) {
                mplayerMediator.showPlayerWindow(isVideoFile);
            }

            mplayer.open(filename, getAdjustedVolume());
        }

        notifyState(getState());
    }

    /**
     * Toggle pause the current song
     */
    public void togglePause() {
        mplayer.togglePause();
        notifyState(getState());
    }

    /**
     * Stops the current song
     */
    public void stop() {
        mplayer.stop();
        currentMedia = null;
        notifyState(getState());
    }

    public void fastForward() {
        mplayer.fastForward();
    }

    public void rewind() {
        mplayer.rewind();
    }

    /**
     * Seeks to a new location in the current song
     */
    public void seek(float timeInSecs) {
        mplayer.seek(timeInSecs);
        notifyState(getState());
    }

    /**
     * Sets the gain(volume) for the outputline
     * 
     * @param gain
     *            - [0.0 <-> 1.0]
     * @throws IOException
     *             - thrown when the soundcard does not support this operation
     */
    public void setVolume(double fGain) {

        volume = Math.max(Math.min(fGain, 1.0), 0.0);
        mplayer.setVolume(getAdjustedVolume());
        PlayerSettings.PLAYER_VOLUME.setValue((float) volume);
        notifyVolumeChanged();
    }

    private int getAdjustedVolume() {
        return (int) (volume * getVolumeGainFactor());
    }

    public double getVolume() {
        return volume;
    }

    public void incrementVolume() {
        setVolume(getVolume() + 0.1);
    }

    public void decrementVolume() {
        setVolume(getVolume() - 0.1);
    }

    protected void notifyVolumeChanged() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireVolumeChanged(volume);
            }
        });
    }

    public static boolean isPlayableFile(File file) {
        return file.exists() && !file.isDirectory() && isPlayableFile(file.getAbsolutePath());
    }

    public static boolean isPlayableFile(String filename) {
        return FileUtils.hasExtension(filename, getPlayableExtensions());
    }

    public static String[] getPlayableExtensions() {
        return PLAYABLE_EXTENSIONS;
    }

    public static boolean isPlayableFile(MediaSource mediaSource) {
        if (mediaSource == null) {
            return false;
        } else if (mediaSource.getFile() != null) {
            return mediaSource.getFile().exists() && isPlayableFile(mediaSource.getFile());
        } else if (mediaSource.getPlaylistItem() != null) {
            return new File(mediaSource.getPlaylistItem().getFilePath()).exists()
                    && isPlayableFile(mediaSource.getPlaylistItem().getFilePath());
        } else if (mediaSource instanceof InternetRadioAudioSource) {
            return true;
        } else if (mediaSource instanceof StreamMediaSource) {
            return true;
        } else if (mediaSource instanceof DeviceMediaSource) {
            return isPlayableFile(((DeviceMediaSource) mediaSource).getFileDescriptor().filePath);
        } else {
            return false;
        }
    }

    /**
     * Notify listeners when a new audio source has been opened.
     * 
     * @param properties
     *            - any properties about the source that we extracted
     */
    protected void notifyOpened(final MediaSource mediaSource) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireOpened(mediaSource);
            }
        });
    }

    /**
     * Notify listeners about an AudioPlayerEvent. This creates general state
     * modifications to the player such as the transition from opened to playing
     * to paused to end of song.
     * 
     * @param code
     *            - the type of player event.
     * @param position
     *            in the stream when the event occurs.
     * @param value
     *            if the event was a modification such as a volume update, list
     *            the new value
     */
    protected void notifyState(final MediaPlaybackState state) {

        if (stateNotificationsEnabled) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    fireState(state);
                }
            });
        }
    }

    /**
     * fires a progress event off a new thread. This lets us safely fire events
     * off of the player thread while using a lock on the input stream
     */
    protected void notifyProgress(final float currentTimeInSecs) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireProgress(currentTimeInSecs);
            }
        });
    }

    protected void notifyIcyInfo(final String data) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireIcyInfo(data);
            }
        });
    }

    /**
     * This is fired every time a new song is loaded and ready to play. The
     * properties map contains information about the type of song such as bit
     * rate, sample rate, media type(MPEG, Streaming,etc..), etc..
     */
    protected void fireOpened(MediaSource mediaSource) {
        for (MediaPlayerListener listener : listenerList) {
            listener.mediaOpened(this, mediaSource);
        }
    }

    /**
     * Fired every time a byte stream is written to the sound card. This lets
     * listeners be aware of what point in the entire file is song is currently
     * playing. This also returns a copy of the written byte[] so it can get
     * passed along to objects such as a FFT for visual feedback of the song
     */
    protected void fireProgress(float currentTimeInSecs) {
        for (MediaPlayerListener listener : listenerList) {
            listener.progressChange(this, currentTimeInSecs);
        }
    }

    protected void fireVolumeChanged(double currentVolume) {
        for (MediaPlayerListener listener : listenerList) {
            listener.volumeChange(this, currentVolume);
        }
    }

    /**
     * Fired every time the state of the player changes. This allows a listener
     * to be aware of state transitions such as from OPENED -> PLAYING ->
     * STOPPED -> EOF
     */
    protected void fireState(MediaPlaybackState state) {
        for (MediaPlayerListener listener : listenerList) {
            listener.stateChange(this, state);
        }
    }

    protected void fireIcyInfo(String data) {
        for (MediaPlayerListener listener : listenerList) {
            listener.icyInfo(this, data);
        }
    }

    /**
     * returns the current state of the player and position of the song being
     * played
     */
    public void refresh() {
        notifyState(getState());
    }

    public void playNextMedia() {
        if (!playNextMedia) {
            return;
        }

        if (currentPlaylist != null && currentPlaylist.isDeleted()) {
            return;
        }

        MediaSource media = null;

        if (getRepeatMode() == RepeatMode.SONG) {
            media = currentMedia;
        } else if (isShuffle()) {
            media = getNextRandomSong(currentMedia);
        } else if (getRepeatMode() == RepeatMode.ALL) {
            media = getNextContinuousMedia(currentMedia);
        } else {
            media = getNextMedia(currentMedia);
        }

        if (media != null) {
            //System.out.println(song.getFile());
            asyncLoadMedia(media, true, true, currentPlaylist, Arrays.asList(playlistFilesView));
        }
    }

    private boolean isPlayerStoppedClosedFailed() {
        MediaPlaybackState state = getState();
        return state == MediaPlaybackState.Stopped || state == MediaPlaybackState.Closed
                || state == MediaPlaybackState.Failed;
    }

    public boolean isThisBeingPlayed(File file) {
        if (isPlayerStoppedClosedFailed()) {
            return false;
        }

        MediaSource currentMedia = getCurrentMedia();
        if (currentMedia == null) {
            return false;
        }

        File currentMediaFile = currentMedia.getFile();

        if (currentMediaFile != null && file.equals(currentMediaFile))
            return true;

        PlaylistItem playlistItem = currentMedia.getPlaylistItem();
        if (playlistItem != null && new File(playlistItem.getFilePath()).equals(file)) {
            return true;
        }

        return false;
    }

    public boolean isThisBeingPlayed(String file) {
        if (isPlayerStoppedClosedFailed()) {
            return false;
        }

        MediaSource currentMedia = getCurrentMedia();
        if (currentMedia == null) {
            return false;
        }

        String currentMediaUrl = currentMedia.getURL();

        if (currentMediaUrl != null && file.toLowerCase().equals(currentMediaUrl.toString().toLowerCase())) {
            return true;
        }

        return false;
    }

    public boolean isThisBeingPlayed(PlaylistItem playlistItem) {
        if (isPlayerStoppedClosedFailed()) {
            return false;
        }

        MediaSource currentMedia = getCurrentMedia();
        if (currentMedia == null) {
            return false;
        }

        PlaylistItem currentMediaFile = currentMedia.getPlaylistItem();

        if (currentMediaFile != null && playlistItem.equals(currentMediaFile))
            return true;

        return false;
    }

    public synchronized void setPlaylistFilesView(List<MediaSource> playlistFilesView) {
        this.playlistFilesView = playlistFilesView.toArray(new MediaSource[playlistFilesView.size()]);
    }

    public MediaSource getNextRandomSong(MediaSource currentMedia) {
        if (playlistFilesView == null) {
            return null;
        }

        MediaSource songFile;
        int count = 4;
        while ((songFile = findRandomMediaFile(currentMedia)) == null && count-- > 0)
            ;

        if (songFile != null) {
            if (count > 0) {
                lastRandomFiles.add(songFile);
                if (lastRandomFiles.size() > 3) {
                    lastRandomFiles.poll();
                }
            } else {
                songFile = currentMedia;
                lastRandomFiles.clear();
                lastRandomFiles.add(songFile);
            }
        }

        return songFile;
    }

    public MediaSource getNextContinuousMedia(MediaSource currentMedia) {
        if (playlistFilesView == null) {
            return null;
        }

        int n = playlistFilesView.length;
        if (n == 1) {
            return playlistFilesView[0];
        }

        for (int i = 0; i < n; i++) {
            try {
                MediaSource f1 = playlistFilesView[i];
                if (currentMedia.equals(f1)) {
                    for (int j = 1; j < n; j++) {
                        MediaSource file = playlistFilesView[(j + i) % n];
                        if (isPlayableFile(file) || file instanceof DeviceMediaSource) {
                            return file;
                        }
                    }
                }
            } catch (Exception e) {
                return null;
            }
        }

        return null;
    }

    public MediaSource getNextMedia(MediaSource currentMedia) {
        if (playlistFilesView == null) {
            return null;
        }

        int n = playlistFilesView.length;
        //        if (n == 1) {
        //            return playlistFilesView.get(0);
        //        }

        //PlaylistFilesView should probably have a HashTable<AudioSource,Integer>
        //Where the integer is the index of the AudioSource on playlistFilesView.
        //This way we could easily find the current song and know the index of the
        //next or previous song.
        //When you have lots of files, I think the search below might
        //be too slow.

        for (int i = 0; i < n; i++) {
            try {
                MediaSource f1 = playlistFilesView[i];
                if (currentMedia.equals(f1)) {
                    for (int j = i + 1; j < n; j++) {
                        MediaSource file = playlistFilesView[j];
                        if (isPlayableFile(file)) {
                            return file;
                        }
                    }
                }
            } catch (Exception e) {
                return null;
            }
        }

        return null;
    }

    public MediaSource getPreviousMedia(MediaSource currentMedia) {
        if (playlistFilesView == null) {
            return null;
        }

        int n = playlistFilesView.length;
        for (int i = 0; i < n; i++) {
            try {
                MediaSource f1 = playlistFilesView[i];
                if (currentMedia.equals(f1)) {
                    for (int j = i - 1; j >= 0; j--) {
                        MediaSource file = playlistFilesView[j];
                        if (isPlayableFile(file)) {
                            return file;
                        }
                    }
                }
            } catch (Exception e) {
                return null;
            }
        }

        return null;
    }

    private MediaSource findRandomMediaFile(MediaSource excludeFile) {
        if (playlistFilesView == null) {
            return null;
        }
        int n = playlistFilesView.length;

        if (n == 0) {
            return null;
        } else if (n == 1) {
            return playlistFilesView[0];
        }

        int index = new Random(System.currentTimeMillis()).nextInt(n);

        for (int i = index; i < n; i++) {
            try {
                MediaSource file = playlistFilesView[i];

                if (!lastRandomFiles.contains(file) && !file.equals(excludeFile) && isPlayableFile(file)) {
                    return file;
                }
            } catch (Exception e) {
                return null;
            }
        }

        return null;
    }

    public boolean canSeek() {
        if (durationInSeconds != -1) {
            return durationInSeconds > 0;
        }

        return mplayer.getDurationInSecs() > 0;
    }

    public float getDurationInSecs() {
        if (durationInSeconds != -1) {
            return durationInSeconds;
        }

        return mplayer.getDurationInSecs();
    }

    @Override
    public void onUIVolumeChanged(float volume) {
        setVolume(volume);
    }

    @Override
    public void onUISeekToTime(float seconds) {
        seek(seconds);
    }

    @Override
    public void onUIPlayPressed() {
        MediaPlaybackState curState = mplayer.getCurrentState();

        if (curState == MediaPlaybackState.Playing || curState == MediaPlaybackState.Paused) {
            togglePause();
        } else if (curState == MediaPlaybackState.Closed) {
            //playMedia();
            LibraryMediator.instance().playCurrentSelection();
        }
    }

    @Override
    public void onUIPausePressed() {
        togglePause();
    }

    @Override
    public void onUIFastForwardPressed() {
        fastForward();
    }

    @Override
    public void onUIRewindPressed() {
        rewind();
    }

    @Override
    public void onUIToggleFullscreenPressed() {
        MPlayerMediator.instance().toggleFullScreen();
    }

    @Override
    public void onUIProgressSlideStart() {
        stateNotificationsEnabled = false;

        if (mplayer.getCurrentState() == MediaPlaybackState.Playing) {
            isPlayPausedForSliding = true;
            mplayer.pause();
        }
    }

    @Override
    public void onUIProgressSlideEnd() {
        if (isPlayPausedForSliding) {
            isPlayPausedForSliding = false;
            mplayer.play();
        }

        stateNotificationsEnabled = true;
    }

    @Override
    public void onUIVolumeIncremented() {
        incrementVolume();
    }

    @Override
    public void onUIVolumeDecremented() {
        decrementVolume();
    }

    @Override
    public void onUITogglePlayPausePressed() {
        togglePause();
    }

    private void playInOS(MediaSource source) {
        if (source == null) {
            return;
        }

        if (source.getFile() != null) {
            GUIMediator.launchFile(source.getFile());
        } else if (source.getPlaylistItem() != null) {
            GUIMediator.launchFile(new File(source.getPlaylistItem().getFilePath()));
        } else if (source.getURL() != null) {
            GUIMediator.openURL(source.getURL());
        }
    }
}