summaryrefslogtreecommitdiffstats
path: root/player/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'player/client.c')
-rw-r--r--player/client.c206
1 files changed, 116 insertions, 90 deletions
diff --git a/player/client.c b/player/client.c
index 4a7307194a..a887a16d2a 100644
--- a/player/client.c
+++ b/player/client.c
@@ -69,6 +69,8 @@ struct mp_client_api {
int num_clients;
uint64_t event_masks; // combined events of all clients, or 0 if unknown
bool shutting_down; // do not allow new clients
+ bool have_terminator; // a client took over the role of destroying the core
+ bool terminate_core_thread; // make libmpv core thread exit
struct mp_custom_protocol *custom_protocols;
int num_custom_protocols;
@@ -95,7 +97,6 @@ struct observe_property {
struct mpv_handle {
// -- immmutable
char name[MAX_CLIENT_NAME];
- bool owner;
struct mp_log *log;
struct MPContext *mpctx;
struct mp_client_api *clients;
@@ -172,14 +173,6 @@ void mp_clients_destroy(struct MPContext *mpctx)
mpctx->clients = NULL;
}
-int mp_clients_num(struct MPContext *mpctx)
-{
- pthread_mutex_lock(&mpctx->clients->lock);
- int num_clients = mpctx->clients->num_clients;
- pthread_mutex_unlock(&mpctx->clients->lock);
- return num_clients;
-}
-
// Test for "fuzzy" initialization of all clients. That is, all clients have
// at least called mpv_wait_event() at least once since creation (or exited).
bool mp_clients_all_initialized(struct MPContext *mpctx)
@@ -221,13 +214,6 @@ bool mp_client_exists(struct MPContext *mpctx, const char *client_name)
return r;
}
-void mp_client_enter_shutdown(struct MPContext *mpctx)
-{
- pthread_mutex_lock(&mpctx->clients->lock);
- mpctx->clients->shutting_down = true;
- pthread_mutex_unlock(&mpctx->clients->lock);
-}
-
struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name)
{
pthread_mutex_lock(&clients->lock);
@@ -272,6 +258,9 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
MP_TARRAY_APPEND(clients, clients->clients, clients->num_clients, client);
+ if (clients->num_clients == 1 && !clients->mpctx->is_cli)
+ client->fuzzy_initialized = true;
+
clients->event_masks = 0;
pthread_mutex_unlock(&clients->lock);
@@ -373,24 +362,34 @@ void mpv_wait_async_requests(mpv_handle *ctx)
pthread_mutex_unlock(&ctx->lock);
}
-void mpv_detach_destroy(mpv_handle *ctx)
+static void get_thread(void *ptr)
+{
+ *(pthread_t *)ptr = pthread_self();
+}
+
+static void mp_destroy_client(mpv_handle *ctx, bool terminate)
{
if (!ctx)
return;
+ struct MPContext *mpctx = ctx->mpctx;
+ struct mp_client_api *clients = ctx->clients;
+
MP_VERBOSE(ctx, "Exiting...\n");
+ if (terminate)
+ mpv_command(ctx, (const char*[]){"quit", NULL});
+
// reserved_events equals the number of asynchronous requests that weren't
// yet replied. In order to avoid that trying to reply to a removed client
// causes a crash, block until all asynchronous requests were served.
mpv_wait_async_requests(ctx);
- osd_set_external(ctx->mpctx->osd, ctx, 0, 0, NULL);
- mp_input_remove_sections_by_owner(ctx->mpctx->input, ctx->name);
-
- struct mp_client_api *clients = ctx->clients;
+ osd_set_external(mpctx->osd, ctx, 0, 0, NULL);
+ mp_input_remove_sections_by_owner(mpctx->input, ctx->name);
pthread_mutex_lock(&clients->lock);
+
for (int n = 0; n < clients->num_clients; n++) {
if (clients->clients[n] == ctx) {
MP_TARRAY_REMOVE_AT(clients->clients, clients->num_clients, n);
@@ -409,76 +408,98 @@ void mpv_detach_destroy(mpv_handle *ctx)
}
talloc_free(ctx);
ctx = NULL;
- // shutdown_clients() sleeps to avoid wasting CPU.
- // mp_hook_test_completion() also relies on this a bit.
- mp_wakeup_core(clients->mpctx);
break;
}
}
- pthread_mutex_unlock(&clients->lock);
assert(!ctx);
+
+ if (mpctx->is_cli) {
+ terminate = false;
+ } else {
+ // If the last mpv_handle got destroyed, destroy the core.
+ if (clients->num_clients == 0)
+ terminate = true;
+
+ // Reserve the right to destroy mpctx for us.
+ if (clients->have_terminator)
+ terminate = false;
+ clients->have_terminator |= terminate;
+ }
+
+ // mp_shutdown_clients() sleeps to avoid wasting CPU.
+ // mp_hook_test_completion() also relies on this a bit.
+ mp_wakeup_core(mpctx);
+
+ pthread_mutex_unlock(&clients->lock);
+
+ // Note that even if num_clients==0, having set have_terminator keeps mpctx
+ // and the core thread alive.
+ if (terminate) {
+ // Make sure the core stops playing files etc. Being able to lock the
+ // dispatch queue requires that the core thread is still active.
+ mp_dispatch_lock(mpctx->dispatch);
+ mpctx->stop_play = PT_QUIT;
+ mp_dispatch_unlock(mpctx->dispatch);
+
+ // Stop the core thread.
+ pthread_mutex_lock(&clients->lock);
+ clients->terminate_core_thread = true;
+ pthread_mutex_unlock(&clients->lock);
+ mp_wakeup_core(mpctx);
+
+ // Blocking wait for all clients and core thread to terminate.
+ pthread_t playthread;
+ mp_dispatch_run(mpctx->dispatch, get_thread, &playthread);
+
+ pthread_join(playthread, NULL);
+
+ mp_destroy(mpctx);
+ }
}
-static void get_thread(void *ptr)
+void mpv_detach_destroy(mpv_handle *ctx)
{
- *(pthread_t *)ptr = pthread_self();
+ mp_destroy_client(ctx, false);
}
void mpv_terminate_destroy(mpv_handle *ctx)
{
- if (!ctx)
- return;
+ mp_destroy_client(ctx, true);
+}
- if (ctx->mpctx->initialized) {
- mpv_command(ctx, (const char*[]){"quit", NULL});
- } else {
- mp_dispatch_lock(ctx->mpctx->dispatch);
- ctx->mpctx->stop_play = PT_QUIT;
- mp_dispatch_unlock(ctx->mpctx->dispatch);
- }
+static bool can_terminate(struct MPContext *mpctx)
+{
+ struct mp_client_api *clients = mpctx->clients;
- if (!ctx->owner) {
- mpv_detach_destroy(ctx);
- return;
- }
+ pthread_mutex_lock(&clients->lock);
+ bool ok = clients->num_clients == 0 && mpctx->outstanding_async == 0 &&
+ (mpctx->is_cli || clients->terminate_core_thread);
+ pthread_mutex_unlock(&clients->lock);
- mp_dispatch_lock(ctx->mpctx->dispatch);
- assert(ctx->mpctx->autodetach);
- ctx->mpctx->autodetach = false;
- mp_dispatch_unlock(ctx->mpctx->dispatch);
+ return ok;
+}
- pthread_t playthread;
- mp_dispatch_run(ctx->mpctx->dispatch, get_thread, &playthread);
+// Can be called on the core thread only. Idempotent.
+void mp_shutdown_clients(struct MPContext *mpctx)
+{
+ struct mp_client_api *clients = mpctx->clients;
- mpv_detach_destroy(ctx);
+ // Prevent that new clients can appear.
+ pthread_mutex_lock(&clients->lock);
+ clients->shutting_down = true;
+ pthread_mutex_unlock(&clients->lock);
- // And this is also the reason why we only allow 1 thread (the owner) to
- // call this function.
- pthread_join(playthread, NULL);
+ while (!can_terminate(mpctx)) {
+ mp_client_broadcast_event(mpctx, MPV_EVENT_SHUTDOWN, NULL);
+ mp_wait_events(mpctx);
+ }
}
-static void *core_thread(void *tag)
+static void *core_thread(void *p)
{
- mpthread_set_name("mpv core");
+ struct MPContext *mpctx = p;
- mpv_handle *ctx = NULL;
- struct MPContext *mpctx = mp_create();
- if (mpctx) {
- mpctx->autodetach = true;
- ctx = mp_new_client(mpctx->clients, "main");
- if (ctx) {
- ctx->owner = true;
- ctx->fuzzy_initialized = true;
- m_config_set_profile(mpctx->mconfig, "libmpv", 0);
- } else {
- mp_destroy(mpctx);
- }
- }
-
- // Let mpv_create() return, and pass it the handle.
- mp_rendezvous(tag, (intptr_t)(void *)ctx);
- if (!ctx)
- return NULL;
+ mpthread_set_name("mpv core");
while (!mpctx->initialized && mpctx->stop_play != PT_QUIT)
mp_idle(mpctx);
@@ -487,24 +508,36 @@ static void *core_thread(void *tag)
mp_play_files(mpctx);
// This actually waits until all clients are gone before actually
- // destroying mpctx.
- mp_destroy(mpctx);
+ // destroying mpctx. Actual destruction is done by whatever destroys
+ // the last mpv_handle.
+ mp_shutdown_clients(mpctx);
return NULL;
}
mpv_handle *mpv_create(void)
{
- char tag;
- pthread_t thread;
- if (pthread_create(&thread, NULL, core_thread, &tag) != 0)
+ struct MPContext *mpctx = mp_create();
+ if (!mpctx)
return NULL;
- mpv_handle *res = (void *)mp_rendezvous(&tag, 0);
- if (!res)
- pthread_join(thread, NULL);
+ m_config_set_profile(mpctx->mconfig, "libmpv", 0);
- return res;
+ mpv_handle *ctx = mp_new_client(mpctx->clients, "main");
+ if (!ctx) {
+ mp_destroy(mpctx);
+ return NULL;
+ }
+
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, core_thread, mpctx) != 0) {
+ ctx->clients->have_terminator = true; // avoid blocking
+ mpv_terminate_destroy(ctx);
+ mp_destroy(mpctx);
+ return NULL;
+ }
+
+ return ctx;
}
mpv_handle *mpv_create_client(mpv_handle *ctx, const char *name)
@@ -517,19 +550,12 @@ mpv_handle *mpv_create_client(mpv_handle *ctx, const char *name)
return new;
}
-static void doinit(void *ctx)
-{
- void **args = ctx;
-
- *(int *)args[1] = mp_initialize(args[0], NULL);
-}
-
int mpv_initialize(mpv_handle *ctx)
{
- int res = 0;
- void *args[2] = {ctx->mpctx, &res};
- mp_dispatch_run(ctx->mpctx->dispatch, doinit, args);
- return res == 0 ? 0 : MPV_ERROR_INVALID_PARAMETER;
+ lock_core(ctx);
+ int res = mp_initialize(ctx->mpctx, NULL) ? MPV_ERROR_INVALID_PARAMETER : 0;
+ unlock_core(ctx);
+ return res;
}
// set ev->data to a new copy of the original data