2018-04-02 18:29:59 +02:00
/ * *
* Project - HomeFlix
*
2020-04-01 19:48:44 +02:00
* Copyright 2016 - 2020 < seil0 @mosad.xyz >
2018-04-02 18:29:59 +02:00
*
* 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 .
*
* /
2018-12-04 22:31:11 +01:00
2018-04-01 23:24:49 +02:00
package kellerkinder.HomeFlix.player ;
2020-04-01 19:48:44 +02:00
import java.nio.ByteBuffer ;
2018-04-02 02:18:43 +02:00
import java.util.Timer ;
import java.util.TimerTask ;
2018-04-01 23:24:49 +02:00
import com.jfoenix.controls.JFXButton ;
2018-04-02 02:18:43 +02:00
import com.jfoenix.controls.JFXSlider ;
2018-04-01 23:24:49 +02:00
2020-04-01 19:48:44 +02:00
import javafx.application.Platform ;
2018-04-02 02:18:43 +02:00
import javafx.beans.value.ChangeListener ;
import javafx.beans.value.ObservableValue ;
2018-04-01 23:24:49 +02:00
import javafx.event.ActionEvent ;
2018-04-02 02:18:43 +02:00
import javafx.event.EventHandler ;
2018-04-01 23:24:49 +02:00
import javafx.fxml.FXML ;
2018-04-02 02:18:43 +02:00
import javafx.scene.Cursor ;
import javafx.scene.image.Image ;
import javafx.scene.image.ImageView ;
2020-04-01 19:48:44 +02:00
import javafx.scene.image.PixelBuffer ;
import javafx.scene.image.PixelFormat ;
import javafx.scene.image.WritableImage ;
2018-04-02 02:18:43 +02:00
import javafx.scene.input.MouseEvent ;
2018-04-01 23:24:49 +02:00
import javafx.scene.layout.HBox ;
2018-04-02 02:18:43 +02:00
import javafx.scene.layout.VBox ;
2018-04-01 23:24:49 +02:00
import javafx.scene.media.Media ;
import javafx.scene.media.MediaPlayer ;
import javafx.scene.media.MediaView ;
2018-04-02 02:18:43 +02:00
import javafx.util.Duration ;
2020-04-01 19:48:44 +02:00
2019-01-14 20:45:49 +01:00
import kellerkinder.HomeFlix.controller.DBController ;
2019-01-08 17:10:33 +01:00
import kellerkinder.HomeFlix.controller.XMLController ;
2018-04-03 18:03:43 +02:00
import kellerkinder.HomeFlix.datatypes.FilmTabelDataType ;
2018-04-01 23:24:49 +02:00
2020-04-01 19:48:44 +02:00
import uk.co.caprica.vlcj.factory.MediaPlayerFactory ;
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 ;
2018-04-01 23:24:49 +02:00
public class PlayerController {
2018-12-08 22:48:13 +01:00
@FXML private MediaView mediaView ;
2020-04-01 19:48:44 +02:00
@FXML private ImageView videoImageView ;
2018-04-07 17:14:35 +02:00
2018-12-08 22:48:13 +01:00
@FXML private VBox bottomVBox ;
2018-04-01 23:24:49 +02:00
2018-12-08 22:48:13 +01:00
@FXML private HBox controlsHBox ;
2018-04-01 23:24:49 +02:00
2018-12-08 22:48:13 +01:00
@FXML private JFXSlider timeSlider ;
2018-04-07 17:14:35 +02:00
2018-12-08 22:48:13 +01:00
@FXML private JFXButton stopBtn ;
@FXML private JFXButton playBtn ;
@FXML private JFXButton fullscreenBtn ;
@FXML private JFXButton nextEpBtn ;
2019-06-19 16:31:49 +02:00
@FXML private ImageView stopIcon ;
@FXML private ImageView playIcon ;
@FXML private ImageView fullscreenIcon ;
2018-04-01 23:24:49 +02:00
private Player player ;
private Media media ;
2020-04-01 19:48:44 +02:00
private MediaPlayer mediaPlayer2 ;
private MediaPlayerFactory mediaPlayerFactory ;
private EmbeddedMediaPlayer embeddedMediaPlayer ;
private WritableImage videoImage ;
private PixelBuffer < ByteBuffer > videoPixelBuffer ;
2018-04-07 17:14:35 +02:00
2018-04-03 18:03:43 +02:00
private FilmTabelDataType film ;
2020-04-01 19:48:44 +02:00
private long currentTime = 0 ;
private long seekTime = 0 ;
private long startTime = 0 ;
private long duration = 0 ;
2018-12-08 22:48:13 +01:00
private int season = 0 ;
private int episode = 0 ;
private int countdown = 0 ;
2018-04-02 02:18:43 +02:00
private boolean mousePressed = false ;
private boolean showControls = true ;
2018-04-07 17:14:35 +02:00
private boolean autoplay ;
2019-06-19 16:31:49 +02:00
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 " ) ;
2018-12-08 23:44:17 +01:00
/ * *
* create a new PlayerWindow object
* @param mainWCon the MainWindowController TODO do we need this ?
* @param player the player object ( needed for closing action )
* @param film the film object
2018-04-02 18:29:59 +02:00
* /
2019-06-17 00:44:44 +02:00
public PlayerController ( Player player , String streamURL ) {
2018-04-01 23:24:49 +02:00
this . player = player ;
2019-06-17 00:44:44 +02:00
this . film = DBController . getInstance ( ) . getStream ( streamURL ) ;
2020-04-01 19:48:44 +02:00
mediaPlayerFactory = new MediaPlayerFactory ( ) ;
embeddedMediaPlayer = mediaPlayerFactory . mediaPlayers ( ) . newEmbeddedMediaPlayer ( ) ;
embeddedMediaPlayer . videoSurface ( ) . set ( new FXCallbackVideoSurface ( ) ) ;
}
public void init2 ( ) {
// 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 ( film . getStreamUrl ( ) ) ;
autoplay = XMLController . isAutoplay ( ) ;
season = ! film . getSeason ( ) . isEmpty ( ) ? Integer . parseInt ( film . getSeason ( ) ) : 0 ;
episode = ! film . getEpisode ( ) . isEmpty ( ) ? Integer . parseInt ( film . getEpisode ( ) ) : 0 ;
initActions2 ( ) ;
2018-12-08 23:44:17 +01:00
}
2020-04-01 19:48:44 +02:00
private void initActions2 ( ) {
embeddedMediaPlayer . events ( ) . addMediaPlayerEventListener ( new HFMediaPlayerEventListener ( ) {
@Override
public void timeChanged ( uk . co . caprica . vlcj . player . base . MediaPlayer mediaPlayer , long newTime ) {
timeSlider . setValue ( ( newTime / 1000 ) / 60 ) ;
}
@Override
public void error ( uk . co . caprica . vlcj . player . base . MediaPlayer mediaPlayer ) {
// Auto-generated method stub
}
@Override
public void lengthChanged ( uk . co . caprica . vlcj . player . base . MediaPlayer mediaPlayer , long newLength ) {
duration = newLength ;
timeSlider . setMax ( ( duration / 1000 ) / 60 ) ;
}
} ) ;
}
public void start2 ( ) {
embeddedMediaPlayer . media ( ) . play ( film . getStreamUrl ( ) ) ;
embeddedMediaPlayer . controls ( ) . skipTime ( ( long ) startTime ) ;
}
public void stop2 ( ) {
DBController . getInstance ( ) . setCurrentTime ( film . getStreamUrl ( ) , embeddedMediaPlayer . status ( ) . time ( ) ) ;
embeddedMediaPlayer . controls ( ) . stop ( ) ;
embeddedMediaPlayer . release ( ) ;
mediaPlayerFactory . release ( ) ;
}
2018-12-08 23:44:17 +01:00
/ * *
* initialize the PlayerWindow
* /
public void init ( ) {
2018-04-02 02:18:43 +02:00
initActions ( ) ;
2018-04-07 17:14:35 +02:00
2018-12-08 23:44:17 +01:00
initMediaPlayer ( ) ;
}
private void initMediaPlayer ( ) {
2018-04-07 17:14:35 +02:00
// start the media if the player is ready
2020-04-01 19:48:44 +02:00
mediaPlayer2 . setOnReady ( new Runnable ( ) {
2018-04-07 17:14:35 +02:00
@Override
public void run ( ) {
2020-04-01 19:48:44 +02:00
duration = ( long ) media . getDuration ( ) . toMillis ( ) ;
2018-04-07 17:14:35 +02:00
timeSlider . setMax ( ( duration / 1000 ) / 60 ) ;
2020-04-01 19:48:44 +02:00
mediaPlayer2 . play ( ) ;
mediaPlayer2 . seek ( Duration . millis ( startTime ) ) ;
2018-04-07 17:14:35 +02:00
}
} ) ;
// every time the play time changes execute this
2020-04-01 19:48:44 +02:00
mediaPlayer2 . currentTimeProperty ( ) . addListener ( new ChangeListener < Duration > ( ) {
2018-04-07 17:14:35 +02:00
@Override
public void changed ( ObservableValue < ? extends Duration > observable , Duration oldValue , Duration newValue ) {
2020-04-01 19:48:44 +02:00
currentTime = ( long ) newValue . toMillis ( ) ; // set the current time
2018-12-08 22:48:13 +01:00
double timeToEnd = ( duration - currentTime ) ;
if ( timeToEnd < 20000 & & episode ! = 0 & & autoplay ) {
// show 20 seconds before the end a button (next episode in 10 seconds)
2018-12-08 23:44:17 +01:00
if ( ! nextEpBtn . isVisible ( ) )
2018-12-08 22:48:13 +01:00
nextEpBtn . setVisible ( true ) ;
2018-12-08 23:44:17 +01:00
if ( countdown ! = ( int ) ( ( timeToEnd - 10000 ) / 1000 ) ) {
countdown = ( int ) ( ( timeToEnd - 10000 ) / 1000 ) ;
2018-12-08 22:48:13 +01:00
nextEpBtn . setText ( " next episode in " + countdown + " seconds " ) ; // TODO translate
bottomVBox . setVisible ( true ) ;
2018-12-08 23:44:17 +01:00
}
2018-12-08 22:48:13 +01:00
// if we are end time -10 seconds, do autoplay, if activated
2018-12-08 23:44:17 +01:00
if ( timeToEnd < 10000 ) {
2018-12-08 22:48:13 +01:00
nextEpBtn . setVisible ( false ) ;
autoPlayNewFilm ( ) ;
2018-04-03 18:23:54 +02:00
}
2018-12-08 22:48:13 +01:00
} else if ( timeToEnd < 120 ) {
// if we are 120ms to the end stop the media
2020-04-01 19:48:44 +02:00
mediaPlayer2 . stop ( ) ;
2019-01-14 20:45:49 +01:00
DBController . getInstance ( ) . setCurrentTime ( film . getStreamUrl ( ) , 0 ) ; // reset old video start time
2019-06-19 16:31:49 +02:00
playIcon . setImage ( playArrow ) ;
2018-12-08 22:48:13 +01:00
} else {
2018-12-08 23:44:17 +01:00
if ( nextEpBtn . isVisible ( ) )
2018-12-08 22:48:13 +01:00
nextEpBtn . setVisible ( false ) ;
2018-04-02 02:18:43 +02:00
}
2018-04-07 17:14:35 +02:00
if ( ! mousePressed ) {
timeSlider . setValue ( ( currentTime / 1000 ) / 60 ) ;
2018-04-02 02:18:43 +02:00
}
2018-04-07 17:14:35 +02:00
}
2018-04-02 02:18:43 +02:00
} ) ;
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
}
2018-04-07 17:14:35 +02:00
2018-04-02 18:29:59 +02:00
/ * *
* initialize some PlayerWindow GUI - Elements actions
* /
2018-04-02 02:18:43 +02:00
private void initActions ( ) {
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
player . getScene ( ) . addEventFilter ( MouseEvent . MOUSE_MOVED , new EventHandler < MouseEvent > ( ) {
2018-04-08 13:22:20 +02:00
// hide controls timer initialization
2018-04-02 02:18:43 +02:00
final Timer timer = new Timer ( ) ;
TimerTask controlAnimationTask = null ; // task to execute save operation
2018-12-08 22:48:13 +01:00
final long delayTime = 3000 ; // hide the controls after 2 seconds
2018-04-02 02:18:43 +02:00
@Override
public void handle ( MouseEvent mouseEvent ) {
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
// show controls
if ( ! showControls ) {
player . getScene ( ) . setCursor ( Cursor . DEFAULT ) ;
bottomVBox . setVisible ( true ) ;
}
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
// hide controls
if ( controlAnimationTask ! = null )
controlAnimationTask . cancel ( ) ;
controlAnimationTask = new TimerTask ( ) {
@Override
public void run ( ) {
bottomVBox . setVisible ( false ) ;
player . getScene ( ) . setCursor ( Cursor . NONE ) ;
showControls = false ;
}
} ;
timer . schedule ( controlAnimationTask , delayTime ) ;
}
} ) ;
2018-04-07 17:14:35 +02:00
// if the mouse on the timeSlider is released seek to the new position
2018-04-02 02:18:43 +02:00
timeSlider . setOnMouseReleased ( new EventHandler < MouseEvent > ( ) {
2018-04-07 17:14:35 +02:00
@Override
public void handle ( MouseEvent event ) {
2020-04-01 19:48:44 +02:00
mediaPlayer2 . seek ( new Duration ( seekTime ) ) ;
2018-04-07 17:14:35 +02:00
mousePressed = false ;
}
2018-04-02 02:18:43 +02:00
} ) ;
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
timeSlider . setOnMousePressed ( new EventHandler < MouseEvent > ( ) {
@Override
public void handle ( MouseEvent event ) {
mousePressed = true ;
2018-04-07 17:14:35 +02:00
}
2018-04-02 02:18:43 +02:00
} ) ;
2018-04-07 17:14:35 +02:00
// get the new seek time
2018-04-02 02:18:43 +02:00
timeSlider . valueProperty ( ) . addListener ( new ChangeListener < Number > ( ) {
@Override
public void changed ( ObservableValue < ? extends Number > ov , Number old_val , Number new_val ) {
2020-04-01 19:48:44 +02:00
seekTime = ( long ) ( ( double ) new_val * 1000 * 60 ) ;
2018-04-02 02:18:43 +02:00
}
} ) ;
}
2018-04-07 17:14:35 +02:00
2018-04-02 02:18:43 +02:00
@FXML
void stopBtnAction ( ActionEvent event ) {
2020-04-01 19:48:44 +02:00
stop2 ( ) ;
2018-04-02 02:18:43 +02:00
player . getStage ( ) . close ( ) ;
2018-04-01 23:24:49 +02:00
}
@FXML
2018-04-07 17:14:35 +02:00
void fullscreenBtnAction ( ActionEvent event ) {
2018-04-01 23:24:49 +02:00
if ( player . getStage ( ) . isFullScreen ( ) ) {
player . getStage ( ) . setFullScreen ( false ) ;
2019-06-19 16:31:49 +02:00
fullscreenIcon . setImage ( fullscreen ) ;
2018-04-01 23:24:49 +02:00
} else {
player . getStage ( ) . setFullScreen ( true ) ;
2019-06-19 16:31:49 +02:00
fullscreenIcon . setImage ( fullscreenExit ) ;
2018-04-01 23:24:49 +02:00
}
}
@FXML
void playBtnAction ( ActionEvent event ) {
2020-04-01 19:48:44 +02:00
if ( embeddedMediaPlayer . status ( ) . isPlaying ( ) ) {
embeddedMediaPlayer . controls ( ) . pause ( ) ;
2019-06-19 16:31:49 +02:00
playIcon . setImage ( playArrow ) ;
2018-04-01 23:24:49 +02:00
} else {
2020-04-01 19:48:44 +02:00
embeddedMediaPlayer . controls ( ) . play ( ) ;
2019-06-19 16:31:49 +02:00
playIcon . setImage ( pause ) ;
2018-04-01 23:24:49 +02:00
}
}
2018-12-08 22:48:13 +01:00
@FXML
void nextEpBtnAction ( ActionEvent event ) {
autoPlayNewFilm ( ) ;
}
private void autoPlayNewFilm ( ) {
autoplay = false ;
2019-01-14 20:45:49 +01:00
DBController . getInstance ( ) . setCurrentTime ( film . getStreamUrl ( ) , 0 ) ; // reset old video start time
FilmTabelDataType nextFilm = DBController . getInstance ( ) . getNextEpisode ( film . getTitle ( ) , episode , season ) ;
2018-12-08 22:48:13 +01:00
if ( nextFilm ! = null ) {
2020-04-01 19:48:44 +02:00
mediaPlayer2 . stop ( ) ;
2018-12-08 23:44:17 +01:00
film = nextFilm ;
init ( ) ;
2018-12-08 22:48:13 +01:00
autoplay = true ;
}
}
2018-04-01 23:24:49 +02:00
public MediaPlayer getMediaPlayer ( ) {
2020-04-01 19:48:44 +02:00
return mediaPlayer2 ;
2018-04-01 23:24:49 +02:00
}
2018-04-02 02:18:43 +02:00
public double getCurrentTime ( ) {
return currentTime ;
}
2020-04-01 19:48:44 +02:00
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 ) ;
} ) ;
}
}
2018-04-02 02:18:43 +02:00
2018-04-01 23:24:49 +02:00
}