Package uk.co.caprica.vlcj.player

Source Code of uk.co.caprica.vlcj.player.DefaultMediaPlayer$NewMediaEventHandler

/*
* This file is part of VLCJ.
*
* VLCJ 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.
*
* VLCJ 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 VLCJ.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2009, 2010, 2011, 2012, 2013, 2014 Caprica Software Limited.
*/

package uk.co.caprica.vlcj.player;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.imageio.ImageIO;

import uk.co.caprica.vlcj.binding.LibVlc;
import uk.co.caprica.vlcj.binding.internal.libvlc_audio_track_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_callback_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_equalizer_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_event_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_event_manager_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_event_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_instance_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_logo_position_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_marquee_position_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_media_list_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_media_player_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_media_stats_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_media_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_media_track_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_navigate_mode_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_position_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_state_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_subtitle_track_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_track_description_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_track_type_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_video_adjust_option_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_video_logo_option_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_video_marquee_option_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_video_track_t;
import uk.co.caprica.vlcj.logger.Logger;
import uk.co.caprica.vlcj.medialist.MediaList;
import uk.co.caprica.vlcj.player.condition.BeforeConditionAbortedException;
import uk.co.caprica.vlcj.player.events.MediaPlayerEvent;
import uk.co.caprica.vlcj.player.events.MediaPlayerEventFactory;
import uk.co.caprica.vlcj.player.events.MediaPlayerEventType;
import uk.co.caprica.vlcj.version.Version;

import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

/**
* Media player implementation.
*/
public abstract class DefaultMediaPlayer extends AbstractMediaPlayer implements MediaPlayer, EqualizerListener {

    /**
     * Collection of media player event listeners.
     */
    private final CopyOnWriteArrayList<MediaPlayerEventListener> eventListenerList = new CopyOnWriteArrayList<MediaPlayerEventListener>();

    /**
     * Factory to create media player events from native events.
     */
    private final MediaPlayerEventFactory eventFactory = new MediaPlayerEventFactory(this);

    /**
     * Background thread to send event notifications to listeners.
     * <p>
     * The single-threaded nature of this executor service ensures that events are delivered to
     * listeners in a thread-safe manner and in their proper sequence.
     */
    private final ExecutorService listenersService = Executors.newSingleThreadExecutor();

    /**
     * Native media player instance.
     */
    private libvlc_media_player_t mediaPlayerInstance;

    /**
     * Native media player event manager.
     */
    private libvlc_event_manager_t mediaPlayerEventManager;

    /**
     * Call-back to handle native media player events.
     */
    private libvlc_callback_t callback;

    /**
     * Native media instance for current media (if there is one).
     */
    private libvlc_media_t mediaInstance;

    /**
     * Mask of the native events that will cause notifications to be sent to listeners.
     */
    private int eventMask = MediaPlayerEventType.ALL.value();

    /**
     * Standard options to be applied to all played media.
     */
    private String[] standardMediaOptions;

    /**
     *
     */
    // FIXME use a Java structure (encapsulate this in an event listener?)
    private libvlc_media_stats_t libvlcMediaStats;

    /**
     * Flag whether or not to automatically replay media after the media has finished playing.
     */
    private boolean repeat;

    /**
     * Flag whether or not to automatically play media sub-items if there are any.
     */
    private boolean playSubItems;

    /**
     * Index of the current sub-item, or -1.
     */
    private int subItemIndex;

    /**
     * Optional name of the directory to save video snapshots to.
     * <p>
     * If this is not set then snapshots will be saved to the user home directory.
     */
    private String snapshotDirectoryName;

    /**
     * Audio equalizer.
     *
     * May be <code>null</code>.
     */
    private Equalizer equalizer;

    /**
     * Native audio equalizer instance.
     */
    private libvlc_equalizer_t equalizerInstance;

    /**
     * Opaque reference to user/application-specific data associated with this media player.
     */
    private Object userData;

    /**
     * Set to true when the player has been released.
     */
    private final AtomicBoolean released = new AtomicBoolean();

    /**
     * Create a new media player.
     *
     * @param libvlc native library interface
     * @param instance libvlc instance
     */
    public DefaultMediaPlayer(LibVlc libvlc, libvlc_instance_t instance) {
        super(libvlc, instance);
        Logger.debug("DefaultMediaPlayer(libvlc={}, instance={})", libvlc, instance);
        createInstance();
    }

    @Override
    public void addMediaPlayerEventListener(MediaPlayerEventListener listener) {
        Logger.debug("addMediaPlayerEventListener(listener={})", listener);
        eventListenerList.add(listener);
    }

    @Override
    public void removeMediaPlayerEventListener(MediaPlayerEventListener listener) {
        Logger.debug("removeMediaPlayerEventListener(listener={})", listener);
        eventListenerList.remove(listener);
    }

    @Override
    public void enableEvents(int eventMask) {
        Logger.debug("enableEvents(eventMask={})", eventMask);
        this.eventMask = eventMask;
    }

    // === Media Controls =======================================================

    @Override
    public void setStandardMediaOptions(String... options) {
        Logger.debug("setStandardMediaOptions(options={})", Arrays.toString(options));
        this.standardMediaOptions = options;
    }

    @Override
    public boolean playMedia(String mrl, String... mediaOptions) {
        Logger.debug("playMedia(mrl={},mediaOptions={})", mrl, Arrays.toString(mediaOptions));
        // First 'prepare' the media...
        if(prepareMedia(mrl, mediaOptions)) {
            // ...then play it
            play();
            return true;
        }
        else {
            return false;
        }
    }

    @Override
    public boolean prepareMedia(String mrl, String... mediaOptions) {
        Logger.debug("prepareMedia(mrl={},mediaOptions={})", mrl, Arrays.toString(mediaOptions));
        return setMedia(mrl, mediaOptions);
    }

    @Override
    public boolean startMedia(String mrl, String... mediaOptions) {
        Logger.debug("startMedia(mrl={}, mediaOptions={})", mrl, Arrays.toString(mediaOptions));
        // First 'prepare' the media...
        if(prepareMedia(mrl, mediaOptions)) {
            // ...then play it and wait for it to start (or error)
            return new MediaPlayerLatch(this).play();
        }
        else {
            return false;
        }
    }

    @Override
    public void parseMedia() {
        Logger.debug("parseMedia()");
        if(mediaInstance != null) {
            libvlc.libvlc_media_parse(mediaInstance);
        }
        else {
            throw new IllegalStateException("No media");
        }
    }

    @Override
    public void requestParseMedia() {
        Logger.debug("requestParseMedia()");
        if(mediaInstance != null) {
            libvlc.libvlc_media_parse_async(mediaInstance);
        }
        else {
            throw new IllegalStateException("No media");
        }
    }

    @Override
    public boolean isMediaParsed() {
        Logger.debug("isMediaParsed()");
        if(mediaInstance != null) {
            return 0 != libvlc.libvlc_media_is_parsed(mediaInstance);
        }
        else {
            throw new IllegalStateException("No media");
        }
    }

    @Override
    public MediaMeta getMediaMeta() {
        Logger.debug("getMediaMeta()");
        return getMediaMeta(mediaInstance);
    }

    @Override
    public MediaMeta getMediaMeta(libvlc_media_t media) {
        Logger.debug("getMediaMeta(media={})", media);
        if(media != null) {
            return new DefaultMediaMeta(libvlc, media);
        }
        else {
            throw new IllegalStateException("No media");
        }
    }

    @Override
    public List<MediaMeta> getSubItemMediaMeta() {
        Logger.debug("getSubItemMediaMeta()");
        return handleSubItems(new SubItemsHandler<List<MediaMeta>>() {
            @Override
            public List<MediaMeta> subItems(int count, libvlc_media_list_t subItems) {
                List<MediaMeta> result = new ArrayList<MediaMeta>(count);
                for(libvlc_media_t subItem : new LibVlcMediaListIterator(libvlc, subItems)) {
                    result.add(getMediaMeta(subItem));
                }
                return result;
            }
        });
    }

    @Override
    public void addMediaOptions(String... mediaOptions) {
        Logger.debug("addMediaOptions(mediaOptions={})", Arrays.toString(mediaOptions));
        if(mediaInstance != null) {
            for(String mediaOption : mediaOptions) {
                Logger.debug("mediaOption={}", mediaOption);
                libvlc.libvlc_media_add_option(mediaInstance, mediaOption);
            }
        }
        else {
            throw new IllegalStateException("No media");
        }
    }

