Реалізація автоматичного перемикання аудіо між пристроями

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація автоматичного перемикання аудіо між пристроями
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація автоматичного переключення аудіо між пристроями

Підключив AirPods — аудіо повинно перейти на них. Відсоединив — повернутися на динамік. Підключив Bluetooth-гарнітуру під час дзвінка — дзвінок не повинен переривуватися. Звучит як базовий OS-функціонал, але в продакшні без явної обробки подій AVAudioSession все це працює непередбачувано.

Як iOS управляє аудіо-маршрутизацією

AVAudioSession — центральний об'єкт. За замовчуванням iOS сама переключає вихідний пристрій при зміні маршруту. Проблема в тому, що додаток може не знати про це, й поточний AVAudioPlayer або AVAudioEngine продовжує працювати на «старому» маршруті до наступної операції відтворення.

Для явного управління — AVAudioSession.routeChangeNotification:

NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleRouteChange(_:)),
    name: AVAudioSession.routeChangeNotification,
    object: nil
)

@objc func handleRouteChange(_ notification: Notification) {
    guard let info = notification.userInfo,
          let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
          let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue)
    else { return }

    switch reason {
    case .newDeviceAvailable:
        // AirPods підключилися — переключаємось на них
        resumePlaybackIfNeeded()
    case .oldDeviceUnavailable:
        // Навушниці відключилися — пауза або перехід на динамік
        if let previousRoute = info[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
            let wasHeadphones = previousRoute.outputs.contains {
                $0.portType == .headphones || $0.portType == .bluetoothA2DP
            }
            if wasHeadphones { pausePlayback() }
        }
    case .categoryChange:
        reconfigureEngine()
    default: break
    }
}

oldDeviceUnavailable з паузою — стандартне поведення, якого очікують користувачі (Spotify, Apple Music). Без паузи аудіо продовжує грати в динамік після випадкового відключення навушників.

AirPods й автоматичне переключення між пристроями

AirPods Pro/Max підтримують Automatic Switching — перехід між iPhone, iPad, Mac. Додаток не може управляти цим переключенням, але може реагувати на його наслідки. При переключенні AirPods між пристроями додаток отримує routeChangeNotification з reason override або categoryChange.

Тонкість: після смени маршруту AVAudioSession.currentRoute оновлюється не мгновенно. На oldDeviceUnavailable ще 50–100ms маршрут показує старий пристрій. Потрібен короткий Task.sleep(nanoseconds: 100_000_000) або перевірка на наступному runloop-цикліку.

AVAudioEngine: пересборка графа при смені маршруту

Якщо додаток використовує AVAudioEngine з ефектами (еквалайзер, реверб), смена маршруту може скинути сесію. Ознака — AVAudioEngine.isRunning повертає false після routeChangeNotification.

Правильний паттерн: підписуємось на AVAudioEngineConfigurationChange й переключаємо граф:

NotificationCenter.default.addObserver(
    forName: .AVAudioEngineConfigurationChange,
    object: audioEngine, queue: .main
) { [weak self] _ in
    self?.rebuildAudioGraph()
    try? self?.audioEngine.start()
}

rebuildAudioGraph() — відсоединяємо всі ноди, міняємо outputNode (який тепер вказує на новий пристрій), підключаємо заново. Без цього кроку AVAudioPlayerNode продовжує відтворювати, але без аудіо — тихо, без помилок у логах.

Android: AudioManager й AudioDeviceCallback

На Android управління маршрутизацією через AudioManager.AudioDeviceCallback:

val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager

audioManager.registerAudioDeviceCallback(object : AudioDeviceCallback() {
    override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
        val bluetooth = addedDevices.firstOrNull {
            it.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
            it.type == AudioDeviceInfo.TYPE_BLE_HEADSET
        }
        bluetooth?.let { switchToDevice(it) }
    }

    override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
        pauseIfHeadphonesRemoved(removedDevices)
    }
}, Handler(Looper.getMainLooper()))

AudioManager.setPreferredDevice() (API 28+) дозволяє принудово вибрати пристрій. На Android 12+ з'явився setCommunicationDevice() спеціально для звонків — не плутайте з звичайним відтворенням.

Типічні помилки

  • Не викликайте AVAudioSession.setActive(true) після reconfig — отримаєте тихе відтворення без помилки
  • Не обробляйте categoryChange при вхідному дзвінку — телефонний дзвінок міняє категорію сесії, після завершення потрібно її відновити
  • Міняйте маршрут на фоновому потоці — AVAudioSession-операції повинні бути на main thread або явно синхронізовані

Терміни

Базова обробка смени маршруту (iOS): 3–5 днів. Повна реалізація з AVAudioEngine, підтримкою Android, обробкою всіх сценаріїв (дзвінки, BT-гарнітури, AirPods): 2–3 тижні. Вартість розраховується індивідуально.