Stop previous stream when a new one starts; ease animations
Starting a movie now closes the current decode engine and bumps a playback generation so the stale video/audio threads stop themselves (they were overlapping the movie's canvas and audio). PlaybackEngine.close is idempotent. Animation records now honor their accelMode (linear/accel/decel easing).
This commit is contained in:
@@ -72,6 +72,11 @@ public final class PlayerApp extends Application {
|
||||
private volatile VsmxNativeObject controllerObject;
|
||||
private long lastMenuKeyNanos = 0;
|
||||
|
||||
/** The active decode engine, closed when a new stream starts. */
|
||||
private volatile PlaybackEngine currentEngine;
|
||||
/** Bumped each time playback starts so stale streams stop themselves. */
|
||||
private volatile long playbackGeneration = 0;
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
// Gradle's --args splits on whitespace; rejoin so paths with spaces work.
|
||||
@@ -200,6 +205,12 @@ public final class PlayerApp extends Application {
|
||||
|
||||
@Override
|
||||
public void stopMovie() {
|
||||
PlaybackEngine engine = currentEngine;
|
||||
if (engine != null) {
|
||||
playbackGeneration++;
|
||||
engine.close();
|
||||
currentEngine = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,17 +241,29 @@ public final class PlayerApp extends Application {
|
||||
|
||||
private void startPlayback(String isoPath, Playlist.Stream stream, int playListNumber) {
|
||||
String streamName = stream.name();
|
||||
long generation = ++playbackGeneration;
|
||||
|
||||
// Stop the previous stream so its video and audio threads don't keep
|
||||
// playing underneath the new one.
|
||||
PlaybackEngine previous = currentEngine;
|
||||
if (previous != null) {
|
||||
previous.close();
|
||||
}
|
||||
currentEngine = null;
|
||||
|
||||
Thread player = new Thread(() -> {
|
||||
try (UmdIsoReader iso = new UmdIsoReader(Path.of(isoPath));
|
||||
PlaybackEngine engine = PlaybackEngine.open(iso, streamName, 0)) {
|
||||
|
||||
currentEngine = engine;
|
||||
|
||||
// Each stream has its own PTS timeline, so re-anchor the master
|
||||
// clock to this stream's first audio chunk.
|
||||
clockStartPts = -1;
|
||||
clockStartNanos = -1;
|
||||
clockOffsetNanos = 0;
|
||||
|
||||
Thread audio = startAudio(engine);
|
||||
Thread audio = startAudio(engine, generation);
|
||||
awaitClock();
|
||||
long basePts = clockStartPts;
|
||||
long baseNanos = clockStartNanos;
|
||||
@@ -248,6 +271,9 @@ public final class PlayerApp extends Application {
|
||||
PlaybackEngine.VideoFrame frame;
|
||||
int lastChapter = -2;
|
||||
while ((frame = engine.nextVideoFrame()) != null) {
|
||||
if (playbackGeneration != generation) {
|
||||
break;
|
||||
}
|
||||
if (skipRequested) {
|
||||
skipRequested = false;
|
||||
break;
|
||||
@@ -279,13 +305,17 @@ public final class PlayerApp extends Application {
|
||||
sleepUntil(targetNanos);
|
||||
}
|
||||
|
||||
// Wait for the audio thread to finish draining before the
|
||||
// engine (and its decoders) is closed by this try block.
|
||||
// Wait for the audio thread to finish before the engine (and
|
||||
// its decoders) is closed by this try block.
|
||||
audio.join(2000);
|
||||
|
||||
MoviePlayer mp = moviePlayer;
|
||||
if (mp != null) {
|
||||
mp.onPlayListEnd(playListNumber);
|
||||
// Only report end-of-stream if we're still the active playback;
|
||||
// otherwise a stale stream would re-trigger the menu loop.
|
||||
if (playbackGeneration == generation) {
|
||||
MoviePlayer mp = moviePlayer;
|
||||
if (mp != null) {
|
||||
mp.onPlayListEnd(playListNumber);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -296,13 +326,16 @@ public final class PlayerApp extends Application {
|
||||
}
|
||||
|
||||
/** Plays decoded PCM through the system audio line, acting as the master A/V clock. */
|
||||
private Thread startAudio(PlaybackEngine engine) {
|
||||
private Thread startAudio(PlaybackEngine engine, long generation) {
|
||||
Thread audio = new Thread(() -> {
|
||||
SourceDataLine line = null;
|
||||
try {
|
||||
line = openLine();
|
||||
PlaybackEngine.AudioChunk chunk;
|
||||
while ((chunk = engine.nextAudioChunk()) != null) {
|
||||
if (playbackGeneration != generation) {
|
||||
break;
|
||||
}
|
||||
if (clockStartPts < 0) {
|
||||
clockStartPts = chunk.pts();
|
||||
clockStartNanos = System.nanoTime();
|
||||
@@ -313,7 +346,12 @@ public final class PlayerApp extends Application {
|
||||
sleepUntil(targetNanos);
|
||||
line.write(chunk.pcm(), 0, chunk.pcm().length);
|
||||
}
|
||||
line.drain();
|
||||
if (playbackGeneration != generation) {
|
||||
// Superseded: cut off any buffered audio immediately.
|
||||
line.flush();
|
||||
} else {
|
||||
line.drain();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user