Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <errno.h>#include <unistd.h>#include <limits.h>#include <poll.h>#include <signal.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/stat.h>#include <sys/un.h>
#include "osdep/io.h"#include "osdep/threads.h"
#include "common/common.h"#include "common/global.h"#include "common/msg.h"#include "input/input.h"#include "libmpv/client.h"#include "options/m_config.h"#include "options/options.h"#include "options/path.h"#include "player/client.h"
#ifndef MSG_NOSIGNAL#define MSG_NOSIGNAL 0#endif
struct mp_ipc_ctx { struct mp_log *log; struct mp_client_api *client_api; const char *path;
mp_thread thread; int death_pipe[2];};
struct client_arg { struct mp_log *log; struct mpv_handle *client;
const char *client_name; int client_fd; bool close_client_fd; bool quit_on_close;
bool writable;};
static int ipc_write_str(struct client_arg *client, const char *buf){ size_t count = strlen(buf); while (count > 0) { ssize_t rc = send(client->client_fd, buf, count, MSG_NOSIGNAL); if (rc <= 0) { if (rc == 0) return -1;
if (errno == EBADF || errno == ENOTSOCK) { client->writable = false; return 0; }
if (errno == EINTR || errno == EAGAIN) continue;
return rc; }
count -= rc; buf += rc; }
return 0;}
static MP_THREAD_VOID client_thread(void *p){ // We don't use MSG_NOSIGNAL because the moldy fruit OS doesn't support it. struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = SA_RESTART }; sigfillset(&sa.sa_mask); sigaction(SIGPIPE, &sa, NULL);
int rc;
struct client_arg *arg = p; bstr client_msg = { talloc_strdup(NULL, ""), 0 };
char *tname = talloc_asprintf(NULL, "ipc/%s", arg->client_name); mp_thread_set_name(tname); talloc_free(tname);
int pipe_fd = mpv_get_wakeup_pipe(arg->client); if (pipe_fd < 0) { MP_ERR(arg, "Could not get wakeup pipe\n"); goto done; }
MP_VERBOSE(arg, "Client connected\n");
struct pollfd fds[2] = { {.events = POLLIN, .fd = pipe_fd}, {.events = POLLIN, .fd = arg->client_fd}, };
fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK);
while (1) { rc = poll(fds, 2, 0); if (rc == 0) rc = poll(fds, 2, -1); if (rc < 0) { MP_ERR(arg, "Poll error\n"); continue; }
if (fds[0].revents & POLLIN) { mp_flush_wakeup_pipe(pipe_fd);
while (1) { mpv_event *event = mpv_wait_event(arg->client, 0);
if (event->event_id == MPV_EVENT_NONE) break;
if (event->event_id == MPV_EVENT_SHUTDOWN) goto done;
if (!arg->writable) continue;
char *event_msg = mp_json_encode_event(event); if (!event_msg) { MP_ERR(arg, "Encoding error\n"); goto done; }
rc = ipc_write_str(arg, event_msg); talloc_free(event_msg); if (rc < 0) { MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); goto done; } } }
if (fds[1].revents & (POLLIN | POLLHUP | POLLNVAL)) { while (1) { char buf[128]; bstr append = { buf, 0 };
ssize_t bytes = read(arg->client_fd, buf, sizeof(buf)); if (bytes < 0) { if (errno == EAGAIN) break;
MP_ERR(arg, "Read error (%s)\n", mp_strerror(errno)); goto done; }
if (bytes == 0) { MP_VERBOSE(arg, "Client disconnected\n"); goto done; }
append.len = bytes;
bstr_xappend(NULL, &client_msg, append);
while (bstrchr(client_msg, '\n') != -1) { char *reply_msg = mp_ipc_consume_next_command(arg->client, NULL, &client_msg);
if (reply_msg && arg->writable) { rc = ipc_write_str(arg, reply_msg); if (rc < 0) { MP_ERR(arg, "Write error (%s)\n", mp_strerror(errno)); talloc_free(reply_msg); goto done; } }
talloc_free(reply_msg); } } } }
done: if (client_msg.len > 0) MP_WARN(arg, "Ignoring unterminated command on disconnect.\n"); talloc_free(client_msg.start); if (arg->close_client_fd) close(arg->client_fd); struct mpv_handle *h = arg->client; bool quit = arg->quit_on_close; talloc_free(arg); if (quit) { mpv_terminate_destroy(h); } else { mpv_destroy(h); } MP_THREAD_RETURN();}
static bool ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client, bool free_on_init_fail){ if (!client->client) client->client = mp_new_client(ctx->client_api, client->client_name); if (!client->client) goto err;
client->log = mp_client_get_log(client->client);
mp_thread client_thr; if (mp_thread_create(&client_thr, client_thread, client)) goto err; mp_thread_detach(client_thr);
return true;
err: if (free_on_init_fail) { if (client->client) mpv_destroy(client->client);
if (client->close_client_fd) close(client->client_fd); }
talloc_free(client); return false;}
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd){ struct client_arg *client = talloc_ptrtype(NULL, client); *client = (struct client_arg){ .client_name = id >= 0 ? talloc_asprintf(client, "ipc-%d", id) : "ipc", .client_fd = fd, .close_client_fd = id >= 0, .quit_on_close = id < 0, .writable = true, };
ipc_start_client(ctx, client, true);}
bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h, int out_fd[2]){ int pair[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair)) return false; mp_set_cloexec(pair[0]); mp_set_cloexec(pair[1]);
struct client_arg *client = talloc_ptrtype(NULL, client); *client = (struct client_arg){ .client = h, .client_name = mpv_client_name(h), .client_fd = pair[1], .close_client_fd = true, .writable = true, };
if (!ipc_start_client(ctx, client, false)) { close(pair[0]); close(pair[1]); return false; }
out_fd[0] = pair[0]; out_fd[1] = -1; return true;}
static MP_THREAD_VOID ipc_thread(void *p){ int rc;
int ipc_fd; struct sockaddr_un ipc_un = {0};
struct mp_ipc_ctx *arg = p;
mp_thread_set_name("ipc/socket");
MP_VERBOSE(arg, "Starting IPC master\n");
ipc_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (ipc_fd < 0) { MP_ERR(arg, "Could not create IPC socket\n"); goto done; }
fchmod(ipc_fd, 0600);
size_t path_len = strlen(arg->path); if (path_len >= sizeof(ipc_un.sun_path) - 1) { MP_ERR(arg, "Could not create IPC socket\n"); goto done; }
ipc_un.sun_family = AF_UNIX, strncpy(ipc_un.sun_path, arg->path, sizeof(ipc_un.sun_path) - 1);
unlink(ipc_un.sun_path);
if (ipc_un.sun_path[0] == '@') { ipc_un.sun_path[0] = '\0'; path_len--; }
size_t addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + path_len; rc = bind(ipc_fd, (struct sockaddr *) &ipc_un, addr_len); if (rc < 0) { MP_ERR(arg, "Could not bind IPC socket\n"); goto done; }
rc = listen(ipc_fd, 10); if (rc < 0) { MP_ERR(arg, "Could not listen on IPC socket\n"); goto done; }
MP_VERBOSE(arg, "Listening to IPC socket.\n");
int client_num = 0;
struct pollfd fds[2] = { {.events = POLLIN, .fd = arg->death_pipe[0]}, {.events = POLLIN, .fd = ipc_fd}, };
while (1) { rc = poll(fds, 2, -1); if (rc < 0) { MP_ERR(arg, "Poll error\n"); continue; }
if (fds[0].revents & POLLIN) goto done;
if (fds[1].revents & POLLIN) { int client_fd = accept(ipc_fd, NULL, NULL); if (client_fd < 0) { MP_ERR(arg, "Could not accept IPC client\n"); goto done; }
ipc_start_client_json(arg, client_num++, client_fd); } }
done: if (ipc_fd >= 0) close(ipc_fd);
MP_THREAD_RETURN();}
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, struct mpv_global *global){ struct MPOpts *opts = mp_get_config_group(NULL, global, &mp_opt_root);
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); *arg = (struct mp_ipc_ctx){ .log = mp_log_new(arg, global->log, "ipc"), .client_api = client_api, .path = mp_get_user_path(arg, global, opts->ipc_path), .death_pipe = {-1, -1}, };
if (opts->ipc_client && opts->ipc_client[0]) { int fd = -1; bstr str = bstr0(opts->ipc_client); if (bstr_eatstart0(&str, "fd://") && str.len) { long long ll = bstrtoll(str, &str, 0); if (!str.len && ll >= 0 && ll <= INT_MAX) fd = ll; } if (fd < 0) { MP_ERR(arg, "Invalid IPC client argument: '%s'\n", opts->ipc_client); } else { ipc_start_client_json(arg, -1, fd); } }
talloc_free(opts);
if (!arg->path || !arg->path[0]) goto out;
if (mp_make_wakeup_pipe(arg->death_pipe) < 0) goto out;
if (mp_thread_create(&arg->thread, ipc_thread, arg)) goto out;
return arg;
out: if (arg->death_pipe[0] >= 0) { close(arg->death_pipe[0]); close(arg->death_pipe[1]); } talloc_free(arg); return NULL;}
void mp_uninit_ipc(struct mp_ipc_ctx *arg){ if (!arg) return;
(void)write(arg->death_pipe[1], &(char){0}, 1); mp_thread_join(arg->thread);
close(arg->death_pipe[0]); close(arg->death_pipe[1]); talloc_free(arg);}