first commit
This commit is contained in:
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user