    @Override
    public void setRepeat(boolean repeat) {
        Logger.debug("setRepeat(repeat={})", repeat);
        this.repeat = repeat;
    }

    @Override
    public boolean getRepeat() {
        Logger.debug("getRepeat()");
        return repeat;
    }

    // === Sub-Item Controls ====================================================

    @Override
    public void setPlaySubItems(boolean playSubItems) {
        Logger.debug("setPlaySubItems(playSubItems={})", playSubItems);
        this.playSubItems = playSubItems;
    }

    @Override
    public int subItemCount() {
        Logger.debug("subItemCount()");
        return handleSubItems(new SubItemsHandler<Integer>() {
            @Override
            public Integer subItems(int count, libvlc_media_list_t subItems) {
                return count;
            }
        });
    }

    @Override
    public int subItemIndex() {
        return subItemIndex;
    }

    @Override
    public List<String> subItems() {
        Logger.debug("subItems()");
        return handleSubItems(new SubItemsHandler<List<String>>() {
            @Override
            public List<String> subItems(int count, libvlc_media_list_t subItems) {
                List<String> result = new ArrayList<String>(count);
                for(libvlc_media_t subItem : new LibVlcMediaListIterator(libvlc, subItems)) {
                    result.add(NativeString.getNativeString(libvlc, libvlc.libvlc_media_get_mrl(subItem)));
                }
                return result;
            }
        });
    }

    @Override
    public List<libvlc_media_t> subItemsMedia() {
        Logger.debug("subItemsMedia()");
        return handleSubItems(new SubItemsHandler<List<libvlc_media_t>>() {
            @Override
            public List<libvlc_media_t> subItems(int count, libvlc_media_list_t subItems) {
                List<libvlc_media_t> result = new ArrayList<libvlc_media_t>(count);
                for(libvlc_media_t subItem : new LibVlcMediaListIterator(libvlc, subItems)) {
                    result.add(subItem);
                }
                return result;
            }
        });
    }

    @Override
    public MediaList subItemsMediaList() {
        Logger.debug("subItemsMediaList()");
        MediaList result;
        if(mediaInstance != null) {
            libvlc_media_list_t mediaListInstance = libvlc.libvlc_media_subitems(mediaInstance);
            result = new MediaList(libvlc, instance, mediaListInstance);
            libvlc.libvlc_media_list_release(mediaListInstance);
        }
        else {
            result = null;
        }
        return result;
    }

    @Override
    public boolean playNextSubItem(String... mediaOptions) {
        Logger.debug("playNextSubItem(mediaOptions={})", Arrays.toString(mediaOptions));
        return playSubItem(subItemIndex + 1, mediaOptions);
    }

    @Override
    public boolean playSubItem(final int index, final String... mediaOptions) {
        Logger.debug("playSubItem(index={},mediaOptions={})", index, Arrays.toString(mediaOptions));
        return handleSubItems(new SubItemsHandler<Boolean>() {
            @Override
            public Boolean subItems(int count, libvlc_media_list_t subItems) {
                if(subItems != null) {
                    Logger.debug("Handling media sub-item...");
                    // Advance the current sub-item (initially it will be -1)...
                    Logger.debug("count={}", count);
                    subItemIndex = index;
                    Logger.debug("subItemIndex={}", subItemIndex);
                    // If the last sub-item already been played...
                    if(subItemIndex >= count) {
                        Logger.debug("End of sub-items reached");
                        if(!repeat) {
                            Logger.debug("Do not repeat sub-items");
                            subItemIndex = -1;
                            Logger.debug("Raising events for end of sub-items");
                            raiseEvent(eventFactory.createMediaEndOfSubItemsEvent(eventMask));
                        }
                        else {
                            Logger.debug("Repeating sub-items");
                            subItemIndex = 0;
                        }
                    }
                    if(subItemIndex != -1) {
                        // Get the required sub item from the list
                        libvlc_media_t subItem = libvlc.libvlc_media_list_item_at_index(subItems, subItemIndex);
                        Logger.debug("subItem={}", subItem);
                        // If there is an item to play...
                        if(subItem != null) {
                            Logger.debug("subItemMrl={}", mrl(subItem));
                            // Set the sub-item as the new media for the media player
                            libvlc.libvlc_media_player_set_media(mediaPlayerInstance, subItem);
                            // Set any standard media options
                            if(standardMediaOptions != null) {
                                for(String standardMediaOption : standardMediaOptions) {
                                    Logger.debug("standardMediaOption={}", standardMediaOption);
                                    libvlc.libvlc_media_add_option(subItem, standardMediaOption);
                                }
                            }
                            // Set any media options
                            if(mediaOptions != null) {
                                for(String mediaOption : mediaOptions) {
                                    Logger.debug("mediaOption={}", mediaOption);
                                    libvlc.libvlc_media_add_option(subItem, mediaOption);
                                }
                            }
                            // Play the media
                            libvlc.libvlc_media_player_play(mediaPlayerInstance);
                            // Release the sub-item
                            libvlc.libvlc_media_release(subItem);
                            // Raise a semantic event to announce the sub-item was played
                            Logger.debug("Raising played event for sub-item {}", subItemIndex);
                            raiseEvent(eventFactory.createMediaSubItemPlayedEvent(subItemIndex, eventMask));
                            // A sub-item was played
                            return true;
                        }
                    }
                }
                // A sub-item was not played
                return false;
            }
        });
    }

    // === Status Controls ======================================================

    @Override
    public boolean isPlayable() {
        Logger.trace("isPlayable()");
        return libvlc.libvlc_media_player_will_play(mediaPlayerInstance) == 1;
    }

    @Override
    public boolean isPlaying() {
        Logger.trace("isPlaying()");
        return libvlc.libvlc_media_player_is_playing(mediaPlayerInstance) == 1;
    }

    @Override
    public boolean isSeekable() {
        Logger.trace("isSeekable()");
        return libvlc.libvlc_media_player_is_seekable(mediaPlayerInstance) == 1;
    }

    @Override
    public boolean canPause() {
        Logger.trace("canPause()");
        return libvlc.libvlc_media_player_can_pause(mediaPlayerInstance) == 1;
    }

    @Override
    public boolean programScrambled() {
        Logger.trace("programScrambled()");
        return libvlc.libvlc_media_player_program_scrambled(mediaPlayerInstance) == 1;
    }

    @Override
    public long getLength() {
        Logger.trace("getLength()");
        return libvlc.libvlc_media_player_get_length(mediaPlayerInstance);
    }

    @Override
    public long getTime() {
        Logger.trace("getTime()");
        return libvlc.libvlc_media_player_get_time(mediaPlayerInstance);
    }

    @Override
    public float getPosition() {
        Logger.trace("getPosition()");
        return libvlc.libvlc_media_player_get_position(mediaPlayerInstance);
    }

    @Override
    public float getFps() {
        Logger.trace("getFps()");
        return libvlc.libvlc_media_player_get_fps(mediaPlayerInstance);
    }

    @Override
    public float getRate() {
        Logger.trace("getRate()");
        return libvlc.libvlc_media_player_get_rate(mediaPlayerInstance);
    }

    @Override
    public int getVideoOutputs() {
        Logger.trace("getVideoOutputs()");
        return libvlc.libvlc_media_player_has_vout(mediaPlayerInstance);
    }

    @Override
    public Dimension getVideoDimension() {
        Logger.debug("getVideoDimension()");
        if(getVideoOutputs() > 0) {
            IntByReference px = new IntByReference();
            IntByReference py = new IntByReference();
            int result = libvlc.libvlc_video_get_size(mediaPlayerInstance, 0, px, py);
            if(result == 0) {
                return new Dimension(px.getValue(), py.getValue());
            }
            else {
                Logger.warn("Video size is not available");
                return null;
            }
        }
        else {
            Logger.warn("Can't get video dimension if no video output has been started");
            return null;
        }
    }

    @Override
    public MediaDetails getMediaDetails() {
        Logger.debug("getMediaDetails()");
        // The media must be playing to get this meta data...
        if(isPlaying()) {
            MediaDetails mediaDetails = new MediaDetails();
            mediaDetails.setTitleCount(getTitleCount());
            mediaDetails.setVideoTrackCount(getVideoTrackCount());
            mediaDetails.setAudioTrackCount(getAudioTrackCount());
            mediaDetails.setSpuCount(getSpuCount());
            mediaDetails.setTitleDescriptions(getTitleDescriptions());
            mediaDetails.setVideoDescriptions(getVideoDescriptions());
            mediaDetails.setAudioDescriptions(getAudioDescriptions());
            mediaDetails.setSpuDescriptions(getSpuDescriptions());
            mediaDetails.setChapterDescriptions(getAllChapterDescriptions());
            return mediaDetails;
        }
        else {
            Logger.warn("Can't get media meta data if media is not playing");
            return null;
        }
    }

