diff --git a/ui/src/main/java/dev/quoije/umd/ui/PlayerApp.java b/ui/src/main/java/dev/quoije/umd/ui/PlayerApp.java index f6a03fe..f16a255 100644 --- a/ui/src/main/java/dev/quoije/umd/ui/PlayerApp.java +++ b/ui/src/main/java/dev/quoije/umd/ui/PlayerApp.java @@ -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);