aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--TODO3
-rw-r--r--src/VideoPlayer.cpp32
-rw-r--r--src/plugins/youtube/YoutubeMediaProxy.cpp40
-rw-r--r--video_player/README.md58
m---------video_player/jsoncpp0
-rw-r--r--video_player/src/main.cpp346
7 files changed, 438 insertions, 44 deletions
diff --git a/.gitmodules b/.gitmodules
index c113260..3789cda 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,6 @@
[submodule "depends/mglpp"]
path = depends/mglpp
url = git://git.dec05eba.com/mglpp
+[submodule "video_player/jsoncpp"]
+ path = video_player/jsoncpp
+ url = git://git.dec05eba.com/jsoncpp
diff --git a/TODO b/TODO
index 1e94118..066be72 100644
--- a/TODO
+++ b/TODO
@@ -219,4 +219,5 @@ Show in bookmarks and history page if a manga has been read (local-manga).
Add "finished reading" to online manga as well, for the manga sites that publish latest chapter in the search page.
Async load visible body item content. This is needed for local-manga if the manga is stored on NFS where recursively reading all manga directories is slow. We only want to read recursively for the manga that is visible on the screen.
Allow using ~/.config/mpv/input.conf when using use_system_mpv_config by merging QuickMedia's input.conf and the users input.conf and creating a temporary file in /tmp and load that. But make sure configs dont clash.
-Allow asynchronously loading body items. This is needed in manga combined plugin. \ No newline at end of file
+Allow asynchronously loading body items. This is needed in manga combined plugin.
+Bold video subtitles. \ No newline at end of file
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index f9ae04d..75871da 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -16,6 +16,32 @@
#include <fcntl.h>
#include <signal.h>
+static ssize_t read_eintr(int fd, void *buffer, size_t size) {
+ while(true) {
+ ssize_t bytes_read = read(fd, buffer, size);
+ if(bytes_read == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ return bytes_read;
+ }
+ }
+}
+
+static ssize_t write_all(int fd, const void *buffer, size_t size) {
+ ssize_t bytes_written = 0;
+ while((size_t)bytes_written < size) {
+ ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written);
+ if(written == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ bytes_written += written;
+ }
+ }
+ return bytes_written;
+}
+
namespace QuickMedia {
static const double RETRY_TIME_SEC = 0.5;
static const int MAX_RETRIES_CONNECT = 1000;
@@ -162,7 +188,7 @@ namespace QuickMedia {
"--cache=yes",
"--cache-on-disk=yes",
"--cache-secs=86400", // 24 hours
- "--sub-font-size=40",
+ "--sub-font-size=60",
"--sub-margin-y=45",
"--sub-border-size=1.95",
//"--force_all_formats=no",
@@ -376,7 +402,7 @@ namespace QuickMedia {
std::string json_errors;
char buffer[2048];
- ssize_t bytes_read = read(ipc_socket, buffer, sizeof(buffer));
+ ssize_t bytes_read = read_eintr(ipc_socket, buffer, sizeof(buffer));
if(bytes_read == -1) {
int err = errno;
if(err != EAGAIN && err != EWOULDBLOCK) {
@@ -517,7 +543,7 @@ namespace QuickMedia {
if(!connected_to_ipc)
return Error::FAIL_NOT_CONNECTED;
- if(send(ipc_socket, cmd, size, 0) == -1) {
+ if(write_all(ipc_socket, cmd, size) == -1) {
fprintf(stderr, "Failed to send to ipc socket, error: %s, command: %.*s\n", strerror(errno), (int)size, cmd);
return Error::FAIL_TO_SEND;
}
diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp
index 6e2c5c4..b33c122 100644
--- a/src/plugins/youtube/YoutubeMediaProxy.cpp
+++ b/src/plugins/youtube/YoutubeMediaProxy.cpp
@@ -18,6 +18,32 @@
// TODO: What if the client sends a new header without reconnecting? is that even allowed by http standard?
// TODO: Detect when download has finished (and close connection).
+static ssize_t read_eintr(int fd, void *buffer, size_t size) {
+ while(true) {
+ ssize_t bytes_read = read(fd, buffer, size);
+ if(bytes_read == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ return bytes_read;
+ }
+ }
+}
+
+static ssize_t write_all(int fd, const void *buffer, size_t size) {
+ ssize_t bytes_written = 0;
+ while((size_t)bytes_written < size) {
+ ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written);
+ if(written == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ bytes_written += written;
+ }
+ }
+ return bytes_written;
+}
+
namespace QuickMedia {
static const int MAX_BUFFER_SIZE = 65536;
static const int64_t RANGE = 5242870;
@@ -232,7 +258,7 @@ namespace QuickMedia {
return Error::OK;
char read_buffer[4096];
- const ssize_t num_bytes_read = read(client_fd, read_buffer, sizeof(read_buffer));
+ const ssize_t num_bytes_read = read_eintr(client_fd, read_buffer, sizeof(read_buffer));
if(num_bytes_read == -1) {
const int err = errno;
if(err == EAGAIN || err == EWOULDBLOCK) {
@@ -308,7 +334,7 @@ namespace QuickMedia {
if(program_status != 0) {
//fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: download failed, exit status: %d\n", program_status);
if(client_fd != -1) {
- write(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1);
+ write_all(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1);
close(client_fd);
client_fd = -1;
client_request_buffer.clear();
@@ -354,7 +380,7 @@ namespace QuickMedia {
if(!start_download_success) {
fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: failed to start download\n");
if(client_fd != -1) {
- write(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1);
+ write_all(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1);
close(client_fd);
client_fd = -1;
client_request_buffer.clear();
@@ -392,7 +418,7 @@ namespace QuickMedia {
return Error::OK;
}
- const ssize_t num_bytes_written = write(client_fd, buffer_start + buffer_offset, num_bytes_to_write);
+ const ssize_t num_bytes_written = write_all(client_fd, buffer_start + buffer_offset, num_bytes_to_write);
if(num_bytes_written == -1) {
const int err = errno;
if(err == EAGAIN || err == EWOULDBLOCK) {
@@ -446,7 +472,7 @@ namespace QuickMedia {
YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::handle_download() {
// TODO: Maybe read even if write is being slow and failing?
if(download_read_buffer_offset == 0) {
- downloader_num_read_bytes = read(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer));
+ downloader_num_read_bytes = read_eintr(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer));
if(downloader_num_read_bytes == -1) {
const int err = errno;
if(err == EAGAIN || err == EWOULDBLOCK) {
@@ -673,7 +699,7 @@ namespace QuickMedia {
return Error::OK;
}
- const ssize_t num_bytes_written = write(fd[1], buffer_start + buffer_offset, num_bytes_to_write);
+ const ssize_t num_bytes_written = write_all(fd[1], buffer_start + buffer_offset, num_bytes_to_write);
if(num_bytes_written == -1) {
const int err = errno;
if(err == EAGAIN || err == EWOULDBLOCK) {
@@ -703,7 +729,7 @@ namespace QuickMedia {
return Error::OK;
if(download_read_buffer_offset == 0) {
- downloader_num_read_bytes = read(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer));
+ downloader_num_read_bytes = read_eintr(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer));
if(downloader_num_read_bytes == -1) {
const int err = errno;
if(err == EAGAIN || err == EWOULDBLOCK) {
diff --git a/video_player/README.md b/video_player/README.md
new file mode 100644
index 0000000..68697d9
--- /dev/null
+++ b/video_player/README.md
@@ -0,0 +1,58 @@
+# QuickMedia Video Player
+The video player internally used by QuickMedia. Uses libmpv.\
+The video player window is embedded inside QuickMedia and QuickMedia and this video player communicate over a file descriptor (socketpair) using json (json without newline formatting; one command per line).
+# IPC commands
+## time-pos
+Return seeking position in file in seconds
+### request
+```
+{
+ "command": "time-pos"
+}
+```
+### response on success
+```
+{
+ "status": "success",
+ "data": 112.432
+}
+```
+### response on error
+```
+{
+ "status": "error",
+ "message": "error message"
+}
+```
+## sub-add
+Add a subtitle file/url that is loaded asynchronously
+### request
+```
+{
+ "command": "sub-add",
+ "data": {
+ "file": "path/to/file/or/url",
+ "title": "title", // Optional
+ "language": "en_us" // Optional
+ }
+}
+```
+### response on success
+```
+{
+ "status": "success"
+}
+```
+### response on error
+```
+{
+ "status": "error",
+ "message": "error message"
+}
+```
+# IPC event
+```
+{
+ "name": "file-loaded"
+}
+``` \ No newline at end of file
diff --git a/video_player/jsoncpp b/video_player/jsoncpp
new file mode 160000
+Subproject f23fb32fd9d9c3d01fa67afa0d75f7ff227647e
diff --git a/video_player/src/main.cpp b/video_player/src/main.cpp
index 3a06f92..42ff936 100644
--- a/video_player/src/main.cpp
+++ b/video_player/src/main.cpp
@@ -3,18 +3,55 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <thread>
+#include <mutex>
#include <mpv/client.h>
+#include <json/json.h>
+
+#define COMMAND_BUFFER_MAX_SIZE 2048
static void usage() {
- fprintf(stderr, "usage: quickmedia-video-player [--wid <window_id>] <file>\n");
+ fprintf(stderr, "usage: quickmedia-video-player [--wid <window_id>] [--ipc-fd <fd>] [--no-config] <file>\n");
fprintf(stderr, " --wid <window_id> The window to embed the video player into. Optional\n");
+ fprintf(stderr, " --ipc-fd <fd> A bi-directional (socketpair) file descriptor to receive commands from. Optional\n");
+ fprintf(stderr, " --no-config Do not load the users mpv config (~/.config/mpv/mpv.conf). Optional, the users mpv config is loaded by default\n");
fprintf(stderr, "examples:\n");
fprintf(stderr, " quickmedia-video-player video.mp4\n");
- fprintf(stderr, " quickmedia-video-player --wid 30481231 video.mp4\n");
+ fprintf(stderr, " quickmedia-video-player --wid 30481231 -- video.mp4\n");
exit(1);
}
+static ssize_t read_eintr(int fd, void *buffer, size_t size) {
+ while(true) {
+ ssize_t bytes_read = read(fd, buffer, size);
+ if(bytes_read == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ return bytes_read;
+ }
+ }
+}
+
+static ssize_t write_all(int fd, const void *buffer, size_t size) {
+ ssize_t bytes_written = 0;
+ while((size_t)bytes_written < size) {
+ ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written);
+ if(written == -1) {
+ if(errno != EINTR)
+ return -1;
+ } else {
+ bytes_written += written;
+ }
+ }
+ return bytes_written;
+}
+
static bool string_to_long(const char *str, long &result) {
errno = 0;
char *endptr = NULL;
@@ -22,22 +59,193 @@ static bool string_to_long(const char *str, long &result) {
return endptr != str && errno == 0;
}
+static bool fd_is_valid(int fd) {
+ errno = 0;
+ return fcntl(fd, F_GETFD) != -1 && errno != EBADF;
+}
+
+static size_t ipc_handler(mpv_handle *mpv_ctx, int fd, char *buffer, size_t buffer_size, bool &disconnected) {
+ ssize_t bytes_read = read_eintr(fd, buffer, buffer_size);
+ if(bytes_read < 0) {
+ fprintf(stderr, "Error: ipc read failed, error: %s\n", strerror(errno));
+ //exit(3);
+ disconnected = false;
+ return 0;
+ }
+
+ mpv_wakeup(mpv_ctx);
+ disconnected = (bytes_read == 0);
+ return bytes_read;
+}
+
+static Json::Value handle_json_command_time_pos(mpv_handle *mpv_ctx) {
+ double time_pos = 0.0;
+ const int res = mpv_get_property(mpv_ctx, "time-pos", MPV_FORMAT_DOUBLE, &time_pos);
+
+ Json::Value response_json(Json::objectValue);
+ if(res < 0) {
+ response_json["status"] = "error";
+ response_json["message"] = mpv_error_string(res);
+ } else {
+ response_json["status"] = "success";
+ response_json["data"] = time_pos;
+ }
+ return response_json;
+}
+
+static Json::Value handle_json_command_sub_add(mpv_handle *mpv_ctx, const Json::Value &json_root) {
+ Json::Value response_json(Json::objectValue);
+
+ const Json::Value &data_json = json_root["data"];
+ if(!data_json.isObject()) {
+ response_json["status"] = "error";
+ response_json["message"] = "expected \"data\" to be an object";
+ return response_json;
+ }
+
+ const Json::Value &file_json = data_json["file"];
+ const Json::Value &title_json = data_json["title"];
+ const Json::Value &language_json = data_json["language"];
+
+ if(!file_json.isString()) {
+ response_json["status"] = "error";
+ response_json["message"] = "expected \"data.file\" to be a string";
+ return response_json;
+ }
+
+ if(!title_json.isString() && !title_json.isNull()) {
+ response_json["status"] = "error";
+ response_json["message"] = "expected optional field \"data.title\" to be a string or omitted";
+ return response_json;
+ }
+
+ if(!language_json.isString() && !language_json.isNull()) {
+ response_json["status"] = "error";
+ response_json["message"] = "expected optional field \"data.language\" to be a string or omitted";
+ return response_json;
+ }
+
+ std::vector<const char*> args;
+ args.push_back("sub-add");
+ args.push_back(file_json.asCString());
+ args.push_back("auto");
+ if(title_json.isString())
+ args.push_back(title_json.asCString());
+ if(language_json.isString()) {
+ if(!title_json.isString())
+ args.push_back(language_json.asCString());
+ args.push_back(language_json.asCString());
+ }
+ args.push_back(nullptr);
+
+ const int res = mpv_command_async(mpv_ctx, 0, args.data());
+ if(res < 0) {
+ response_json["status"] = "error";
+ response_json["message"] = mpv_error_string(res);
+ } else {
+ response_json["status"] = "success";
+ }
+ return response_json;
+}
+
+static void handle_json_command(mpv_handle *mpv_ctx, const Json::Value &json_root, int fd) {
+ if(!json_root.isObject()) {
+ fprintf(stderr, "Error: expected command json root to be an object\n");
+ return;
+ }
+
+ const Json::Value &command_json = json_root["command"];
+ if(!command_json.isString()) {
+ fprintf(stderr, "Error: command json is missing field \"command\" or it's not a string\n");
+ return;
+ }
+
+ Json::Value response_json;
+ if(strcmp(command_json.asCString(), "time-pos") == 0) {
+ response_json = handle_json_command_time_pos(mpv_ctx);
+ } else if(strcmp(command_json.asCString(), "sub-add") == 0) {
+ response_json = handle_json_command_sub_add(mpv_ctx, json_root);
+ } else {
+ Json::Value response_json(Json::objectValue);
+ response_json["status"] = "error";
+ response_json["message"] = "invalid command " + command_json.asString() + ", expected time-pos or sub-add";
+ }
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+ const std::string response_str = Json::writeString(builder, response_json) + "\n";
+
+ ssize_t bytes_written = write_all(fd, response_str.data(), response_str.size());
+ if(bytes_written < 0) {
+ fprintf(stderr, "Error: ipc write failed, error: %s\n", strerror(errno));
+ //exit(3);
+ return;
+ }
+}
+
+static void handle_request_commands_line_by_line(mpv_handle *mpv_ctx, int fd, char *command_buffer, size_t &command_buffer_size, Json::Value &json_root, std::string &json_errors) {
+ size_t command_offset = 0;
+ while(command_offset < command_buffer_size) {
+ const void *space_p = memchr(command_buffer + command_offset, '\n', command_buffer_size - command_offset);
+ if(!space_p)
+ space_p = command_buffer + command_buffer_size;
+
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ json_errors.clear();
+ if(json_reader->parse(command_buffer + command_offset, (const char*)space_p, &json_root, &json_errors)) {
+ handle_json_command(mpv_ctx, json_root, fd);
+ } else {
+ fprintf(stderr, "Error: failed to parse command as json, error: %s\n", json_errors.c_str());
+ }
+ command_offset = ((const char*)space_p + 1) - command_buffer;
+ }
+ command_buffer_size = 0;
+}
+
+static void send_event(const char *event_name, int fd) {
+ Json::Value json_root(Json::objectValue);
+ json_root["name"] = event_name;
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+ const std::string response_str = Json::writeString(builder, json_root) + "\n";
+
+ ssize_t bytes_written = write_all(fd, response_str.data(), response_str.size());
+ if(bytes_written < 0) {
+ fprintf(stderr, "Error: ipc write failed, error: %s\n", strerror(errno));
+ //exit(3);
+ return;
+ }
+}
+
static inline void check_error(int status) {
if (status < 0) {
- fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
+ fprintf(stderr, "Error: mpv error: %s\n", mpv_error_string(status));
exit(2);
}
}
-int main(int argc, char **argv) {
+struct Args {
long wid_num = 0;
+ long ipc_fd_num = 0;
+
const char *wid = nullptr;
+ const char *ipc_fd = nullptr;
const char *file_to_play = nullptr;
+ bool no_config = false;
+};
+
+static Args parse_args(int argc, char **argv) {
+ Args args;
+
for(int i = 1; i < argc; ++i) {
const char *arg = argv[i];
if(strcmp(arg, "--wid") == 0) {
- if(wid) {
+ if(args.wid) {
fprintf(stderr, "Error: option --wid was specified multiple times\n");
usage();
}
@@ -47,8 +255,23 @@ int main(int argc, char **argv) {
usage();
}
- wid = argv[i + 1];
+ args.wid = argv[i + 1];
+ ++i;
+ } else if(strcmp(arg, "--ipc-fd") == 0) {
+ if(args.ipc_fd) {
+ fprintf(stderr, "Error: option --ipc-fd was specified multiple times\n");
+ usage();
+ }
+
+ if(i + 1 == argc) {
+ fprintf(stderr, "Error: missing fd after option --ipc-fd\n");
+ usage();
+ }
+
+ args.ipc_fd = argv[i + 1];
++i;
+ } else if(strcmp(arg, "--no-config") == 0) {
+ args.no_config = true;
} else if(strcmp(arg, "--") == 0) {
if(i + 1 == argc) {
fprintf(stderr, "Error: missing file option after --\n");
@@ -58,63 +281,120 @@ int main(int argc, char **argv) {
usage();
}
- file_to_play = argv[i + 1];
+ args.file_to_play = argv[i + 1];
++i;
} else if(strncmp(arg, "--", 2) == 0) {
fprintf(stderr, "Error: invalid option %s\n", arg);
usage();
} else {
- if(file_to_play) {
+ if(args.file_to_play) {
fprintf(stderr, "Error: file option was specified multiple times\n");
usage();
}
- file_to_play = arg;
+ args.file_to_play = arg;
}
}
- if(!file_to_play) {
+ if(!args.file_to_play) {
fprintf(stderr, "Error: missing file option\n");
usage();
}
- if(wid) {
- if(!string_to_long(wid, wid_num)) {
- fprintf(stderr, "Error: invalid number %s was specified for option --wid\n", wid);
+ if(args.wid) {
+ if(!string_to_long(args.wid, args.wid_num)) {
+ fprintf(stderr, "Error: invalid number %s was specified for option --wid\n", args.wid);
usage();
}
}
- mpv_handle *ctx = mpv_create();
- if (!ctx) {
- printf("failed creating context\n");
+ if(args.ipc_fd) {
+ if(!string_to_long(args.ipc_fd, args.ipc_fd_num)) {
+ fprintf(stderr, "Error: invalid number %s was specified for option --ipc-fd\n", args.ipc_fd);
+ usage();
+ }
+
+ if(!fd_is_valid(args.ipc_fd_num)) {
+ fprintf(stderr, "Error: invalid fd %s was specified for option --ipc-fd\n", args.ipc_fd);
+ usage();
+ }
+ }
+
+ return args;
+}
+
+int main(int argc, char **argv) {
+ // This is needed for mpv_create or it will fail
+ setlocale(LC_ALL, "C");
+
+ Args args = parse_args(argc, argv);
+
+ mpv_handle *mpv_ctx = mpv_create();
+ if (!mpv_ctx) {
+ fprintf(stderr, "Error: failed to create mpv context\n");
return 1;
}
- check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes"));
- check_error(mpv_set_option_string(ctx, "input-vo-keyboard", "yes"));
- check_error(mpv_set_option_string(ctx, "osc", "yes"));
+ check_error(mpv_set_option_string(mpv_ctx, "input-default-bindings", "yes"));
+ check_error(mpv_set_option_string(mpv_ctx, "input-vo-keyboard", "yes"));
+ check_error(mpv_set_option_string(mpv_ctx, "osc", "yes"));
- check_error(mpv_set_option_string(ctx, "profile", "gpu-hq"));
- check_error(mpv_set_option_string(ctx, "vo", "gpu"));
- check_error(mpv_set_option_string(ctx, "hwdec", "auto"));
- check_error(mpv_set_option_string(ctx, "config", "yes"));
+ check_error(mpv_set_option_string(mpv_ctx, "profile", "gpu-hq"));
+ check_error(mpv_set_option_string(mpv_ctx, "vo", "gpu"));
+ check_error(mpv_set_option_string(mpv_ctx, "hwdec", "auto"));
+ if(!args.no_config)
+ check_error(mpv_set_option_string(mpv_ctx, "config", "yes"));
+
+ if(args.wid)
+ check_error(mpv_set_option_string(mpv_ctx, "wid", args.wid));
- if(wid)
- check_error(mpv_set_option_string(ctx, "wid", wid));
+ check_error(mpv_initialize(mpv_ctx));
- check_error(mpv_initialize(ctx));
+ const char *cmd[] = { "loadfile", args.file_to_play, NULL };
+ check_error(mpv_command(mpv_ctx, cmd));
- const char *cmd[] = { "loadfile", file_to_play, NULL };
- check_error(mpv_command(ctx, cmd));
+ char command_buffer[COMMAND_BUFFER_MAX_SIZE];
+ size_t command_buffer_size = 0;
+ std::mutex command_mutex;
+ bool ipc_disconnected = false;
- while (true) {
- mpv_event *event = mpv_wait_event(ctx, 10000);
- printf("event: %s\n", mpv_event_name(event->event_id));
- if (event->event_id == MPV_EVENT_SHUTDOWN)
+ bool running = true;
+ // TODO: Clean cleanup instead of terminating... To make that possible we need to find a way to wake up the read call that is waiting for data
+ if(args.ipc_fd) {
+ std::thread([&]() mutable {
+ while(running) {
+ command_buffer_size = ipc_handler(mpv_ctx, args.ipc_fd_num, command_buffer, COMMAND_BUFFER_MAX_SIZE, ipc_disconnected);
+ std::lock_guard<std::mutex> lock(command_mutex); // Wait until the command has been handled in the main loop in the main thread
+ }
+ }).detach();
+ }
+
+ Json::Value json_root;
+ std::string json_errors;
+
+ while (running) {
+ mpv_event *event = mpv_wait_event(mpv_ctx, -1.0);
+ if (event->event_id == MPV_EVENT_SHUTDOWN) {
+ running = false;
break;
+ }
+
+ if(event->event_id != MPV_EVENT_NONE)
+ send_event(mpv_event_name(event->event_id), args.ipc_fd_num);
+
+ // TODO: Check if we can get here without mpv_wakeup being called from ipc_handler
+ std::lock_guard<std::mutex> lock(command_mutex);
+
+ // Other end of the ipc socket has disconnected
+ if(ipc_disconnected) {
+ fprintf(stderr, "Warning: the other end of the ipc fd was closed, closing the video player...\n");
+ running = false;
+ break;
+ }
+
+ handle_request_commands_line_by_line(mpv_ctx, args.ipc_fd_num, command_buffer, command_buffer_size, json_root, json_errors);
}
- mpv_terminate_destroy(ctx);
+ mpv_terminate_destroy(mpv_ctx);
return 0;
}