Skip to content

Commit 56b7c72

Browse files
authored
Merge pull request #257 from alnitak/audioContext
feat: trying to disable audio context
2 parents 2823b95 + a76ef21 commit 56b7c72

File tree

13 files changed

+359
-25
lines changed

13 files changed

+359
-25
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"name": "Flutter debug",
99
"type": "dart",
1010
"request": "launch",
11-
"program": "lib/buffer_stream/simple_noise_stream.dart",
11+
"program": "lib/audio_context/audio_context.dart",
1212
"flutterMode": "debug",
1313
// "env": {
1414
// "NO_OPUS_OGG_LIBS": "1"

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#### 3.2.0 (XX Xxx 2025)
2+
- fix #104, #245, #249. Itis now possible to use a 3rd party plugin like `audio_session` to manage audio context.
3+
- new audio context example in `example/lib/audio_context/audio_context.dart`.
24
- fix: GetPosition for buffer streams and Web hot reload/restard #258 and #259
35

46
#### 3.1.12 (21 Jun 2025)

example/ios/Podfile.lock

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
PODS:
2+
- audio_session (0.0.1):
3+
- Flutter
24
- DKImagePickerController/Core (4.3.9):
35
- DKImagePickerController/ImageDataManager
46
- DKImagePickerController/Resource
@@ -45,6 +47,7 @@ PODS:
4547
- SwiftyGif (5.4.5)
4648

4749
DEPENDENCIES:
50+
- audio_session (from `.symlinks/plugins/audio_session/ios`)
4851
- file_picker (from `.symlinks/plugins/file_picker/ios`)
4952
- Flutter (from `Flutter`)
5053
- flutter_soloud (from `.symlinks/plugins/flutter_soloud/ios`)
@@ -58,6 +61,8 @@ SPEC REPOS:
5861
- SwiftyGif
5962

6063
EXTERNAL SOURCES:
64+
audio_session:
65+
:path: ".symlinks/plugins/audio_session/ios"
6166
file_picker:
6267
:path: ".symlinks/plugins/file_picker/ios"
6368
Flutter:
@@ -68,15 +73,16 @@ EXTERNAL SOURCES:
6873
:path: ".symlinks/plugins/path_provider_foundation/darwin"
6974

7075
SPEC CHECKSUMS:
76+
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
7177
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
7278
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
7379
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
7480
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
75-
flutter_soloud: 83c1c00897fd596a13784f897c17a4f6f789d6fc
81+
flutter_soloud: 02a4ecb1b8365e2be3e2a012b8b8ee0d99e6c05c
7682
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
7783
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
7884
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
7985

80-
PODFILE CHECKSUM: 168a5f7d8b3f3a912497c5c046d9c11dacc079fc
86+
PODFILE CHECKSUM: 2c9265d0c975a4d2eb77a8fbb80da000589fe91a
8187

8288
COCOAPODS: 1.16.2

