Add pause/resume (Space, P, and media Play/Pause keys)
Pausing stops the audio line and blocks both playback threads; the A/V clock is re-anchored on resume so video and audio continue exactly where they left off without fast-forwarding.
This commit is contained in:
@@ -55,6 +55,11 @@ public final class PlayerApp extends Application {
|
|||||||
private volatile long clockStartPts = -1;
|
private volatile long clockStartPts = -1;
|
||||||
private volatile long clockStartNanos = -1;
|
private volatile long clockStartNanos = -1;
|
||||||
|
|
||||||
|
private final Object pauseLock = new Object();
|
||||||
|
private volatile boolean paused = false;
|
||||||
|
private volatile long clockOffsetNanos = 0;
|
||||||
|
private long pauseStartNanos = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
// Gradle's --args splits on whitespace; rejoin so paths with spaces work.
|
// Gradle's --args splits on whitespace; rejoin so paths with spaces work.
|
||||||
@@ -99,6 +104,14 @@ public final class PlayerApp extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleKey(KeyCode code) {
|
private void handleKey(KeyCode code) {
|
||||||
|
switch (code) {
|
||||||
|
case SPACE, P, PLAY, PAUSE -> {
|
||||||
|
togglePause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default -> { }
|
||||||
|
}
|
||||||
|
|
||||||
Controller c = controller;
|
Controller c = controller;
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
return;
|
return;
|
||||||
@@ -190,7 +203,8 @@ public final class PlayerApp extends Application {
|
|||||||
frame.vPlane(), frame.vStride());
|
frame.vPlane(), frame.vStride());
|
||||||
blitVideo(argb, frame.width(), frame.height());
|
blitVideo(argb, frame.width(), frame.height());
|
||||||
|
|
||||||
long targetNanos = baseNanos + (frame.pts() - basePts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
checkPaused();
|
||||||
|
long targetNanos = baseNanos + clockOffsetNanos + (frame.pts() - basePts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
||||||
sleepUntil(targetNanos);
|
sleepUntil(targetNanos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +232,8 @@ public final class PlayerApp extends Application {
|
|||||||
clockStartPts = chunk.pts();
|
clockStartPts = chunk.pts();
|
||||||
clockStartNanos = System.nanoTime();
|
clockStartNanos = System.nanoTime();
|
||||||
}
|
}
|
||||||
long targetNanos = clockStartNanos
|
checkPaused(line);
|
||||||
|
long targetNanos = clockStartNanos + clockOffsetNanos
|
||||||
+ (chunk.pts() - clockStartPts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
+ (chunk.pts() - clockStartPts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
||||||
sleepUntil(targetNanos);
|
sleepUntil(targetNanos);
|
||||||
line.write(chunk.pcm(), 0, chunk.pcm().length);
|
line.write(chunk.pcm(), 0, chunk.pcm().length);
|
||||||
@@ -267,6 +282,54 @@ public final class PlayerApp extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void togglePause() {
|
||||||
|
synchronized (pauseLock) {
|
||||||
|
if (!paused) {
|
||||||
|
paused = true;
|
||||||
|
pauseStartNanos = System.nanoTime();
|
||||||
|
} else {
|
||||||
|
clockOffsetNanos += System.nanoTime() - pauseStartNanos;
|
||||||
|
paused = false;
|
||||||
|
pauseLock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Blocks while playback is paused. */
|
||||||
|
private void checkPaused() {
|
||||||
|
synchronized (pauseLock) {
|
||||||
|
while (paused) {
|
||||||
|
try {
|
||||||
|
pauseLock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Blocks while playback is paused, stopping/starting the audio line. */
|
||||||
|
private void checkPaused(SourceDataLine line) {
|
||||||
|
synchronized (pauseLock) {
|
||||||
|
boolean wasPaused = paused;
|
||||||
|
while (paused) {
|
||||||
|
if (line != null && line.isRunning()) {
|
||||||
|
line.stop();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pauseLock.wait(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wasPaused && line != null && !line.isRunning()) {
|
||||||
|
line.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void blitVideo(int[] argb, int width, int height) {
|
private void blitVideo(int[] argb, int width, int height) {
|
||||||
WritableImage image = new WritableImage(width, height);
|
WritableImage image = new WritableImage(width, height);
|
||||||
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), argb, 0, width);
|
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), argb, 0, width);
|
||||||
|
|||||||
Reference in New Issue
Block a user