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

View file

@ -83,11 +83,13 @@ public class MPV implements TaskProcess {
private MPVReader reader; private MPVReader reader;
private Thread readerThread; private Thread readerThread;
private Float lastPlaybackTime = null;
private static final Path MPV_SOCKET_PATH = Path.of("/tmp/mptv-mpv.sock"); private static final Path MPV_SOCKET_PATH = Path.of("/tmp/mptv-mpv.sock");
private static final long WAIT_MILLIS = 250; private static final long WAIT_MILLIS = 250;
private static final int WAIT_ATTEMPTS = 5; 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++) { for (int i = 0; i < WAIT_ATTEMPTS; i++) {
try { try {
Thread.sleep(WAIT_MILLIS); Thread.sleep(WAIT_MILLIS);
@ -101,10 +103,12 @@ public class MPV implements TaskProcess {
logger.info(String.format("connected to socket %s", socket.toString())); logger.info(String.format("connected to socket %s", socket.toString()));
} catch (SocketException e) { } catch (SocketException e) {
logger.error("SocketException", e); logger.info("SocketException.. trying to connect");
closeConnection(); closeConnection();
} catch (IOException e) {
throw new MPVSocketFailure(e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
break; throw new MPVSocketFailure(e);
} }
} }
} }
@ -118,19 +122,62 @@ public class MPV implements TaskProcess {
} }
@Override @Override
public boolean spawn() throws IOException { public boolean spawn() {
process = Runtime.getRuntime().exec(new String[] { try {
"mpv", "--vo=gpu", "--ao=pulse", "--fullscreen", "--input-ipc-server=" + MPV_SOCKET_PATH, url 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 @Override
public boolean isAlive() { 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 @Override
@ -153,7 +200,7 @@ public class MPV implements TaskProcess {
private static final long COMMAND_TIMEOUT = 2000L; private static final long COMMAND_TIMEOUT = 2000L;
public MPVCommandResult writeCommand(MPVCommand command) { public MPVCommandResult executeCommand(MPVCommand command) {
try { try {
commandRequestId = command.setRequestId(requestIdCounter++); commandRequestId = command.setRequestId(requestIdCounter++);

View file

@ -1,11 +1,15 @@
package com.mykola2312.mptv.mpv; package com.mykola2312.mptv.mpv;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
public class MPVCommandResult { public class MPVCommandResult {
public int request_id; public int request_id;
public String error; public String error;
// parse always as string to avoid headache with different types
@JsonProperty("data")
public String data; public String data;
public static MPVCommandResult deserialize(String data) throws JsonProcessingException { 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; package com.mykola2312.mptv.mpv;
public enum MPVProperty { public enum MPVProperty {
VOLUME ("volume"); VOLUME ("volume"),
PLAYBACK_TIME ("playback-time");
private final String name; 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 @Override
public boolean spawn() throws IOException { public boolean spawn() {
process = Runtime.getRuntime().exec(new String[] { try {
"unbuffer", exec, "dump", "--gpio", String.valueOf(gpio) process = Runtime.getRuntime().exec(new String[] {
}); "unbuffer", exec, "dump", "--gpio", String.valueOf(gpio)
});
} catch (IOException e) {
return false;
}
input = process.getInputStream(); input = process.getInputStream();
reader = new PiIRReader(this, input); reader = new PiIRReader(this, input);

View file

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

View file

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

View file

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