example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
2930
shouldUseLaunchSchemeArgsEnv = "YES">
3031
<MacroExpansion>
3132
<BuildableReference
@@ -54,11 +55,13 @@
5455
buildConfiguration = "Debug"
5556
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
5657
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
58+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
5759
launchStyle = "0"
5860
useCustomWorkingDirectory = "NO"
5961
ignoresPersistentStateOnLaunch = "NO"
6062
debugDocumentVersioning = "YES"
6163
debugServiceExtension = "internal"
64+
enableGPUValidationMode = "1"
6265
allowLocationSimulation = "YES">
6366
<BuildableProductRunnable
6467
runnableDebuggingMode = "0">
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import 'dart:developer' as dev;
2+
3+
import 'package:audio_session/audio_session.dart';
4+
import 'package:flutter/foundation.dart';
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_soloud/flutter_soloud.dart';
7+
import 'package:logging/logging.dart';
8+
9+
void main() async {
10+
// The `flutter_soloud` package logs everything
11+
// (from severe warnings to fine debug messages)
12+
// using the standard `package:logging`.
13+
// You can listen to the logs as shown below.
14+
Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
15+
Logger.root.onRecord.listen((record) {
16+
dev.log(
17+
record.message,
18+
time: record.time,
19+
level: record.level.value,
20+
name: record.loggerName,
21+
zone: record.zone,
22+
error: record.error,
23+
stackTrace: record.stackTrace,
24+
);
25+
});
26+
27+
WidgetsFlutterBinding.ensureInitialized();
28+
29+
/// Initialize the player.
30+
await SoLoud.instance.init();
31+
32+
runApp(
33+
const MaterialApp(
34+
home: AudioContext(),
35+
),
36+
);
37+
}
38+
39+
enum ContextState {
40+
playing,
41+
paused,
42+
stopped,
43+
ducking,
44+
unknown,
45+
}
46+
47+
/// Simple usecase of flutter_soloud plugin
48+
class AudioContext extends StatefulWidget {
49+
const AudioContext({super.key});
50+
51+
@override
52+
State<AudioContext> createState() => _AudioContextState();
53+
}
54+
55+
class _AudioContextState extends State<AudioContext> {
56+
final soloud = SoLoud.instance;
57+
late final AudioSession session;
58+
AudioSource? sound;
59+
SoundHandle? soundHandle;
60+
ValueNotifier<ContextState> isPlaying = ValueNotifier(ContextState.stopped);
61+
62+
@override
63+
void initState() {
64+
super.initState();
65+
// Initialize the audio session.
66+
AudioSession.instance.then((audioSession) async {
67+
session = audioSession;
68+
await session.configure(
69+
const AudioSessionConfiguration(
70+
androidWillPauseWhenDucked: true,
71+
androidAudioAttributes: AndroidAudioAttributes(
72+
usage: AndroidAudioUsage.media,
73+
contentType: AndroidAudioContentType.music,
74+
),
75+
androidAudioFocusGainType:
76+
AndroidAudioFocusGainType.gainTransientMayDuck,
77+
avAudioSessionCategory: AVAudioSessionCategory.playback,
78+
avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.none,
79+
),
80+
);
81+
82+
// Listen to audio interruptions and pause or duck as appropriate.
83+
_handleInterruptions(session);
84+
});
85+
}
86+
87+
@override
88+
void dispose() {
89+
SoLoud.instance.deinit();
90+
super.dispose();
91+
}
92+
93+
@override
94+
Widget build(BuildContext context) {
95+
return Scaffold(
96+
body: FutureBuilder<AudioSession>(
97+
future: AudioSession.instance,
98+
builder: (context, asyncSnapshot) {
99+
final session = asyncSnapshot.data;
100+
if (session == null) return const CircularProgressIndicator();
101+
return Center(
102+
child: Column(
103+
mainAxisSize: MainAxisSize.min,
104+
spacing: 12,
105+
children: [
106+
ElevatedButton(
107+
onPressed: () async {
108+
await SoLoud.instance.disposeAllSources();
109+
110+
sound = await soloud
111+
.loadAsset('assets/audio/8_bit_mentality.mp3');
112+
soundHandle = await soloud.play(sound!, looping: true);
113+
114+
await session.setActive(true);
115+
isPlaying.value = ContextState.playing;
116+
},
117+
child: const Text('play asset'),
118+
),
119+
120+
ElevatedButton(
121+
onPressed: () async {
122+
await session.setActive(true);
123+
soloud
124+
..setPause(soundHandle!, false)
125+
..fadeGlobalVolume(1, const Duration(milliseconds: 300));
126+
isPlaying.value = ContextState.playing;
127+
},
128+
child: const Text('unpause'),
129+
),
130+
131+
// Display the current state.
132+
ValueListenableBuilder<ContextState>(
133+
valueListenable: isPlaying,
134+
builder: (context, state, child) {
135+
return Text(
136+
'Current state: ${state.name}',
137+
style: const TextStyle(fontSize: 20),
138+
);
139+
},
140+
),
141+
],
142+
),
143+
);
144+
},
145+
),
146+
);
147+
}
148+
149+
void fadeoutThenPause() {
150+
if (soundHandle == null) return;
151+
soloud.fadeGlobalVolume(0, const Duration(milliseconds: 300));
152+
Future.delayed(const Duration(milliseconds: 300), () {
153+
// After fading out, we can pause.
154+
soloud.setPause(soundHandle!, true);
155+
isPlaying.value = ContextState.paused;
156+
});
157+
}
158+
159+
void fadeinThenResume() {
160+
if (soundHandle == null) return;
161+
isPlaying.value = ContextState.playing;
162+
soloud
163+
..setPause(soundHandle!, false)
164+
..fadeGlobalVolume(1, const Duration(milliseconds: 300));
165+
}
166+
167+
void _handleInterruptions(AudioSession audioSession) {
168+
audioSession.becomingNoisyEventStream.listen((_) {
169+
// The user unplugged the headphones, so we should pause
170+
// or lower the volume.
171+
debugPrint('audio_context: becomingNoisy, pausing...');
172+
if (soundHandle == null) return;
173+
soloud.setPause(soundHandle!, true);
174+
isPlaying.value = ContextState.paused;
175+
});
176+
audioSession.interruptionEventStream.listen((event) {
177+
debugPrint('audio_context: interruption begin: ${event.begin}');
178+
debugPrint('audio_context: interruption type: ${event.type}');
179+
if (soundHandle == null) return;
180+
if (event.begin) {
181+
switch (event.type) {
182+
case AudioInterruptionType.duck:
183+
// Another app started playing audio and we should duck.
184+
soloud.fadeGlobalVolume(0.1, const Duration(milliseconds: 300));
185+
isPlaying.value = ContextState.ducking;
186+
case AudioInterruptionType.pause:
187+
// Another app started playing audio and we should pause.
188+
fadeoutThenPause();
189+
isPlaying.value = ContextState.paused;
190+
case AudioInterruptionType.unknown:
191+
// Another app started playing audio and we should pause.
192+
soloud.setPause(soundHandle!, true);
193+
isPlaying.value = ContextState.unknown;
194+
}
195+
} else {
196+
switch (event.type) {
197+
case AudioInterruptionType.duck:
198+
// The interruption ended and we should unduck.
199+
soloud.fadeGlobalVolume(1, const Duration(milliseconds: 300));
200+
isPlaying.value = ContextState.playing;
201+
case AudioInterruptionType.pause:
202+
// The interruption ended and we should resume.
203+
fadeinThenResume();
204+
isPlaying.value = ContextState.playing;
205+
case AudioInterruptionType.unknown:
206+
// The interruption ended but we should not resume.
207+
isPlaying.value = ContextState.unknown;
208+
}
209+
}
210+
});
211+
audioSession.devicesChangedEventStream.listen((event) {
212+
debugPrint('audio_context: Devices added: ${event.devicesAdded}');
213+
debugPrint('audio_context: Devices removed: ${event.devicesRemoved}');
214+
});
215+
}
216+
}

