From e64090d88f7860a3a14ca0663506990deaf31381 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Sat, 20 Dec 2025 20:19:08 +0800 Subject: [PATCH] feat: ability to resume mic redirection after a phone call --- VoidLink/Input/InertialScroller.swift | 2 +- VoidLink/Input/MicHandler.swift | 38 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/VoidLink/Input/InertialScroller.swift b/VoidLink/Input/InertialScroller.swift index 8f037679..a0427ad7 100644 --- a/VoidLink/Input/InertialScroller.swift +++ b/VoidLink/Input/InertialScroller.swift @@ -2,7 +2,7 @@ // InertialScroller.swift // VoidLink // -// Created by True砖家 on 2025/11/5. +// Created by True砖家 on 2025/12/5. // Copyright © 2025 True砖家 on Bilibili. All rights reserved. // diff --git a/VoidLink/Input/MicHandler.swift b/VoidLink/Input/MicHandler.swift index 8bb33ca0..4d82c57b 100644 --- a/VoidLink/Input/MicHandler.swift +++ b/VoidLink/Input/MicHandler.swift @@ -16,6 +16,7 @@ import Collections @objcMembers public class MicHandler: NSObject { + private var notificationTokens = [NSObjectProtocol]() private let engine = AVAudioEngine() private let playerNode = AVAudioPlayerNode() private var audioSink: Any? @@ -48,6 +49,16 @@ public class MicHandler: NSObject { @objc public init(useBuiltinMic:Bool) { super.init() + + let token = NotificationCenter.default.addObserver( + forName: AVAudioSession.interruptionNotification, + object: nil, + queue: .main + ) { [weak self] notification in + self?.handleInterruption(notification) + } + notificationTokens.append(token) + self.useBuiltinMic = useBuiltinMic do { try configureSession() @@ -140,6 +151,29 @@ public class MicHandler: NSObject { /* ----------- Audio Session -------------*/ + private func handleInterruption(_ notification: Notification) { + guard let info = notification.userInfo, + let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) + else { return } + + switch type { + case .began: + self.stopTapping(stopEngine: true) + case .ended: + do { + try configureEngine() + } catch { + notify(error) + } + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + self.startTapping() + } + @unknown default: + break + } + } + private func configureOpus(sampleRate: Int32, channels: Int) throws { var err: Int32 = 0 guard let enc = opus_encoder_create(sampleRate, Int32(channels), OPUS_APPLICATION_VOIP, &err), err == OPUS_OK else { @@ -336,6 +370,10 @@ public class MicHandler: NSObject { } @objc public func clean() { + for token in notificationTokens { + NotificationCenter.default.removeObserver(token) + } + notificationTokens.removeAll() self.timer?.clean() }