Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "Flutter debug",
"type": "dart",
"request": "launch",
"program": "lib/buffer_stream/simple_noise_stream.dart",
"program": "lib/audio_context/audio_context.dart",
"flutterMode": "debug",
// "env": {
// "NO_OPUS_OGG_LIBS": "1"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#### 3.2.0 (XX Xxx 2025)
- fix #104, #245, #249. Itis now possible to use a 3rd party plugin like `audio_session` to manage audio context.
- new audio context example in `example/lib/audio_context/audio_context.dart`.
- fix: GetPosition for buffer streams and Web hot reload/restard #258 and #259

#### 3.1.12 (21 Jun 2025)
Expand Down
10 changes: 8 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- audio_session (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
Expand Down Expand Up @@ -45,6 +47,7 @@ PODS:
- SwiftyGif (5.4.5)

DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_soloud (from `.symlinks/plugins/flutter_soloud/ios`)
Expand All @@ -58,6 +61,8 @@ SPEC REPOS:
- SwiftyGif

EXTERNAL SOURCES:
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
Expand All @@ -68,15 +73,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"

SPEC CHECKSUMS:
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_soloud: 83c1c00897fd596a13784f897c17a4f6f789d6fc
flutter_soloud: 02a4ecb1b8365e2be3e2a012b8b8ee0d99e6c05c
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4

PODFILE CHECKSUM: 168a5f7d8b3f3a912497c5c046d9c11dacc079fc
PODFILE CHECKSUM: 2c9265d0c975a4d2eb77a8fbb80da000589fe91a

COCOAPODS: 1.16.2
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
Expand Down Expand Up @@ -54,11 +55,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
216 changes: 216 additions & 0 deletions example/lib/audio_context/audio_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import 'dart:developer' as dev;

import 'package:audio_session/audio_session.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_soloud/flutter_soloud.dart';
import 'package:logging/logging.dart';

void main() async {
// The `flutter_soloud` package logs everything
// (from severe warnings to fine debug messages)
// using the standard `package:logging`.
// You can listen to the logs as shown below.
Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
Logger.root.onRecord.listen((record) {
dev.log(
record.message,
time: record.time,
level: record.level.value,
name: record.loggerName,
zone: record.zone,
error: record.error,
stackTrace: record.stackTrace,
);
});

WidgetsFlutterBinding.ensureInitialized();

/// Initialize the player.
await SoLoud.instance.init();

runApp(
const MaterialApp(
home: AudioContext(),
),
);
}

enum ContextState {
playing,
paused,
stopped,
ducking,
unknown,
}

/// Simple usecase of flutter_soloud plugin
class AudioContext extends StatefulWidget {
const AudioContext({super.key});

@override
State<AudioContext> createState() => _AudioContextState();
}

class _AudioContextState extends State<AudioContext> {
final soloud = SoLoud.instance;
late final AudioSession session;
AudioSource? sound;
SoundHandle? soundHandle;
ValueNotifier<ContextState> isPlaying = ValueNotifier(ContextState.stopped);

@override
void initState() {
super.initState();
// Initialize the audio session.
AudioSession.instance.then((audioSession) async {
session = audioSession;
await session.configure(
const AudioSessionConfiguration(
androidWillPauseWhenDucked: true,
androidAudioAttributes: AndroidAudioAttributes(
usage: AndroidAudioUsage.media,
contentType: AndroidAudioContentType.music,
),
androidAudioFocusGainType:
AndroidAudioFocusGainType.gainTransientMayDuck,
avAudioSessionCategory: AVAudioSessionCategory.playback,
avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.none,
),
);

// Listen to audio interruptions and pause or duck as appropriate.
_handleInterruptions(session);
});
}

@override
void dispose() {
SoLoud.instance.deinit();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<AudioSession>(
future: AudioSession.instance,
builder: (context, asyncSnapshot) {
final session = asyncSnapshot.data;
if (session == null) return const CircularProgressIndicator();
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: [
ElevatedButton(
onPressed: () async {
await SoLoud.instance.disposeAllSources();

sound = await soloud
.loadAsset('assets/audio/8_bit_mentality.mp3');
soundHandle = await soloud.play(sound!, looping: true);

await session.setActive(true);
isPlaying.value = ContextState.playing;
},
child: const Text('play asset'),
),

ElevatedButton(
onPressed: () async {
await session.setActive(true);
soloud
..setPause(soundHandle!, false)
..fadeGlobalVolume(1, const Duration(milliseconds: 300));
isPlaying.value = ContextState.playing;
},
child: const Text('unpause'),
),

// Display the current state.
ValueListenableBuilder<ContextState>(
valueListenable: isPlaying,
builder: (context, state, child) {
return Text(
'Current state: ${state.name}',
style: const TextStyle(fontSize: 20),
);
},
),
],
),
);
},
),
);
}

