implement MPV playback check to restart stuck player (happens with network issues). Make TaskProcess returning just boolean, therefore forcing exception handling to be in implementation. Fix MPV's isAlive call in spawn - you shouldn't do that here, because MPV state at that moment is undefined. Finally make MPVCommandResult spewing String and not nulls

This commit is contained in:
mykola2312 2024-04-29 08:15:01 +03:00
parent b666981e1e
commit acdad8acb0
10 changed files with 106 additions and 33 deletions

View file

@ -84,7 +84,6 @@ public class Main {
TaskDispatcher dispatcher = new TaskDispatcher();
dispatcher.updateTaskConfig(config.tasks);
dispatcher.registerTask(crawler);
dispatcher.registerTask(processService);
new Thread(dispatcher).start();
@ -95,12 +94,10 @@ public class Main {
// start PiIR
PiIR piir = new PiIR(config.piir);
try {
piir.spawn();
if (piir.spawn()) {
processService.registerProcess(piir);
} catch (IOException e) {
logger.error("failed to spawn piir. fatal. exiting", e);
} else {
logger.error("failed to spawn piir. exiting");
System.exit(1);
}

View file

@ -83,11 +83,13 @@ public class MPV implements TaskProcess {
private MPVReader reader;
private Thread readerThread;
private Float lastPlaybackTime = null;
private static final Path MPV_SOCKET_PATH = Path.of("/tmp/mptv-mpv.sock");
private static final long WAIT_MILLIS = 250;
private static final int WAIT_ATTEMPTS = 5;
private void waitForConnection(Path socketPath) throws IOException {
private void waitForConnection(Path socketPath) throws MPVSocketFailure {
for (int i = 0; i < WAIT_ATTEMPTS; i++) {
try {
Thread.sleep(WAIT_MILLIS);
@ -101,10 +103,12 @@ public class MPV implements TaskProcess {
logger.info(String.format("connected to socket %s", socket.toString()));
} catch (SocketException e) {
logger.error("SocketException", e);
logger.info("SocketException.. trying to connect");
closeConnection();
} catch (IOException e) {
throw new MPVSocketFailure(e);
} catch (InterruptedException e) {
break;
throw new MPVSocketFailure(e);
}
}
}
@ -118,19 +122,62 @@ public class MPV implements TaskProcess {
}
@Override
public boolean spawn() throws IOException {
process = Runtime.getRuntime().exec(new String[] {
"mpv", "--vo=gpu", "--ao=pulse", "--fullscreen", "--input-ipc-server=" + MPV_SOCKET_PATH, url
});
public boolean spawn() {
try {
process = Runtime.getRuntime().exec(new String[] {
"mpv", "--vo=gpu", "--ao=pulse", "--fullscreen", "--input-ipc-server=" + MPV_SOCKET_PATH, url
});
waitForConnection(MPV_SOCKET_PATH);
waitForConnection(MPV_SOCKET_PATH);
} catch (IOException e) {
return false;
} catch (MPVSocketFailure e) {
return false;
}
return isAlive();
return process.isAlive();
}
private boolean checkPlayback() {
try {
// get playback
MPVCommandResult result = executeCommand(
new MPVGetProperty(MPVProperty.PLAYBACK_TIME));
Float playbackTime;
try {
playbackTime = Float.parseFloat(result.data);
} catch (NumberFormatException e) {
logger.error("FAILED TO PARSE PLAYBACK DATA: " + result.data);
return false;
}
logger.info("playbackTime " + playbackTime);
// if we have previous playback - compare them,
// if not changed, then player stuck
if (lastPlaybackTime != null) {
boolean playbackChanged = (playbackTime - lastPlaybackTime) > 0.1;
lastPlaybackTime = playbackTime;
return playbackChanged;
} else { // just set first playback
lastPlaybackTime = playbackTime;
return true;
}
} catch (MPVCommandTimeout e) {
logger.warn("mpv ipc timeout bruh");
return false;
}
}
@Override
public boolean isAlive() {
return process != null ? process.isAlive() : false;
// if we have process, check if playback still going on
if (process != null) {
return checkPlayback();
} else {
return false;
}
}
@Override
@ -153,7 +200,7 @@ public class MPV implements TaskProcess {
private static final long COMMAND_TIMEOUT = 2000L;
public MPVCommandResult writeCommand(MPVCommand command) {
public MPVCommandResult executeCommand(MPVCommand command) {
try {
commandRequestId = command.setRequestId(requestIdCounter++);

View file

@ -1,11 +1,15 @@
package com.mykola2312.mptv.mpv;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MPVCommandResult {
public int request_id;
public String error;
// parse always as string to avoid headache with different types
@JsonProperty("data")
public String data;
public static MPVCommandResult deserialize(String data) throws JsonProcessingException {

View file

@ -0,0 +1,17 @@
package com.mykola2312.mptv.mpv;
import java.util.Arrays;
import java.util.List;
public class MPVGetProperty extends MPVCommand {
private final MPVProperty property;
public MPVGetProperty(MPVProperty property) {
this.property = property;
}
@Override
protected List<String> serializeCommand() {
return Arrays.asList("get_property", property.toString());
}
}

View file

@ -1,7 +1,8 @@
package com.mykola2312.mptv.mpv;
public enum MPVProperty {
VOLUME ("volume");
VOLUME ("volume"),
PLAYBACK_TIME ("playback-time");
private final String name;

View file

@ -0,0 +1,7 @@
package com.mykola2312.mptv.mpv;
public class MPVSocketFailure extends RuntimeException {
public MPVSocketFailure(Throwable cause) {
super("fatal IPC failure. cannot continue");
}
}

View file

@ -117,10 +117,14 @@ public class PiIR implements TaskProcess {
}
@Override
public boolean spawn() throws IOException {
process = Runtime.getRuntime().exec(new String[] {
"unbuffer", exec, "dump", "--gpio", String.valueOf(gpio)
});
public boolean spawn() {
try {
process = Runtime.getRuntime().exec(new String[] {
"unbuffer", exec, "dump", "--gpio", String.valueOf(gpio)
});
} catch (IOException e) {
return false;
}
input = process.getInputStream();
reader = new PiIRReader(this, input);

View file

@ -29,6 +29,7 @@ public class ProcessService implements Task {
public void dispatch() {
for (TaskProcess process : processes) {
if (!process.isAlive()) {
logger.info("restarting process " + process.toString());
try {
process.stop();
process.spawn();

View file

@ -1,9 +1,7 @@
package com.mykola2312.mptv.task;
import java.io.IOException;
public interface TaskProcess {
public boolean spawn() throws IOException;
public boolean spawn();
public boolean isAlive();
public void stop();
}

View file

@ -21,7 +21,6 @@ import static com.mykola2312.mptv.tables.Channel.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
public class MenuPanel extends JPanel {
private static final Logger logger = LoggerFactory.getLogger(MenuPanel.class);
@ -81,14 +80,12 @@ public class MenuPanel extends JPanel {
}
MPV newPlayer = new MPV(url);
try {
if (newPlayer.spawn()) {
player = newPlayer;
if (newPlayer.spawn()) {
player = newPlayer;
Main.processService.registerProcess(player);
}
} catch (IOException e) {
logger.error("failed to spawn player", e);
Main.processService.registerProcess(player);
} else {
logger.error("failed to spawn mpv");
}
}