    @Override
    public String getAspectRatio() {
        Logger.debug("getAspectRatio()");
        return NativeString.getNativeString(libvlc, libvlc.libvlc_video_get_aspect_ratio(mediaPlayerInstance));
    }

    @Override
    public float getScale() {
        Logger.debug("getScale()");
        return libvlc.libvlc_video_get_scale(mediaPlayerInstance);
    }

    @Override
    public String getCropGeometry() {
        Logger.debug("getCropGeometry()");
        return NativeString.getNativeString(libvlc, libvlc.libvlc_video_get_crop_geometry(mediaPlayerInstance));
    }

    @Override
    public libvlc_media_stats_t getMediaStatistics() {
        Logger.trace("getMediaStatistics()");
        return getMediaStatistics(mediaInstance);
    }

    @Override
    public libvlc_media_stats_t getMediaStatistics(libvlc_media_t media) {
        Logger.trace("getMediaStatistics(media={})", media);
        // Must first check that the media is playing otherwise a fatal JVM crash
        // will occur - potentially this could still cause a fatal crash if the
        // media item supplied is not the one actually playing right now
        if(isPlaying() && media != null) {
            libvlc.libvlc_media_get_stats(media, libvlcMediaStats);
        }
        return libvlcMediaStats;
    }

    // FIXME do not return the native structure, should be a Java enum
    @Override
    public libvlc_state_t getMediaState() {
        Logger.debug("getMediaState()");
        libvlc_state_t state = null;
        if(mediaInstance != null) {
            state = libvlc_state_t.state(libvlc.libvlc_media_get_state(mediaInstance));
        }
        return state;
    }

    // FIXME do not return the native structure, should be a Java enum
    @Override
    public libvlc_state_t getMediaPlayerState() {
        Logger.debug("getMediaPlayerState()");
        return libvlc_state_t.state(libvlc.libvlc_media_player_get_state(mediaPlayerInstance));
    }

    // === Title/Track Controls =================================================

    @Override
    public int getTitleCount() {
        Logger.debug("getTitleCount()");
        return libvlc.libvlc_media_player_get_title_count(mediaPlayerInstance);
    }

    @Override
    public int getTitle() {
        Logger.debug("getTitle()");
        return libvlc.libvlc_media_player_get_title(mediaPlayerInstance);
    }

    @Override
    public void setTitle(int title) {
        Logger.debug("setTitle(title={})", title);
        libvlc.libvlc_media_player_set_title(mediaPlayerInstance, title);
    }

    @Override
    public int getVideoTrackCount() {
        Logger.debug("getVideoTrackCount()");
        return libvlc.libvlc_video_get_track_count(mediaPlayerInstance);
    }

    @Override
    public int getVideoTrack() {
        Logger.debug("getVideoTrack()");
        return libvlc.libvlc_video_get_track(mediaPlayerInstance);
    }

    @Override
    public int setVideoTrack(int track) {
        Logger.debug("setVideoTrack(track={})", track);
        libvlc.libvlc_video_set_track(mediaPlayerInstance, track);
        return getVideoTrack();
    }

    @Override
    public int getAudioTrackCount() {
        Logger.debug("getVideoTrackCount()");
        return libvlc.libvlc_audio_get_track_count(mediaPlayerInstance);
    }

    @Override
    public int getAudioTrack() {
        Logger.debug("getAudioTrack()");
        return libvlc.libvlc_audio_get_track(mediaPlayerInstance);
    }

    @Override
    public int setAudioTrack(int track) {
        Logger.debug("setAudioTrack(track={})", track);
        libvlc.libvlc_audio_set_track(mediaPlayerInstance, track);
        return getAudioTrack();
    }

    // === Basic Playback Controls ==============================================

    @Override
    public void play() {
        Logger.debug("play()");
        onBeforePlay();
        libvlc.libvlc_media_player_play(mediaPlayerInstance);
        Logger.debug("after play");
    }

    @Override
    public boolean start() {
        return new MediaPlayerLatch(this).play();
    }

    @Override
    public void stop() {
        Logger.debug("stop()");
        libvlc.libvlc_media_player_stop(mediaPlayerInstance);
    }

    @Override
    public void setPause(boolean pause) {
        Logger.debug("setPause(pause={})", pause);
        libvlc.libvlc_media_player_set_pause(mediaPlayerInstance, pause ? 1 : 0);
    }

    @Override
    public void pause() {
        Logger.debug("pause()");
        libvlc.libvlc_media_player_pause(mediaPlayerInstance);
    }

    @Override
    public void nextFrame() {
        Logger.debug("nextFrame()");
        libvlc.libvlc_media_player_next_frame(mediaPlayerInstance);
    }

    @Override
    public void skip(long delta) {
        Logger.debug("skip(delta={})", delta);
        long current = getTime();
        Logger.debug("current={}", current);
        if(current != -1) {
            setTime(current + delta);
        }
    }

    @Override
    public void skipPosition(float delta) {
        Logger.debug("skipPosition(delta={})", delta);
        float current = getPosition();
        Logger.debug("current={}", current);
        if(current != -1) {
            setPosition(current + delta);
        }
    }

    @Override
    public void setTime(long time) {
        Logger.debug("setTime(time={})", time);
        libvlc.libvlc_media_player_set_time(mediaPlayerInstance, Math.max(time, 0));
    }

    @Override
    public void setPosition(float position) {
        Logger.debug("setPosition(position={})", position);
        libvlc.libvlc_media_player_set_position(mediaPlayerInstance, Math.max(position, 0));
    }

    @Override
    public int setRate(float rate) {
        Logger.debug("setRate(rate={})", rate);
        return libvlc.libvlc_media_player_set_rate(mediaPlayerInstance, rate);
    }

    @Override
    public void setAspectRatio(String aspectRatio) {
        Logger.debug("setAspectRatio(aspectRatio={})", aspectRatio);
        libvlc.libvlc_video_set_aspect_ratio(mediaPlayerInstance, aspectRatio);
    }

    @Override
    public void setScale(float factor) {
        Logger.debug("setScale(factor={})", factor);
        libvlc.libvlc_video_set_scale(mediaPlayerInstance, factor);
    }

    @Override
    public void setCropGeometry(String cropGeometry) {
        Logger.debug("setCropGeometry(cropGeometry={})", cropGeometry);
        libvlc.libvlc_video_set_crop_geometry(mediaPlayerInstance, cropGeometry);
    }

    // === Audio Controls =======================================================

    @Override
    public boolean setAudioOutput(String output) {
        Logger.debug("setAudioOutput(output={})", output);
        return 0 == libvlc.libvlc_audio_output_set(mediaPlayerInstance, output);
    }

    @Override
    public void setAudioOutputDevice(String output, String outputDeviceId) {
        Logger.debug("setAudioOutputDevice(output={},outputDeviceId={})", output, outputDeviceId);
        libvlc.libvlc_audio_output_device_set(mediaPlayerInstance, output, outputDeviceId);
    }

    @Override
    public void mute() {
        Logger.debug("mute()");
        libvlc.libvlc_audio_toggle_mute(mediaPlayerInstance);
    }

    @Override
    public void mute(boolean mute) {
        Logger.debug("mute(mute={})", mute);
        libvlc.libvlc_audio_set_mute(mediaPlayerInstance, mute ? 1 : 0);
    }

    @Override
    public boolean isMute() {
        Logger.debug("isMute()");
        return libvlc.libvlc_audio_get_mute(mediaPlayerInstance) != 0;
    }

    @Override
    public int getVolume() {
        Logger.debug("getVolume()");
        return libvlc.libvlc_audio_get_volume(mediaPlayerInstance);
    }

    @Override
    public void setVolume(int volume) {
        Logger.debug("setVolume(volume={})", volume);
        libvlc.libvlc_audio_set_volume(mediaPlayerInstance, volume);
    }

    @Override
    public int getAudioChannel() {
        Logger.debug("getAudioChannel()");
        return libvlc.libvlc_audio_get_channel(mediaPlayerInstance);
    }

    @Override
    public void setAudioChannel(int channel) {
        Logger.debug("setAudioChannel(channel={})", channel);
        libvlc.libvlc_audio_set_channel(mediaPlayerInstance, channel);
    }

