Audiounterbrechungen und Ausgabewechsel unter iOS testen

Audiounterbrechungen und Ausgabewechsel unter iOS testen

Der Player besteht alle automatisierten Tests auf einem Mac in der Cloud. Zieht jemand danach die Kopfhörer ab, läuft die Wiedergabe trotzdem über die Lautsprecher weiter. Das Problem liegt möglicherweise nicht am Decoder: Was nach einer Änderung des Ausgabepfads geschehen soll, wurde nicht gesondert getestet. Bei einer Audiounterbrechung durch das System gilt Ähnliches: Ihr Ende bedeutet nicht, dass die Wiedergabe gewünscht ist. Erst wenn Systembenachrichtigungen und Wiedergabeentscheidungen getrennt sind, lässt sich diese Logik auch auf einem Build-Knoten ohne Kopfhörer zuverlässig prüfen.

Ereignisse und erwartetes Verhalten festlegen

Definieren Sie zuerst die Produktregeln und implementieren Sie danach die Verarbeitung der Benachrichtigungen. Hier gelten vorsichtige Regeln: Eine Unterbrechung während der Wiedergabe pausiert den Player und hält fest, dass zuvor etwas abgespielt wurde. Die Wiedergabe wird nur fortgesetzt, wenn die Benachrichtigung über das Ende der Unterbrechung dies erlaubt. Pausiert jemand während der Unterbrechung selbst, muss die zuvor vorgesehene Fortsetzung entfallen. Wird das Ausgabegerät getrennt, wird unmittelbar pausiert. Aus dem Namen des neuen Ausgabepfads allein lässt sich nicht ableiten, ob die Wiedergabe über die Lautsprecher erwünscht ist.

Eingehendes Ereignis Wiedergabestatus Fortsetzungsabsicht
Unterbrechung beginnt während der Wiedergabe Pausiert Festhalten, dass zuvor etwas abgespielt wurde
Unterbrechung endet und Fortsetzung ist erlaubt Gemäß Fortsetzungsabsicht entscheiden Löschen
Wiedergabe wird manuell pausiert Pausiert Löschen
Bisheriges Ausgabegerät ist nicht mehr verfügbar Pausiert Löschen

„Bisheriges Ausgabegerät ist nicht mehr verfügbar“ bezeichnet einen Ereignisgrund, nicht jede Änderung des Ausgabepfads. Beim Wechsel zu einem anderen bereits verbundenen Gerät sollte etwa nicht ohne entsprechende Produktentscheidung die Regel für abgezogene Kopfhörer gelten.

Wiedergabeentscheidungen in einer reinen Zustandsmaschine isolieren

Die Benachrichtigungen werden in einer Adapterschicht ausgewertet; die Kernlogik empfängt nur eindeutig definierte Ereignisse. Die folgenden Typen können im App-Target liegen und vom Test-Target importiert werden. isPlaying beschreibt den von der App angestrebten Wiedergabestatus. Den tatsächlichen Aufruf des Players als Seiteneffekt sollte die äußere Schicht anhand der Zustandsänderung ausführen.

enum AudioEvent {
    case interruptionBegan
    case interruptionEnded(shouldResume: Bool)
    case outputDisconnected
    case userPaused
}

struct PlaybackState {
    var isPlaying = false
    var resumeAfterInterruption = false

    mutating func apply(_ event: AudioEvent) {
        switch event {
        case .interruptionBegan:
            resumeAfterInterruption = isPlaying
            isPlaying = false
        case .interruptionEnded(let shouldResume):
            isPlaying = resumeAfterInterruption && shouldResume
            resumeAfterInterruption = false
        case .outputDisconnected, .userPaused:
            isPlaying = false
            resumeAfterInterruption = false
        }
    }
}

Dieses Beispiel deckt nur einen Unterbrechungszyklus ab. Wenn die App mehrere Wiedergabesitzungen gleichzeitig verarbeitet oder eine Warteschlange bietet, die sich auch nach dem Pausieren bedienen lässt, muss zunächst das Zustandsmodell erweitert werden. Erst danach sollte die Reihenfolge der Ereignisse festgelegt werden. Ein einzelner boolescher Wert kann nicht die gesamte Semantik der Sitzungen abbilden.

Systembenachrichtigungen in Ereignisse übersetzen

Die Adapterschicht überwacht AVAudioSession.interruptionNotification und liest den Unterbrechungstyp aus userInfo. Beim Ende einer Unterbrechung liest sie die Option zur Fortsetzung und übergibt das Ergebnis an die Zustandsmaschine. Bei AVAudioSession.routeChangeNotification prüft sie den Grund der Änderung und übersetzt nur oldDeviceUnavailable in outputDisconnected. Fehlende oder unbekannte Felder sollten protokolliert und untersucht werden, statt sie eigenmächtig als Erlaubnis zur Fortsetzung zu werten.

Benachrichtigungs-Callbacks und Aktionen in der Benutzeroberfläche können aus unterschiedlichen Ausführungskontexten kommen. Führen Sie die Ereignisse sequenziell demselben Zustandsverwalter zu und rufen Sie anschließend anhand des alten und neuen Zustands die Pause oder Wiedergabe auf. So kann die zufällige Ankunftsreihenfolge von „manuell pausiert“ und „Unterbrechung beendet“ die Entscheidung der Person nicht überschreiben.

Grenzfälle auf dem Build-Knoten testen

