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 clockStartNanos = -1;
|
||||
|
||||
private final Object pauseLock = new Object();
|
||||
private volatile boolean paused = false;
|
||||
private volatile long clockOffsetNanos = 0;
|
||||
private long pauseStartNanos = 0;
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
// 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) {
|
||||
switch (code) {
|
||||
case SPACE, P, PLAY, PAUSE -> {
|
||||
togglePause();
|
||||
return;
|
||||
}
|
||||
default -> { }
|
||||
}
|
||||
|
||||
Controller c = controller;
|
||||
if (c == null) {
|
||||
return;
|
||||
@@ -190,7 +203,8 @@ public final class PlayerApp extends Application {
|
||||
frame.vPlane(), frame.vStride());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -218,7 +232,8 @@ public final class PlayerApp extends Application {
|
||||
clockStartPts = chunk.pts();
|
||||
clockStartNanos = System.nanoTime();
|
||||
}
|
||||
long targetNanos = clockStartNanos
|
||||
checkPaused(line);
|
||||
long targetNanos = clockStartNanos + clockOffsetNanos
|
||||
+ (chunk.pts() - clockStartPts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
||||
sleepUntil(targetNanos);
|
||||
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) {
|
||||
WritableImage image = new WritableImage(width, height);
|
||||
image.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), argb, 0, width);
|
||||
|
||||
Reference in New Issue
Block a user