    @Override
    public long getAudioDelay() {
        Logger.debug("getAudioDelay()");
        return libvlc.libvlc_audio_get_delay(mediaPlayerInstance);
    }

    @Override
    public void setAudioDelay(long delay) {
        Logger.debug("setAudioDelay(delay={})", delay);
        libvlc.libvlc_audio_set_delay(mediaPlayerInstance, delay);
    }

    // === Chapter Controls =====================================================

    @Override
    public int getChapterCount() {
        Logger.trace("getChapterCount()");
        return libvlc.libvlc_media_player_get_chapter_count(mediaPlayerInstance);
    }

    @Override
    public int getChapter() {
        Logger.trace("getChapter()");
        return libvlc.libvlc_media_player_get_chapter(mediaPlayerInstance);
    }

    @Override
    public void setChapter(int chapterNumber) {
        Logger.debug("setChapter(chapterNumber={})", chapterNumber);
        libvlc.libvlc_media_player_set_chapter(mediaPlayerInstance, chapterNumber);
    }

    @Override
    public void nextChapter() {
        Logger.debug("nextChapter()");
        libvlc.libvlc_media_player_next_chapter(mediaPlayerInstance);
    }

    @Override
    public void previousChapter() {
        Logger.debug("previousChapter()");
        libvlc.libvlc_media_player_previous_chapter(mediaPlayerInstance);
    }

    // === DVD Menu Navigation Controls =========================================

    @Override
    public void menuActivate() {
        Logger.debug("menuActivate()");
        libvlc.libvlc_media_player_navigate(mediaPlayerInstance, libvlc_navigate_mode_e.libvlc_navigate_activate.intValue());
    }

    @Override
    public void menuUp() {
        Logger.debug("menuUp()");
        libvlc.libvlc_media_player_navigate(mediaPlayerInstance, libvlc_navigate_mode_e.libvlc_navigate_up.intValue());
    }

    @Override
    public void menuDown() {
        Logger.debug("menuDown()");
        libvlc.libvlc_media_player_navigate(mediaPlayerInstance, libvlc_navigate_mode_e.libvlc_navigate_down.intValue());
    }

    @Override
    public void menuLeft() {
        Logger.debug("menuLeft()");
        libvlc.libvlc_media_player_navigate(mediaPlayerInstance, libvlc_navigate_mode_e.libvlc_navigate_left.intValue());
    }

    @Override
    public void menuRight() {
        Logger.debug("menuRight()");
        libvlc.libvlc_media_player_navigate(mediaPlayerInstance, libvlc_navigate_mode_e.libvlc_navigate_right.intValue());
    }

    // === Sub-Picture/Sub-Title Controls =======================================

    @Override
    public int getSpuCount() {
        Logger.debug("getSpuCount()");
        return libvlc.libvlc_video_get_spu_count(mediaPlayerInstance);
    }

    @Override
    public int getSpu() {
        Logger.debug("getSpu()");
        return libvlc.libvlc_video_get_spu(mediaPlayerInstance);
    }

    @Override
    public int setSpu(int spu) {
        Logger.debug("setSpu(spu={})", spu);
        libvlc.libvlc_video_set_spu(mediaPlayerInstance, spu);
        return getSpu();
    }

    @Override
    public int cycleSpu() {
        Logger.debug("cycleSpu()");
        int spu = getSpu();
        int spuCount = getSpuCount();
        Logger.debug("spuCount={}", spuCount);
        if(spu >= spuCount) {
            spu = 0;
        }
        else {
            spu ++ ;
        }
        return setSpu(spu);
    }

    @Override
    public long getSpuDelay() {
        Logger.debug("getSpuDelay()");
        return libvlc.libvlc_video_get_spu_delay(mediaPlayerInstance);
    }

    @Override
    public void setSpuDelay(long delay) {
        Logger.debug("setSpuDelay(delay={})", delay);
        libvlc.libvlc_video_set_spu_delay(mediaPlayerInstance, delay);
    }

    @Override
    public void setSubTitleFile(String subTitleFileName) {
        Logger.debug("setSubTitleFile(subTitleFileName={})", subTitleFileName);
        libvlc.libvlc_video_set_subtitle_file(mediaPlayerInstance, subTitleFileName);
    }

    @Override
    public void setSubTitleFile(File subTitleFile) {
        Logger.debug("setSubTitleFile(subTitleFile={})", subTitleFile);
        setSubTitleFile(subTitleFile.getAbsolutePath());
    }

    // === Teletext Controls ====================================================

    @Override
    public int getTeletextPage() {
        Logger.debug("getTeletextPage()");
        return libvlc.libvlc_video_get_teletext(mediaPlayerInstance);
    }

    @Override
    public void setTeletextPage(int pageNumber) {
        Logger.debug("setTeletextPage(pageNumber={})", pageNumber);
        libvlc.libvlc_video_set_teletext(mediaPlayerInstance, pageNumber);
    }

    @Override
    public void toggleTeletext() {
        Logger.debug("toggleTeletext()");
        libvlc.libvlc_toggle_teletext(mediaPlayerInstance);
    }

    // === Description Controls =================================================

    @Override
    public List<TrackDescription> getTitleDescriptions() {
        Logger.debug("getTitleDescriptions()");
        libvlc_track_description_t trackDescriptions = libvlc.libvlc_video_get_title_description(mediaPlayerInstance);
        return getTrackDescriptions(trackDescriptions);
    }

    @Override
    public List<TrackDescription> getVideoDescriptions() {
        Logger.debug("getVideoDescriptions()");
        libvlc_track_description_t trackDescriptions = libvlc.libvlc_video_get_track_description(mediaPlayerInstance);
        return getTrackDescriptions(trackDescriptions);
    }

    @Override
    public List<TrackDescription> getAudioDescriptions() {
        Logger.debug("getAudioDescriptions()");
        libvlc_track_description_t trackDescriptions = libvlc.libvlc_audio_get_track_description(mediaPlayerInstance);
        return getTrackDescriptions(trackDescriptions);
    }

    @Override
    public List<TrackDescription> getSpuDescriptions() {
        Logger.debug("getSpuDescriptions()");
        libvlc_track_description_t trackDescriptions = libvlc.libvlc_video_get_spu_description(mediaPlayerInstance);
        return getTrackDescriptions(trackDescriptions);
    }

    @Override
    public List<String> getChapterDescriptions(int title) {
        Logger.debug("getChapterDescriptions(title={})", title);
        List<String> trackDescriptionList;
        if(title >= 0 && title < getTitleCount()) {
            trackDescriptionList = new ArrayList<String>();
            libvlc_track_description_t trackDescriptions = libvlc.libvlc_video_get_chapter_description(mediaPlayerInstance, title);
            libvlc_track_description_t trackDescription = trackDescriptions;
            while(trackDescription != null) {
                trackDescriptionList.add(trackDescription.psz_name);
                trackDescription = trackDescription.p_next;
            }
            if(trackDescriptions != null) {
                libvlc.libvlc_track_description_list_release(trackDescriptions.getPointer());
            }
        }
        else {
            trackDescriptionList = null;
        }
        return trackDescriptionList;
    }

    @Override
    public List<String> getChapterDescriptions() {
        Logger.debug("getChapterDescriptions()");
        return getChapterDescriptions(getTitle());
    }

    @Override
    public List<List<String>> getAllChapterDescriptions() {
        Logger.debug("getAllChapterDescriptions()");
        int titleCount = getTitleCount();
        List<List<String>> result = new ArrayList<List<String>>(titleCount);
        for(int i = 0; i < titleCount; i ++ ) {
            result.add(getChapterDescriptions(i));
        }
        return result;
    }

    @Override
    public List<TrackInfo> getTrackInfo(TrackType... types) {
        Logger.debug("getTrackInfo(types={})", Arrays.toString(types));
        return getTrackInfo(mediaInstance, types);
    }

    @Override
    public List<TrackInfo> getTrackInfo(libvlc_media_t media, TrackType... types) {
        Logger.debug("getTrackInfo(media={},types={})", media, Arrays.toString(types));
        List<TrackInfo> result = null;
        if(media != null) {
            // Convert the types parameter for ease of use
            Set<TrackType> requestedTypes;
            if(types == null || types.length == 0) {
                requestedTypes = null;
            }
            else {
                requestedTypes = new HashSet<TrackType>(types.length);
                for(TrackType type : types) {
                    requestedTypes.add(type);
                }
            }
            result = getTrackInfo(media, requestedTypes);
        }
        return result;
    }

