Project-HomeFlix/src/main/java/kellerkinder/HomeFlix/player/PlayerController.java

376 lines
12 KiB
Java

/**
* Project-HomeFlix
*
* Copyright 2016-2020 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package kellerkinder.HomeFlix.player;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelBuffer;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.media.MediaView;
import kellerkinder.HomeFlix.controller.DBController;
import kellerkinder.HomeFlix.controller.XMLController;
import kellerkinder.HomeFlix.datatypes.FilmTabelDataType;
import uk.co.caprica.vlcj.factory.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.base.MediaPlayer;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.player.embedded.videosurface.CallbackVideoSurface;
import uk.co.caprica.vlcj.player.embedded.videosurface.VideoSurfaceAdapters;
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.BufferFormat;
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.BufferFormatCallback;
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.RenderCallback;
import uk.co.caprica.vlcj.player.embedded.videosurface.callback.format.RV32BufferFormat;
public class PlayerController {
@FXML private MediaView mediaView;
@FXML private ImageView videoImageView;
@FXML private HBox hBoxTop;
@FXML private HBox controlsHBox;
@FXML private VBox bottomVBox;
@FXML private JFXSlider timeSlider;
@FXML private JFXButton btnBack;
@FXML private JFXButton playBtn;
@FXML private JFXButton fullscreenBtn;
@FXML private JFXButton nextEpBtn;
@FXML private ImageView stopIcon;
@FXML private ImageView playIcon;
@FXML private ImageView fullscreenIcon;
@FXML private Label lblTitle;
@FXML private Label lblEndTime;
private Player player;
private MediaPlayerFactory mediaPlayerFactory;
private EmbeddedMediaPlayer embeddedMediaPlayer;
private WritableImage videoImage;
private PixelBuffer<ByteBuffer> videoPixelBuffer;
private FilmTabelDataType media;
private long startTime = 0;
private long currentTime = 0;
private long endTime = 0;
private long skipTime = 0;
private long duration = 0;
private int season = 0;
private int episode = 0;
private boolean mousePressed = false;
private boolean showControls = true;
private boolean autoplay;
private Image playArrow = new Image("icons/baseline_play_arrow_black_48dp.png");
private Image pause = new Image("icons/baseline_pause_black_48dp.png");
private Image fullscreen = new Image("icons/baseline_fullscreen_black_48dp.png");
private Image fullscreenExit = new Image("icons/baseline_fullscreen_exit_black_48dp.png");
/**
* create a new PlayerWindow object
* @param player the player object (needed for closing action)
* @param film the film object
*/
public PlayerController(Player player, String mediaURL) {
this.player = player;
this.media = DBController.getInstance().getStream(mediaURL);
mediaPlayerFactory = new MediaPlayerFactory();
embeddedMediaPlayer = mediaPlayerFactory.mediaPlayers().newEmbeddedMediaPlayer();
embeddedMediaPlayer.videoSurface().set(new FXCallbackVideoSurface());
}
public void init() {
// initialize the image view
videoImageView.setPreserveRatio(true);
videoImageView.fitWidthProperty().bind(player.getStage().widthProperty());
videoImageView.fitHeightProperty().bind(player.getStage().heightProperty());
// set needed variables
startTime = (long) DBController.getInstance().getCurrentTime(media.getStreamUrl());
autoplay = XMLController.isAutoplay();
season = !media.getSeason().isEmpty() ? Integer.parseInt(media.getSeason()) : 0;
episode = !media.getEpisode().isEmpty() ? Integer.parseInt(media.getEpisode()) : 0;
lblTitle.setText(media.getTitle());
initPlayerWindow();
initMediaPlayer();
initSlider();
}
/**
* initialize some PlayerWindow GUI-Elements actions
*/
private void initPlayerWindow() {
player.getScene().addEventFilter(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
// hide controls timer initialization
final Timer timer = new Timer();
TimerTask controlAnimationTask = null; // task to execute save operation
final long delayTime = 3000; // hide the controls after 2 seconds
@Override
public void handle(MouseEvent mouseEvent) {
// show controls
if (!showControls) {
player.getScene().setCursor(Cursor.DEFAULT);
hBoxTop.setVisible(true);
bottomVBox.setVisible(true);
}
// hide controls
if (controlAnimationTask != null)
controlAnimationTask.cancel();
controlAnimationTask = new TimerTask() {
@Override
public void run() {
hBoxTop.setVisible(false);
bottomVBox.setVisible(false);
player.getScene().setCursor(Cursor.NONE);
showControls = false;
}
};
timer.schedule(controlAnimationTask, delayTime);
}
});
}
private void initMediaPlayer() {
embeddedMediaPlayer.events().addMediaPlayerEventListener( new HFMediaPlayerEventListener() {
@Override
public void timeChanged(MediaPlayer mediaPlayer, long newTime) {
currentTime = newTime;
endTime = duration - newTime;
Platform.runLater(() -> {
updateControls();
});
}
@Override
public void error(MediaPlayer mediaPlayer) {
// Auto-generated method stub
}
@Override
public void lengthChanged(MediaPlayer mediaPlayer, long newLength) {
duration = newLength;
timeSlider.setMax((duration / 1000) / 60); // TODO move timeslider to seconds
}
});
}
private void initSlider() {
// if the mouse on the timeSlider is released, skip to the new position
timeSlider.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
embeddedMediaPlayer.controls().skipTime(skipTime);
mousePressed = false;
}
});
timeSlider.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
mousePressed = true;
}
});
// get the new skip time
timeSlider.valueProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
skipTime = ((new_val.longValue() * 1000 * 60) - currentTime);
}
});
}
public void start() {
embeddedMediaPlayer.media().play(media.getStreamUrl());
embeddedMediaPlayer.controls().skipTime((long) startTime); // skipt to the start time
}
public void stop() {
DBController.getInstance().setCurrentTime(media.getStreamUrl(), embeddedMediaPlayer.status().time());
embeddedMediaPlayer.controls().stop();
embeddedMediaPlayer.release();
mediaPlayerFactory.release();
}
/**
* call this every second to update all timers
*/
private void updateControls() {
// update slider position, if the mouse does not press on the time
if (!mousePressed) {
timeSlider.setValue((currentTime / 1000) / 60);
}
// update endTime label
lblEndTime.setText(String.format("%d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(endTime) % 24,
TimeUnit.MILLISECONDS.toMinutes(endTime) % 60,
TimeUnit.MILLISECONDS.toSeconds(endTime) % 60));
// show the next episode button 30 seconds before the end of a episode
if (endTime < 31000 && episode != 0 && autoplay) {
int countdown = (int) ((endTime / 1000) - 20); // a 10 seconds countdown
if (!nextEpBtn.isVisible()) {
nextEpBtn.setVisible(true);
}
if (endTime > 20000) {
nextEpBtn.setText(XMLController.getLocalBundle().getString("nextEpisode")
+ countdown + XMLController.getLocalBundle().getString("seconds"));
bottomVBox.setVisible(true);
System.out.println("next episode in " + countdown + " seconds");
} else {
nextEpBtn.setVisible(false);
// TODO start next episode
System.out.println("playing next episode ...");
}
}
}
@FXML
void btnBackAction(ActionEvent event) {
stop();
player.getStage().close();
}
@FXML
void fullscreenBtnAction(ActionEvent event) {
if (player.getStage().isFullScreen()) {
player.getStage().setFullScreen(false);
fullscreenIcon.setImage(fullscreen);
} else {
player.getStage().setFullScreen(true);
fullscreenIcon.setImage(fullscreenExit);
}
}
@FXML
void playBtnAction(ActionEvent event) {
if (embeddedMediaPlayer.status().isPlaying()) {
embeddedMediaPlayer.controls().pause();
playIcon.setImage(playArrow);
} else {
embeddedMediaPlayer.controls().play();
playIcon.setImage(pause);
}
}
@FXML
void nextEpBtnAction(ActionEvent event) {
playNextMedia(); // TODO
}
private void playNextMedia() {
autoplay = false;
DBController.getInstance().setCurrentTime(media.getStreamUrl(), 0); // reset old video start time
FilmTabelDataType nextMedia = DBController.getInstance().getNextEpisode(media.getTitle(), episode, season);
if (nextMedia != null) {
//mediaPlayer2.stop();
media = nextMedia;
autoplay = true;
}
}
public double getCurrentTime() {
return currentTime;
}
private class FXCallbackVideoSurface extends CallbackVideoSurface {
FXCallbackVideoSurface() {
super(new FXBufferFormatCallback(), new FXRenderCallback(), true,
VideoSurfaceAdapters.getVideoSurfaceAdapter());
}
}
private class FXBufferFormatCallback implements BufferFormatCallback {
private int sourceWidth;
private int sourceHeight;
@Override
public BufferFormat getBufferFormat(int sourceWidth, int sourceHeight) {
this.sourceWidth = sourceWidth;
this.sourceHeight = sourceHeight;
return new RV32BufferFormat(sourceWidth, sourceHeight);
}
@Override
public void allocatedBuffers(ByteBuffer[] buffers) {
assert buffers[0].capacity() == sourceWidth * sourceHeight * 4;
PixelFormat<ByteBuffer> pixelFormat = PixelFormat.getByteBgraPreInstance();
videoPixelBuffer = new PixelBuffer<>(sourceWidth, sourceHeight, buffers[0], pixelFormat);
videoImage = new WritableImage(videoPixelBuffer);
videoImageView.setImage(videoImage);
}
}
private class FXRenderCallback implements RenderCallback {
@Override
public void display(uk.co.caprica.vlcj.player.base.MediaPlayer mediaPlayer, ByteBuffer[] nativeBuffers,
BufferFormat bufferFormat) {
Platform.runLater(() -> {
videoPixelBuffer.updateBuffer(pb -> null);
});
}
}
}