This commit adds notifications for hot plugging of devices. It also extends the old behaviour of the `audio-out-detected-device` property which is now backed by the hotplugging code. This allows clients to be notified when the actual audio output device changes. Maybe hotplugging should be supported for ao_coreaudio_exclusive too, but it's device selection code is a bit fragile.
Unified · Split
| @@ -6,3 +6,8 @@ mp.observe_property("audio-device-list", "native", function(name, val) · 5 unmodified lines | |||
| 6 | print(" - '" .. e.name .. "' (" .. e.description .. ")") | 6 | print(" - '" .. e.name .. "' (" .. e.description .. ")") |
| 7 | end | 7 | end |
| 8 | end) | 8 | end) |
| 9 | |||
| 10 | mp.observe_property("audio-out-detected-device", "native", function(name, val) | ||
| 11 | print("Detected audio device changed:") | ||
| 12 | print(" - '" .. val) | ||
| 13 | end) | ||
| @@ -450,11 +450,6 @@ bool ao_untimed(struct ao *ao) · 449 unmodified lines | |||
| 450 | return ao->untimed; | 450 | return ao->untimed; |
| 451 | } | 451 | } |
| 452 | 452 | ||
| 453 | const char *ao_get_detected_device(struct ao *ao) | ||
| 454 | { | ||
| 455 | return ao->detected_device; | ||
| 456 | } | ||
| 457 | |||
| 458 | // --- | 453 | // --- |
| 459 | 454 | ||
| 460 | struct ao_hotplug { | 455 | struct ao_hotplug { |
| @@ -502,6 +497,11 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp) · 41 unmodified lines | |||
| 502 | return false; | 497 | return false; |
| 503 | } | 498 | } |
| 504 | 499 | ||
| 500 | const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp) | ||
| 501 | { | ||
| 502 | return hp->ao->detected_device; | ||
| 503 | } | ||
| 504 | |||
| 505 | // The return value is valid until the next call to this API. | 505 | // The return value is valid until the next call to this API. |
| 506 | struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp) | 506 | struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp) |
| 507 | { | 507 | { |
| @@ -80,7 +80,6 @@ void ao_uninit(struct ao *ao); · 79 unmodified lines | |||
| 80 | void ao_get_format(struct ao *ao, struct mp_audio *format); | 80 | void ao_get_format(struct ao *ao, struct mp_audio *format); |
| 81 | const char *ao_get_name(struct ao *ao); | 81 | const char *ao_get_name(struct ao *ao); |
| 82 | const char *ao_get_description(struct ao *ao); | 82 | const char *ao_get_description(struct ao *ao); |
| 83 | const char *ao_get_detected_device(struct ao *ao); | ||
| 84 | bool ao_untimed(struct ao *ao); | 83 | bool ao_untimed(struct ao *ao); |
| 85 | int ao_play(struct ao *ao, void **data, int samples, int flags); | 84 | int ao_play(struct ao *ao, void **data, int samples, int flags); |
| 86 | int ao_control(struct ao *ao, enum aocontrol cmd, void *arg); | 85 | int ao_control(struct ao *ao, enum aocontrol cmd, void *arg); |
| @@ -100,6 +99,7 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global, · 13 unmodified lines | |||
| 100 | struct input_ctx *input_ctx); | 99 | struct input_ctx *input_ctx); |
| 101 | void ao_hotplug_destroy(struct ao_hotplug *hp); | 100 | void ao_hotplug_destroy(struct ao_hotplug *hp); |
| 102 | bool ao_hotplug_check_update(struct ao_hotplug *hp); | 101 | bool ao_hotplug_check_update(struct ao_hotplug *hp); |
| 102 | const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp); | ||
| 103 | struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp); | 103 | struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp); |
| 104 | 104 | ||
| 105 | void ao_print_devices(struct mpv_global *global, struct mp_log *log); | 105 | void ao_print_devices(struct mpv_global *global, struct mp_log *log); |
| @@ -143,14 +143,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) · 142 unmodified lines | |||
| 143 | static bool init_chmap(struct ao *ao); | 143 | static bool init_chmap(struct ao *ao); |
| 144 | static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd); | 144 | static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd); |
| 145 | 145 | ||
| 146 | static int init(struct ao *ao) | 146 | static bool reinit_device(struct ao *ao) { |
| 147 | { | ||
| 148 | if (AF_FORMAT_IS_IEC61937(ao->format)) { | ||
| 149 | MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n"); | ||
| 150 | ao->redirect = "coreaudio_exclusive"; | ||
| 151 | return CONTROL_ERROR; | ||
| 152 | } | ||
| 153 | |||
| 154 | struct priv *p = ao->priv; | 147 | struct priv *p = ao->priv; |
| 155 | 148 | ||
| 156 | OSStatus err = ca_select_device(ao, ao->device, &p->device); | 149 | OSStatus err = ca_select_device(ao, ao->device, &p->device); |
| @@ -161,6 +154,23 @@ static int init(struct ao *ao) · 4 unmodified lines | |||
| 161 | CHECK_CA_ERROR("failed to get device UID"); | 154 | CHECK_CA_ERROR("failed to get device UID"); |
| 162 | ao->detected_device = talloc_steal(ao, uid); | 155 | ao->detected_device = talloc_steal(ao, uid); |
| 163 | 156 | ||
| 157 | return true; | ||
| 158 | |||
| 159 | coreaudio_error: | ||
| 160 | return false; | ||
| 161 | } | ||
| 162 | |||
| 163 | static int init(struct ao *ao) | ||
| 164 | { | ||
| 165 | if (AF_FORMAT_IS_IEC61937(ao->format)) { | ||
| 166 | MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n"); | ||
| 167 | ao->redirect = "coreaudio_exclusive"; | ||
| 168 | return CONTROL_ERROR; | ||
| 169 | } | ||
| 170 | |||
| 171 | if (!reinit_device(ao)) | ||
| 172 | goto coreaudio_error; | ||
| 173 | |||
| 164 | if (!init_chmap(ao)) | 174 | if (!init_chmap(ao)) |
| 165 | goto coreaudio_error; | 175 | goto coreaudio_error; |
| 166 | 176 | ||
| @@ -361,6 +371,67 @@ static void uninit(struct ao *ao) · 194 unmodified lines | |||
| 361 | AudioComponentInstanceDispose(p->audio_unit); | 371 | AudioComponentInstanceDispose(p->audio_unit); |
| 362 | } | 372 | } |
| 363 | 373 | ||
| 374 | static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, | ||
| 375 | const AudioObjectPropertyAddress addr[], | ||
| 376 | void *ctx) { | ||
| 377 | reinit_device(ctx); | ||
| 378 | ao_hotplug_event(ctx); | ||
| 379 | return noErr; | ||
| 380 | } | ||
| 381 | |||
| 382 | static uint32_t hotplug_properties[] = { | ||
| 383 | kAudioHardwarePropertyDevices, | ||
| 384 | kAudioHardwarePropertyDefaultOutputDevice | ||
| 385 | }; | ||
| 386 | |||
| 387 | static int hotplug_init(struct ao *ao) | ||
| 388 | { | ||
| 389 | if (!reinit_device(ao)) | ||
| 390 | goto coreaudio_error; | ||
| 391 | |||
| 392 | OSStatus err = noErr; | ||
| 393 | for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { | ||
| 394 | AudioObjectPropertyAddress addr = { | ||
| 395 | hotplug_properties[i], | ||
| 396 | kAudioObjectPropertyScopeGlobal, | ||
| 397 | kAudioObjectPropertyElementMaster | ||
| 398 | }; | ||
| 399 | err = AudioObjectAddPropertyListener( | ||
| 400 | kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao); | ||
| 401 | if (err != noErr) { | ||
| 402 | char *c1 = fourcc_repr(ao, hotplug_properties[i]); | ||
| 403 | char *c2 = fourcc_repr(ao, err); | ||
| 404 | MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2); | ||
| 405 | goto coreaudio_error; | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | return 0; | ||
| 410 | |||
| 411 | coreaudio_error: | ||
| 412 | return -1; | ||
| 413 | } | ||
| 414 | |||
| 415 | static void hotplug_uninit(struct ao *ao) | ||
| 416 | { | ||
| 417 | OSStatus err = noErr; | ||
| 418 | for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) { | ||
| 419 | AudioObjectPropertyAddress addr = { | ||
| 420 | hotplug_properties[i], | ||
| 421 | kAudioObjectPropertyScopeGlobal, | ||
| 422 | kAudioObjectPropertyElementMaster | ||
| 423 | }; | ||
| 424 | err = AudioObjectRemovePropertyListener( | ||
| 425 | kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao); | ||
| 426 | if (err != noErr) { | ||
| 427 | char *c1 = fourcc_repr(ao, hotplug_properties[i]); | ||
| 428 | char *c2 = fourcc_repr(ao, err); | ||
| 429 | MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2); | ||
| 430 | } | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | |||
| 364 | // Channel Mapping functions | 435 | // Channel Mapping functions |
| 365 | static const int speaker_map[][2] = { | 436 | static const int speaker_map[][2] = { |
| 366 | { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL }, | 437 | { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL }, |
| @@ -513,13 +584,15 @@ coreaudio_error: · 146 unmodified lines | |||
| 513 | #define OPT_BASE_STRUCT struct priv | 584 | #define OPT_BASE_STRUCT struct priv |
| 514 | 585 | ||
| 515 | const struct ao_driver audio_out_coreaudio = { | 586 | const struct ao_driver audio_out_coreaudio = { |
| 516 | .description = "CoreAudio AudioUnit", | 587 | .description = "CoreAudio AudioUnit", |
| 517 | .name = "coreaudio", | 588 | .name = "coreaudio", |
| 518 | .uninit = uninit, | 589 | .uninit = uninit, |
| 519 | .init = init, | 590 | .init = init, |
| 520 | .control = control, | 591 | .control = control, |
| 521 | .pause = stop, | 592 | .pause = stop, |
| 522 | .resume = start, | 593 | .resume = start, |
| 523 | .list_devs = ca_get_device_list, | 594 | .hotplug_init = hotplug_init, |
| 524 | .priv_size = sizeof(struct priv), | 595 | .hotplug_uninit = hotplug_uninit, |
| 596 | .list_devs = ca_get_device_list, | ||
| 597 | .priv_size = sizeof(struct priv), | ||
| 525 | }; | 598 | }; |
| @@ -1578,10 +1578,12 @@ static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg · 1577 unmodified lines | |||
| 1578 | static int mp_property_ao_detected_device(void *ctx,struct m_property *prop, | 1578 | static int mp_property_ao_detected_device(void *ctx,struct m_property *prop, |
| 1579 | int action, void *arg) | 1579 | int action, void *arg) |
| 1580 | { | 1580 | { |
| 1581 | MPContext *mpctx = ctx; | 1581 | struct MPContext *mpctx = ctx; |
| 1582 | struct command_ctx *cmd = mpctx->command_ctx; | ||
| 1582 | if (!mpctx->ao) | 1583 | if (!mpctx->ao) |
| 1583 | return M_PROPERTY_UNAVAILABLE; | 1584 | return M_PROPERTY_UNAVAILABLE; |
| 1584 | return m_property_strdup_ro(action, arg, ao_get_detected_device(mpctx->ao)); | 1585 | const char *d = ao_hotplug_get_detected_device(cmd->hotplug); |
| 1586 | return m_property_strdup_ro(action, arg, d); | ||
| 1585 | } | 1587 | } |
| 1586 | 1588 | ||
| 1587 | /// Audio delay (RW) | 1589 | /// Audio delay (RW) |
| @@ -3524,7 +3526,7 @@ static const char *const *const mp_event_property_change[] = { · 1936 unmodified lines | |||
| 3524 | "detected-hwdec"), | 3526 | "detected-hwdec"), |
| 3525 | E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate", | 3527 | E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate", |
| 3526 | "samplerate", "channels", "audio", "volume", "mute", "balance", | 3528 | "samplerate", "channels", "audio", "volume", "mute", "balance", |
| 3527 | "volume-restore-data", "current-ao", "audio-out-detected-device"), | 3529 | "volume-restore-data", "current-ao"), |
| 3528 | E(MPV_EVENT_SEEK, "seeking", "core-idle"), | 3530 | E(MPV_EVENT_SEEK, "seeking", "core-idle"), |
| 3529 | E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle"), | 3531 | E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle"), |
| 3530 | E(MPV_EVENT_METADATA_UPDATE, "metadata", "filtered-metadata"), | 3532 | E(MPV_EVENT_METADATA_UPDATE, "metadata", "filtered-metadata"), |
| @@ -3534,6 +3536,7 @@ static const char *const *const mp_event_property_change[] = { · 3 unmodified lines | |||
| 3534 | E(MP_EVENT_WIN_RESIZE, "window-scale"), | 3536 | E(MP_EVENT_WIN_RESIZE, "window-scale"), |
| 3535 | E(MP_EVENT_WIN_STATE, "window-minimized", "display-names"), | 3537 | E(MP_EVENT_WIN_STATE, "window-minimized", "display-names"), |
| 3536 | E(MP_EVENT_AUDIO_DEVICES, "audio-device-list"), | 3538 | E(MP_EVENT_AUDIO_DEVICES, "audio-device-list"), |
| 3539 | E(MP_EVENT_DETECTED_AUDIO_DEVICE, "audio-out-detected-device"), | ||
| 3537 | }; | 3540 | }; |
| 3538 | #undef E | 3541 | #undef E |
| 3539 | 3542 | ||
| @@ -4875,8 +4878,10 @@ static void command_event(struct MPContext *mpctx, int event, void *arg) · 1335 unmodified lines | |||
| 4875 | 4878 | ||
| 4876 | // This is a bit messy: ao_hotplug wakes up the player, and then we have | 4879 | // This is a bit messy: ao_hotplug wakes up the player, and then we have |
| 4877 | // to recheck the state. Then the client(s) will read the property. | 4880 | // to recheck the state. Then the client(s) will read the property. |
| 4878 | if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug)) | 4881 | if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug)) { |
| 4879 | mp_notify_property(mpctx, "audio-device-list"); | 4882 | mp_notify_property(mpctx, "audio-device-list"); |
| 4883 | mp_notify_property(mpctx, "audio-out-detected-device"); | ||
| 4884 | } | ||
| 4880 | } | 4885 | } |
| 4881 | 4886 | ||
| 4882 | void mp_notify(struct MPContext *mpctx, int event, void *arg) | 4887 | void mp_notify(struct MPContext *mpctx, int event, void *arg) |
| @@ -48,6 +48,7 @@ enum { · 47 unmodified lines | |||
| 48 | MP_EVENT_WIN_RESIZE, | 48 | MP_EVENT_WIN_RESIZE, |
| 49 | MP_EVENT_WIN_STATE, | 49 | MP_EVENT_WIN_STATE, |
| 50 | MP_EVENT_AUDIO_DEVICES, | 50 | MP_EVENT_AUDIO_DEVICES, |
| 51 | MP_EVENT_DETECTED_AUDIO_DEVICE, | ||
| 51 | }; | 52 | }; |
| 52 | 53 | ||
| 53 | bool mp_hook_test_completion(struct MPContext *mpctx, char *type); | 54 | bool mp_hook_test_completion(struct MPContext *mpctx, char *type); |