1 /*
   2  * Copyright (c) 2010, 2018, 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 }