example/macos/Flutter/GeneratedPluginRegistrant.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import FlutterMacOS
66
import Foundation
77

8+
import audio_session
89
import file_picker
910
import path_provider_foundation
1011

1112
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
13+
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
1214
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
1315
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
1416
}

example/pubspec.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ packages:
99
url: "https://pub.dev"
1010
source: hosted
1111
version: "2.13.0"
12+
audio_session:
13+
dependency: "direct main"
14+
description:
15+
name: audio_session
16+
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
17+
url: "https://pub.dev"
18+
source: hosted
19+
version: "0.2.2"
1220
boolean_selector:
1321
dependency: transitive
1422
description:
@@ -263,6 +271,14 @@ packages:
263271
url: "https://pub.dev"
264272
source: hosted
265273
version: "2.1.8"
274+
rxdart:
275+
dependency: transitive
276+
description:
277+
name: rxdart
278+
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
279+
url: "https://pub.dev"
280+
source: hosted
281+
version: "0.28.0"
266282
sky_engine:
267283
dependency: transitive
268284
description: flutter

example/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ environment:
1010
sdk: '>=3.0.0 <4.0.0'
1111

1212
dependencies:
13+
audio_session: ^0.2.2
14+
1315
cupertino_icons: ^1.0.8
1416

15-
#https://pub.dev/packages/file_picker
1617
file_picker: ^8.1.2
1718

1819
flutter:
@@ -21,7 +22,6 @@ dependencies:
2122
flutter_soloud:
2223
path: ../
2324

24-
#https://pub.dev/packages/logging
2525
logging: ^1.2.0
2626

2727
web_socket_channel: ^3.0.1

0 commit comments

Comments
 (0)