1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.jfxmediaimpl; 27 28 import com.sun.glass.utils.NativeLibLoader; 29 import com.sun.media.jfxmedia.*; 30 import com.sun.media.jfxmedia.events.MediaErrorListener; 31 import com.sun.media.jfxmedia.locator.Locator; 32 import com.sun.media.jfxmedia.logging.Logger; 33 import com.sun.media.jfxmediaimpl.platform.PlatformManager; 34 import java.lang.ref.WeakReference; 35 import java.security.AccessController; 36 import java.security.PrivilegedActionException; 37 import java.security.PrivilegedExceptionAction; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.ListIterator; 42 import java.util.Map; 43 import java.util.WeakHashMap; 44 45 /** 46 * A class representing a native media engine. 47 */ 48 public class NativeMediaManager { 49 /** 50 * Whether the native layer has been initialized. 51 */ 52 private static boolean isNativeLayerInitialized = false; 53 /** 54 * The {@link MediaErrorListener}s. 55 */ 56 // FIXME: Change to WeakHashMap<MediaErrorListener,Boolean> as it's more efficient 57 private final List<WeakReference<MediaErrorListener>> errorListeners = 58 new ArrayList(); 59 private final static NativeMediaPlayerDisposer playerDisposer = 60 new NativeMediaPlayerDisposer(); 61 /** 62 * List of all un-disposed players. 63 */ 64 private final static Map<MediaPlayer,Boolean> allMediaPlayers = 65 new WeakHashMap(); 66 67 // cached content types, so we don't have to poll and sort each time, this list 68 // should never change once we're initialized 69 private final List<String> supportedContentTypes = 70 new ArrayList(); 71 private final List<String> supportedProtocols = 72 new ArrayList<>(); 73 74 /** 75 * The NativeMediaManager singleton. 76 */ 77 private static class NativeMediaManagerInitializer { 78 private static final NativeMediaManager globalInstance 79 = new NativeMediaManager(); 80 } 81 82 /** 83 * Get the default 84 * <code>NativeMediaManager</code>. 85 * 86 * @return the singleton 87 * <code>NativeMediaManager</code> instance. 88 */ 89 public static NativeMediaManager getDefaultInstance() { 90 return NativeMediaManagerInitializer.globalInstance; 91 } 92 93 //************************************************************************** 94 //***** Constructors 95 //************************************************************************** 96 /** 97 * Create a <code>NativeMediaManager</code>. 98 */ 99 protected NativeMediaManager() { 100 /* 101 * Load native libraries. This must be done early as platforms may need 102 * to attempt loading their own native libs that are dependent on these 103 * This is a slight performance hit, but necessary otherwise we could 104 * erroneously report content types for platforms that cannot be loaded 105 */ 106 try { 107 AccessController.doPrivileged((PrivilegedExceptionAction) () -> { 108 ArrayList<String> dependencies = new ArrayList<>(); 109 if (HostUtils.isWindows() || HostUtils.isMacOSX()) { 110 NativeLibLoader.loadLibrary("glib-lite"); 111 } 112 113 if (!HostUtils.isLinux() && !HostUtils.isIOS()) { 114 NativeLibLoader.loadLibrary("gstreamer-lite"); 115 } else { 116 dependencies.add("gstreamer-lite"); 117 } 118 if (HostUtils.isLinux()) { 119 dependencies.add("fxplugins"); 120 dependencies.add("avplugin"); 121 dependencies.add("avplugin-54"); 122 dependencies.add("avplugin-56"); 123 dependencies.add("avplugin-57"); 124 dependencies.add("avplugin-ffmpeg-56"); 125 dependencies.add("avplugin-ffmpeg-57"); 126 } 127 if (HostUtils.isMacOSX()) { 128 dependencies.add("fxplugins"); 129 dependencies.add("glib-lite"); 130 dependencies.add("jfxmedia_avf"); 131 } 132 if (HostUtils.isWindows()) { 133 dependencies.add("fxplugins"); 134 dependencies.add("glib-lite"); 135 } 136 NativeLibLoader.loadLibrary("jfxmedia", dependencies); 137 return null; 138 }); 139 } catch (PrivilegedActionException pae) { 140 MediaUtils.error(null, MediaError.ERROR_MANAGER_ENGINEINIT_FAIL.code(), 141 "Unable to load one or more dependent libraries.", pae); 142 } 143 144 // Get the Logger native side rolling before we load platforms 145 if (!Logger.initNative()) { 146 MediaUtils.error(null, MediaError.ERROR_MANAGER_LOGGER_INIT.code(), 147 "Unable to init logger", null); 148 } 149 } 150 151 /** 152 * Initialize the native layer if it has not been so already. 153 */ 154 synchronized static void initNativeLayer() { 155 if (!isNativeLayerInitialized) { 156 // load platforms 157 PlatformManager.getManager().loadPlatforms(); 158 159 // Set the native initialization flag, even if initialization failed. 160 isNativeLayerInitialized = true; 161 } 162 } 163 164 //************************************************************************** 165 //***** Public control functions 166 //************************************************************************** 167 168 private synchronized void loadContentTypes() { 169 if (!supportedContentTypes.isEmpty()) { 170 // already populated, just return 171 return; 172 } 173 174 List<String> npt = PlatformManager.getManager().getSupportedContentTypes(); 175 if (null != npt && !npt.isEmpty()) { 176 supportedContentTypes.addAll(npt); 177 } 178 179 if (Logger.canLog(Logger.DEBUG)) { 180 StringBuilder sb = new StringBuilder("JFXMedia supported content types:\n"); 181 for (String type : supportedContentTypes) { 182 sb.append(" "); 183 sb.append(type); 184 sb.append("\n"); 185 } 186 Logger.logMsg(Logger.DEBUG, sb.toString()); 187 } 188 } 189 190 private synchronized void loadProtocols() { 191 if (!supportedProtocols.isEmpty()) { 192 // already populated, just return 193 return; 194 } 195 196 List<String> npt = PlatformManager.getManager().getSupportedProtocols(); 197 if (null != npt && !npt.isEmpty()) { 198 supportedProtocols.addAll(npt); 199 } 200 201 if (Logger.canLog(Logger.DEBUG)) { 202 StringBuilder sb = new StringBuilder("JFXMedia supported protocols:\n"); 203 for (String type : supportedProtocols) { 204 sb.append(" "); 205 sb.append(type); 206 sb.append("\n"); 207 } 208 Logger.logMsg(Logger.DEBUG, sb.toString()); 209 } 210 } 211 212 /** 213 * Whether a media source having the indicated content type may be played. 214 * 215 * @see MediaManager#canPlayContentType(java.lang.String) 216 * 217 * @throws IllegalArgumentException if 218 * <code>contentType</code> is 219 * <code>null</code>. 220 */ 221 public boolean canPlayContentType(String contentType) { 222 if (contentType == null) { 223 throw new IllegalArgumentException("contentType == null!"); 224 } 225 226 if (supportedContentTypes.isEmpty()) { 227 loadContentTypes(); 228 } 229 230 /* 231 * Don't just use supportedContentType.contains(contentType) as that 232 * is case sensitive, which we do not want 233 */ 234 for (String type : supportedContentTypes) { 235 if (contentType.equalsIgnoreCase(type)) { 236 return true; 237 } 238 } 239 240 return false; 241 } 242 243 public String[] getSupportedContentTypes() { 244 if (supportedContentTypes.isEmpty()) { 245 loadContentTypes(); 246 } 247 248 return supportedContentTypes.toArray(new String[1]); 249 } 250 251 /** 252 * Whether a media source having the indicated protocol may be played. 253 * 254 * @see MediaManager#canPlayProtocol(java.lang.String) 255 * 256 * @throws IllegalArgumentException if 257 * <code>protocol</code> is 258 * <code>null</code>. 259 */ 260 public boolean canPlayProtocol(String protocol) { 261 if (protocol == null) { 262 throw new IllegalArgumentException("protocol == null!"); 263 } 264 265 if (supportedProtocols.isEmpty()) { 266 loadProtocols(); 267 } 268 269 /* 270 * Don't just use supportedProtocols.contains(protocol) as that 271 * is case sensitive, which we do not want 272 */ 273 for (String type : supportedProtocols) { 274 if (protocol.equalsIgnoreCase(type)) { 275 return true; 276 } 277 } 278 279 return false; 280 } 281 282 public static MetadataParser getMetadataParser(Locator locator) { 283 return PlatformManager.getManager().createMetadataParser(locator); 284 } 285 286 /** 287 * @see MediaManager#getPlayer(com.sun.media.jfxmedia.locator.Locator, int) 288 */ 289 public MediaPlayer getPlayer(Locator locator) { 290 // FIXME: remove this 291 initNativeLayer(); 292 293 MediaPlayer player = PlatformManager.getManager().createMediaPlayer(locator); 294 if (null == player) { 295 throw new MediaException("Could not create player!"); 296 } 297 298 // Cache a reference to the player. 299 allMediaPlayers.put(player, Boolean.TRUE); 300 301 return player; 302 } 303 304 /** 305 * Get a player for the media locator. A preference may be set as to whether 306 * to allow a full scan of the media. 307 * 308 * FIXME: Nuke permitFullScan, it is unused and has no effect 309 * 310 * @param locator 311 * @param permitFullScan 312 * @return MediaPlayer object 313 */ 314 public Media getMedia(Locator locator) { 315 initNativeLayer(); 316 return PlatformManager.getManager().createMedia(locator); 317 } 318 319 /** 320 * @see 321 * MediaManager#addMediaErrorListener(com.sun.media.jfxmedia.events.MediaErrorListener) 322 */ 323 public void addMediaErrorListener(MediaErrorListener listener) { 324 if (listener != null) { 325 // Since we have only one instance of NativeMediaManager, all media players 326 // created during application lifecycle will keep weak references to error 327 // listeners in errorListeners. Lets clean up unused references. 328 // FIXME: change to WeakHashMap<MEL,Boolean> as it's more efficient 329 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 330 MediaErrorListener l = it.next().get(); 331 if (l == null) { 332 it.remove(); 333 } 334 } 335 336 this.errorListeners.add(new WeakReference<MediaErrorListener>(listener)); 337 } 338 } 339 340 /** 341 * @see 342 * MediaManager#removeMediaErrorListener(com.sun.media.jfxmedia.events.MediaErrorListener) 343 */ 344 public void removeMediaErrorListener(MediaErrorListener listener) { 345 if (listener != null) { 346 // FIXME: change to WeakHashMap<MEL,Boolean> as it's more efficient 347 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 348 MediaErrorListener l = it.next().get(); 349 if (l == null || l == listener) { 350 it.remove(); 351 } 352 } 353 } 354 } 355 356 /** 357 * This function will register MediaPlayer for disposing when obj parameter 358 * does not have any strong reference. 359 * 360 * FIXME: Nuke this and use MediaDisposer instead 361 * 362 * @param obj - Object to watch for strong references 363 * @param player - MediaPlayer to dispose 364 */ 365 public static void registerMediaPlayerForDispose(Object obj, MediaPlayer player) { 366 MediaDisposer.addResourceDisposer(obj, player, playerDisposer); 367 } 368 369 /** 370 * Retrieve all un-disposed {@link MediaPlayer}s. 371 * 372 * @return a {@link List} of all un-disposed players or 373 * <code>null</code>. 374 */ 375 public List<MediaPlayer> getAllMediaPlayers() { 376 List<MediaPlayer> allPlayers = null; 377 378 if (!allMediaPlayers.isEmpty()) { 379 allPlayers = new ArrayList<MediaPlayer>(allMediaPlayers.keySet()); 380 } 381 382 return allPlayers; 383 } 384 385 //************************************************************************** 386 //***** Private functions 387 //************************************************************************** 388 List<WeakReference<MediaErrorListener>> getMediaErrorListeners() { 389 return this.errorListeners; 390 } 391 392 private static class NativeMediaPlayerDisposer implements MediaDisposer.ResourceDisposer { 393 394 public void disposeResource(Object resource) { 395 // resource is a MediaPlayer 396 MediaPlayer player = (MediaPlayer) resource; 397 if (player != null) { 398 player.dispose(); 399 } 400 } 401 } 402 }