// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>.
//
// TotalRecall is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 only.
//
// TotalRecall is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TotalRecall. If not, see <http://www.gnu.org/licenses/>.
package edu.upenn.psych.memory.nativestatelessplayer;
import info.Constants;
import info.SysInfo;
import java.io.File;
import java.util.List;
import behaviors.UpdatingAction;
import edu.upenn.psych.memory.precisionplayer.PrecisionEvent;
import edu.upenn.psych.memory.precisionplayer.PrecisionEventLauncher;
import edu.upenn.psych.memory.precisionplayer.PrecisionListener;
import edu.upenn.psych.memory.precisionplayer.PrecisionPlayer;
public class NativeStatelessPlaybackThread extends Thread {
private final long startFrame;
private final long endFrame;
private final List<PrecisionListener> listeners;
private final NativeStatelessPlayer myPlayer;
private final File audioFile;
private LibPennTotalRecall myLib;
private volatile boolean finish;
protected NativeStatelessPlaybackThread(LibPennTotalRecall lib, NativeStatelessPlayer player, File file, long startFrame, long endFrame, List<PrecisionListener> listeners) {
this.audioFile = file;
this.listeners = listeners;
this.startFrame = startFrame;
this.endFrame = endFrame;
this.myPlayer = player;
this.myLib = lib;
this.finish = false;
}
@Override
public void run() {
try {
// System.out.println(getClass().getName() + ": " + startFrame + " to " + endFrame);
int returnCode = myLib.startPlayback(audioFile.getAbsolutePath(), startFrame, endFrame);
if(returnCode < 0) {
myLib.stopPlayback();
String message = "Unable to start playback.\n";
switch(returnCode) {
case(-2):
message += "No audio device found.";
break;
case(-3):
message += "Unable to find or open file.";
break;
case(-4):
message += "Inconsistent state. Trying to repair";
break;
case(-5):
message += "I/O error.";
break;
default:
String os = System.getProperty("os.name");
if(os != null && os.toLowerCase().contains("linux")) {
message += "\n" + Constants.programName + " prefers exclusive access to the sound system.\n" +
"Please close all sound-emitting programs and web pages and try again.";
}
else {
message += "Unspecified error.";
}
break;
}
if(listeners != null) {
myPlayer.setStatus(PrecisionPlayer.Status.READY);
PrecisionEventLauncher trigger = new PrecisionEventLauncher(PrecisionEvent.EventCode.ERROR, -1, message, listeners);
trigger.start();
return;
}
}
while(finish == false) {
long framesElapsed = myLib.streamPosition();
long curFrame = framesElapsed + startFrame;
if(curFrame >= endFrame) {
if(curFrame > Integer.MAX_VALUE) {
//apparently this is a result of FMOD code currently not self-stopping, Issue 11
System.err.println("applying FMOD last-frame-is-huge workaround");
curFrame = endFrame;
}
else {
stopPlayback();
}
}
if(listeners != null) {
if(framesElapsed > 0) {
for(PrecisionListener ppl: listeners) {
ppl.progress(curFrame);
}
}
}
if(myLib.playbackInProgress() == false) {
if(SysInfo.sys.isWindowsAny) { //Fix Issue 9
try {
Thread.sleep(150);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
break;
}
try {
UpdatingAction.getStamps().add(System.currentTimeMillis());
Thread.sleep(30);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
if(finish == false) {
myLib.stopPlayback(); //this is EOM. we must still call stopPlayback() to close the native stream
if(listeners != null) {
//there is no way to guarantee the hearing frame at this line is actually the final frame
//however, PrecisionPlayer requires EOM events report that they occur at the final frame, so we oblige
myPlayer.setStatus(PrecisionPlayer.Status.READY);
PrecisionEventLauncher trigger = new PrecisionEventLauncher(PrecisionEvent.EventCode.EOM, endFrame, null, listeners);
trigger.start();
}
}
}
catch(Throwable t) {
try {
myLib.stopPlayback();
}
catch(Throwable t2) {
t2.printStackTrace();
}
if(listeners != null) {
myPlayer.setStatus(PrecisionPlayer.Status.READY);
PrecisionEventLauncher trigger = new PrecisionEventLauncher(PrecisionEvent.EventCode.ERROR, -1, t.getMessage(), listeners);
trigger.start();
t.printStackTrace();
}
}
if(myLib.playbackInProgress()) {
try{
Thread.sleep(500);
}
catch(InterruptedException e) {
}
}
}
protected long stopPlayback() {
finish = true;
long stopFrame = myLib.stopPlayback();
return stopFrame;
}
}