    /**
     * Get track info using the new libvlc 2.1.0+ implementation.
     *
     * @param set of desired track types
     * @param media media descriptor
     * @return track info
     */
    private List<TrackInfo> getTrackInfo(libvlc_media_t media, Set<TrackType> types) {
        Logger.debug("newGetTrackInfo(media={},types={})", media, types);
        PointerByReference tracksPointer = new PointerByReference();
        int numberOfTracks = libvlc.libvlc_media_tracks_get(media, tracksPointer);
        Logger.debug("numberOfTracks={}", numberOfTracks);
        List<TrackInfo> result = new ArrayList<TrackInfo>(numberOfTracks);
        if(numberOfTracks > 0) {
            Pointer[] tracks = tracksPointer.getValue().getPointerArray(0, numberOfTracks);
            for(Pointer track : tracks) {
                libvlc_media_track_t trackInfo = new libvlc_media_track_t(track);
                switch(libvlc_track_type_t.valueOf(trackInfo.i_type)) {
                    case libvlc_track_unknown:
                        if(types == null || types.contains(TrackType.UNKNOWN)) {
                            result.add(new UnknownTrackInfo(
                                trackInfo.i_codec,
                                trackInfo.i_original_fourcc,
                                trackInfo.i_id,
                                trackInfo.i_profile,
                                trackInfo.i_level,
                                trackInfo.i_bitrate,
                                NativeString.getNativeString(libvlc, trackInfo.psz_language),
                                NativeString.getNativeString(libvlc, trackInfo.psz_description)
                            ));
                        }
                        break;

                    case libvlc_track_video:
                        if(types == null || types.contains(TrackType.VIDEO)) {
                            trackInfo.u.setType(libvlc_video_track_t.class);
                            trackInfo.u.read();
                            result.add(new VideoTrackInfo(
                                trackInfo.i_codec,
                                trackInfo.i_original_fourcc,
                                trackInfo.i_id,
                                trackInfo.i_profile,
                                trackInfo.i_level,
                                trackInfo.i_bitrate,
                                NativeString.getNativeString(libvlc, trackInfo.psz_language),
                                NativeString.getNativeString(libvlc, trackInfo.psz_description),
                                trackInfo.u.video.i_width,
                                trackInfo.u.video.i_height,
                                trackInfo.u.video.i_sar_num,
                                trackInfo.u.video.i_sar_den,
                                trackInfo.u.video.i_frame_rate_num,
                                trackInfo.u.video.i_frame_rate_den
                            ));
                        }
                        break;

                    case libvlc_track_audio:
                        if(types == null || types.contains(TrackType.AUDIO)) {
                            trackInfo.u.setType(libvlc_audio_track_t.class);
                            trackInfo.u.read();
                            result.add(new AudioTrackInfo(
                                trackInfo.i_codec,
                                trackInfo.i_original_fourcc,
                                trackInfo.i_id,
                                trackInfo.i_profile,
                                trackInfo.i_level,
                                trackInfo.i_bitrate,
                                NativeString.getNativeString(libvlc, trackInfo.psz_language),
                                NativeString.getNativeString(libvlc, trackInfo.psz_description),
                                trackInfo.u.audio.i_channels,
                                trackInfo.u.audio.i_rate
                            ));
                        }
                        break;

                    case libvlc_track_text:
                        if(types == null || types.contains(TrackType.TEXT)) {
                            trackInfo.u.setType(libvlc_subtitle_track_t.class);
                            trackInfo.u.read();
                            result.add(new TextTrackInfo(
                                trackInfo.i_codec,
                                trackInfo.i_original_fourcc,
                                trackInfo.i_id,
                                trackInfo.i_profile,
                                trackInfo.i_level,
                                trackInfo.i_bitrate,
                                NativeString.getNativeString(libvlc, trackInfo.psz_language),
                                NativeString.getNativeString(libvlc, trackInfo.psz_description),
                                NativeString.getNativeString(libvlc, trackInfo.u.subtitle.psz_encoding)
                            ));
                        }
                        break;
                }
            }
        }
        return result;
    }

    @Override
    public List<List<TrackInfo>> getSubItemTrackInfo(TrackType... types) {
        Logger.debug("getSubItemTrackInfo(types={})", Arrays.toString(types));
        return handleSubItems(new SubItemsHandler<List<List<TrackInfo>>>() {
            @Override
            public List<List<TrackInfo>> subItems(int count, libvlc_media_list_t subItems) {
                List<List<TrackInfo>> result = new ArrayList<List<TrackInfo>>(count);
                for(libvlc_media_t subItem : new LibVlcMediaListIterator(libvlc, subItems)) {
                    result.add(getTrackInfo(subItem));
                }
                return result;
            }
        });
    }

    /**
     * Get track descriptions.
     *
     * @param trackDescriptions native track descriptions, this pointer will be freed by this method
     * @return collection of track descriptions
     */
    private List<TrackDescription> getTrackDescriptions(libvlc_track_description_t trackDescriptions) {
        Logger.debug("getTrackDescriptions()");
        List<TrackDescription> trackDescriptionList = new ArrayList<TrackDescription>();
        libvlc_track_description_t trackDescription = trackDescriptions;
        while(trackDescription != null) {
            trackDescriptionList.add(new TrackDescription(trackDescription.i_id, trackDescription.psz_name));
            trackDescription = trackDescription.p_next;
        }
        if(trackDescriptions != null) {
            libvlc.libvlc_track_description_list_release(trackDescriptions.getPointer());
        }
        return trackDescriptionList;
    }

    // === Snapshot Controls ====================================================

    @Override
    public void setSnapshotDirectory(String snapshotDirectoryName) {
        Logger.debug("setSnapshotDirectory(snapshotDirectoryName={})", snapshotDirectoryName);
        this.snapshotDirectoryName = snapshotDirectoryName;
    }

    @Override
    public boolean saveSnapshot() {
        Logger.debug("saveSnapshot()");
        return saveSnapshot(0, 0);
    }

    @Override
    public boolean saveSnapshot(int width, int height) {
        Logger.debug("saveSnapshot(width={},height={})", width, height);
        File snapshotDirectory = new File(snapshotDirectoryName == null ? System.getProperty("user.home") : snapshotDirectoryName);
        File snapshotFile = new File(snapshotDirectory, "vlcj-snapshot-" + System.currentTimeMillis() + ".png");
        return saveSnapshot(snapshotFile, width, height);
    }

    @Override
    public boolean saveSnapshot(File file) {
        Logger.debug("saveSnapshot(file={})", file);
        return saveSnapshot(file, 0, 0);
    }

    @Override
    public boolean saveSnapshot(File file, int width, int height) {
        Logger.debug("saveSnapshot(file={},width={},height={})", file, width, height);
        File snapshotDirectory = file.getParentFile();
        if(snapshotDirectory == null) {
            snapshotDirectory = new File(".");
            Logger.debug("No directory specified for snapshot, snapshot will be saved to {}", snapshotDirectory.getAbsolutePath());
        }
        if(!snapshotDirectory.exists()) {
            snapshotDirectory.mkdirs();
        }
        if(snapshotDirectory.exists()) {
            boolean snapshotTaken = libvlc.libvlc_video_take_snapshot(mediaPlayerInstance, 0, file.getAbsolutePath(), width, height) == 0;
            Logger.debug("snapshotTaken={}", snapshotTaken);
            return snapshotTaken;
        }
        else {
            throw new RuntimeException("Directory does not exist and could not be created for '" + file.getAbsolutePath() + "'");
        }
    }

    @Override
    public BufferedImage getSnapshot() {
        Logger.debug("getSnapshot()");
        return getSnapshot(0, 0);
    }

    @Override
    public BufferedImage getSnapshot(int width, int height) {
        Logger.debug("getSnapshot(width={},height={})", width, height);
        File file = null;
        try {
            file = File.createTempFile("vlcj-snapshot-", ".png");
            Logger.debug("file={}", file.getAbsolutePath());
            return ImageIO.read(new File(new WaitForSnapshot(this, file, width, height).await()));
        }
        catch(IOException e) {
            throw new RuntimeException("Failed to get snapshot image", e);
        }
        catch(InterruptedException e) {
            throw new RuntimeException("Failed to get snapshot image", e);
        }
        catch(BeforeConditionAbortedException e) {
            Logger.debug("Failed to take snapshot");
            return null;
        }
        finally {
            if(file != null) {
                boolean deleted = file.delete();
                Logger.debug("deleted={}", deleted);
            }
        }
    }

    // === Logo Controls ========================================================

