package com.skcraft.playblock.player; import static com.skcraft.playblock.util.EnvUtils.getPlatform; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.logging.log4j.Level; import uk.co.caprica.vlcj.player.MediaPlayerFactory; import com.skcraft.playblock.PlayBlock; import com.skcraft.playblock.util.EnvUtils; import com.skcraft.playblock.util.EnvUtils.Arch; import com.skcraft.playblock.util.EnvUtils.Platform; import com.skcraft.playblock.util.PlayBlockPaths; import com.sun.jna.NativeLibrary; /** * Manages media player instances. */ public class MediaManager { private static final String[] INSTALL_MESSAGE = { "To view this video,", "install " + (EnvUtils.getJvmArch() == Arch.X86_64 ? "64-bit" : "32-bit") + " VLC", "by pressing F4.", }; private final ExecutorService executor; private TextureCache textureCache = new TextureCache(); private MediaPlayerFactory factory; private MediaRenderer activeRenderer; private float volume = 1; private EmbeddedInstaller installer; // Simple texture cache static { // In order to find VLC, we need to build a list of search paths, // which is not easy as it seems! for (File file : PlayBlockPaths.getSearchPaths()) { NativeLibrary.addSearchPath("libvlc", file.getAbsolutePath()); } } /** * Construct a new media manager. */ public MediaManager() { // In order to prevent freezing when changing media, the player will be // called from this dedicated thread executor = Executors.newFixedThreadPool(1); try { factory = new MediaPlayerFactory(getFactoryOptions()); } catch (Throwable t) { PlayBlock.log(Level.WARN, "Failed to find VLC!", t); } volume = PlayBlock.getClientRuntime().getClientOptions().getFloat("volume", 1); } /** * Returns whether the VLC library loaded successfully. * * @return true if it is available */ public boolean isSupported() { return factory != null; } /** * Get the in-game installer. * * @return the installer */ public EmbeddedInstaller getInstaller() { if (installer == null) { installer = new EmbeddedInstaller(); } return installer; } /** * Get the message shown on the screen for unsupported usres. * * @return a list of lines */ public String[] getUnsupportedMessage() { switch (getInstaller().getState()) { case NOT_INSTALLING: return INSTALL_MESSAGE; default: return new String[] { getInstaller().getStatusMessage() }; } } /** * Generate the VLC options required. * * @return the list of options */ private String[] getFactoryOptions() { List<String> options = new ArrayList<String>(); // Don't show a title on the video when media is played options.add("--no-video-title-show"); // Mac OS X support if (getPlatform() == Platform.MAC_OS_X) { options.add("--vout=macosx"); } String[] arr = new String[options.size()]; options.toArray(arr); return arr; } /** * Returns whether a new player can be acquired and started. * * <p> * For performance reasons, only one player can be playing at a time. * </p> * * @return true true if there is a free player available */ public boolean hasNoRenderer() { return activeRenderer == null; } /** * Acquire a new renderer and set it as the active renderer. * * @param width * the width of the video * @param height * the height of the video * @return the renderer */ public MediaRenderer acquireRenderer(int width, int height) { if (activeRenderer != null) { release(activeRenderer); } activeRenderer = newRenderer(width, height); return activeRenderer; } /** * Create a new renderer to render media on. * * <p> * The rendered is tracked using this instance and it can be released * selectively or all together. * </p> * * @param width * the width of the player * @param height * the height of the player * @return a new media renderer */ protected MediaRenderer newRenderer(final int width, final int height) { if (!isSupported()) { return null; } int textureIndex = textureCache.createTexture(width, height); // Create and initialize the renderer MediaRenderer instance = new MediaRenderer(this, width, height, textureIndex); instance.initialize(factory, getVolume()); return instance; } /** * Frees up a renderer. * * @param instance * the renderer */ public void release(final MediaRenderer instance) { if (!isSupported()) { return; } if (instance.isReleased()) { return; // Don't re-release } // For thread safety reasons, mark the release flag instance.markForRelease(); // Start releasing -- this does not block instance.release(); // Release the texture for the screen int textureIndex = instance.getTextureIndex(); if (textureIndex > 0) { textureCache.deleteTexture(textureIndex); } // Clear the active renderer if (instance == activeRenderer) { activeRenderer = null; } } /** * Execute a given {@link Runnable} in the dedicated thread for interacting * with VLC. * * @param runnable * the object to run */ void executeThreadSafe(Runnable runnable) { executor.execute(runnable); } /** * Release all known renderers. */ public void releaseAll() { if (activeRenderer != null) { release(activeRenderer); } } /** * Unload the manager and also any renderers. */ public void unload() { releaseAll(); if (factory != null) { factory.release(); factory = null; } } /** * Set the volume of the player. * * @param volume * the volume */ public void setVolume(float volume) { this.volume = volume; if (activeRenderer != null) { activeRenderer.setVolume(volume); } } /** * Return the volume of the player. * * @return the volume */ public float getVolume() { return volume; } }