-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXtermVideoPlayer.java
More file actions
189 lines (171 loc) · 7.05 KB
/
Copy pathXtermVideoPlayer.java
File metadata and controls
189 lines (171 loc) · 7.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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;
import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_QUIET;
/**
* 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
*/
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();
// 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);
// Create a window for the image. The resize event is overridden so
// that the internal image field changes in size with the window.
TWindow window = new TWindow(this, filename, 0, 0,
getScreen().getWidth() / 2, getScreen().getHeight() / 2) {
@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);
}
}
};
window.setAlpha(255);
// Add the image field. We will load frames into this.
image = window.addImage(0, 0, window.getWidth() - 2,
window.getHeight() - 2, new BufferedImage(720, 360,
BufferedImage.TYPE_INT_ARGB), 0, 0);
// Be able to see the movie no matter what size the image/window is.
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 {
// 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.
Java2DFrameConverter conv = new Java2DFrameConverter();
grabber = new FFmpegFrameGrabber(file);
grabber.start();
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.
while (isRunning()) {
BufferedImage frame;
frame = conv.convert(grabber.grabImage());
long now = System.currentTimeMillis();
if ((image != null) && (frame != null)) {
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;
}
// 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
}
}
}
} catch (Exception e) {
// Show this exception to the user.
invokeLater(new Runnable() {
public void run() {
new TExceptionDialog(XtermVideoPlayer.this, e);
}
});
// And exit the thread ...
}
if (grabber != null) {
// Try to shut down FFmpeg.
try {
grabber.release();
} catch (Exception e) {
// Show this exception to the user.
invokeLater(new Runnable() {
public void run() {
new TExceptionDialog(XtermVideoPlayer.this, e);
}
});
}
}
}
/**
* Show FPS.
*/
@Override
protected void onPreDraw() {
menuTrayText = String.format("FPS %d", getFramesPerSecond());
}
// Main entry point.
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);
}
XtermVideoPlayer app = new XtermVideoPlayer(file.getName());
// The application will spin on its thread.
(new Thread(app)).start();
// Frames will continue to grab on the main thread.
app.getFrames(new File(args[0]));
}
}