    @Override
    public void enableLogo(boolean enable) {
        Logger.debug("enableLogo(enable={})", enable);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_enable.intValue(), enable ? 1 : 0);
    }

    @Override
    public void setLogoOpacity(int opacity) {
        Logger.debug("setLogoOpacity(opacity={})", opacity);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_opacity.intValue(), opacity);
    }

    @Override
    public void setLogoOpacity(float opacity) {
        Logger.debug("setLogoOpacity(opacity={})", opacity);
        int opacityValue = Math.round(opacity * 255.0f);
        Logger.debug("opacityValue={}", opacityValue);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_opacity.intValue(), opacityValue);
    }

    @Override
    public void setLogoLocation(int x, int y) {
        Logger.debug("setLogoLocation(x={},y={})", x, y);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_x.intValue(), x);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_y.intValue(), y);
    }

    @Override
    public void setLogoPosition(libvlc_logo_position_e position) {
        Logger.debug("setLogoPosition(position={})", position);
        libvlc.libvlc_video_set_logo_int(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_position.intValue(), position.intValue());
    }

    @Override
    public void setLogoFile(String logoFile) {
        Logger.debug("setLogoFile(logoFile={})", logoFile);
        libvlc.libvlc_video_set_logo_string(mediaPlayerInstance, libvlc_video_logo_option_t.libvlc_logo_file.intValue(), logoFile);
    }

    @Override
    public void setLogoImage(RenderedImage logoImage) {
        Logger.debug("setLogoImage(logoImage={})", logoImage);
        File file = null;
        try {
            // Create a temporary file for the logo...
            file = File.createTempFile("vlcj-logo-", ".png");
            ImageIO.write(logoImage, "png", file);
            // ...then set the logo as normal
            setLogoFile(file.getAbsolutePath());
        }
        catch(IOException e) {
            throw new RuntimeException("Failed to set logo image", e);
        }
        finally {
            if(file != null) {
                boolean deleted = file.delete();
                Logger.debug("deleted={}", deleted);
            }
        }
    }

    // === Marquee Controls =====================================================

    @Override
    public void enableMarquee(boolean enable) {
        Logger.debug("enableMarquee(enable={})", enable);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Enable.intValue(), enable ? 1 : 0);
    }

    @Override
    public void setMarqueeText(String text) {
        Logger.debug("setMarqueeText(text={})", text);
        libvlc.libvlc_video_set_marquee_string(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Text.intValue(), text);
    }

    @Override
    public void setMarqueeColour(Color colour) {
        Logger.debug("setMarqueeColour(colour={})", colour);
        setMarqueeColour(colour.getRGB() & 0x00ffffff);
    }

    @Override
    public void setMarqueeColour(int colour) {
        Logger.debug("setMarqueeColour(colour={})", colour);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Color.intValue(), colour);
    }

    @Override
    public void setMarqueeOpacity(int opacity) {
        Logger.debug("setMarqueeOpacity(opacity={})", opacity);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Opacity.intValue(), opacity);
    }

    @Override
    public void setMarqueeOpacity(float opacity) {
        Logger.debug("setMarqueeOpacity(opacity={})", opacity);
        int opacityValue = Math.round(opacity * 255.0f);
        Logger.debug("opacityValue={}", opacityValue);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Opacity.intValue(), opacityValue);
    }

    @Override
    public void setMarqueeSize(int size) {
        Logger.debug("setMarqueeSize(size={})", size);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Size.intValue(), size);
    }

    @Override
    public void setMarqueeTimeout(int timeout) {
        Logger.debug("setMarqueeTimeout(timeout={})", timeout);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Timeout.intValue(), timeout);
    }

    @Override
    public void setMarqueeLocation(int x, int y) {
        Logger.debug("setMarqueeLocation(x={},y={})", x, y);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_X.intValue(), x);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Y.intValue(), y);
    }

    @Override
    public void setMarqueePosition(libvlc_marquee_position_e position) {
        Logger.debug("setMarqueePosition(position={})", position);
        libvlc.libvlc_video_set_marquee_int(mediaPlayerInstance, libvlc_video_marquee_option_t.libvlc_marquee_Position.intValue(), position.intValue());
    }

    // === Filter Controls ======================================================

    @Override
    public void setDeinterlace(DeinterlaceMode deinterlaceMode) {
        Logger.debug("setDeinterlace(deinterlaceMode={})", deinterlaceMode);
        libvlc.libvlc_video_set_deinterlace(mediaPlayerInstance, deinterlaceMode != null ? deinterlaceMode.mode() : null);
    }

    // === Video Adjustment Controls ============================================

    @Override
    public void setAdjustVideo(boolean adjustVideo) {
        Logger.debug("setAdjustVideo(adjustVideo={})", adjustVideo);
        libvlc.libvlc_video_set_adjust_int(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Enable.intValue(), adjustVideo ? 1 : 0);
    }

    @Override
    public boolean isAdjustVideo() {
        Logger.debug("isAdjustVideo()");
        return libvlc.libvlc_video_get_adjust_int(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Enable.intValue()) == 1;
    }

    @Override
    public float getContrast() {
        Logger.debug("getContrast()");
        return libvlc.libvlc_video_get_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Contrast.intValue());
    }

    @Override
    public void setContrast(float contrast) {
        Logger.debug("setContrast(contrast={})", contrast);
        libvlc.libvlc_video_set_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Contrast.intValue(), contrast);
    }

    @Override
    public float getBrightness() {
        Logger.debug("getBrightness()");
        return libvlc.libvlc_video_get_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Brightness.intValue());
    }

    @Override
    public void setBrightness(float brightness) {
        Logger.debug("setBrightness(brightness={})", brightness);
        libvlc.libvlc_video_set_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Brightness.intValue(), brightness);
    }

    @Override
    public int getHue() {
        Logger.debug("getHue()");
        return libvlc.libvlc_video_get_adjust_int(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Hue.intValue());
    }

    @Override
    public void setHue(int hue) {
        Logger.debug("setHue(hue={})", hue);
        libvlc.libvlc_video_set_adjust_int(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Hue.intValue(), hue);
    }

    @Override
    public float getSaturation() {
        Logger.debug("getSaturation()");
        return libvlc.libvlc_video_get_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Saturation.intValue());
    }

    @Override
    public void setSaturation(float saturation) {
        Logger.debug("setSaturation(saturation={})", saturation);
        libvlc.libvlc_video_set_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Saturation.intValue(), saturation);
    }

    @Override
    public float getGamma() {
        Logger.debug("getGamma()");
        return libvlc.libvlc_video_get_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Gamma.intValue());
    }

    @Override
    public void setGamma(float gamma) {
        Logger.debug("setGamma(gamma={})", gamma);
        libvlc.libvlc_video_set_adjust_float(mediaPlayerInstance, libvlc_video_adjust_option_t.libvlc_adjust_Gamma.intValue(), gamma);
    }

    // === Video Title Controls =================================================

    @Override
    public void setVideoTitleDisplay(libvlc_position_e position, int timeout) {
        Logger.debug("setVideoTitleDisplay(position={},timeout={})", position, timeout);
        libvlc.libvlc_media_player_set_video_title_display(mediaPlayerInstance, position.intValue(), timeout);
    }

    // === Audio Equalizer Controls =============================================

    @Override
    public Equalizer getEqualizer() {
        Logger.debug("getEqualizer()");
        return equalizer;
    }

    @Override
    public void setEqualizer(Equalizer equalizer) {
        Logger.debug("setEqualizer(equalizer={})", equalizer);
        if(this.equalizer != null) {
            this.equalizer.removeEqualizerListener(this);
            libvlc.libvlc_audio_equalizer_release(equalizerInstance);
            equalizerInstance = null;
        }
        this.equalizer = equalizer;
        if(this.equalizer != null) {
            equalizerInstance = libvlc.libvlc_audio_equalizer_new();
            this.equalizer.addEqualizerListener(this);
        }
        applyEqualizer();
    }

    /**
     * Apply the audio equalizer settings to the native media player.
     */
    private void applyEqualizer() {
        Logger.trace("applyEqualizer()");
        Logger.trace("equalizerInstance={}", equalizerInstance);
        if(equalizerInstance != null) {
            Logger.trace("Set equalizer");
            libvlc.libvlc_audio_equalizer_set_preamp(equalizerInstance, equalizer.getPreamp());
            for(int i = 0; i < libvlc.libvlc_audio_equalizer_get_band_count(); i ++ ) {
                libvlc.libvlc_audio_equalizer_set_amp_at_index(equalizerInstance, equalizer.getAmp(i), i);
            }
            libvlc.libvlc_media_player_set_equalizer(mediaPlayerInstance, equalizerInstance);
        }
        else {
            Logger.trace("Disable equalizer");
            libvlc.libvlc_media_player_set_equalizer(mediaPlayerInstance, null);
        }
    }

    // === Implementation =======================================================

    @Override
    public String mrl() {
        Logger.debug("mrl()");
        if(mediaInstance != null) {
            return NativeString.getNativeString(libvlc, libvlc.libvlc_media_get_mrl(mediaInstance));
        }
        else {
            throw null;
        }
    }

    @Override
    public String mrl(libvlc_media_t mediaInstance) {
        Logger.debug("mrl(mediaInstance={})", mediaInstance);
        return NativeString.getNativeString(libvlc, libvlc.libvlc_media_get_mrl(mediaInstance));
    }

    @Override
    public Object userData() {
        Logger.debug("userData()");
        return userData;
    }

    @Override
    public void userData(Object userData) {
        Logger.debug("userData(userData={})", userData);
        this.userData = userData;
    }

    @Override
    public final void release() {
        Logger.debug("release()");
        if(released.compareAndSet(false, true)) {
            destroyInstance();
            onAfterRelease();
        }
    }

    @Override
    public final libvlc_media_player_t mediaPlayerInstance() {
        return mediaPlayerInstance;
    }

    /**
     * Allow sub-classes to do something just before the video is started.
     */
    protected void onBeforePlay() {
        // Base implementation does nothing
    }

    /**
     * Allow sub-classes to clean-up.
     */
    protected void onAfterRelease() {
        // Base implementation does nothing
    }

    /**
     * Create and prepare the native media player resources.
     */
    private void createInstance() {
        Logger.debug("createInstance()");

        mediaPlayerInstance = libvlc.libvlc_media_player_new(instance);
        Logger.debug("mediaPlayerInstance={}", mediaPlayerInstance);

        mediaPlayerEventManager = libvlc.libvlc_media_player_event_manager(mediaPlayerInstance);
        Logger.debug("mediaPlayerEventManager={}", mediaPlayerEventManager);

        registerEventListener();

        // The order these handlers execute in is important for proper operation
        eventListenerList.add(new NewMediaEventHandler());
        eventListenerList.add(new RepeatPlayEventHandler());
        eventListenerList.add(new SubItemEventHandler());
    }

    /**
     * Clean up the native media player resources.
     */
    private void destroyInstance() {
        Logger.debug("destroyInstance()");

        Logger.debug("Detach media events...");
        deregisterMediaEventListener();
        Logger.debug("Media events detached.");

        if(mediaInstance != null) {
            Logger.debug("Release media...");
            libvlc.libvlc_media_release(mediaInstance);
            Logger.debug("Media released.");
        }

        Logger.debug("Detach media player events...");
        deregisterEventListener();
        Logger.debug("Media player events detached.");

        eventListenerList.clear();

        if(mediaPlayerInstance != null) {
            Logger.debug("Release media player...");
            libvlc.libvlc_media_player_release(mediaPlayerInstance);
            Logger.debug("Media player released.");
        }

        if(equalizer != null) {
            equalizer.removeEqualizerListener(this);
            equalizer = null;
        }

        if(equalizerInstance != null) {
            libvlc.libvlc_audio_equalizer_release(equalizerInstance);
            equalizerInstance = null;
        }

        Logger.debug("Shut down listeners...");
        listenersService.shutdown();
        Logger.debug("Listeners shut down.");
    }

    /**
     * Register a call-back to receive native media player events.
     */
    private void registerEventListener() {
        Logger.debug("registerEventListener()");
        callback = new EventCallback();
        libvlc_event_e lastKnownEvent = lastKnownEvent();
        for(libvlc_event_e event : libvlc_event_e.values()) {
            if(event.intValue() >= libvlc_event_e.libvlc_MediaPlayerMediaChanged.intValue() && event.intValue() <= lastKnownEvent.intValue()) {
                Logger.debug("event={}", event);
                int result = libvlc.libvlc_event_attach(mediaPlayerEventManager, event.intValue(), callback, null);
                Logger.debug("result={}", result);
            }
        }
    }

    /**
     * De-register the call-back used to receive native media player events.
     */
    private void deregisterEventListener() {
        Logger.debug("deregisterEventListener()");
        if(callback != null) {
            libvlc_event_e lastKnownEvent = lastKnownEvent();
            for(libvlc_event_e event : libvlc_event_e.values()) {
                if(event.intValue() >= libvlc_event_e.libvlc_MediaPlayerMediaChanged.intValue() && event.intValue() <= lastKnownEvent.intValue()) {
                    Logger.debug("event={}", event);
                    libvlc.libvlc_event_detach(mediaPlayerEventManager, event.intValue(), callback, null);
                }
            }
            callback = null;
        }
    }

    /**
     * Get the last known event type supported by the run-time native event manager.
     * <p>
     * This is required to support earlier than LibVLC 2.2.0, and can be removed when such support
     * is no longer required.
     *
     * @return event type
     */
    private libvlc_event_e lastKnownEvent() {
        libvlc_event_e result;
        Version version = new Version(libvlc.libvlc_get_version());
        if(version.atLeast(new Version("2.2.0"))) {
            result = libvlc_event_e.libvlc_MediaPlayerESSelected;
        }
        else {
            result = libvlc_event_e.libvlc_MediaPlayerVout;
        }
        return result;
    }

    /**
     * Register a call-back to receive media native events.
     */
    private void registerMediaEventListener() {
        Logger.debug("registerMediaEventListener()");
        // If there is a media, register a new listener...
        if(mediaInstance != null) {
            libvlc_event_manager_t mediaEventManager = libvlc.libvlc_media_event_manager(mediaInstance);
            for(libvlc_event_e event : libvlc_event_e.values()) {
                if(event.intValue() >= libvlc_event_e.libvlc_MediaMetaChanged.intValue() && event.intValue() <= libvlc_event_e.libvlc_MediaStateChanged.intValue()) {
                    Logger.debug("event={}", event);
                    int result = libvlc.libvlc_event_attach(mediaEventManager, event.intValue(), callback, null);
                    Logger.debug("result={}", result);
                }
            }
        }
    }

    /**
     * De-register the call-back used to receive native media events.
     */
    private void deregisterMediaEventListener() {
        Logger.debug("deregisterMediaEventListener()");
        // If there is a media, deregister the listener...
        if(mediaInstance != null) {
            libvlc_event_manager_t mediaEventManager = libvlc.libvlc_media_event_manager(mediaInstance);
            for(libvlc_event_e event : libvlc_event_e.values()) {
                if(event.intValue() >= libvlc_event_e.libvlc_MediaMetaChanged.intValue() && event.intValue() <= libvlc_event_e.libvlc_MediaStateChanged.intValue()) {
                    Logger.debug("event={}", event);
                    libvlc.libvlc_event_detach(mediaEventManager, event.intValue(), callback, null);
                }
            }
        }
    }

    /**
     * Raise an event.
     *
     * @param mediaPlayerEvent event to raise, may be <code>null</code>
     */
    private void raiseEvent(MediaPlayerEvent mediaPlayerEvent) {
        Logger.trace("raiseEvent(mediaPlayerEvent={}", mediaPlayerEvent);
        if(mediaPlayerEvent != null) {
            listenersService.submit(new NotifyEventListenersRunnable(mediaPlayerEvent));
        }
    }

    /**
     * Set new media for the native media player.
     * <p>
     * This method cleans up the previous media if there was one before associating new media with
     * the media player.
     *
     * @param media media resource locator (MRL)
     * @param mediaOptions zero or more media options
     * @throws IllegalArgumentException if the supplied MRL could not be parsed
     */
    private boolean setMedia(String media, String... mediaOptions) {
        Logger.debug("setMedia(media={},mediaOptions={})", media, Arrays.toString(mediaOptions));
        // If there is a current media, clean it up
        if(mediaInstance != null) {
            // Release the media event listener
            deregisterMediaEventListener();
            // Release the native resource
            libvlc.libvlc_media_release(mediaInstance);
            mediaInstance = null;
        }
        // Reset sub-items
        subItemIndex = -1;
        // Encode the MRL if necessary (if it is a local file that contains Unicode characters)
        media = MediaResourceLocator.encodeMrl(media);
        // Create new media...
        if(MediaResourceLocator.isLocation(media)) {
            Logger.debug("Treating mrl as a location");
            mediaInstance = libvlc.libvlc_media_new_location(instance, media);
        }
        else {
            Logger.debug("Treating mrl as a path");
            mediaInstance = libvlc.libvlc_media_new_path(instance, media);
        }
        Logger.debug("mediaInstance={}", mediaInstance);
        if(mediaInstance != null) {
            // Set the standard media options (if any)...
            if(standardMediaOptions != null) {
                for(String standardMediaOption : standardMediaOptions) {
                    Logger.debug("standardMediaOption={}", standardMediaOption);
                    libvlc.libvlc_media_add_option(mediaInstance, standardMediaOption);
                }
            }
            // Set the particular media options (if any)...
            if(mediaOptions != null) {
                for(String mediaOption : mediaOptions) {
                    Logger.debug("mediaOption={}", mediaOption);
                    libvlc.libvlc_media_add_option(mediaInstance, mediaOption);
                }
            }
            // Attach a listener to the new media
            registerMediaEventListener();
            // Set the new media on the media player
            libvlc.libvlc_media_player_set_media(mediaPlayerInstance, mediaInstance);
        }
        else {
            Logger.error("Failed to create native media resource for '{}'", media);
        }
        // Prepare a new statistics object to re-use for the new media item
        libvlcMediaStats = new libvlc_media_stats_t();
        return mediaInstance != null;
    }

    /**
     * Handle sub-items.
     * <p>
     * This method contains the common code that is required when iterating over the media sub-items
     * - the sub-items are obtained from the media player, the list is locked, the sub-items are
     * processed by a {@link SubItemsHandler} implementation, then the list is unlocked and
     * released.
     *
     * @param <T> type of result
     * @param subItemsHandler handler implementation
     * @return result
     */
    private <T> T handleSubItems(SubItemsHandler<T> subItemsHandler) {
        Logger.debug("handleSubItems()");
        libvlc_media_list_t subItemList = null;
        try {
            if(mediaInstance != null) {
                // Get the list of sub-items
                subItemList = libvlc.libvlc_media_subitems(mediaInstance);
                Logger.debug("subItemList={}", subItemList);
                if(subItemList != null) {
                    // Lock the sub-item list
                    libvlc.libvlc_media_list_lock(subItemList);
                }
                // Invoke the handler
                return subItemsHandler.subItems(subItemList != null ? libvlc.libvlc_media_list_count(subItemList) : 0, subItemList);
            }
            else {
                throw new IllegalStateException("No media");
            }
        }
        finally {
            if(subItemList != null) {
                libvlc.libvlc_media_list_unlock(subItemList);
                libvlc.libvlc_media_list_release(subItemList);
            }
        }
    }

    /**
     * A call-back to handle events from the native media player.
     * <p>
     * There are some important implementation details for this callback:
     * <ul>
     * <li>First, the event notifications are off-loaded to a different thread so as to prevent
     * application code re-entering libvlc in an event call-back which may lead to a deadlock in the
     * native code;</li>
     * <li>Second, the native event union structure refers to natively allocated memory which will
     * not be in the scope of the thread used to actually dispatch the event notifications.</li>
     * </ul>
     * Without copying the fields at this point from the native union structure, the native memory
     * referred to by the native event is likely to get deallocated and overwritten by the time the
     * notification thread runs. This would lead to unreliable data being sent with the
     * notification, or even a fatal JVM crash.
     */
    private final class EventCallback implements libvlc_callback_t {
        @Override
        public void callback(libvlc_event_t event, Pointer userData) {
            Logger.trace("callback(event={},userData={})", event, userData);
            // Create a new media player event for the native event - due to internal implementation
            // details the event listener list is never empty so it is redundant to check that here
            MediaPlayerEvent mediaPlayerEvent = eventFactory.createEvent(event, eventMask);
            if(event != null) {
                raiseEvent(mediaPlayerEvent);
            }
        }
    }

    /**
     * A runnable task used to fire event notifications.
     * <p>
     * Care must be taken not to re-enter the native library during an event notification so the
     * notifications are off-loaded to a separate thread.
     * <p>
     * These events therefore do <em>not</em> run on the Event Dispatch Thread.
     */
    private final class NotifyEventListenersRunnable implements Runnable {

        /**
         * Event to notify.
         */
        private final MediaPlayerEvent mediaPlayerEvent;

        /**
         * Create a runnable.
         *
         * @param mediaPlayerEvent event to notify
         */
        private NotifyEventListenersRunnable(MediaPlayerEvent mediaPlayerEvent) {
            this.mediaPlayerEvent = mediaPlayerEvent;
        }

        @Override
        public void run() {
            Logger.trace("run()");
            for(int i = eventListenerList.size() - 1; i >= 0; i -- ) {
                MediaPlayerEventListener listener = eventListenerList.get(i);
                try {
                    mediaPlayerEvent.notify(listener);
                }
                catch(Exception e) {
                    Logger.warn("Event listener {} threw an exception", e, listener);
                    // Continue with the next listener...
                }
            }
            Logger.trace("runnable exits");
        }
    }

    /**
     * Event listener implementation that handles a new item being played.
     * <p>
     * This is not for sub-items.
     */
    private final class NewMediaEventHandler extends MediaPlayerEventAdapter {

        @Override
        public void mediaChanged(MediaPlayer mediaPlayer, libvlc_media_t media, String mrl) {
            Logger.debug("mediaChanged(mediaPlayer={},media={},mrl={})", mediaPlayer, media, mrl);
            // If this is not a sub-item...
            if(subItemIndex() == -1) {
                // Raise a semantic event to announce the media was changed
                Logger.debug("Raising event for new media");
                raiseEvent(eventFactory.createMediaNewEvent(eventMask));
            }
        }
    }

    /**
     * Event listener implementation that handles auto-repeat.
     */
    private final class RepeatPlayEventHandler extends MediaPlayerEventAdapter {

        @Override
        public void finished(MediaPlayer mediaPlayer) {
            Logger.debug("finished(mediaPlayer={})", mediaPlayer);
            if(repeat && mediaInstance != null) {
                int subItemCount = subItemCount();
                Logger.debug("subitemCount={}", subItemCount);
                if(subItemCount == 0) {
                    String mrl = NativeString.getNativeString(libvlc, libvlc.libvlc_media_get_mrl(mediaInstance));
                    Logger.debug("auto repeat mrl={}", mrl);
                    // It is not sufficient to simply call play(), the MRL must explicitly
                    // be played again - this is the reason why the repeat play might not
                    // be seamless
                    mediaPlayer.playMedia(mrl);
                }
                else {
                    Logger.debug("Sub-items handling repeat");
                }
            }
            else {
                Logger.debug("No repeat");
            }
        }
    }

    /**
     * Event listener implementation that handles media sub-items.
     * <p>
     * Some media types when you 'play' them do not actually play any media and instead sub-items
     * are created and attached to the current media descriptor.
     * <p>
     * This event listener responds to the media player "finished" event by getting the current
     * media from the player and automatically playing the first sub-item (if there is one).
     * <p>
     * If there is more than one sub-item, then they will simply be played in order, and repeated
     * depending on the value of the "repeat" property.
     */
    private final class SubItemEventHandler extends MediaPlayerEventAdapter {
        @Override
        public void finished(MediaPlayer mediaPlayer) {
            Logger.debug("finished(mediaPlayer={})", mediaPlayer);
            // If a sub-item being played...
            if(subItemIndex != -1) {
                // Raise a semantic event to announce the sub-item was finished
                Logger.debug("Raising finished event for sub-item {}", subItemIndex);
                raiseEvent(eventFactory.createMediaSubItemFinishedEvent(subItemIndex, eventMask));
            }
            // If set to automatically play sub-items...
            if(playSubItems) {
                // ...play the next sub-item
                playNextSubItem();
            }
        }
    }

    /**
     * Specification for a component that handles media list sub-items.
     *
     * @param <T> desired result type
     */
    private interface SubItemsHandler<T> {

        /**
         * Handle sub-items.
         *
         * @param count number of sub-items in the list, will always be zero or greater
         * @param subItems sub-item list, may be <code>null</code>
         * @return result of processing the sub-items
         */
        T subItems(int count, libvlc_media_list_t subItems);
    }

    // === EqualizerListener ====================================================

    @Override
    public final void equalizerChanged(Equalizer equalizer) {
        Logger.trace("equalizerChanged(equalizer={})", equalizer);
        applyEqualizer();
    }
}
TOP

Related Classes of uk.co.caprica.vlcj.player.DefaultMediaPlayer$NewMediaEventHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.