ryanheise / audio_service

Flutter plugin to play audio in the background while the screen is off.
796 stars 474 forks source link

Controls inside the Android notification may not change #1002

Closed serhii-k closed 1 year ago

serhii-k commented 1 year ago

Documented behaviour

Your audio handler must broadcast state changes so that the system notification and smart watches (etc) know what state to display. Your app's Flutter UI may also listen to these state changes so that it knows what state to display. Thus, the audio handler provides a single source of truth for your audio state to all clients.

Actual behaviour

Let's say we have MediaControl.pause, MediaControl.stop inside the notification. When a user presses the Pause icon the AudioHandler.pause() is called. Let's say here we're adding controls: [MediaControl.play, MediaControl.stop] to the playbackStream. Unfortunately, sometimes this doesn't change the controls inside the Android notification.

It can be a bug inside the plugin implementation, or it can be a bug of Android...

Minimal reproduction project

Official example: main.dart

Reproduction steps

Please, see the next message for the code.

  1. Start this example.
  2. Press the 'Play' button inside the UI.
  3. Uncover the Android notification.
  4. When pressed the "Play/Pause" button several times, it stops to change. It gets stuck either in the Play or Pause state. For example: a) the audio service is playing b) we press "Pause" inside the notification c) the audio service pauses, but the "Pause" control inside the notification doesn't change to "Play" d) thus we can't resume the playback from the notification

However: We can resume the playback from the app UI, or by pressing a media button of headphones.

Output of flutter doctor

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.3)
[✓] VS Code (version 1.52.1)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!

Devices exhibiting the bug

This issue is easily reproducible on my Samsung Galaxy A53 5G (Android 13). It takes 3-4 pause-play changes to appear. Or sometimes it's even enough to press the "Pause" once. It looks like the icon is changing, but then returns back to "Pause".

This issue is also quite easily reproducible on the API 33 emulator. Display the notification, and press quickly over the "Play/Pause" control. After a few seconds the control gets stuck in "Pause".

As well I could reproduce in on the API 29 emulator.

serhii-k commented 1 year ago

I have squeezed the code to the bare minimum, where this bug is still reproducible.

Please see the following videos. The behaviour is the same as with the original main.dart example (or any other).

import 'dart:async';

import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';

late AudioHandler _audioHandler;

Future<void> main() async {
  _audioHandler = await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
      androidNotificationChannelName: 'Audio playback',
      androidNotificationOngoing: true,
    ),
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Audio Service Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _audioHandler.play();
          },
          child: const Icon(Icons.play_arrow),
        ),
      ),
    );
  }
}

class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
  static const _item = MediaItem(
    id: 'mediaItem',
    title: 'Test',
  );

  AudioPlayerHandler() {
    mediaItem.add(_item);
  }

  @override
  Future<void> play() async {
    playbackState.add(playbackState.value.copyWith(
      controls: [MediaControl.pause, MediaControl.stop],
      processingState: AudioProcessingState.ready,
      playing: true,
    ));
  }

  @override
  Future<void> pause() async {
    playbackState.add(playbackState.value.copyWith(
      controls: [MediaControl.play, MediaControl.stop],
      processingState: AudioProcessingState.ready,
      playing: false,
    ));
  }

  @override
  Future<void> stop() async {
    playbackState.add(playbackState.value.copyWith(
      controls: [],
      processingState: AudioProcessingState.idle,
      playing: false,
    ));
  }
}
serhii-k commented 1 year ago

notification-controls.webm

On a real device I don't have to press that fast. It happens easily after 3-4 consecutive presses with 1-2 seconds in between.

P.S. What a timing :)

serhii-k commented 1 year ago

I have just tried the main.dart example on my phone. The notification controls also got stuck in the Pause state.

serhii-k commented 1 year ago

https://user-images.githubusercontent.com/10563821/222978561-c9ac04ce-9f19-44ae-abe1-3b7dbb2a4207.mp4

An explanation:

  1. I press the Floating Action Button to start the playback.
  2. In the notification I hit the Pause. And it breaks right away! It hasn't been changed to Play.
  3. When I reopen the notification, the Pause button still doesn't work properly. I hit several times the Pause and nothing happens.
  4. I press the FAB again to restart the playback. The notification controls now work properly.
  5. I hit several times the Play-Pause, and it breaks again.
serhii-k commented 1 year ago

My friend with Samsung Galaxy S22 Ultra (Android 13) also reports this issue on his device.

serhii-k commented 1 year ago

One more observation. If the second video is played at 0.25 speed, for a frame we can see that the controls actually change to Play, Stop, but then for some reason they switch back to the Pause, Stop state. In two cases when this control button breaks.

ryanheise commented 1 year ago

I'm sorry before I get through to the rest of your bug report:

Minimal reproduction project

Official example: In the next message, I'm going to put a very simple example that can be added to the official examples.

If you're going to submit a minimal reproduction project, please do so according to the instructions by forking this repo and sharing the link. I require that for my setup.

serhii-k commented 1 year ago

Hello @ryanheise I have edited my two initial comments for this issue. This issue is reproducible with the main.dart example. However, I'm also adding the minimum code that still reproduces this bug.

serhii-k commented 1 year ago

Solved by setting the compileSdkVersion and targetSdkVersion to 33.

serhii-k commented 1 year ago

I've decided to reopen this issue. I think the solution should be mentioned in the README. Because if we target SDK 31 the behaviour of notification controls on Android 33 is literally very disappointing.

serhii-k commented 1 year ago

Closing this issue because in Flutter 3.3.7 compileSdkVersion and targetSdkVersion are set to 33 by default.

github-actions[bot] commented 1 year ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with audio_service.