audio_service icon indicating copy to clipboard operation
audio_service copied to clipboard

Avoid this brief interruption of the notification window

Open csacchetti opened this issue 3 years ago • 8 comments

Feature proposal

I am asking if there is a possibility to avoid this brief interruption of the notification window (in the lock screen) of the audio file when a new song is loaded. This flash happens when loading a new file (the attached video shows the problem) and I noticed that it is a problem that all plugins with audio background that load meta data at load time have. The only plugin that does not have this problem is audioFilePlayer which separates the two moments. It loads the audio file only with the path and then separately takes care of the meta data that is sent when you want it. This ensures that the notification finiestra does not stop for a moment when new tracks are loaded. The problem with audioFilePlayer is that while it is kept up to date it only has the basic functions and does not seem to want to add any. For this reason I ask if it is possible to have the same result with audio_service and just_audio which on the contrary are continuously growing. Thank you

Motivating use case(s)

I ask for this functionality because it looks better without this interruption

https://user-images.githubusercontent.com/46923349/169343648-f53a8cc5-ffe3-4b3f-87c1-6d21f5c3e0d0.mov

n

csacchetti avatar May 19 '22 15:05 csacchetti

This has been noticed before, and I would like to fix it. Can you describe in more detail how this supposed solution would work? In particular, you say:

which separates the two moments. It loads the audio file only with the path and then separately takes care of the meta data that is sent when you want it.

I'm not clear on when you say "only with the path".

ryanheise avatar May 19 '22 15:05 ryanheise

I have seen that there are two kinds of approaches to the problem. The one of those who put all the metadata when load/open the file For example it does so Audio_service or even assets_audio_player and almost all assets_audio_player

final audio = Audio.network("/assets/audio/country.mp3", 
    metas: Metas(
            title:  "Country",
            artist: "Florent Champigny",
            album: "CountryAlbum",
            image: MetasImage.asset("assets/images/country.jpg"), //can be MetasImage.network
          ),
   );

audio_service

var item = MediaItem(
  id: 'https://example.com/audio.mp3',
  album: 'Album name',
  title: 'Track title',
  artist: 'Artist name',
  duration: const Duration(milliseconds: 123456),
  artUri: Uri.parse('https://example.com/album.jpg'),
);

The second approach is the one used by audioFilePlayer which separates the handling of the audio file from the notifications

// Load from assets, store as a variable.
Audio audio = Audio.load('assets/bar.mp3');
...
/// Use the [Audio];
audio.play();
...
audio.pause();
...
audio.resume();

To handle notifications it loads a stand-alone part of the plugin

import 'package:audiofileplayer/audio_system.dart'; and notifications are handled independently

void infoMediaData() async {
    AudioSystem.instance.setMetadata(AudioMetadata(
        title: ref.read(rosariumObject).mysteryName,
        artist: ref
                .read(rosariumObject)
                .mysteriesList![indexMystery]
                .numberMystery! +
            " - " +
            ref
                .read(rosariumObject)
                .mysteriesList![0]
                .elementsList![indexAve]
                .myLabel!,
        artBytes: await imageLockScreen));
  }

  void setButtonMediaData() {
    //AudioSystem.instance.setPlaybackState(doSetPlayback, positionSeconds);

    // Button for lookScreen in Android
    AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
      ref.read(audioStateProv) == AudioState.playing
          ? AndroidMediaButtonType.pause
          : AndroidMediaButtonType.play,
      AndroidMediaButtonType.previous,
      AndroidMediaButtonType.next,
    ], androidCompactIndices: <int>[
      1,
      0,
      2,
    ]);
    // Button for lookScreen in iOS
    AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
      MediaActionType.playPause,
      MediaActionType.pause,
      MediaActionType.next,
      MediaActionType.previous,
    });
  }

  // Build image for lookScreen
  Future<Uint8List> get imageLockScreen async {
    final ByteData bytes =
        await rootBundle.load("assets/images/logo/hail_mary.png");
    return bytes.buffer.asUint8List();
  }

  void _mediaEventListener(MediaEvent mediaEvent) {
    final MediaActionType type = mediaEvent.type;
    if (type == MediaActionType.play) {
      playLogic(false);
    } else if (type == MediaActionType.pause) {
      if (ref.read(audioStateProv) == AudioState.playing) pauseLogic();
    } else if (type == MediaActionType.playPause) {
      if (ref.read(audioStateProv) == AudioState.playing) {
        pauseLogic();
      } else {
        playLogic(false);
      }
    } else if (type == MediaActionType.stop) {
      if (ref.read(audioStateProv) == AudioState.pause ||
          ref.read(audioStateProv) == AudioState.playing) myStop();
    } else if (type == MediaActionType.next) {
      myPause();
      ref.read(audioStateProv.state).state = AudioState.pause;
      internalIndex += 2;
      rosariumLogic();
    } else if (type == MediaActionType.previous) {
      logicRosariumReverse();
    }
  }

