使用以下方法播放时,我能够获取视频快照;
mediaView.snapshot(null, null);
现在我想拍摄具有一定持续时间间隙的快照,如下所示:
public class Main extends Application {
Media media = null;
MediaPlayer mediaPlayer = null;
MediaView mediaView = null;
public static void main(String[] args) {
launch(args);
}
public void start(final Stage primaryStage) {
primaryStage.setTitle("Video Snapshot");
media = new Media(new File("filePathTo.mp4").toURI().toString());
mediaPlayer = new MediaPlayer(media);
mediaView = new MediaView(mediaPlayer);
Pane pane = new Pane();
mediaPlayer.setOnReady(new Runnable() {
public void run() {
writeToFile(0.0);
writeToFile(1000.0);
writeToFile(10000.0);;
writeToFile(50000.0);
}
});
Scene scene = new Scene(pane, 640, 480, Color.BLACK);
primaryStage.setScene(scene);
mediaPlayer.play();
primaryStage.show();
}
private void writeToFile(Double double1) {
mediaView.getMediaPlayer().seek( Duration.millis(double1) );
WritableImage writableImage = mediaView.snapshot(null, null);
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
try {
ImageIO.write( SwingFXUtils.fromFXImage( writableImage, new BufferedImage(200,200,BufferedImage.TYPE_INT_RGB) ), "png", byteOutput );
byteOutput.writeTo(new FileOutputStream( Paths.get(double1.toString()).toFile() + ".png" ));
} catch (IOException e) {
e.printStackTrace();
}
}
}
但是现在我得到了相同的图像,而不是不同的图像,尽管返回的 writableImage 对象不同。检查文件 0.0.png、1000.0.png、10000.0.png、50000.0.png 文件。一切看起来都一样。我怎样才能获得不同的快照?
最佳答案
问题很可能是在您拍摄快照时尚未渲染新像素。您需要给 JavaFX 时间来渲染视频。以下示例对我有用:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
import javax.imageio.ImageIO;
public class Main extends Application {
public void start(Stage primaryStage) {
var player = new MediaPlayer(new Media(Path.of("video.mp4").toUri().toString()));
player.setAutoPlay(true);
var view = new MediaView(player);
player.setOnPlaying(() -> {
snapshotVideo(view, 0)
.thenCompose(unused -> snapshotVideo(view, 1000))
.thenCompose(unused -> snapshotVideo(view, 2000))
.thenCompose(unused -> snapshotVideo(view, 3000));
});
primaryStage.setScene(new Scene(new Pane(view), 1000, 650));
primaryStage.setTitle("Video Snapshot");
primaryStage.show();
}
private CompletableFuture<Void> snapshotVideo(MediaView view, double seekMillis) {
var future = new CompletableFuture<Void>();
view.getMediaPlayer().seek(Duration.millis(seekMillis));
Platform.runLater(() -> {
var fxImage = view.snapshot(null, null);
var bufImage = SwingFXUtils.fromFXImage(fxImage, null);
var filename = String.format("snapshot-%.0f.png", seekMillis);
try (var out = Files.newOutputStream(Path.of(filename))) {
ImageIO.write(bufImage, "png", out);
future.complete(null);
} catch (IOException ex) {
ex.printStackTrace();
future.completeExceptionally(ex);
}
});
return future;
}
}
它使用 Platform#runLater(Runnable)
和 CompletableFuture
的组合。
它会寻找所需的时间,然后在
runLater
调用中运行快照+保存代码。这是为了给 JavaFX 时间来渲染新的目标帧。我不知道这是否保证有效。如果我没记错的话,JavaFX 媒体实现有一个或多个后台线程来处理音频/视频。但我不知道这些线程是否会“落后”。此外,提前寻找足够远的位置可能会导致缓冲。因此,您可能必须通过监听MediaPlayer
的status
属性来处理该问题。它使用
CompletableFuture
来确保在拍摄并保存前一个快照之前不会拍摄下一个快照。将图像保存代码移至后台线程可能是个好主意。我认为重要的是,在尝试拍摄下一张快照之前,快照已完全拍摄完毕。
我也会在玩家玩时开始拍摄这些快照,而不仅仅是准备。不确定这是否重要。我什至不确定玩家是否需要玩游戏才能发挥作用。
最后,JavaFX Media API 可能不是用于这项工作的最佳工具。如果我想要 MP4 视频中不同时间的帧,我会尝试找到一个可以直接从文件中抓取这些帧的库。
关于java - MediaView 没有返回不同的快照,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73960311/