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 | 6 | print(" - '" .. e.name .. "' (" .. e.description .. ")") |
| 7 | 7 | end |
| 8 | 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 | 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 | 455 | struct ao_hotplug { |
| @@ -502,6 +497,11 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp) · 41 unmodified lines | ||
| 502 | 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 | 505 | // The return value is valid until the next call to this API. |
| 506 | 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 | 80 | void ao_get_format(struct ao *ao, struct mp_audio *format); |
| 81 | 81 | const char *ao_get_name(struct ao *ao); |
| 82 | 82 | const char *ao_get_description(struct ao *ao); |
| 83 | −const char *ao_get_detected_device(struct ao *ao); | |
| 84 | 83 | bool ao_untimed(struct ao *ao); |
| 85 | 84 | int ao_play(struct ao *ao, void **data, int samples, int flags); |
| 86 | 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 | 99 | struct input_ctx *input_ctx); |
| 101 | 100 | void ao_hotplug_destroy(struct ao_hotplug *hp); |
| 102 | 101 | bool ao_hotplug_check_update(struct ao_hotplug *hp); |
| 102 | +const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp); | |
| 103 | 103 | struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp); |
| 104 | 104 | |
| 105 | 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 | 143 | static bool init_chmap(struct ao *ao); |
| 144 | 144 | static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd); |
| 145 | 145 | |
| 146 | −static int init(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 | − | |
| 146 | +static bool reinit_device(struct ao *ao) { | |
| 154 | 147 | struct priv *p = ao->priv; |
| 155 | 148 | |
| 156 | 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 | 154 | CHECK_CA_ERROR("failed to get device UID"); |
| 162 | 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 | 174 | if (!init_chmap(ao)) |
| 165 | 175 | goto coreaudio_error; |
| 166 | 176 | |
| @@ -361,6 +371,67 @@ static void uninit(struct ao *ao) · 194 unmodified lines | ||
| 361 | 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 | 435 | // Channel Mapping functions |
| 365 | 436 | static const int speaker_map[][2] = { |
| 366 | 437 | { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL }, |
| @@ -513,13 +584,15 @@ coreaudio_error: · 146 unmodified lines | ||
| 513 | 584 | #define OPT_BASE_STRUCT struct priv |
| 514 | 585 | |
| 515 | 586 | const struct ao_driver audio_out_coreaudio = { |
| 516 | − .description = "CoreAudio AudioUnit", | |
| 517 | − .name = "coreaudio", | |
| 518 | − .uninit = uninit, | |
| 519 | − .init = init, | |
| 520 | − .control = control, | |
| 521 | − .pause = stop, | |
| 522 | − .resume = start, | |
| 523 | − .list_devs = ca_get_device_list, | |
| 524 | − .priv_size = sizeof(struct priv), | |
| 587 | + .description = "CoreAudio AudioUnit", | |
| 588 | + .name = "coreaudio", | |
| 589 | + .uninit = uninit, | |
| 590 | + .init = init, | |
| 591 | + .control = control, | |
| 592 | + .pause = stop, | |
| 593 | + .resume = start, | |
| 594 | + .hotplug_init = hotplug_init, | |
| 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 | 1578 | static int mp_property_ao_detected_device(void *ctx,struct m_property *prop, |
| 1579 | 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 | 1583 | if (!mpctx->ao) |
| 1583 | 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 | 1589 | /// Audio delay (RW) |
| @@ -3524,7 +3526,7 @@ static const char *const *const mp_event_property_change[] = { · 1936 unmodified lines | ||
| 3524 | 3526 | "detected-hwdec"), |
| 3525 | 3527 | E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate", |
| 3526 | 3528 | "samplerate", "channels", "audio", "volume", "mute", "balance", |
| 3527 | − "volume-restore-data", "current-ao", "audio-out-detected-device"), | |
| 3529 | + "volume-restore-data", "current-ao"), | |
| 3528 | 3530 | E(MPV_EVENT_SEEK, "seeking", "core-idle"), |
| 3529 | 3531 | E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle"), |
| 3530 | 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 | 3536 | E(MP_EVENT_WIN_RESIZE, "window-scale"), |
| 3535 | 3537 | E(MP_EVENT_WIN_STATE, "window-minimized", "display-names"), |
| 3536 | 3538 | E(MP_EVENT_AUDIO_DEVICES, "audio-device-list"), |
| 3539 | + E(MP_EVENT_DETECTED_AUDIO_DEVICE, "audio-out-detected-device"), | |
| 3537 | 3540 | }; |
| 3538 | 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 | 4879 | // This is a bit messy: ao_hotplug wakes up the player, and then we have |
| 4877 | 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 | 4882 | mp_notify_property(mpctx, "audio-device-list"); |
| 4883 | + mp_notify_property(mpctx, "audio-out-detected-device"); | |
| 4884 | + } | |
| 4880 | 4885 | } |
| 4881 | 4886 | |
| 4882 | 4887 | void mp_notify(struct MPContext *mpctx, int event, void *arg) |
| @@ -48,6 +48,7 @@ enum { · 47 unmodified lines | ||
| 48 | 48 | MP_EVENT_WIN_RESIZE, |
| 49 | 49 | MP_EVENT_WIN_STATE, |
| 50 | 50 | MP_EVENT_AUDIO_DEVICES, |
| 51 | + MP_EVENT_DETECTED_AUDIO_DEVICE, | |
| 51 | 52 | }; |
| 52 | 53 | |
| 53 | 54 | bool mp_hook_test_completion(struct MPContext *mpctx, char *type); |