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