audio_service

repository·minor·Indexed 21 days ago

https://github.com/ryanheise/audio_service

A Flutter plugin that enables background audio playback and interaction with system media controls, including notifications, lock screen, headset buttons, Android Auto, and Apple CarPlay. It supports Android, iOS, Web, and Linux. The plugin uses an AudioHandler concept to encapsulate audio logic and broadcast state changes to the system and Flutter UI.

Tokens
9.4K
Snippets
27
Records
38
Agent score
75%

What's inside audio_service

  1. How audio_service works: The AudioHandler concept

    minor

    The audio_service plugin works by encapsulating your audio logic within an AudioHandler. This handler implements standard system callbacks that respond to media playback requests from various sources (e.g., lock screen, headset buttons, Android Auto, Apple CarPlay) in a uniform way.

    You implement these callbacks to trigger your specific audio engine (like just_audio for music or flutter_tts for speech). The AudioHandler acts as the single source of truth, broadcasting state changes to both the system (notifications/controls) and your Flutter UI.

    class MyAudioHandler extends BaseAudioHandler
        with QueueHandler, 
        SeekHandler {
    
      final _player = AudioPlayer(); // e.g. just_audio
      
      Future<void> play() => _player.play();
      Future<void> pause() => _player.pause();
      Future<void> stop() => _player.stop();
      Future<void> seek(Duration position) => _player.seek(position);
      Future<void> skipToQueueItem(int i) => _player.seek(Duration.zero, index: i);
    }
  2. Initialize the audio_service plugin

    minor

    To use the plugin, you must define your AudioHandler implementation and register it during app startup using AudioService.init. It is recommended to store the returned handler in a singleton for easy access throughout your app.

    You can configure Android-specific notification settings via AudioServiceConfig.

    Future<void> main() async {
      // store this in a singleton
      _audioHandler = await AudioService.init(
        builder: () => MyAudioHandler(),
        config: AudioServiceConfig(
          androidNotificationChannelId: 'com.mycompany.myapp.channel.audio',
          androidNotificationChannelName: 'Music playback',
        ),
      );
      runApp(new MyApp());
    }
  3. iOS setup for audio_service

    minor

    To allow audio playback in the background on iOS, add the audio background mode to your Info.plist file.

    Warning: The OS may kill your process if it sits idly without playing audio. If you need to pause between tracks, consider playing a silent audio track instead of using an idle timer.

    <key>UIBackgroundModes</key>
    <array>
      <string>audio</string>
    </array>
  4. Configure the audio session with audio_session

    minor

    To manage how your app interacts with other audio apps (e.g., ducking volume vs. pausing), use the audio_session package. This allows you to define usage scenarios like speech() for podcast players.

    If your audio plugin does not automatically activate the session, you can manually call session.setActive(true). You can also listen to session.interruptionEventStream to handle interruptions or session.becomingNoisyEventStream to handle headphone unplugging events.

    Note: If using multiple audio plugins, apply your preferred configuration using audio_session after all other audio plugins have loaded to prevent them from overriding your settings.

    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration.speech());
    
    // Manually activate if the plugin doesn't
    if (await session.setActive(true)) {
      // Now play audio.
    } else {
      // The request was denied
    }
  5. Customize the iOS launch screen assets

    minor

    To change the image displayed during the app's launch on iOS, you can either replace the image files directly in the ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory or use Xcode for a more visual approach.

    Using Xcode:

    1. Open your Flutter project's iOS workspace using open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog.
    open ios/Runner.xcworkspace
  6. Android setup for audio_service

    minor

    To enable background audio services on Android, you must modify AndroidManifest.xml and handle resource preservation.

    1. Update AndroidManifest.xml

    Add the required permissions, update your existing activity, and add the AudioService and MediaButtonReceiver elements.

    2. Handle Foreground Service Restrictions

    On Android 12+, to avoid ForegroundServiceStartNotAllowedException, you can:

    • Set androidStopForegroundOnPause to false in your AudioServiceConfig (keeps the service in the foreground during pause).
    • Or, keep the default true and request the user to disable battery optimization using the optimize_battery package.

    3. Prevent Icon Stripping

    If using custom notification icons, create android/app/src/main/res/raw/keep.xml to prevent R8 from stripping them during build.

    <!-- AndroidManifest.xml snippet -->
    <manifest xmlns:tools="http://schemas.android.com/tools" ...>
      <uses-permission android:name="android.permission.WAKE_LOCK"/>
      <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
      <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
      
      <application ...>
        <activity android:name="com.ryanheise.audioservice.AudioServiceActivity" ...> ... </activity>
        
        <service android:name="com.ryanheise.audioservice.AudioService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="true" tools:ignore="Instantiatable">
          <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService" />
          </intent-filter>
        </service>
    
        <receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
            android:exported="true" tools:ignore="Instantiatable">
          <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
          </intent-filter>
        </receiver> 
      </application>
    </manifest>
  7. Implement a new platform for audio_service

    minor

    To create a new platform-specific implementation of the audio_service plugin, you must extend the AudioServicePlatform class found in lib/audio_service_platform_interface.dart. Your implementation should contain the logic for the platform-specific audio behaviors.

    Once implemented, you must register your implementation as the default instance by assigning it to AudioServicePlatform.instance during plugin registration.

    // 1. Extend the platform interface
    class MyPlatformAudioService extends AudioServicePlatform {
      // Implement required methods here
    }
    
    // 2. Register the implementation
    AudioServicePlatform.instance = MyPlatformAudioService();
  8. Use a custom Android Activity with audio_service

    minor

    If your app uses a custom activity (e.g., MainActivity) instead of the default AudioServiceActivity, you must update AndroidManifest.xml to point to your class and link it to the audio_service shared FlutterEngine by inheriting from the provided base classes.

    <!-- In AndroidManifest.xml -->
    <activity android:name=".MainActivity" ...>
    // For a regular Activity
    import com.ryanheise.audioservice.AudioServiceActivity;
    class MainActivity extends AudioServiceActivity { ... }
    
    // For a FragmentActivity
    import com.ryanheise.audioservice.AudioServiceFragmentActivity;
    class MainActivity extends AudioServiceFragmentActivity { ... }
  9. Use SwitchAudioHandler to swap audio handlers at runtime

    minor

    A SwitchAudioHandler allows you to switch the active AudioHandler being used by the application. It delegates all method calls and stream events to an inner handler. When you set a new inner handler, the SwitchAudioHandler automatically cancels subscriptions to the old handler and starts listening to the new one.

    Use this when you need to switch between different playback implementations (e.g., switching from a local file player to a streaming player) without changing the client-side code that interacts with the handler.

    // Initialize with a default handler
    final switchHandler = SwitchAudioHandler();
    
    // Later, swap to a specific implementation
    switchHandler.inner = MyCustomAudioHandler();
  10. Implement the AudioHandler interface

    minor

    The AudioHandler is the primary interface for controlling audio playback and providing state updates. It allows your app to be controlled by external agents like lock screens, headsets, and car audio systems.

    Important: You cannot subclass AudioHandler directly. You must subclass BaseAudioHandler (or CompositeAudioHandler for multiple behaviors) and then implement the required methods.

    Key capabilities include:

    • Playback Control: play(), pause(), stop(), seek(Duration position), skipToNext(), skipToPrevious().
    • Queue Management: addQueueItem(), updateQueue(), removeQueueItem(), skipToQueueItem(int index).
    • Media Management: prepareFromMediaId(), playMediaItem(), updateMediaItem().
    • State Observation: Accessing playbackState, mediaItem, queue, and customEvent streams.
    • Custom Actions: Using customAction(String name, [Map<String, dynamic>? extras]) to implement app-specific logic.
    // Example of what an implementation might look like (conceptually)
    class MyAudioHandler extends BaseAudioHandler {
      @override
      Future<void> play() async {
        // implementation logic
      }
    
      @override
      Future<void> pause() async {
        // implementation logic
      }
      
      // ... other overrides
    }
  11. Define MediaAction and MediaControl

    minor

    The audio_service plugin uses MediaAction to represent the intent of a user interaction and MediaControl to represent the actual button shown in the UI (Android notification, iOS control center, etc.).

    Supported MediaActions

    Common actions include:

    • MediaAction.play, MediaAction.pause, MediaAction.stop
    • MediaAction.skipToNext, MediaAction.skipToPrevious
    • MediaAction.seek, MediaAction.playPause
    • MediaAction.setRepeatMode, MediaAction.setShuffleMode
    • MediaAction.custom (for user-defined actions)

    MediaControl

    Each MediaControl maps a MediaAction to a specific UI element. You can use predefined controls or create custom ones:

    • Predefined: MediaControl.play, MediaControl.pause, MediaControl.stop, MediaControl.next, etc.
    • Custom: Use MediaControl.custom to define a unique action name and optional extras.
    // Example of a custom control
    MediaControl.custom(
      androidIcon: 'drawable/ic_custom_action',
      label: 'My Custom Action',
      name: 'my_action_name',
      extras: {'key': 'value'},
    );