// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.videomapping.video;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Adjustable;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.text.DateFormat;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openstreetmap.josm.gui.util.GuiHelper;
import uk.co.caprica.vlcj.player.DeinterlaceMode;
/** basic UI of a videoplayer for multiple videos incl. notifications
*/
public class VideoPlayer extends JFrame implements WindowListener, VideosObserver, VideoPlayerObserver {
private static final int notificationIntervall = 500;
protected JPanel screenPanel, controlsPanel, canvasPanel;
private JSlider timeline;
private JButton play, back, forward;
private JToggleButton loop, mute;
private JSlider speed;
// private DateFormat videoTimeFormat;
private VideoEngine videoengine;
private long jumpLength;
private long loopLength;
private Timer loopingTimer;
private boolean isManualJump;
private Timer notificationTimer;
private List<VideoPlayerObserver> observers;
public VideoPlayer(DateFormat videoTimeFormat) throws HeadlessException {
super();
// this.videoTimeFormat=videoTimeFormat;
//setup playback notifications
videoengine = new VideoEngine(this);
videoengine.addObserver(this);
observers = new LinkedList<>();
addObserver(this);
//setup GUI
setSize(400, 300); //later we apply movie size
setAlwaysOnTop(true);
createUI();
addUI();
addUIListeners();
setVisible(true);
setAlwaysOnTop(true);
this.addWindowListener(this);
}
public Video addVideo(File videofile, String id) {
Video video = new Video(videofile, id, videoengine.mediaPlayerFactory);
canvasPanel.add(video.panel);
video.canvas.setSize(new Dimension(300, 300)); // will be updated by the video engine itself
videoengine.add(video);
pack();
startNotificationTimer();
return video;
}
public List<Video> getVideos() {
return videoengine.getVideos();
}
public void pause() {
videoengine.pause();
if (videoengine.isNoVideoPlaying())
stopNotificationTimer();
else
startNotificationTimer();
}
public void pauseAll() {
stopNotificationTimer();
videoengine.pauseAll();
}
public void backward() {
videoengine.jumpFor(-jumpLength);
}
public void forward() {
videoengine.jumpFor(jumpLength);
}
public void setSpeed(Integer percent) {
speed.setValue(percent);
}
public Integer getSpeed() {
return speed.getValue();
}
public void setDeinterlacer(DeinterlaceMode deinterlacer) {
videoengine.setDeinterlacer(deinterlacer);
}
public void setSubtitles(boolean enabled) {
videoengine.setSubtitles(enabled);
}
public void mute() {
videoengine.mute();
}
//TODO auf mehrere Videos umstellen
public void toggleLooping() {
if (loopingTimer == null) {
//do reset after loop time experienced
final long videoResetTime = videoengine.getVideoTime()-loopLength/2;
TimerTask reset = new TimerTask() {
@Override
public void run() {
videoengine.jumpTo(videoResetTime);
}
};
loopingTimer = new Timer();
loopingTimer.schedule(reset, loopLength/2, loopLength);
} else {
loopingTimer.cancel();
loopingTimer = null;
}
}
//create all normal player controls
private void createUI() {
//setIconImage();
timeline = new JSlider(0, 100, 0);
timeline.setMajorTickSpacing(5);
timeline.setMinorTickSpacing(1);
timeline.setPaintTicks(true);
play = new JButton(tr("play"));
back = new JButton("<");
forward = new JButton(">");
loop = new JToggleButton(tr("loop"));
mute = new JToggleButton(tr("mute"));
speed = new JSlider(0, 200, 100);
speed.setMajorTickSpacing(50);
speed.setPaintTicks(true);
speed.setOrientation(Adjustable.VERTICAL);
Hashtable<Integer, JLabel> labelTable = new Hashtable<>();
labelTable.put(new Integer(100), new JLabel("1x"));
labelTable.put(new Integer(50), new JLabel("-2x"));
labelTable.put(new Integer(200), new JLabel("2x"));
speed.setLabelTable(labelTable);
speed.setPaintLabels(true);
}
//puts all player controls to screen
private void addUI() {
//create layouts
this.setLayout(new BorderLayout());
screenPanel = new JPanel();
screenPanel.setLayout(new BorderLayout());
controlsPanel = new JPanel();
controlsPanel.setLayout(new FlowLayout());
canvasPanel = new JPanel();
canvasPanel.setLayout(new FlowLayout());
add(screenPanel, BorderLayout.CENTER);
add(controlsPanel, BorderLayout.SOUTH);
//fill screen panel
screenPanel.add(canvasPanel, BorderLayout.CENTER);
screenPanel.add(timeline, BorderLayout.SOUTH);
screenPanel.add(speed, BorderLayout.EAST);
controlsPanel.add(play);
controlsPanel.add(back);
controlsPanel.add(forward);
controlsPanel.add(loop);
controlsPanel.add(mute);
loop.setSelected(false);
mute.setSelected(false);
}
//add UI functionality
private void addUIListeners() {
play.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
pause();
}
});
back.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
backward();
}
});
forward.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
forward();
}
});
loop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
toggleLooping();
}
});
mute.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
mute();
}
});
timeline.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
//skip events, fired by this sliede, one cycle ago
if (!isManualJump) {
isManualJump = true;
videoengine.jumpToPosition((timeline.getValue()));
}
}
});
speed.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent arg0) {
if (!speed.getValueIsAdjusting()) {
videoengine.setSpeed(speed.getValue());
}
}
});
}
public void setJumpLength(long ms) {
jumpLength = ms;
}
public void setLoopLength(long ms) {
loopLength = ms;
}
public void enableSingleVideoMode(boolean enabled) {
pauseAll();
videoengine.enableSingleVideoMode(enabled);
}
public void addObserver(VideoPlayerObserver observer) {
observers.add(observer);
}
private void stopNotificationTimer() {
/*
if(notificationTimer!=null)
{
notificationTimer.cancel();
notificationTimer=null;
}
*/
}
private void startNotificationTimer() {
notificationTimer = new Timer();
notificationTimer.schedule(new TimerTask() {
@Override
public void run() {
notifyObservers();
}
}, notificationIntervall, notificationIntervall);
}
private void notifyObservers() {
for (VideoPlayerObserver observer : observers) {
observer.update_plays(); // TODO hier müssten gleich die Zeiten übergeben werden
}
}
@Override
public void windowActivated(WindowEvent arg0) { }
@Override
public void windowClosed(WindowEvent arg0) { }
@Override
public void windowClosing(WindowEvent arg0) {
videoengine.unload();
}
@Override
public void windowDeactivated(WindowEvent arg0) { }
@Override
public void windowDeiconified(WindowEvent arg0) { }
@Override
public void windowIconified(WindowEvent arg0) { }
@Override
public void windowOpened(WindowEvent arg0) { }
@Override
public void update(VideoObserversEvents event) {
switch (event) {
case resizing:
{
pack();
break;
}
case speeding:
{
speed.setValue(videoengine.getSpeed());
break;
}
case jumping:
{
break;
}
}
}
//keep internal controls up to date during playback
@Override
public void update_plays() {
GuiHelper.runInEDT(new Runnable() {
@Override
public void run() {
timeline.setValue(videoengine.getPosition());
setTitle(Long.toString(videoengine.getVideoTime()));
}
});
isManualJump = false;
}
public boolean isCorrectlyInitiliazed() {
return videoengine != null && videoengine.mediaPlayerFactory != null;
}
}