By separating the two moments you are able to keep the window constant even when loading different files

Now you may ask why I haven't adopted this plugin. I have adopted it, but the author, who seems to me to be very good made the choice not to implement new functions and I think your work is more complete and continuously progressing.

I unfortunately am not yet able to intervene directly in the code to help you but what I can do I will gladly do. I think beyond all that the engineering of audioFilePlayer is very interesting and can help.

Thank you for your continued work and attention to the development of the plugin that makes it unique.

csacchetti avatar May 19 '22 19:05 csacchetti

Speaking of engineering with audioFilePlayer you can play various files. In my case I have an audio with a voice which is the main one, with also the metadata for the lock screen, and a background music that you can put on and off and it has its own management. With AudioFilePlayer you handle it normally (as I think you are trying to do with just_audio_backgroun), while with audio_service it seems to me that you have to create another hendler file

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

Is this right, or is it instead me misunderstanding and can it be done with a simple handler? You can run the two handlers simultaneously or only switch from one to another?

csacchetti avatar May 19 '22 20:05 csacchetti

I don't understand your description of the difference between these two APIs. They seem equivalent to me.

In particular, you say that the problem is that audio_service handles the audio and the metadata together and they should be separated. But in fact, audio_service does not handle the audio, it handles only the metadata. So to the contrary, the fact is that the audio and metadata are so extremely separated that they are placed in separate plugins.

ryanheise avatar May 20 '22 02:05 ryanheise

As I said, I'm not that experienced. I can tell you that I have tried various plugins and the one that from this point of view, of notifications (also simplicity, keeping multiplayer is another strong point) that works best is audioFilePlayer. I think Iglesia (author of the plugin) is very good. I suggest you try to study what he has done to see if you can solve this problem.

csacchetti avatar May 20 '22 07:05 csacchetti

But if you want to have two audios, one with a voice in the foreground and a music in the background resulting in AudioProcessingState referring to that music and not halla voice) is it necessary to make two AudioHandlers and then two Isolates? If yes the correct initialization is this?

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

But I don't want to have to do the switch but want to have them both on all the time because the two audios go at the same time. I need two AudioProcessingStates one referring to the voice and one referring to the music in the background.

How can this be done? Is it possible?

csacchetti avatar May 20 '22 10:05 csacchetti

As far as of two audios together and other special configurations I have seen that with customAction you can do pretty much anything you want. Great, the plugin shows itself to be very flexible. Going back to the problem with the notification window in the lock screen I noticed that if you simply change the metadata with mediaItem.add() the window remains stable and behaves correctly. The problem occurs when you load the audio file, for example with _player.setAsset(path). You would need to unlink the window of notification from the loading of the audio. In my opinion, the difference with audioFilePlayer is this. If you could unlink the lock screen notification from the loading of the audio file it would be done.

csacchetti avatar May 23 '22 20:05 csacchetti

Just to point out, _player.setAsset(path) has no relevance to audio_service as it is not a method in this plugin. mediaItem.add(...) is a relevant method, so that part of things relates to when your app decides to call mediaItem.add(). Any "linking" is something that your app is doing, rather than this plugin.

ryanheise avatar May 24 '22 03:05 ryanheise