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.List; 40 import java.util.ListIterator; 41 import java.util.Map; 42 import java.util.WeakHashMap; 43 44 /** 45 * A class representing a native media engine. 46 */ 47 public class NativeMediaManager { 48 /** 49 * Whether the native layer has been initialized. 50 */ 51 private static boolean isNativeLayerInitialized = false; 52 /** 53 * The {@link MediaErrorListener}s. 54 */ 55 // FIXME: Change to WeakHashMap<MediaErrorListener,Boolean> as it's more efficient 56 private final List<WeakReference<MediaErrorListener>> errorListeners = 57 new ArrayList(); 58 private final static NativeMediaPlayerDisposer playerDisposer = 59 new NativeMediaPlayerDisposer(); 60 /** 61 * List of all un-disposed players. 62 */ 63 private final static Map<MediaPlayer,Boolean> allMediaPlayers = 64 new WeakHashMap(); 65 66 // cached content types, so we don't have to poll and sort each time, this list 67 // should never change once we're initialized 68 private final List<String> supportedContentTypes = 69 new ArrayList(); 70 private final List<String> supportedProtocols = 71 new ArrayList<>(); 72 73 /** 74 * The NativeMediaManager singleton. 75 */ 76 private static class NativeMediaManagerInitializer { 77 private static final NativeMediaManager globalInstance 78 = new NativeMediaManager(); 79 } 80 81 /** 82 * Get the default 83 * <code>NativeMediaManager</code>. 84 * 85 * @return the singleton 86 * <code>NativeMediaManager</code> instance. 87 */ 88 public static NativeMediaManager getDefaultInstance() { 89 return NativeMediaManagerInitializer.globalInstance; 90 } 91 92 //************************************************************************** 93 //***** Constructors 94 //************************************************************************** 95 /** 96 * Create a <code>NativeMediaManager</code>. 97 */ 98 protected NativeMediaManager() { 99 /* 100 * Load native libraries. This must be done early as platforms may need 101 * to attempt loading their own native libs that are dependent on these 102 * This is a slight performance hit, but necessary otherwise we could 103 * erroneously report content types for platforms that cannot be loaded 104 */ 105 try { 106 AccessController.doPrivileged((PrivilegedExceptionAction) () -> { 107 if (HostUtils.isWindows() || HostUtils.isMacOSX()) { 108 NativeLibLoader.loadLibrary("glib-lite"); 109 } 110 111 if (!HostUtils.isLinux() && !HostUtils.isIOS()) { 112 NativeLibLoader.loadLibrary("gstreamer-lite"); 113 } 114 115 NativeLibLoader.loadLibrary("jfxmedia"); 116 return null; 117 }); 118 } catch (PrivilegedActionException pae) { 119 MediaUtils.error(null, MediaError.ERROR_MANAGER_ENGINEINIT_FAIL.code(), 120 "Unable to load one or more dependent libraries.", pae); 121 } 122 123 // Get the Logger native side rolling before we load platforms 124 if (!Logger.initNative()) { 125 MediaUtils.error(null, MediaError.ERROR_MANAGER_LOGGER_INIT.code(), 126 "Unable to init logger", null); 127 } 128 } 129 130 /** 131 * Initialize the native layer if it has not been so already. 132 */ 133 synchronized static void initNativeLayer() { 134 if (!isNativeLayerInitialized) { 135 // load platforms 136 PlatformManager.getManager().loadPlatforms(); 137 138 // Set the native initialization flag, even if initialization failed. 139 isNativeLayerInitialized = true; 140 } 141 } 142 143 //************************************************************************** 144 //***** Public control functions 145 //************************************************************************** 146 147 private synchronized void loadContentTypes() { 148 if (!supportedContentTypes.isEmpty()) { 149 // already populated, just return 150 return; 151 } 152 153 List<String> npt = PlatformManager.getManager().getSupportedContentTypes(); 154 if (null != npt && !npt.isEmpty()) { 155 supportedContentTypes.addAll(npt); 156 } 157 158 if (Logger.canLog(Logger.DEBUG)) { 159 StringBuilder sb = new StringBuilder("JFXMedia supported content types:\n"); 160 for (String type : supportedContentTypes) { 161 sb.append(" "); 162 sb.append(type); 163 sb.append("\n"); 164 } 165 Logger.logMsg(Logger.DEBUG, sb.toString()); 166 } 167 } 168 169 private synchronized void loadProtocols() { 170 if (!supportedProtocols.isEmpty()) { 171 // already populated, just return 172 return; 173 } 174 175 List<String> npt = PlatformManager.getManager().getSupportedProtocols(); 176 if (null != npt && !npt.isEmpty()) { 177 supportedProtocols.addAll(npt); 178 } 179 180 if (Logger.canLog(Logger.DEBUG)) { 181 StringBuilder sb = new StringBuilder("JFXMedia supported protocols:\n"); 182 for (String type : supportedProtocols) { 183 sb.append(" "); 184 sb.append(type); 185 sb.append("\n"); 186 } 187 Logger.logMsg(Logger.DEBUG, sb.toString()); 188 } 189 } 190 191 /** 192 * Whether a media source having the indicated content type may be played. 193 * 194 * @see MediaManager#canPlayContentType(java.lang.String) 195 * 196 * @throws IllegalArgumentException if 197 * <code>contentType</code> is 198 * <code>null</code>. 199 */ 200 public boolean canPlayContentType(String contentType) { 201 if (contentType == null) { 202 throw new IllegalArgumentException("contentType == null!"); 203 } 204 205 if (supportedContentTypes.isEmpty()) { 206 loadContentTypes(); 207 } 208 209 /* 210 * Don't just use supportedContentType.contains(contentType) as that 211 * is case sensitive, which we do not want 212 */ 213 for (String type : supportedContentTypes) { 214 if (contentType.equalsIgnoreCase(type)) { 215 return true; 216 } 217 } 218 219 return false; 220 } 221 222 public String[] getSupportedContentTypes() { 223 if (supportedContentTypes.isEmpty()) { 224 loadContentTypes(); 225 } 226 227 return supportedContentTypes.toArray(new String[1]); 228 } 229 230 /** 231 * Whether a media source having the indicated protocol may be played. 232 * 233 * @see MediaManager#canPlayProtocol(java.lang.String) 234 * 235 * @throws IllegalArgumentException if 236 * <code>protocol</code> is 237 * <code>null</code>. 238 */ 239 public boolean canPlayProtocol(String protocol) { 240 if (protocol == null) { 241 throw new IllegalArgumentException("protocol == null!"); 242 } 243 244 if (supportedProtocols.isEmpty()) { 245 loadProtocols(); 246 } 247 248 /* 249 * Don't just use supportedProtocols.contains(protocol) as that 250 * is case sensitive, which we do not want 251 */ 252 for (String type : supportedProtocols) { 253 if (protocol.equalsIgnoreCase(type)) { 254 return true; 255 } 256 } 257 258 return false; 259 } 260 261 public static MetadataParser getMetadataParser(Locator locator) { 262 return PlatformManager.getManager().createMetadataParser(locator); 263 } 264 265 /** 266 * @see MediaManager#getPlayer(com.sun.media.jfxmedia.locator.Locator, int) 267 */ 268 public MediaPlayer getPlayer(Locator locator) { 269 // FIXME: remove this 270 initNativeLayer(); 271 272 MediaPlayer player = PlatformManager.getManager().createMediaPlayer(locator); 273 if (null == player) { 274 throw new MediaException("Could not create player!"); 275 } 276 277 // Cache a reference to the player. 278 allMediaPlayers.put(player, Boolean.TRUE); 279 280 return player; 281 } 282 283 /** 284 * Get a player for the media locator. A preference may be set as to whether 285 * to allow a full scan of the media. 286 * 287 * FIXME: Nuke permitFullScan, it is unused and has no effect 288 * 289 * @param locator 290 * @param permitFullScan 291 * @return MediaPlayer object 292 */ 293 public Media getMedia(Locator locator) { 294 initNativeLayer(); 295 return PlatformManager.getManager().createMedia(locator); 296 } 297 298 /** 299 * @see 300 * MediaManager#addMediaErrorListener(com.sun.media.jfxmedia.events.MediaErrorListener) 301 */ 302 public void addMediaErrorListener(MediaErrorListener listener) { 303 if (listener != null) { 304 // Since we have only one instance of NativeMediaManager, all media players 305 // created during application lifecycle will keep weak references to error 306 // listeners in errorListeners. Lets clean up unused references. 307 // FIXME: change to WeakHashMap<MEL,Boolean> as it's more efficient 308 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 309 MediaErrorListener l = it.next().get(); 310 if (l == null) { 311 it.remove(); 312 } 313 } 314 315 this.errorListeners.add(new WeakReference<MediaErrorListener>(listener)); 316 } 317 } 318 319 /** 320 * @see 321 * MediaManager#removeMediaErrorListener(com.sun.media.jfxmedia.events.MediaErrorListener) 322 */ 323 public void removeMediaErrorListener(MediaErrorListener listener) { 324 if (listener != null) { 325 // FIXME: change to WeakHashMap<MEL,Boolean> as it's more efficient 326 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 327 MediaErrorListener l = it.next().get(); 328 if (l == null || l == listener) { 329 it.remove(); 330 } 331 } 332 } 333 } 334 335 /** 336 * This function will register MediaPlayer for disposing when obj parameter 337 * does not have any strong reference. 338 * 339 * FIXME: Nuke this and use MediaDisposer instead 340 * 341 * @param obj - Object to watch for strong references 342 * @param player - MediaPlayer to dispose 343 */ 344 public static void registerMediaPlayerForDispose(Object obj, MediaPlayer player) { 345 MediaDisposer.addResourceDisposer(obj, player, playerDisposer); 346 } 347 348 /** 349 * Retrieve all un-disposed {@link MediaPlayer}s. 350 * 351 * @return a {@link List} of all un-disposed players or 352 * <code>null</code>. 353 */ 354 public List<MediaPlayer> getAllMediaPlayers() { 355 List<MediaPlayer> allPlayers = null; 356 357 if (!allMediaPlayers.isEmpty()) { 358 allPlayers = new ArrayList<MediaPlayer>(allMediaPlayers.keySet()); 359 } 360 361 return allPlayers; 362 } 363 364 //************************************************************************** 365 //***** Private functions 366 //************************************************************************** 367 List<WeakReference<MediaErrorListener>> getMediaErrorListeners() { 368 return this.errorListeners; 369 } 370 371 private static class NativeMediaPlayerDisposer implements MediaDisposer.ResourceDisposer { 372 373 public void disposeResource(Object resource) { 374 // resource is a MediaPlayer 375 MediaPlayer player = (MediaPlayer) resource; 376 if (player != null) { 377 player.dispose(); 378 } 379 } 380 } 381 }