/**
* Copyright 2009 Marc Stogaitis and Mimi Sun
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gmote.server.media.vlc;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.gmote.common.FileInfo;
import org.gmote.common.FileInfo.FileType;
import org.gmote.common.Protocol.Command;
import org.gmote.common.media.MediaMetaInfo;
import org.gmote.server.PlatformUtil;
import org.gmote.server.ServerUtil;
import org.gmote.server.StringEncrypter;
import org.gmote.server.media.MediaCommandHandler;
import org.gmote.server.media.MediaInfoUpdater;
import org.gmote.server.media.MediaPlayerInterface;
import org.gmote.server.media.UnsupportedCommandException;
import org.gmote.server.settings.DefaultSettings;
import org.gmote.server.settings.DefaultSettingsEnum;
import org.gmote.server.settings.SupportedFiletypeSettings;
import org.videolan.jvlc.Audio;
import org.videolan.jvlc.JVLC;
import org.videolan.jvlc.LoggerMessage;
import org.videolan.jvlc.LoggerVerbosityLevel;
import org.videolan.jvlc.MediaDescriptor;
import org.videolan.jvlc.MediaPlayer;
import org.videolan.jvlc.Playlist;
import org.videolan.jvlc.VLCException;
import org.videolan.jvlc.Video;
import org.videolan.jvlc.event.MediaPlayerListener;
@SuppressWarnings("deprecation")
public class VlcMediaPlayer implements MediaPlayerInterface {
private static final int MAX_DELAY_ATTEMPTS = 5;
private static Logger LOGGER = Logger.getLogger(VlcMediaPlayer.class.getName());
private static final String VLC_LOG_NAME = "/logs/vlc.log";
private static final long VIDEO_DELAY_TIMEOUT = 10 * 1000;
// VLC Player.
JVLC jvlc;
MediaCommandHandler commandHandler;
VlcMediaPlayerFrame mediaPlayerFrame;
// Player state.
private boolean playingVideo = false;
public VlcMediaPlayer() {
}
@Override
public void initialise(String[] arguments) {
// Construct a vlc media player object.
if (PlatformUtil.isLinux()) {
String[] param = new String[] { "-vvv", "--fullscreen", "--extraintf=hotkeys",
"--no-plugins-cache" };
jvlc = new JVLC(param);
} else {
String vlcPath = ServerUtil.instance().findInstallDirectory().replaceAll("/", "\\\\") + "\\bin\\vlc";
for (String arg : arguments) {
String[] argSplit = arg.split("=");
if (argSplit.length == 2) {
if (argSplit[0].equalsIgnoreCase("vlcpath")) {
vlcPath = argSplit[1];
}
}
}
String[] param = new String[] { "-vvv", "--plugin-path=" + vlcPath + "\\plugins",
"--no-plugins-cache" };
try {
jvlc = new JVLC(param);
} catch (UnsatisfiedLinkError e) {
String message = "Error while gmote tried to start vlc. Make sure that vlc is installed and in your system PATH variable. Please see the logs for more details or visit www.gmote.org/faq : " + e.getMessage();
LOGGER.log(Level.SEVERE, message, e);
JOptionPane.showMessageDialog(null, message);
System.exit(1);
}
}
if (Boolean.parseBoolean(DefaultSettings.instance().getSetting(DefaultSettingsEnum.LOG_VLC))) {
jvlc.setLogVerbosity(LoggerVerbosityLevel.INFO);
} else {
jvlc.setLogVerbosity(LoggerVerbosityLevel.WARNING);
}
jvlc.setLogVerbosity(LoggerVerbosityLevel.DEBUG);
writeVlcLog();
}
@Override
public synchronized void controlPlayer(Command command) throws UnsupportedCommandException {
// Delegate to the appropriate handler.
if (commandHandler != null) {
commandHandler.executeCommand(command);
if (command == Command.CLOSE) {
doClose();
}
}
}
/**
* Closes the media. Synchronized since we can call this method by receiving
* an request from the client or by receiving an 'end reached' event on the
* media listener.
*/
private synchronized void doClose() {
if (playingVideo && mediaPlayerFrame != null) {
mediaPlayerFrame.closeFrame();
}
playingVideo = false;
Runtime.getRuntime().gc();
}
/**
* Launches a media file in the media player.
*
* @throws UnsupportedEncodingException
* @throws UnsupportedCommandException
*
* @see {@link VlcPlaylistCommandHandler} for information about deprecation
* warning
*/
@Override
public synchronized void runFile(FileInfo fileInfo)
throws UnsupportedEncodingException, UnsupportedCommandException {
// Stop the player if it is already playing
MediaInfoUpdater.instance().setPlayerToPoll(null);
if (commandHandler != null) {
if (playingVideo) {
controlPlayer(Command.CLOSE);
} else {
controlPlayer(Command.STOP);
}
}
// Set the default volume.
Audio audio = new Audio(jvlc);
audio.setVolume(Integer.parseInt(DefaultSettings.instance().getSetting(
DefaultSettingsEnum.VOLUME)));
String fileName = fileInfo.getAbsolutePath();
FileType fileType = fileInfo.getFileType();
if (fileType == FileType.PLAYLIST || fileType == FileType.MUSIC) {
runMusic(fileName, fileType);
} else {
// Setup the player with the current file to play.
runMovie(fileName, fileType);
}
}
@Override
public MediaMetaInfo getNewMediaInfo() {
if (commandHandler == null) {
return null;
}
return commandHandler.getNewMediaInfo();
}
private void runMovie(String fileName, FileType fileType) throws UnsupportedCommandException {
// Appends dvdsimple to the file name so that vlc will skip all of the dvd
// menus.
if (fileType == FileType.DVD_DRIVE) {
fileName = "dvdsimple://" + fileName;
}
MediaDescriptor mediaDescriptor = new MediaDescriptor(jvlc, fileName);
MediaPlayer player = mediaDescriptor.getMediaPlayer();
commandHandler = VlcDefaultCommandHandler.instance(player, jvlc);
commandHandler.setMediaIsOpen(true);
if (!PlatformUtil.isLinux()) {
// Setup the frame that the player will play in.
mediaPlayerFrame = new VlcMediaPlayerFrame(this, player);
// Make the window maximized.
mediaPlayerFrame.createFullScreenWindow();
// Tell VLC about the frame it will play in.
jvlc.setVideoOutput(mediaPlayerFrame.getCanvas());
}
playingVideo = true;
controlPlayer(Command.PLAY);
long startTime = new Date().getTime();
while (!player.hasVideoOutput() && new Date().getTime() - startTime < VIDEO_DELAY_TIMEOUT) {
sleep(100);
}
if (!player.hasVideoOutput()) {
controlPlayer(Command.CLOSE);
LOGGER.log(Level.SEVERE, "Unable to launch video file");
} else if (PlatformUtil.isLinux()){
Video video = new Video(jvlc);
video.setFullscreen(player, true);
}
}
private void runMusic(String fileName, FileType fileType) throws UnsupportedCommandException {
Playlist playList;
// Special handling for playlists since there is a bug in jvlc related to
// playlists.
playList = new Playlist(jvlc);
int idOfOriginal = -1;
try {
if (fileType == FileType.PLAYLIST) {
playList.add(fileName, "Playlist");
playList.next();
} else {
// Add all of the files of the directory in the playlist.
File originalFile = new File(fileName);
File[] allFilesInDirectory = originalFile.getParentFile().listFiles();
if (DefaultSettings.instance().getSetting(DefaultSettingsEnum.SHUFFLE_SONGS)
.equalsIgnoreCase("true")) {
Collections.shuffle(Arrays.asList(allFilesInDirectory));
}
for (File file : allFilesInDirectory) {
String name = file.getName();
FileType type = SupportedFiletypeSettings.fileNameToFileType(name);
if (type == FileType.MUSIC) {
int id = playList.add(file.getAbsolutePath(), file.getName());
if (originalFile.equals(file)) {
idOfOriginal = id;
}
}
}
}
} catch (VLCException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
commandHandler = VlcPlaylistCommandHandler.instance(playList, idOfOriginal, jvlc);
commandHandler.setMediaIsOpen(true);
controlPlayer(Command.PLAY);
try {
for (int i = 0; i < MAX_DELAY_ATTEMPTS && idOfOriginal != -1
&& (playList.getCurrentIndex() != idOfOriginal); i++) {
sleep(100);
}
} catch (VLCException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
throw new UnsupportedCommandException(e.getMessage());
}
// Wait until the song starts playing.
sleep(100);
try {
long startTime = new Date().getTime();
while (!playList.isRunning() && new Date().getTime() - startTime < VIDEO_DELAY_TIMEOUT) {
sleep(100);
}
} catch (VLCException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
MediaInfoUpdater.instance().sendMediaUpdate(getNewMediaInfo());
MediaInfoUpdater.instance().setPlayerToPoll(this);
}
private void writeVlcLog() {
if (Boolean.parseBoolean(DefaultSettings.instance().getSetting(DefaultSettingsEnum.LOG_VLC))) {
Iterator<LoggerMessage> it = jvlc.getLogger().iterator();
try {
URL url = StringEncrypter.class.getResource(VLC_LOG_NAME);
BufferedWriter writer = new BufferedWriter(new FileWriter(url.getPath().replaceAll("%20",
" ")));
while (it.hasNext()) {
LoggerMessage message = it.next();
writer.write(new Date() + " " + message.getMessage());
writer.newLine();
}
writer.close();
} catch (FileNotFoundException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
}
}
/**
* Convenience function that simply logs the exception.
*
* @param timeInMili
*/
private void sleep(long timeInMili) {
try {
Thread.sleep(timeInMili);
} catch (InterruptedException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
}
/**
* Allows us to close media files automatically when they are done playing.
* NOTE: We stopped using this class since we experienced this behavior: - If
* we played a movie, closed it, played it again closed it... and did this
* several times, the entire app would eventually crash. We would get the
* following error in the logs: *** LibVLC Exception not handled: This object
* event manager doesn't know about
* 'libvlc_MediaPlayerPlaying,0C06F720,00000000' event observer Set a
* breakpoint in 'libvlc_exception_not_handled' to debug. LibVLC Exception not
* handled: This object event manager doesn't know about
* 'libvlc_MediaPlayerPaused,0C06F720,00000000' event observer Set a
* breakpoint in 'libvlc_exception_not_handled' to debug. LibVLC Exception not
* handled: This object event manager doesn't know about
* 'libvlc_MediaPlayerStopped,0C06F720,00000000' event observer Set a
* breakpoint in 'libvlc_exception_not_handled' to debug. LibVLC Exception not
* handled: This object event manager doesn't know about
* 'libvlc_MediaPlayerForward,0C06F720,00000000' event observer
*
* @author Marc
*
*/
public class PlayerListener implements MediaPlayerListener {
@Override
public void endReached(MediaPlayer mediaPlayer) {
}
@Override
public void errorOccurred(MediaPlayer mediaPlayer) {
LOGGER.severe("errorOccurred was called");
}
@Override
public void paused(MediaPlayer mediaPlayer) {
}
@Override
public void playing(MediaPlayer mediaPlayer) {
}
@Override
public void positionChanged(MediaPlayer mediaPlayer) {
}
@Override
public void stopped(MediaPlayer mediaPlayer) {
}
@Override
public void timeChanged(MediaPlayer mediaPlayer, long newTime) {
}
}
@Override
public List<FileInfo> getBaseLibraryFiles() {
return null;
}
@Override
public List<FileInfo> getLibrarySubFiles(FileInfo fileInfo) {
return null;
}
@Override
public boolean isRunning() {
return true;
}
}