first commit

This commit is contained in:
2026-07-16 18:51:55 +02:00
commit 8c42077112
96 changed files with 12526 additions and 0 deletions
@@ -0,0 +1,13 @@
package zv.mpv_client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MpvClientApplication {
public static void main(String[] args) {
SpringApplication.run(MpvClientApplication.class, args);
}
}
@@ -0,0 +1,56 @@
package zv.mpv_client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import zv.mpv_client.dto.mpv.MpvPauseResultDto;
import zv.mpv_client.dto.mpv.MpvSocatResultDto;
import zv.mpv_client.service.MpvService;
@RestController
@RequestMapping("/mpv")
public class MpvController {
@Autowired
private MpvService mpvService;
@PostMapping("/play/yt/music/{id}")
public ResponseEntity<String> playYtMusic(@PathVariable String id){
mpvService.playYtMusic(id);
return ResponseEntity.ok("200");
}
@GetMapping("/status")
public ResponseEntity<Boolean> isPlaying(){
return ResponseEntity.ok(mpvService.isPlaying());
}
@PostMapping("/quit")
public ResponseEntity<MpvSocatResultDto> quit(){
return ResponseEntity.ok(mpvService.quit());
}
@PostMapping("/status/volume/change/{value}")
public ResponseEntity<MpvSocatResultDto> changeVolume(@PathVariable int value){
return ResponseEntity.ok(mpvService.changeVolumeBy(value));
}
@PostMapping("/status/volume/set/{value}")
public ResponseEntity<MpvSocatResultDto> setVolume(@PathVariable int value){
return ResponseEntity.ok(mpvService.setVolumeTo(value));
}
@GetMapping("/status/volume")
public ResponseEntity<MpvSocatResultDto> getVolume(){
return ResponseEntity.ok(mpvService.getVolume());
}
@GetMapping("/status/remaining")
public ResponseEntity<MpvSocatResultDto> getTimeRemaining(){
return ResponseEntity.ok(mpvService.getTimeRemaining());
}
@PostMapping("/pause")
public ResponseEntity<MpvPauseResultDto> pause(){
return ResponseEntity.ok(mpvService.pause());
}
}
@@ -0,0 +1,10 @@
package zv.mpv_client.dto;
import lombok.Data;
@Data
public class ClientRegistrationRequestDto {
String host;
}
@@ -0,0 +1,12 @@
package zv.mpv_client.dto.mpv;
import lombok.Data;
import zv.mpv_client.enums.MPVStatus;
@Data
public class MpvPauseResultDto {
MpvSocatResultDto sockat;
MPVStatus status;
}
@@ -0,0 +1,26 @@
package zv.mpv_client.dto.mpv;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
public class MpvSocatResultDto {
int request_id;
String error;
Object data;
@JsonIgnore
public Boolean getBoolean(){
if(data == null) {return null;}
return (boolean) data;
}
@JsonIgnore
public Float getFloat(){
if(data == null) {return null;}
return (float) data;
}
}
@@ -0,0 +1,5 @@
package zv.mpv_client.enums;
public enum MPVStatus {
PLAYING,PAUSED
}
@@ -0,0 +1,182 @@
package zv.mpv_client.service;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.google.gson.*;
import zv.mpv_client.dto.mpv.MpvPauseResultDto;
import zv.mpv_client.dto.mpv.MpvSocatResultDto;
import zv.mpv_client.enums.MPVStatus;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
@Log4j2
public class MpvService {
@Value("${mpv.socket.location}")
private String mpvSocket;
public String playYtMusic(String id) {
try{
if(isPlaying()){
quit();
}
}
catch(RuntimeException e){
log.trace("bash process failed, most likely socat does not exists, yet");
}
log.trace("Starting MPV client with youtube music id: {}", id);
List<String> args = List.of("mpv", "--no-video", "--audio-display=no", "--quiet", "--input-ipc-server="+mpvSocket, "https://music.youtube.com/watch?v=" + id);
mpv(args);
return null;
}
@SneakyThrows
public MpvSocatResultDto quit() {
List<String> args = List.of("bash", "-c", "echo '{ \"command\": [\"quit\"] }' | socat - "+mpvSocket);
ProcessBuilder pb = new ProcessBuilder(args);
Process process = pb.start();
process.waitFor();
String output;
try (var stream = process.getInputStream()) {
output = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
if(output.isBlank()){
log.trace("mpv status check returned empty string: returning false");
return null;
}
Gson gson = new Gson();
MpvSocatResultDto dto =gson.fromJson(output, MpvSocatResultDto.class);
log.trace("mpv status check: {}",dto);
return dto;
}
public Boolean isPlaying(){
return !isPaused();
}
@SneakyThrows
public MpvSocatResultDto getPlayingStatus(){
List<String> args = List.of("bash", "-c", "echo '{ \"command\": [\"get_property\", \"pause\"] }' | socat - "+mpvSocket);
MpvSocatResultDto dto = bash(args);
log.trace("mpv quit result: {}",bash(args));
return dto;
}
@SneakyThrows
public Boolean isPaused(){
MpvSocatResultDto playingStatus = getPlayingStatus();
if(playingStatus == null){
return true;
}
return playingStatus.getBoolean() != null && playingStatus.getBoolean();
}
@SneakyThrows
public MpvPauseResultDto pause() {
List<String> args = List.of("bash", "-c", "echo '{ \"command\": [\"cycle\", \"pause\"] }' | socat - "+mpvSocket);
MpvSocatResultDto socat =bash(args);
MpvPauseResultDto result = new MpvPauseResultDto();
result.setSockat(socat);
result.setStatus(isPlaying() ? MPVStatus.PLAYING : MPVStatus.PAUSED);
log.trace("mpv pause: {}",socat);
return result;
}
public MpvSocatResultDto changeVolumeBy(int value){
List<String> args = List.of("bash", "-c", "echo '{ \"command\": [\"add\", \"volume\", "+value+"] }' | socat - "+mpvSocket);
bash(args);
return getVolume();
}
public MpvSocatResultDto setVolumeTo(int value){
List<String> args = List.of("bash", "-c", "echo '{ \"command\": [\"set_property\", \"volume\", "+value+"] }' | socat - "+mpvSocket);
bash(args);
return getVolume();
}
public MpvSocatResultDto getVolume(){
List<String> args_value = List.of("bash", "-c", "echo '{ \"command\": [\"get_property\", \"volume\"] }' | socat - "+mpvSocket);
return bash(args_value);
}
public MpvSocatResultDto getTimeRemaining(){
List<String> args_value = List.of("bash", "-c", "echo '{ \"command\": [\"get_property\", \"time-remaining\"] }' | socat - "+mpvSocket);
return bash(args_value);
}
private void mpv(List<String> args) {
Thread.ofVirtual().start(() -> {
try {
log.trace("Running MPV with args: {}", args);
ProcessBuilder pb = new ProcessBuilder(args);
Process process = pb.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
@SneakyThrows
private MpvSocatResultDto bash(List<String> args){
if(!isSockatUsable()){
return new MpvSocatResultDto();
}
log.trace("executing bash with args: {}", args);
ProcessBuilder pb = new ProcessBuilder(args);
Process process = pb.start();
process.waitFor();
String output;
if(process.exitValue()==0){
try (var stream = process.getInputStream()) {
output = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
if(output.isBlank()){
return new MpvSocatResultDto();
}
Gson gson = new Gson();
return gson.fromJson(output, MpvSocatResultDto.class);
}
try (var stream = process.getErrorStream()) {
output = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
throw new RuntimeException("Bash process failed: "+output);
}
@SneakyThrows
private boolean isSockatUsable(){
Path path = Paths.get(mpvSocket);
if (!Files.exists(path)) {
return false;
}
ProcessBuilder pb = new ProcessBuilder("bash", "-c", "ss -xlx | grep \"" +mpvSocket + "\"");
Process process = pb.start();
process.waitFor();
return process.exitValue()==0;
}
}
@@ -0,0 +1,41 @@
package zv.mpv_client.service;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import zv.mpv_client.dto.ClientRegistrationRequestDto;
@Service
public class ServerConnector {
@Autowired
private ApplicationContext context;
@Value("${musix.client.host}")
private String clientHost;
@Value("${musix.server.host}")
private String serverHost;
@PostConstruct
public void init(){
RestClient RESTClient = RestClient.builder()
.baseUrl(serverHost+"/api/client/register")
.build();
ClientRegistrationRequestDto body = new ClientRegistrationRequestDto();
body.setHost(clientHost);
String response = RESTClient.post()
.contentType(MediaType.APPLICATION_JSON)
.body(body)
.retrieve().body(String.class);
System.out.println(response);
}
}
@@ -0,0 +1,35 @@
package zv.mpv_client.service;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
@Service
public class SocketReadWrite {
//@PostConstruct
@SneakyThrows
public void init(){
ServerSocketChannel server = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
UnixDomainSocketAddress socketAddress1 = UnixDomainSocketAddress.of("/tmp/mpvsocket");
SocketChannel socketChannel = SocketChannel.open(socketAddress1);
ByteBuffer buf = ByteBuffer.wrap("{ \"command\": [\"quit\"] }\n".getBytes());
//socketChannel.write(buf);
ByteBuffer buf2 = ByteBuffer.wrap("{ \"command\": [\"get_property\", \"pause\"] }\n".getBytes());
socketChannel.write(buf2);
ByteBuffer bufread = ByteBuffer.allocate(64);
socketChannel.read(bufread);
bufread.flip();
System.out.println(new String(bufread.array()).split("\n")[0]);
//System.out.printf("Read %d bytes\n", bufread.remaining());
}
}
@@ -0,0 +1,9 @@
spring.application.name=mpv-client
server.port=8081
musix.client.host=http://127.0.0.1:8081
musix.server.host=http://127.0.0.1:8080
logging.level.zv.mpv_client=TRACE
mpv.socket.location=/tmp/mpvsocket
@@ -0,0 +1,13 @@
package zv.mpv_client;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MpvClientApplicationTests {
@Test
void contextLoads() {
}
}