Decken Sie mit XCTest zuerst zwei leicht zu übersehende Fälle ab: War vor der Unterbrechung keine Wiedergabe aktiv, darf sie nicht von selbst beginnen. Wurde während der Unterbrechung manuell pausiert, darf sie nicht automatisch fortgesetzt werden. Die Tests benötigen weder echte Kopfhörer noch Systembenachrichtigungen zu einem bestimmten Zeitpunkt.

import XCTest

final class PlaybackStateTests: XCTestCase {
    func testInterruptionDoesNotStartIdlePlayer() {
        var state = PlaybackState()
        state.apply(.interruptionBegan)
        state.apply(.interruptionEnded(shouldResume: true))
        XCTAssertFalse(state.isPlaying)
    }

    func testUserPauseCancelsResume() {
        var state = PlaybackState(isPlaying: true)
        state.apply(.interruptionBegan)
        state.apply(.userPaused)
        state.apply(.interruptionEnded(shouldResume: true))
        XCTAssertFalse(state.isPlaying)
    }

    func testDisconnectedOutputStopsPlayback() {
        var state = PlaybackState(isPlaying: true)
        state.apply(.outputDisconnected)
        XCTAssertFalse(state.isPlaying)
        XCTAssertFalse(state.resumeAfterInterruption)
    }
}

Prüfen Sie auf dem Mac in der Cloud zunächst mit xcodebuild -list -project App.xcodeproj das Projekt und das Scheme. Führen Sie dann mit dem tatsächlich vom Team geteilten Scheme xcodebuild test -project App.xcodeproj -scheme App -destination 'platform=iOS Simulator,name=<已安装的设备名称>' aus. Projektname, Scheme und Gerätename im Beispiel müssen ersetzt werden; xcrun simctl list devices available zeigt die derzeit verfügbaren Geräte an. Das Test-Target muss auf die Typen der Zustandsmaschine zugreifen können, und das Scheme muss dieses Test-Target enthalten.

Bestandene Unit-Tests belegen nur, dass eine vorgegebene Ereignisfolge zur erwarteten Entscheidung führt. Sie beweisen weder, dass Systembenachrichtigungen tatsächlich eintreffen, noch, über welches Gerät der Ton ausgegeben wird.

Integration auf einem Gerät abnehmen

Die Prüfung auf einem physischen Gerät sollte mindestens vier Schritte umfassen: Ziehen Sie während der Wiedergabe die Kopfhörer ab und prüfen Sie, dass die Lautsprecher nicht unerwartet übernehmen. Lösen Sie eine kontrollierbare Audiounterbrechung durch das System aus und beobachten Sie die Benachrichtigungen für Beginn und Ende. Pausieren Sie während der Unterbrechung manuell und prüfen Sie, dass die Wiedergabe anschließend pausiert bleibt. Verbinden Sie das Ausgabegerät erneut und prüfen Sie, dass die App nicht allein deshalb die Wiedergabe startet. Es genügt, Ereignistyp, Grund und abschließenden Wiedergabestatus zu protokollieren. Inhalte von Nutzern und Zugangsdaten gehören nicht ins Protokoll.

Wenn die Unit-Tests bestehen, auf dem Gerät aber weiterhin ein Fehler auftritt, prüfen Sie zuerst, ob die Überwachung der Benachrichtigungen über den gesamten Lebenszyklus der Sitzung aktiv ist. Untersuchen Sie danach die Auswertung der Benachrichtigungsfelder, die Reihenfolge der Ereigniszustellung und die Übereinstimmung des tatsächlichen Player-Status mit PlaybackState. Besonders wichtig ist die Unterscheidung zwischen „Die Zustandsmaschine verlangt eine Pause“ und „Der Player hat tatsächlich pausiert“: Ersteres sichern die Tests ab, Letzteres erfordert eine Prüfung der Adapterschicht und die Abnahme auf dem Gerät.

Prüfpunkte im Merge-Prozess verankern

Prüfen Sie vor dem Merge: Die Tests der Zustandsmaschine decken Leerlauf, Unterbrechung, manuelles Pausieren und das Trennen des Ausgabegeräts ab. Die Adapterschicht behandelt unbekannte Benachrichtigungsfelder vorsichtig. Alle Ereignisse durchlaufen denselben sequenziellen Ausführungspfad. Das Team hat mindestens eine Abnahme auf dem Zielgerät dokumentiert. Ein Mac in der Cloud eignet sich dafür, deterministische Zustandstests wiederholt auszuführen; für den tatsächlichen Audioausgabepfad bleibt die Beobachtung am Gerät maßgeblich. Sind beide Prüfebenen getrennt, lässt sich bei einem Fehler gezielt nach Produktregeln, der Übersetzung von Benachrichtigungen oder der Tonausgabe selbst suchen.

Häufig gestellte Fragen

Reicht ein Simulator-Test für das Abziehen eines Headsets?

Nein. Er prüft die Reaktion auf ein eingespeistes Ereignis; Benachrichtigungen und tatsächliche Ausgabewege müssen zusätzlich am Gerät geprüft werden.

Soll die Wiedergabe nach jeder Unterbrechung automatisch weiterlaufen?

Nein. Vorherige Wiedergabe, die Freigabe zur Fortsetzung und eine mögliche Pause durch den Nutzer müssen berücksichtigt werden.

UDPMac Cloud-Mac

Führen Sie Ihren nächsten Xcode-Build auf einer dedizierten physischen Maschine aus.

Wählen Sie Apple-Silicon-Modell, Standort und Laufzeit und führen Sie Ihre eigenen Build-Prozesse über eine Remote-Verbindung aus. Die tatsächliche Verfügbarkeit sehen Sie beim Bestellen in der Konsole.

Cloud-Mac-Mietoptionen ansehen