void fadeoutThenPause() {
if (soundHandle == null) return;
soloud.fadeGlobalVolume(0, const Duration(milliseconds: 300));
Future.delayed(const Duration(milliseconds: 300), () {
// After fading out, we can pause.
soloud.setPause(soundHandle!, true);
isPlaying.value = ContextState.paused;
});
}

void fadeinThenResume() {
if (soundHandle == null) return;
isPlaying.value = ContextState.playing;
soloud
..setPause(soundHandle!, false)
..fadeGlobalVolume(1, const Duration(milliseconds: 300));
}

void _handleInterruptions(AudioSession audioSession) {
audioSession.becomingNoisyEventStream.listen((_) {
// The user unplugged the headphones, so we should pause
// or lower the volume.
debugPrint('audio_context: becomingNoisy, pausing...');
if (soundHandle == null) return;
soloud.setPause(soundHandle!, true);
isPlaying.value = ContextState.paused;
});
audioSession.interruptionEventStream.listen((event) {
debugPrint('audio_context: interruption begin: ${event.begin}');
debugPrint('audio_context: interruption type: ${event.type}');
if (soundHandle == null) return;
if (event.begin) {
switch (event.type) {
case AudioInterruptionType.duck:
// Another app started playing audio and we should duck.
soloud.fadeGlobalVolume(0.1, const Duration(milliseconds: 300));
isPlaying.value = ContextState.ducking;
case AudioInterruptionType.pause:
// Another app started playing audio and we should pause.
fadeoutThenPause();
isPlaying.value = ContextState.paused;
case AudioInterruptionType.unknown:
// Another app started playing audio and we should pause.
soloud.setPause(soundHandle!, true);
isPlaying.value = ContextState.unknown;
}
} else {
switch (event.type) {
case AudioInterruptionType.duck:
// The interruption ended and we should unduck.
soloud.fadeGlobalVolume(1, const Duration(milliseconds: 300));
isPlaying.value = ContextState.playing;
case AudioInterruptionType.pause:
// The interruption ended and we should resume.
fadeinThenResume();
isPlaying.value = ContextState.playing;
case AudioInterruptionType.unknown:
// The interruption ended but we should not resume.
isPlaying.value = ContextState.unknown;
}
}
});
audioSession.devicesChangedEventStream.listen((event) {
debugPrint('audio_context: Devices added: ${event.devicesAdded}');
debugPrint('audio_context: Devices removed: ${event.devicesRemoved}');
});
}
}
2 changes: 2 additions & 0 deletions example/macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation

import audio_session
import file_picker
import path_provider_foundation

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}
16 changes: 16 additions & 0 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
audio_session:
dependency: "direct main"
description:
name: audio_session
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
boolean_selector:
dependency: transitive
description:
Expand Down Expand Up @@ -263,6 +271,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
sky_engine:
dependency: transitive
description: flutter
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ environment:
sdk: '>=3.0.0 <4.0.0'

dependencies:
audio_session: ^0.2.2

cupertino_icons: ^1.0.8

#https://pub.dev/packages/file_picker
file_picker: ^8.1.2

flutter:
Expand All @@ -21,7 +22,6 @@ dependencies:
flutter_soloud:
path: ../

#https://pub.dev/packages/logging
logging: ^1.2.0

web_socket_channel: ^3.0.1
Expand Down
Loading