jexer/examples/XtermVideoPlayer.java

190 lines
7 KiB
Java
Raw Permalink Normal View History

2022-01-04 22:01:16 -07:00
import java.awt.image.BufferedImage;
import java.io.File;
import jexer.TApplication;
import jexer.TExceptionDialog;
import jexer.TImage;
import jexer.TWindow;
import jexer.event.TResizeEvent;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
2022-01-05 06:09:42 -07:00
import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_QUIET;
2022-01-04 22:01:16 -07:00
2022-01-05 06:09:42 -07:00
/**
* XtermVideoPlayer plays videos in an Xterm. It uses the JavaCV library to
* obtain images from a video file and puts them into a TImage. The initial
* few seconds will often stutter as the Java JIT compiler steps in.
*
* See JavaCV at https://github.com/bytedeco/javacv .
*
* Compile it with:
* javac -cp javacv.jar:ffmpeg.jar:jexer.jar XtermVideoPlayer.java
*
* Run it with (assuming platform is linux-x86_64):
* java -cp javacv.jar:ffmpeg.jar:ffmpeg-linux-x86_64.jar:jexer.jar:. XtermVideoPlayer filename
*/
2022-01-04 22:01:16 -07:00
public class XtermVideoPlayer extends TApplication {
// The image reference.
private TImage image;
// Constructor sets up widgets.
public XtermVideoPlayer(String filename) throws Exception {
super(BackendType.XTERM);
// Create standard menus for Tool, File, and Window.
addToolMenu();
addFileMenu();
addWindowMenu();
2022-01-25 19:04:08 -07:00
// Make it cute.
getTheme().setFemme();
System.setProperty("jexer.TWindow.borderStyleForeground", "round");
System.setProperty("jexer.TWindow.borderStyleModal", "round");
System.setProperty("jexer.TWindow.borderStyleMoving", "round");
System.setProperty("jexer.TWindow.borderStyleInactive", "round");
setDesktop(null);
setHideStatusBar(true);
2022-01-05 06:09:42 -07:00
// Create a window for the image. The resize event is overridden so
// that the internal image field changes in size with the window.
2022-01-04 22:01:16 -07:00
TWindow window = new TWindow(this, filename, 0, 0,
2022-01-05 06:09:42 -07:00
getScreen().getWidth() / 2, getScreen().getHeight() / 2) {
2022-01-04 22:01:16 -07:00
@Override
public void onResize(final TResizeEvent event) {
if (event.getType() == TResizeEvent.Type.WIDGET) {
TResizeEvent imageSize;
imageSize = new TResizeEvent(event.getBackend(),
TResizeEvent.Type.WIDGET, event.getWidth() - 2,
event.getHeight() - 2);
image.onResize(imageSize);
}
}
};
2022-01-25 19:04:08 -07:00
window.setAlpha(255);
2022-01-04 22:01:16 -07:00
2022-01-05 06:09:42 -07:00
// Add the image field. We will load frames into this.
2022-01-04 22:01:16 -07:00
image = window.addImage(0, 0, window.getWidth() - 2,
window.getHeight() - 2, new BufferedImage(720, 360,
BufferedImage.TYPE_INT_ARGB), 0, 0);
2022-01-05 06:09:42 -07:00
// Be able to see the movie no matter what size the image/window is.
2022-01-04 22:01:16 -07:00
image.setScaleType(TImage.Scale.SCALE);
}
// Function that grabs frames from a file and displays them in image.
private void getFrames(final File file) {
FFmpegFrameGrabber grabber = null;
try {
2022-01-05 06:09:42 -07:00
// Make ffmpeg quiet. If we don't do this there will be stuff
// emitted to stderr.
av_log_set_level(AV_LOG_QUIET);
// Start the video decoder.
2022-01-04 22:01:16 -07:00
Java2DFrameConverter conv = new Java2DFrameConverter();
grabber = new FFmpegFrameGrabber(file);
grabber.start();
2022-01-05 06:09:42 -07:00
long lastFrameMillis = 0;
// Ideally we would sync with the real video speed and drop
// frames as needed. For this example we will grab at the
// reported fps, and (due to how TApplication runs all of its
// invokeLaters at once) catch up to dropped frames.
double frameRate = grabber.getVideoFrameRate();
int fps = (int) frameRate;
final long FRAME_TIME = 1000 / fps;
// Get a rough frame count.
int totalFrames = grabber.getLengthInFrames();
int frameCount = 0;
// Keep adding images as long as the application is running.
2022-01-04 22:01:16 -07:00
while (isRunning()) {
BufferedImage frame;
2022-01-05 06:09:42 -07:00
frame = conv.convert(grabber.grabImage());
long now = System.currentTimeMillis();
2022-01-04 22:01:16 -07:00
if ((image != null) && (frame != null)) {
2022-01-05 06:09:42 -07:00
frameCount++;
if (now - lastFrameMillis > FRAME_TIME) {
invokeLater(new Runnable() {
public void run() {
image.setImage(frame);
doRepaint();
}
});
lastFrameMillis = now;
}
} else if (((image != null) && (frame == null)
&& (frameCount > 0))
|| (frameCount >= totalFrames)
) {
// We are out of frames, the movie is over.
image.getWindow().setTitle(image.getWindow().getTitle() +
" (Completed)");
doRepaint();
break;
2022-01-04 22:01:16 -07:00
}
2022-01-05 06:09:42 -07:00
// Pause until is time for the next frame.
long sleepMillis = FRAME_TIME - (now - lastFrameMillis);
if ((sleepMillis > 0) && (sleepMillis <= (int) (1000 / fps))) {
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
// SQUASH
}
2022-01-04 22:01:16 -07:00
}
}
} catch (Exception e) {
// Show this exception to the user.
invokeLater(new Runnable() {
public void run() {
new TExceptionDialog(XtermVideoPlayer.this, e);
}
});
2022-01-05 06:09:42 -07:00
// And exit the thread ...
2022-01-04 22:01:16 -07:00
}
if (grabber != null) {
2022-01-05 06:09:42 -07:00
// Try to shut down FFmpeg.
2022-01-04 22:01:16 -07:00
try {
grabber.release();
} catch (Exception e) {
// Show this exception to the user.
invokeLater(new Runnable() {
public void run() {
new TExceptionDialog(XtermVideoPlayer.this, e);
}
});
}
}
}
2022-01-25 19:04:08 -07:00
/**
* Show FPS.
*/
@Override
protected void onPreDraw() {
menuTrayText = String.format("FPS %d", getFramesPerSecond());
}
2022-01-05 06:09:42 -07:00
// Main entry point.
2022-01-04 22:01:16 -07:00
public static void main(String [] args) throws Exception {
if (args.length == 0) {
System.err.println("USAGE: java XtermVideoPlayer filename");
System.exit(-1);
}
File file = new File(args[0]);
if (!file.canRead()) {
System.err.println("Cannot read from file: " + args[0]);
System.exit(-1);
}
2022-01-05 06:09:42 -07:00
XtermVideoPlayer app = new XtermVideoPlayer(file.getName());
2022-01-04 22:01:16 -07:00
// The application will spin on its thread.
(new Thread(app)).start();
2022-01-05 06:09:42 -07:00
// Frames will continue to grab on the main thread.
2022-01-04 22:01:16 -07:00
app.getFrames(new File(args[0]));
}
}