sclass JFFMPEGVideoPlayer extends JSelfSwingable { //SurfaceShowingAutoZoomedImage imageSurface; ImageSurface imageSurface; JPanel buttons = rightAlignedLine(); JButton btnPlayPause = jDisabledButton("Pause", r playPause); ImageStreamFromVideoFile stream; long playStartTime; new BoolVar paused; RSTVar imageToDisplay = new(image -> imageSurface.setImage(image)); gettable File videoFile; event videoStarted(File videoFile); event userSelectedVideo(File videoFile); void init { //imageSurface = swing(-> new SurfaceShowingAutoZoomedImage); imageSurface = doubleBufferedImageSurface(); imageSurface.setAutoZoomToDisplay(true); buttons.add(btnPlayPause); buttons.add(jbutton("Open video...", rThread openDialog)); bindToComponent(imageSurface, null, rThread closeStream); setComponent( centerAndSouth( jscroll_center_borderless(imageSurface), withMargin(buttons) )); } selfType standardZoom() { imageSurface.standardZoom(); this; } *() { init(); } *(File videoFile) { init(); if (videoFile != null) onFirstShow(imageSurface, -> play(videoFile)); } void openDialog swing { new JFileChooser fc; fc.setDialogTitle("Open video"); fc.setCurrentDirectory(dirToOpen()); File videoFile = execFileChooser(fc); if (videoFile == null) ret; print("openDialog " + nListeners(onUserSelectedVideo)); userSelectedVideo(videoFile); play(videoFile); } File dirToOpen() { if (videoFile != null) ret dirOfFile(videoFile); ret videosDir(); } void play(File videoFile) { printCommaCombine("JFFMPEGVideoPlayer.play " + videoFile, nListeners(onVideoStarted)); if (!isFile(videoFile)) ret with print("Not a file"); thread { // TODO: use something better than just a new thread if (videoFile == null) ret; JFFMPEGVideoPlayer.this.videoFile = videoFile; pcall-infobox { closeStream(); stream = new ImageStreamFromVideoFile; stream.open(videoFile); stream.onHaveImage((image, pos) -> { waitWhileTrue(paused); if (playStartTime == 0 || playStartTime+pos.toMillis() < sysNow()) playStartTime = sysNow()-pos.toMillis(); else sleepUntilSysTime(playStartTime+pos.toMillis()); possiblyDisplay(image); }); paused.set(false); enableButton(btnPlayPause); stream.startDecodeThread(); videoStarted(videoFile); } } } void possiblyDisplay(BufferedImage image) { imageToDisplay.set(cloneBufferedImage(image)); } void closeStream { disableButton(btnPlayPause); possiblyDisplay(whiteImage(10)); dispose stream; playStartTime = 0; } void playPause() swing { paused.set(!paused!); setText(btnPlayPause, paused! ? "Play" : "Pause"); } }