Skip to content

Commit f074a93

Browse files
committed
Support for AudioSession to play while in the background
1 parent d84c066 commit f074a93

File tree

3 files changed

+62
-12
lines changed

3 files changed

+62
-12
lines changed

Sources/Navigator/Audiobook/AudioNavigator.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,28 @@ open class _AudioNavigator: _MediaNavigator, _AudioSessionUser, Loggable {
2323

2424
private let publication: Publication
2525
private let initialLocation: Locator?
26-
27-
public init(publication: Publication, initialLocation: Locator? = nil) {
26+
public let audioConfiguration: _AudioSession.Configuration
27+
28+
public init(
29+
publication: Publication,
30+
initialLocation: Locator? = nil,
31+
audioConfig: _AudioSession.Configuration = .init(
32+
category: .playback,
33+
mode: .default,
34+
routeSharingPolicy: {
35+
if #available(iOS 11.0, *) {
36+
return .longForm
37+
} else {
38+
return .default
39+
}
40+
}(),
41+
options: []
42+
)
43+
) {
2844
self.publication = publication
2945
self.initialLocation = initialLocation
3046
?? publication.readingOrder.first.flatMap { publication.locate($0) }
47+
self.audioConfiguration = audioConfig
3148

3249
let durations = publication.readingOrder.map { $0.duration ?? 0 }
3350
self.durations = durations

Sources/Navigator/TTS/AVTTSEngine.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,17 @@ public class AVTTSEngine: NSObject, TTSEngine, AVSpeechSynthesizerDelegate, Logg
3838
/// Creates a new `AVTTSEngine` instance.
3939
///
4040
/// - Parameters:
41+
/// - audioSessionConfig: AudioSession configuration used while playing utterances. If `nil`, utterances won't
42+
/// play when the app is in the background.
4143
/// - debug: Print the state machine transitions.
42-
public init(debug: Bool = false) {
44+
public init(
45+
audioSessionConfig: _AudioSession.Configuration? = .init(
46+
category: .playback,
47+
mode: .spokenAudio,
48+
options: .mixWithOthers
49+
),
50+
debug: Bool = false
51+
) {
4352
let config = TTSConfiguration(
4453
defaultLanguage: Language(code: .bcp47(AVSpeechSynthesisVoice.currentLanguageCode())),
4554
rate: avRateRange.percentageForValue(Double(AVSpeechUtteranceDefaultSpeechRate)),
@@ -49,6 +58,7 @@ public class AVTTSEngine: NSObject, TTSEngine, AVSpeechSynthesizerDelegate, Logg
4958
self.defaultConfig = config
5059
self.config = config
5160
self.debug = debug
61+
self.audioSessionUser = audioSessionConfig.map { AudioSessionUser(config: $0) }
5262

5363
super.init()
5464
synthesizer.delegate = self
@@ -283,7 +293,11 @@ public class AVTTSEngine: NSObject, TTSEngine, AVSpeechSynthesizerDelegate, Logg
283293

284294
case let .play(utterance):
285295
synthesizer.speak(avUtterance(from: utterance))
286-
296+
297+
if let user = audioSessionUser {
298+
_AudioSession.shared.start(with: user)
299+
}
300+
287301
case .stop:
288302
synthesizer.stopSpeaking(at: .immediate)
289303

@@ -313,6 +327,24 @@ public class AVTTSEngine: NSObject, TTSEngine, AVSpeechSynthesizerDelegate, Logg
313327
return AVSpeechSynthesisVoice(language: language)
314328
}
315329
}
330+
331+
// MARK: - Audio session
332+
333+
private let audioSessionUser: AudioSessionUser?
334+
335+
private final class AudioSessionUser: R2Shared._AudioSessionUser {
336+
let audioConfiguration: _AudioSession.Configuration
337+
338+
init(config: _AudioSession.Configuration) {
339+
self.audioConfiguration = config
340+
}
341+
342+
deinit {
343+
_AudioSession.shared.end(for: self)
344+
}
345+
346+
func play() {}
347+
}
316348
}
317349

318350
private extension TTSVoice {

Sources/Shared/Toolkit/Media/AudioSession.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import AVFoundation
1313
import Foundation
1414

1515
/// An user of the `AudioSession`, for example a media player object.
16-
@available(iOS 10.0, *)
1716
public protocol _AudioSessionUser: AnyObject {
1817

1918
/// Audio session configuration to use for this user.
@@ -25,28 +24,31 @@ public protocol _AudioSessionUser: AnyObject {
2524

2625
}
2726

28-
@available(iOS 10.0, *)
2927
public extension _AudioSessionUser {
30-
3128
var audioConfiguration: _AudioSession.Configuration { .init() }
32-
3329
}
3430

3531
/// Manages an activated `AVAudioSession`.
3632
///
3733
/// **WARNING:** This API is experimental and may change or be removed in a future release without
3834
/// notice. Use with caution.
39-
@available(iOS 10.0, *)
4035
public final class _AudioSession: Loggable {
4136

4237
public struct Configuration {
4338
let category: AVAudioSession.Category
4439
let mode: AVAudioSession.Mode
40+
let routeSharingPolicy: AVAudioSession.RouteSharingPolicy
4541
let options: AVAudioSession.CategoryOptions
4642

47-
public init(category: AVAudioSession.Category = .playback, mode: AVAudioSession.Mode = .default, options: AVAudioSession.CategoryOptions = []) {
43+
public init(
44+
category: AVAudioSession.Category = .playback,
45+
mode: AVAudioSession.Mode = .default,
46+
routeSharingPolicy: AVAudioSession.RouteSharingPolicy = .default,
47+
options: AVAudioSession.CategoryOptions = []
48+
) {
4849
self.category = category
4950
self.mode = mode
51+
self.routeSharingPolicy = routeSharingPolicy
5052
self.options = options
5153
}
5254
}
@@ -74,7 +76,7 @@ public final class _AudioSession: Loggable {
7476
do {
7577
let config = user.audioConfiguration
7678
if #available(iOS 11.0, *) {
77-
try audioSession.setCategory(config.category, mode: config.mode, policy: .longForm, options: config.options)
79+
try audioSession.setCategory(config.category, mode: config.mode, policy: config.routeSharingPolicy, options: config.options)
7880
} else {
7981
try audioSession.setCategory(config.category, mode: config.mode, options: config.options)
8082
}
@@ -154,5 +156,4 @@ public final class _AudioSession: Loggable {
154156
break
155157
}
156158
}
157-
158159
}

0 commit comments

Comments
 (0)