// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.video; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.SourceDataLine; public class MvePlayer { private boolean playing, paused, stopped; private long startTime, delayTime; private AudioQueue audioQueue; private SourceDataLine dataLine; public MvePlayer() { playing = true; paused = false; stopped = true; dataLine = null; audioQueue = new AudioQueue(); } public void play(ImageRenderer renderer, MveDecoder decoder) throws Exception { if (renderer == null || renderer.bufferCount() <= 0 || decoder == null || !decoder.isOpen()) return; setPlaying(true); setStopped(false); setPaused(false); // initializing initAudio(decoder); decoder.setDefaultAudioOutput(audioQueue); // preloading frames for (int i = 0; i < renderer.bufferCount() && decoder.hasNextFrame(); i++) { if (decoder.processNextFrame()) { // attached frame data contains current frame delay as Integer object renderer.attachData(new Integer(decoder.getFrameDelay())); if (decoder.audioInitialized()) initAudio(decoder); } else { decoder.setDefaultAudioOutput(null); stopPlay(); throw new Exception("Error decoding video data"); } } dataLine.start(); outputAudioFrame(true); renderer.updateRenderer(); while (isPlaying() && decoder.hasNextFrame()) { Object dataObj = renderer.fetchData(); if (dataObj instanceof Integer) { setTimerDelay(((Integer)dataObj).longValue() * 1000L + timeRemaining()); } else { // audio-only frames do not contain timing information setTimerDelay(0L); } if (decoder.processNextFrame()) { renderer.attachData(new Integer(decoder.getFrameDelay())); // has audio been re-initialized? if (decoder.audioInitialized()) { setTimerDelay(0L); initAudio(decoder); dataLine.start(); outputAudioFrame(true); // flushing video buffer chain for (int i = 0; i < renderer.bufferCount(); i++) renderer.flipBuffers(); } } else { decoder.setDefaultAudioOutput(null); stopPlay(); throw new Exception("Error decoding video data"); } // skip audio-only frames if (timeRemaining() == 0L) continue; outputAudioFrame(false); // has playback been paused? if (isPaused()) { dataLine.stop(); dataLine.flush(); while (isPlaying() && isPaused()) { try { Thread.sleep(1); } catch (InterruptedException e) { } } dataLine.start(); if (isPlaying()) { // reloading last audio block outputAudioFrame(true); } continue; } // waiting for the next frame to be displayed sleepUntil(5000000L); renderer.updateRenderer(); audioQueue.skipNext(); } // cleanup decoder related objects renderer.clearBuffers(); decoder.setDefaultAudioOutput(null); audioQueue.clear(); // clean up audio if (!isPlaying()) { dataLine.drain(); } dataLine.stop(); dataLine = null; setPlaying(false); setPaused(false); setStopped(true); } public void stopPlay() { setPlaying(false); while (!isStopped()) { try { Thread.sleep(50L); } catch (InterruptedException e) { } } try { Thread.sleep(100L); } catch (InterruptedException e) { } } public void pausePlay() { if (!isStopped()) { setPaused(true); } } public void continuePlay() { if (!isStopped()) { setPaused(false); } } public boolean isPlaying() { return playing; } public boolean isPaused() { return paused; } public boolean isStopped() { return stopped; } private synchronized void setStopped(boolean b) { if (b != stopped) { stopped = b; } } private synchronized void setPaused(boolean b) { if (b != paused) { paused = b; } } private synchronized void setPlaying(boolean b) { if (b != playing) { playing = b; } } private void setTimerDelay(long nanoseconds) { if (nanoseconds < 0L) nanoseconds = 0L; startTime = System.nanoTime() & Long.MAX_VALUE; delayTime = nanoseconds; } private long timeRemaining() { long res = 0L; long curTime = System.nanoTime() & Long.MAX_VALUE; if (curTime < startTime) { res = delayTime - (Long.MAX_VALUE - startTime + curTime); } else { res = delayTime - (curTime - startTime); } if (res < 0L) res = 0L; return res; } // waits until only 'remaining' time (in ns) of the current timer remains private void sleepUntil(long remaining) { if (timeRemaining() > 2000000L) { while (timeRemaining() > remaining) { // sleep as much as possible try { Thread.sleep(1); } catch (InterruptedException e) { } } } if (timeRemaining() <= 2000000L) { while (timeRemaining() > remaining) { // waste remaining nanoseconds } } } private void initAudio(MveDecoder decoder) throws Exception { if (decoder != null) { // closing old source data line if (dataLine != null) { dataLine.drain(); dataLine.stop(); dataLine = null; } audioQueue.clear(); // initializing new source data line AudioFormat audioFormat = decoder.getAudioFormat(); try { dataLine = AudioSystem.getSourceDataLine(audioFormat); } catch (Exception e) { e.printStackTrace(); throw new Exception("Unsupported audio format"); } dataLine.open(audioFormat, 16384); } } private void outputAudioFrame(boolean autoRemove) { if (audioQueue.hasNext()) { byte[] audioBlock = autoRemove ? audioQueue.getNextData() : audioQueue.peekNextData(); if (audioBlock != null) { dataLine.write(audioBlock, 0, audioBlock.length); } } } }