summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/input.rst7
-rw-r--r--TOOLS/lua/README.md2
-rw-r--r--TOOLS/lua/audio-hotplug-test.lua8
-rw-r--r--audio/out/ao.c112
-rw-r--r--audio/out/ao.h10
-rw-r--r--audio/out/internal.h23
-rw-r--r--player/command.c21
-rw-r--r--player/command.h1
8 files changed, 151 insertions, 33 deletions
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 954520147a..f2efd080b8 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -1640,6 +1640,13 @@ Property list
human readable free form text. The description is an empty string if none
was received.
+ The special entry with the name set to ``auto`` selects the default audio
+ output driver and the default device.
+
+ The property can be watched with the property observation mechanism in
+ the client API and in Lua scripts. (Technically, change notification is
+ enabled the first time this property is read.)
+
``audio-device`` (RW)
Set the audio device. This directly reads/writes the ``--audio-device``
option, but on write accesses, the audio output will be scheduled for
diff --git a/TOOLS/lua/README.md b/TOOLS/lua/README.md
index f7cdf2a590..e9044823cd 100644
--- a/TOOLS/lua/README.md
+++ b/TOOLS/lua/README.md
@@ -10,3 +10,5 @@ to mpv's command line.
Where appropriate, they may also be placed in ~/.config/mpv/scripts/ from
where they will be automatically loaded when mpv starts.
+
+Some of these are just for testing mpv internals.
diff --git a/TOOLS/lua/audio-hotplug-test.lua b/TOOLS/lua/audio-hotplug-test.lua
new file mode 100644
index 0000000000..8dedc68cbe
--- /dev/null
+++ b/TOOLS/lua/audio-hotplug-test.lua
@@ -0,0 +1,8 @@
+local utils = require("mp.utils")
+
+mp.observe_property("audio-device-list", "native", function(name, val)
+ print("Audio device list changed:")
+ for index, e in ipairs(val) do
+ print(" - '" .. e.name .. "' (" .. e.description .. ")")
+ end
+end)
diff --git a/audio/out/ao.c b/audio/out/ao.c
index ccb39f7f9e..a2fa2fb104 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -386,14 +386,25 @@ int ao_query_and_reset_events(struct ao *ao, int events)
int actual_events = 0;
if (atomic_load(&ao->request_reload)) // don't need to reset it
actual_events |= AO_EVENT_RELOAD;
+ if (atomic_load(&ao->request_hotplug))
+ actual_events |= AO_EVENT_HOTPLUG;
return actual_events & events;
}
-// Request that the player core destroys and recreates the AO.
+// Request that the player core destroys and recreates the AO. Fully thread-safe.
void ao_request_reload(struct ao *ao)
{
atomic_store(&ao->request_reload, true);
- mp_input_wakeup(ao->input_ctx);
+ if (ao->input_ctx)
+ mp_input_wakeup(ao->input_ctx);
+}
+
+// Notify the player that the device list changed. Fully thread-safe.
+void ao_hotplug_event(struct ao *ao)
+{
+ atomic_store(&ao->request_hotplug, true);
+ if (ao->input_ctx)
+ mp_input_wakeup(ao->input_ctx);
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
@@ -444,26 +455,88 @@ const char *ao_get_detected_device(struct ao *ao)
return ao->detected_device;
}
-struct ao_device_list *ao_get_device_list(struct mpv_global *global)
+// ---
+
+struct ao_hotplug {
+ struct mpv_global *global;
+ struct input_ctx *input_ctx;
+ // A single AO instance is used to listen to hotplug events. It wouldn't
+ // make much sense to allow multiple AO drivers; all sane platforms have
+ // a single such audio API.
+ // This is _not_ the same AO instance as used for playing audio.
+ struct ao *ao;
+ // cached
+ struct ao_device_list *list;
+ bool needs_update;
+};
+
+struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
+ struct input_ctx *input_ctx)
+{
+ struct ao_hotplug *hp = talloc_ptrtype(NULL, hp);
+ *hp = (struct ao_hotplug){
+ .global = global,
+ .input_ctx = input_ctx,
+ .needs_update = true,
+ };
+ return hp;
+}
+
+static void get_devices(struct ao *ao, struct ao_device_list *list)
+{
+ int num = list->num_devices;
+ if (ao->driver->list_devs)
+ ao->driver->list_devs(ao, list);
+ // Add at least a default entry
+ if (list->num_devices == num)
+ ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
+}
+
+bool ao_hotplug_check_update(struct ao_hotplug *hp)
+{
+ if (hp->ao && ao_query_and_reset_events(hp->ao, AO_EVENT_HOTPLUG)) {
+ hp->needs_update = true;
+ atomic_store(&hp->ao->request_hotplug, false);
+ return true;
+ }
+ return false;
+}
+
+// The return value is valid until the next call to this API.
+struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
{
- struct ao_device_list *list = talloc_zero(NULL, struct ao_device_list);
+ if (hp->list && !hp->needs_update)
+ return hp->list;
+
+ talloc_free(hp->list);
+ struct ao_device_list *list = talloc_zero(hp, struct ao_device_list);
+ hp->list = list;
+
MP_TARRAY_APPEND(list, list->devices, list->num_devices,
(struct ao_device_desc){"auto", "Autoselect device"});
+
for (int n = 0; audio_out_drivers[n]; n++) {
const struct ao_driver *d = audio_out_drivers[n];
if (d == &audio_out_null)
break; // don't add unsafe/special entries
- struct ao *ao = ao_alloc(true, global, NULL, (char *)d->name, NULL);
+
+ struct ao *ao = ao_alloc(true, hp->global, hp->input_ctx,
+ (char *)d->name, NULL);
if (!ao)
continue;
- int num = list->num_devices;
- if (d->list_devs)
- d->list_devs(ao, list);
- // Add at least a default entry
- if (list->num_devices == num)
- ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
- talloc_free(ao);
+
+ if (ao->driver->hotplug_init) {
+ if (!hp->ao && ao->driver->hotplug_init(ao) >= 0)
+ hp->ao = ao; // keep this one
+ if (hp->ao && hp->ao->driver == d)
+ get_devices(hp->ao, list);
+ } else {
+ get_devices(ao, list);
+ }
+ if (ao != hp->ao)
+ talloc_free(ao);
}
+ hp->needs_update = false;
return list;
}
@@ -478,13 +551,24 @@ void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
MP_TARRAY_APPEND(list, list->devices, list->num_devices, c);
}
+void ao_hotplug_destroy(struct ao_hotplug *hp)
+{
+ if (!hp)
+ return;
+ if (hp->ao && hp->ao->driver->hotplug_uninit)
+ hp->ao->driver->hotplug_uninit(hp->ao);
+ talloc_free(hp->ao);
+ talloc_free(hp);
+}
+
void ao_print_devices(struct mpv_global *global, struct mp_log *log)
{
- struct ao_device_list *list = ao_get_device_list(global);
+ struct ao_hotplug *hp = ao_hotplug_create(global, NULL);
+ struct ao_device_list *list = ao_hotplug_get_device_list(hp);
mp_info(log, "List of detected audio devices:\n");
for (int n = 0; n < list->num_devices; n++) {
struct ao_device_desc *desc = &list->devices[n];
mp_info(log, " '%s' (%s)\n", desc->name, desc->desc);
}
- talloc_free(list);
+ ao_hotplug_destroy(hp);
}
diff --git a/audio/out/ao.h b/audio/out/ao.h
index dbbed24873..7b85ec80ba 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -48,6 +48,7 @@ enum aocontrol {
enum {
AO_EVENT_RELOAD = 1,
+ AO_EVENT_HOTPLUG = 2,
};
typedef struct ao_control_vol {
@@ -92,8 +93,15 @@ void ao_drain(struct ao *ao);
bool ao_eof_reached(struct ao *ao);
int ao_query_and_reset_events(struct ao *ao, int events);
void ao_request_reload(struct ao *ao);
+void ao_hotplug_event(struct ao *ao);
+
+struct ao_hotplug;
+struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
+ struct input_ctx *input_ctx);
+void ao_hotplug_destroy(struct ao_hotplug *hp);
+bool ao_hotplug_check_update(struct ao_hotplug *hp);
+struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp);
-struct ao_device_list *ao_get_device_list(struct mpv_global *global);
void ao_print_devices(struct mpv_global *global, struct mp_log *log);
#endif /* MPLAYER_AUDIO_OUT_H */
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 9414208923..19402861a2 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -59,7 +59,8 @@ struct ao {
// Used during init: if init fails, redirect to this ao
char *redirect;
- atomic_bool request_reload;
+ // Internal events (use ao_request_reload(), ao_hotplug_event())
+ atomic_bool request_reload, request_hotplug;
int buffer;
double def_buffer;
@@ -161,17 +162,19 @@ struct ao_driver {
// Return the list of devices currently available in the system. Use
// ao_device_list_add() to add entries. The selected device will be set as
// ao->device (using ao_device_desc.name).
- // Warning: the ao struct passed doesn't necessarily have ao_driver->init()
- // called on it - in this case, ->uninit() won't be called either
- // after this function. The idea is that list_devs can be called
- // both when no audio or when audio is active. the latter can
- // happen if the audio config change at runtime, and in this case
- // we don't want to force a new connection to the audio server
- // just to update the device list. For runtime updates, ->init()
- // will have been called. In both cases, ao->priv is properly
- // allocated. (Runtime updates are not used/supported yet.)
+ // Warning: the ao struct passed is not initialized with ao_driver->init().
+ // Instead, hotplug_init/hotplug_uninit is called. If these
+ // callbacks are not set, no driver initialization call is done
+ // on the ao struct.
void (*list_devs)(struct ao *ao, struct ao_device_list *list);
+ // If set, these are called before/after ao_driver->list_devs is called.
+ // It is also assumed that the driver can do hotplugging - which means
+ // it is expected to call ao_hotplug_event(ao) whenever the system's
+ // audio device list changes. The player will then call list_devs() again.
+ int (*hotplug_init)(struct ao *ao);
+ void (*hotplug_uninit)(struct ao *ao);
+
// For option parsing (see vo.h)
int priv_size;
const void *priv_defaults;
diff --git a/player/command.c b/player/command.c
index f20e6050b4..07a9206b53 100644
--- a/player/command.c
+++ b/player/command.c
@@ -90,7 +90,7 @@ struct command_ctx {
int num_hooks;
int64_t hook_seq; // for hook_handler.seq
- struct ao_device_list *cached_ao_devices;
+ struct ao_hotplug *hotplug;
};
struct overlay {
@@ -1560,14 +1560,12 @@ static int mp_property_audio_devices(void *ctx, struct m_property *prop,
{
struct MPContext *mpctx = ctx;
struct command_ctx *cmd = mpctx->command_ctx;
- if (!cmd->cached_ao_devices)
- cmd->cached_ao_devices = ao_get_device_list(mpctx->global);
- if (!cmd->cached_ao_devices)
- return M_PROPERTY_ERROR;
- talloc_steal(cmd, cmd->cached_ao_devices);
+ if (!cmd->hotplug)
+ cmd->hotplug = ao_hotplug_create(mpctx->global, mpctx->input);
- return m_property_read_list(action, arg, cmd->cached_ao_devices->num_devices,
- get_device_entry, cmd->cached_ao_devices);
+ struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug);
+ return m_property_read_list(action, arg, list->num_devices,
+ get_device_entry, list);
}
static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg)
@@ -3535,6 +3533,7 @@ static const char *const *const mp_event_property_change[] = {
"demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache"),
E(MP_EVENT_WIN_RESIZE, "window-scale"),
E(MP_EVENT_WIN_STATE, "window-minimized", "display-names"),
+ E(MP_EVENT_AUDIO_DEVICES, "audio-device-list"),
};
#undef E
@@ -4819,6 +4818,7 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
void command_uninit(struct MPContext *mpctx)
{
overlay_uninit(mpctx);
+ ao_hotplug_destroy(mpctx->command_ctx->hotplug);
talloc_free(mpctx->command_ctx);
mpctx->command_ctx = NULL;
}
@@ -4868,6 +4868,11 @@ static void command_event(struct MPContext *mpctx, int event, void *arg)
// Update chapters - does nothing if something else is visible.
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
}
+
+ // This is a bit messy: ao_hotplug wakes up the player, and then we have
+ // to recheck the state. Then the client(s) will read the property.
+ if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug))
+ mp_notify_property(mpctx, "audio-device-list");
}
void mp_notify(struct MPContext *mpctx, int event, void *arg)
diff --git a/player/command.h b/player/command.h
index 30387946d3..96a58b1f80 100644
--- a/player/command.h
+++ b/player/command.h
@@ -47,6 +47,7 @@ enum {
MP_EVENT_CACHE_UPDATE,
MP_EVENT_WIN_RESIZE,
MP_EVENT_WIN_STATE,
+ MP_EVENT_AUDIO_DEVICES,
};
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);