/* * 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-2016 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.co.caprica.vlcj.binding.LibVlc; import uk.co.caprica.vlcj.binding.internal.libvlc_audio_output_device_t; 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_chapter_description_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_parse_flag_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_media_type_e; 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_title_description_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.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.player.media.Media; import uk.co.caprica.vlcj.player.media.callback.CallbackMedia; import uk.co.caprica.vlcj.player.media.simple.SimpleMedia; import uk.co.caprica.vlcj.version.LibVlcVersion; import uk.co.caprica.vlcj.version.Version; import com.sun.jna.Pointer; import com.sun.jna.Structure; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; /** * Media player implementation. */ public abstract class DefaultMediaPlayer extends AbstractMediaPlayer implements MediaPlayer, EqualizerListener { /** * Log. */ private final Logger logger = LoggerFactory.getLogger(DefaultMediaPlayer.class); /** * 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 long 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(); /** * Media that was last played (including media options). * <p> * This reference also serves to keep the media instance pinned and prevented from garbage * collection - this is critical if the media is using native callbacks. */ private Media lastPlayedMedia; /** * 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); if (listener != null) { eventListenerList.add(listener); } else { throw new IllegalArgumentException("listener can not be null"); } } @Override public void removeMediaPlayerEventListener(MediaPlayerEventListener listener) { logger.debug("removeMediaPlayerEventListener(listener={})", listener); if (listener != null) { eventListenerList.remove(listener); } else { throw new IllegalArgumentException("listener can not be null"); } } @Override public void enableEvents(long 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)); return playMedia(new SimpleMedia(mrl, mediaOptions)); } @Override public boolean playMedia(Media media) { logger.debug("playMedia(media={})", media); // First 'prepare' the media... if(prepareMedia(media)) { // ...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 prepareMedia(new SimpleMedia(mrl, mediaOptions)); } @Override public boolean prepareMedia(Media media) { logger.debug("prepareMedia(media={})", media); return setMedia(media); } @Override public boolean startMedia(String mrl, String... mediaOptions) { logger.debug("startMedia(mrl={}, mediaOptions={})", mrl, Arrays.toString(mediaOptions)); return startMedia(new SimpleMedia(mrl, mediaOptions)); } @Override public boolean startMedia(Media media) { logger.debug("startMedia(media={})", media); // First 'prepare' the media... if(prepareMedia(media)) { // ...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 requestParseMediaWithOptions(libvlc_media_parse_flag_t... options) { logger.debug("requestParseMediaWithOptions(options={})", options != null ? Arrays.toString(options) : ""); return requestParseMediaWithOptions(0, options); } @Override public boolean requestParseMediaWithOptions(int timeout, libvlc_media_parse_flag_t... options) { logger.debug("requestParseMediaWithOptions(timeout={},options={})", timeout, options != null ? Arrays.toString(options) : ""); if(LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_300)) { int flags = 0; for (libvlc_media_parse_flag_t option : options) { flags |= option.intValue(); } return libvlc.libvlc_media_parse_with_options(mediaInstance, flags, timeout) == 0; } else { return false; } } @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 MediaMetaData getMediaMetaData() { logger.debug("getMediaMetaData()"); return getMediaMeta().asMediaMetaData(); } @Override public List<MediaMetaData> getSubItemMediaMetaData() { logger.debug("getSubItemMediaMetaData()"); List<MediaMetaData> result; List<MediaMeta> metas = getSubItemMediaMeta(); if(metas != null) { result = new ArrayList<MediaMetaData>(metas.size()); for(MediaMeta meta : getSubItemMediaMeta()) { result.add(meta.asMediaMetaData()); } } else { result = null; } 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 String getAudioOutputDevice() { logger.debug("getAudioOutputDevice()"); if(LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_300)) { return NativeString.getNativeString(libvlc, libvlc.libvlc_audio_output_device_get(mediaPlayerInstance)); } else { return null; } } @Override public List<AudioDevice> getAudioOutputDevices() { logger.debug("getAudioOutputDevices()"); if(LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_220)) { List<AudioDevice> result = new ArrayList<AudioDevice>(); libvlc_audio_output_device_t audioDevices = libvlc.libvlc_audio_output_device_enum(mediaPlayerInstance); if (audioDevices != null) { // Must prevent automatic synchronisation on the native structure, otherwise a // fatal JVM crash will occur when the native release call is made - not quite // sure why this is needed here audioDevices.setAutoSynch(false); libvlc_audio_output_device_t audioDevice = audioDevices; while(audioDevice != null) { // The native strings must be copied here, but not freed (they are freed natively // in the subsequent release call) String device = NativeString.copyNativeString(libvlc, audioDevice.psz_device); String description = NativeString.copyNativeString(libvlc, audioDevice.psz_description); result.add(new AudioDevice(device, description)); audioDevice = audioDevice.p_next; } libvlc.libvlc_audio_output_device_list_release(audioDevices); } return result; } else { logger.warn("Audio output device enumeration requires libvlc 2.2.0 or later"); return null; } } @Override public boolean mute() { logger.debug("mute()"); libvlc.libvlc_audio_toggle_mute(mediaPlayerInstance); return isMute(); } @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 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>>(Math.max(titleCount, 0)); for(int i = 0; i < titleCount; i ++ ) { result.add(getChapterDescriptions(i)); } return result; } @Override public List<TitleDescription> getExtendedTitleDescriptions() { logger.debug("getExtendedTitleDescriptions()"); List<TitleDescription> result; if (LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_300)) { PointerByReference titles = new PointerByReference(); int titleCount = libvlc.libvlc_media_player_get_full_title_descriptions(mediaPlayerInstance, titles); if (titleCount != -1) { result = new ArrayList<TitleDescription>(titleCount); Pointer[] pointers = titles.getValue().getPointerArray(0, titleCount); for (Pointer pointer : pointers) { libvlc_title_description_t titleDescription = (libvlc_title_description_t) Structure.newInstance(libvlc_title_description_t.class, pointer); titleDescription.read(); result.add(new TitleDescription(titleDescription.i_duration, NativeString.copyNativeString(libvlc, titleDescription.psz_name), titleDescription.b_menu != 0)); } libvlc.libvlc_title_descriptions_release(titles.getValue(), titleCount); } else { result = new ArrayList<TitleDescription>(0); } } else { result = new ArrayList<TitleDescription>(0); } return result; } @Override public List<ChapterDescription> getExtendedChapterDescriptions() { logger.debug("getExtendedChapterDescriptions()"); return getExtendedChapterDescriptions(getTitle()); } @Override public List<ChapterDescription> getExtendedChapterDescriptions(int title) { logger.debug("getExtendedChapterDescriptions(title={})", title); List<ChapterDescription> result; if (LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_300)) { PointerByReference chapters = new PointerByReference(); int chapterCount = libvlc.libvlc_media_player_get_full_chapter_descriptions(mediaPlayerInstance, title, chapters); if (chapterCount != -1) { result = new ArrayList<ChapterDescription>(chapterCount); Pointer[] pointers = chapters.getValue().getPointerArray(0, chapterCount); for (Pointer pointer : pointers) { libvlc_chapter_description_t chapterDescription = (libvlc_chapter_description_t) Structure.newInstance(libvlc_chapter_description_t.class, pointer); chapterDescription.read(); result.add(new ChapterDescription(chapterDescription.i_time_offset, chapterDescription.i_duration, NativeString.getNativeString(libvlc, chapterDescription.psz_name))); } libvlc.libvlc_chapter_descriptions_release(chapters.getValue(), chapterCount); } else { result = new ArrayList<ChapterDescription>(0); } } else { result = new ArrayList<ChapterDescription>(0); } 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 types 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.copyNativeString(libvlc, trackInfo.psz_language), NativeString.copyNativeString(libvlc, trackInfo.psz_description), getCodecDescription(libvlc_track_type_t.libvlc_track_unknown, trackInfo.i_codec) )); } 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.copyNativeString(libvlc, trackInfo.psz_language), NativeString.copyNativeString(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, getCodecDescription(libvlc_track_type_t.libvlc_track_video, trackInfo.i_codec) )); } 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.copyNativeString(libvlc, trackInfo.psz_language), NativeString.copyNativeString(libvlc, trackInfo.psz_description), trackInfo.u.audio.i_channels, trackInfo.u.audio.i_rate, getCodecDescription(libvlc_track_type_t.libvlc_track_audio, trackInfo.i_codec) )); } 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.copyNativeString(libvlc, trackInfo.psz_language), NativeString.copyNativeString(libvlc, trackInfo.psz_description), NativeString.copyNativeString(libvlc, trackInfo.u.subtitle.psz_encoding), getCodecDescription(libvlc_track_type_t.libvlc_track_text, trackInfo.i_codec) )); } break; } } libvlc.libvlc_media_tracks_release(tracksPointer.getValue(), numberOfTracks); // FIXME maybe should copy the nativestring here? } return result; } @Override public libvlc_media_type_e getMediaType() { logger.debug("getMediaType()"); return getMediaType(mediaInstance); } @Override public libvlc_media_type_e getMediaType(libvlc_media_t media) { logger.debug("getMediaType(media={})", media); if (media != null) { return libvlc_media_type_e.mediaType(libvlc.libvlc_media_get_type(media)); } else { return null; } } @Override public String getCodecDescription(libvlc_track_type_t type, int codec) { logger.debug("getCodecDescription(type={},codec={})", type, codec); if(LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_300)) { return libvlc.libvlc_media_get_codec_description(type.intValue(), codec); } else { return ""; } } @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); if (file.exists()) { // ...then set the logo as normal setLogoFile(file.getAbsolutePath()); // Flag the temporary file to be deleted when the JVM exits - the file can not be // deleted immediately because setLogoFile ultimately invokes an asynchronous // native method to set the logo from the file file.deleteOnExit(); } } catch(IOException e) { throw new RuntimeException("Failed to set logo image", e); } } @Override public void setLogo(Logo logo) { logger.debug("setLogo(logo={})", logo); logo.apply(this); } // === 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()); } @Override public void setMarquee(Marquee marquee) { logger.debug("setMarquee(marquee={})", marquee); marquee.apply(this); } // === 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 { return 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()); eventListenerList.add(new ResetMediaHandler()); } /** * 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("3.0.0"))) { result = libvlc_event_e.libvlc_MediaPlayerChapterChanged; } else if(version.atLeast(new Version("2.2.0"))) { result = libvlc_event_e.libvlc_MediaPlayerScrambledChanged; } 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); libvlc_event_e lastKnownMediaEvent = lastKnownMediaEvent(); for(libvlc_event_e event : libvlc_event_e.values()) { if(event.intValue() >= libvlc_event_e.libvlc_MediaMetaChanged.intValue() && event.intValue() <= lastKnownMediaEvent.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); libvlc_event_e lastKnownMediaEvent = lastKnownMediaEvent(); for(libvlc_event_e event : libvlc_event_e.values()) { if(event.intValue() >= libvlc_event_e.libvlc_MediaMetaChanged.intValue() && event.intValue() <= lastKnownMediaEvent.intValue()) { logger.debug("event={}", event); libvlc.libvlc_event_detach(mediaEventManager, event.intValue(), callback, null); } } } } /** * Get the last known media 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 lastKnownMediaEvent() { libvlc_event_e result; Version version = new Version(libvlc.libvlc_get_version()); if(version.atLeast(new Version("3.0.0"))) { result = libvlc_event_e.libvlc_MediaParsedStatus; } else if(version.atLeast(new Version("2.1.5"))) { result = libvlc_event_e.libvlc_MediaSubItemTreeAdded; } else { result = libvlc_event_e.libvlc_MediaStateChanged; } return result; } /** * 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 and options * @throws IllegalArgumentException if the supplied MRL could not be parsed */ private boolean setMedia(Media media) { logger.debug("setMedia(media={})", media); // Remember the the media and options for possible replay later (also to keep the media // instance pinned to prevent it from being garbage collected - critical when using the // native media callbacks) this.lastPlayedMedia = media; // 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; // Create the native media handle for the given media mediaInstance = createMediaInstance(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(media.mediaOptions() != null) { for(String mediaOption : media.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(); boolean result = mediaInstance != null; logger.debug("result={}", result); return result; } /** * Create a native media handle for the given media. * * @param media media * @return native media handle */ private libvlc_media_t createMediaInstance(Media media) { logger.debug("createMediaInstance(media={})", media); // Create new media... libvlc_media_t result; if (media instanceof SimpleMedia) { String mrl = ((SimpleMedia) media).mrl(); if(MediaResourceLocator.isLocation(mrl)) { logger.debug("Treating mrl as a location"); result = libvlc.libvlc_media_new_location(instance, mrl); } else { logger.debug("Treating mrl as a path"); result = libvlc.libvlc_media_new_path(instance, mrl); } } else if (media instanceof CallbackMedia) { CallbackMedia callbackMedia = (CallbackMedia)media; result = libvlc.libvlc_media_new_callbacks(instance, callbackMedia.getOpen(), callbackMedia.getRead(), callbackMedia.getSeek(), callbackMedia.getClose(), callbackMedia.getOpaque() ); } else { throw new IllegalStateException("Don't know about media type " + media); } return result; } /** * 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, will never be <code>null</code> * @throws IllegalStateException if no current media */ 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 {}", listener, e.getMessage()); // 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, lastPlayedMedia.mediaOptions()); } 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(); } } } /** * Event listener implementation that resets the media ready for replay after is has finished. * <p> * This enables an application to invoke play() after the media is finished. * <p> * Without this, an application must invoke stop(), then play(). */ private final class ResetMediaHandler extends MediaPlayerEventAdapter { @Override public void finished(MediaPlayer mediaPlayer) { if (subItemCount() == 0) { resetMedia(); } } } /** * Reset the media so it can be replayed. */ private void resetMedia() { logger.debug("resetMedia()"); setMedia(lastPlayedMedia); } /** * 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(); } }