package org.limewire.ui.swing.player; import java.awt.Canvas; import java.awt.Container; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import javax.media.IncompatibleSourceException; import javax.media.MediaLocator; import javax.media.Player; import javax.media.protocol.DataSource; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.limewire.inject.LazySingleton; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.player.api.AudioPlayer; import org.limewire.util.ExceptionUtils; import org.limewire.util.OSUtils; import com.google.inject.Inject; import com.google.inject.Provider; @LazySingleton class MediaPlayerFactoryImpl implements MediaPlayerFactory { private static final Log LOG = LogFactory.getLog(MediaPlayerFactoryImpl.class); private final Provider<AudioPlayer> player; private final List<String> handlers = new ArrayList<String>(2); private final JPanel p = new JPanel(); @Inject public MediaPlayerFactoryImpl(Provider<AudioPlayer> player) { this.player = player; registerHandlers(); } private void registerHandlers() { if(OSUtils.isWindows()) { handlers.add("net.sf.fmj.ds.media.content.unknown.Handler"); } else if(OSUtils.isMacOSX()) { handlers.add("net.sf.fmj.qt.media.content.unknown.JavaToCocoaHandler"); handlers.add("net.sf.fmj.qt.media.content.unknown.QuickTimeForJavaHandler"); } } public Player createMediaPlayer(File file, final Container parentComponent) throws IncompatibleSourceException { if (!OSUtils.isWindows() && !OSUtils.isMacOSX()) { throw new IllegalStateException("Video is only supported on Windows and Mac"); } Player handler = null; //attempt to cycle through the list of player handles. If one fails // attempt loading file with the next one for(String handle : handlers) { try { if(LOG.isDebugEnabled()) LOG.debug("loading " + handle); Class clazz = Class.forName(handle); Player player = (Player)clazz.newInstance(); setupPlayer(player, file); return player; } catch (IncompatibleSourceException e) { } catch (ClassNotFoundException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } } if(OSUtils.isWindows7()) { try { if(LOG.isDebugEnabled()) LOG.debug("loading MediaFoundationPlayer"); Player player = createWindows7MFPlayer(file, parentComponent); setupPlayer(player, file); return player; } catch (IncompatibleSourceException e) { } } if(LOG.isDebugEnabled()) LOG.debug("Loading default java player"); // default to the java sound player if all else fails handler = new JavaSoundPlayer(player); setupPlayer(handler, file); parentComponent.add(p); return handler; } /** * Creates a MF Player on Windows 7. Unlike other players, a Container must created and * realized before the player is created since it requires a handle to the Container. * As a result we wait and add the Canvas prior to attempting to create the Player but * only for MFPlayer. */ private Player createWindows7MFPlayer(File file, final Container parentComponent) throws IncompatibleSourceException { //DS failed. Now we try MF. final AtomicReference<Canvas> mfCanvas = new AtomicReference<Canvas>(); try { //create new canvas and add to parentComponent so we can get an hwnd SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { Canvas canvas = new Canvas(); parentComponent.add(canvas); //addNotify to make sure we have a working hwnd parentComponent.addNotify(); mfCanvas.set(canvas); } }); } catch (InterruptedException e) { throw new IncompatibleSourceException(e.toString() + " \n" + ExceptionUtils.getStackTrace(e)); } catch (InvocationTargetException e) { throw new IncompatibleSourceException(e.toString() + " \n" + ExceptionUtils.getStackTrace(e)); } final Player handler = new net.sf.fmj.mf.media.content.unknown.Handler(mfCanvas.get()); SwingUtilities.invokeLater(new Runnable() { public void run() { parentComponent.setPreferredSize(mfCanvas.get().getPreferredSize()); } }); return handler; } /** * Attempts to set the source on the player and realize it. After a player is realized * the player is ready to use. If the realization fails an IncompatibleSourceException * is thrown. */ private void setupPlayer(Player player, File file) throws IncompatibleSourceException { try { player.setSource(createDataSource(file)); } catch (IOException e) { throw new IncompatibleSourceException(e.toString() + " \n" + ExceptionUtils.getStackTrace(e)); } catch (UnsatisfiedLinkError e) { throw new IncompatibleSourceException(e.toString() + " \n" + ExceptionUtils.getStackTrace(e)); } player.realize(); } /** * Creates a DataSource from the file for use with the Player. */ private DataSource createDataSource(File file) throws MalformedURLException{ //FMJ's handling of files is incredibly fragile. This works with both quicktime and directshow. DataSource source = new net.sf.fmj.media.protocol.file.DataSource(); if (OSUtils.isMacOSX()) { // On OS-X escaping characters such as spaces, parenthesis, etc are causing the // files not to be loaded by FMJ. So let's not escape any characters. // NOTE: only three backslashes since absolute path returns one. Need 4 to escape // the string!! source.setLocator(new MediaLocator("file:///" + file.getAbsolutePath())); } else { // This produces a URL with the form file:/path rather than file:///path // This URL form is accepted on Windows but not OS X. String urlString = file.toURI().toURL().toExternalForm(); source.setLocator(new MediaLocator(urlString)); } return source; } }