如何使用audio_service和just_audio在Flutter中查找当前歌曲时长

2023-12-29

当您设置MediaItem in 音频服务 https://pub.dev/packages/audio_service你还不知道歌曲时长,因为只是音频 https://pub.dev/packages/just_audio目前还没有任何更改可以告诉您。

The FAQs https://github.com/ryanheise/audio_service/wiki/FAQ#how-do-i-update-an-existing-mediaitem说要更新MediaItem像这样:

modifiedMediaItem = mediaItem.copyWith(duration: duration);
AudioServiceBackground.setMediaItem(modifiedMediaItem);

但我不清楚如何或在哪里执行此操作。这GitHub 存储库中的示例应用程序 https://github.com/ryanheise/audio_service/tree/master/example通过提供预先计算的时间来回避这个问题。 (GitHub问题 https://github.com/ryanheise/audio_service/issues/543)

如何以及在哪里将持续时间从 just_audio 传输到 audio_service 以便它可以更新侦听器?

我发现了一些有用的东西,所以我在下面添加了一个答案。


一旦你有了你的只是音频 https://pub.dev/packages/just_audio AudioPlayer设置后,您可以监听持续时间流中的更改,然后在那里进行更新:

_player.durationStream.listen((duration) {
  final songIndex = _player.playbackEvent.currentIndex;
  print('current index: $songIndex, duration: $duration');
  final modifiedMediaItem = mediaItem.copyWith(duration: duration);
  _queue[songIndex] = modifiedMediaItem;
  AudioServiceBackground.setMediaItem(_queue[songIndex]);
  AudioServiceBackground.setQueue(_queue);
});

Notes:

  • 这是你里面的音频服务 https://pub.dev/packages/audio_service BackgroundAudioTask class.
  • 当我尝试使用_player.currentIndex直接我就得到了奇怪的行为(前两首歌都有索引0在索引开始递增之前)(GitHub问题 https://github.com/ryanheise/just_audio/issues/255)。这就是为什么我在这里使用播放事件来获取当前索引。
  • 对于我的例子,我使用的是List<MediaItem>对于队列。我实际上不需要在最后一行中使用 setQueue,因为 UI 没有监听队列中的更改,但我认为无论如何这样做都是好的。

富勒代码示例

这是我的全部background_audio_service.dart以供参考。这是一个改编文档示例 https://github.com/ryanheise/audio_service/blob/master/example/lib/main.dart:

import 'dart:async';

import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:just_audio/just_audio.dart';

void audioPlayerTaskEntrypoint() async {
  AudioServiceBackground.run(() => AudioPlayerTask());
}

class AudioPlayerTask extends BackgroundAudioTask {
  AudioPlayer _player = new AudioPlayer();
  AudioProcessingState _skipState;
  StreamSubscription<PlaybackEvent> _eventSubscription;

  List<MediaItem> _queue = [];
  List<MediaItem> get queue => _queue;

  int get index => _player.playbackEvent.currentIndex;
  MediaItem get mediaItem => index == null ? null : queue[index];

  @override
  Future<void> onStart(Map<String, dynamic> params) async {
    _loadMediaItemsIntoQueue(params);
    await _setAudioSession();
    _propogateEventsFromAudioPlayerToAudioServiceClients();
    _performSpecialProcessingForStateTransistions();
    _loadQueue();
  }

  void _loadMediaItemsIntoQueue(Map<String, dynamic> params) {
    _queue.clear();
    final List mediaItems = params['data'];
    for (var item in mediaItems) {
      final mediaItem = MediaItem.fromJson(item);
      _queue.add(mediaItem);
    }
  }

  Future<void> _setAudioSession() async {
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration.music());
  }

  void _propogateEventsFromAudioPlayerToAudioServiceClients() {
    _eventSubscription = _player.playbackEventStream.listen((event) {
      _broadcastState();
    });
  }

  void _performSpecialProcessingForStateTransistions() {
    _player.processingStateStream.listen((state) {
      switch (state) {
        case ProcessingState.completed:
          onStop();
          break;
        case ProcessingState.ready:
          _skipState = null;
          break;
        default:
          break;
      }
    });
  }

  Future<void> _loadQueue() async {
    AudioServiceBackground.setQueue(queue);
    try {
      await _player.load(ConcatenatingAudioSource(
        children:
            queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
      ));
      _player.durationStream.listen((duration) {
        _updateQueueWithCurrentDuration(duration);
      });
      onPlay();
    } catch (e) {
      print('Error: $e');
      onStop();
    }
  }

  void _updateQueueWithCurrentDuration(Duration duration) {
    final songIndex = _player.playbackEvent.currentIndex;
    print('current index: $songIndex, duration: $duration');
    final modifiedMediaItem = mediaItem.copyWith(duration: duration);
    _queue[songIndex] = modifiedMediaItem;
    AudioServiceBackground.setMediaItem(_queue[songIndex]);
    AudioServiceBackground.setQueue(_queue);
  }

  @override
  Future<void> onSkipToQueueItem(String mediaId) async {
    final newIndex = queue.indexWhere((item) => item.id == mediaId);
    if (newIndex == -1) return;
    _skipState = newIndex > index
        ? AudioProcessingState.skippingToNext
        : AudioProcessingState.skippingToPrevious;
    _player.seek(Duration.zero, index: newIndex);
  }

  @override
  Future<void> onPlay() => _player.play();

  @override
  Future<void> onPause() => _player.pause();

  @override
  Future<void> onSeekTo(Duration position) => _player.seek(position);

  @override
  Future<void> onFastForward() => _seekRelative(fastForwardInterval);

  @override
  Future<void> onRewind() => _seekRelative(-rewindInterval);

  @override
  Future<void> onStop() async {
    await _player.dispose();
    _eventSubscription.cancel();
    await _broadcastState();
    await super.onStop();
  }

  /// Jumps away from the current position by [offset].
  Future<void> _seekRelative(Duration offset) async {
    var newPosition = _player.position + offset;
    if (newPosition < Duration.zero) newPosition = Duration.zero;
    if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
    await _player.seek(newPosition);
  }

  /// Broadcasts the current state to all clients.
  Future<void> _broadcastState() async {
    await AudioServiceBackground.setState(
      controls: [
        MediaControl.skipToPrevious,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.skipToNext,
      ],
      androidCompactActions: [0, 1, 2],
      processingState: _getProcessingState(),
      playing: _player.playing,
      position: _player.position,
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
    );
  }

  /// Maps just_audio's processing state into into audio_service's playing
  /// state. If we are in the middle of a skip, we use [_skipState] instead.
  AudioProcessingState _getProcessingState() {
    if (_skipState != null) return _skipState;
    switch (_player.processingState) {
      case ProcessingState.none:
        return AudioProcessingState.stopped;
      case ProcessingState.loading:
        return AudioProcessingState.connecting;
      case ProcessingState.buffering:
        return AudioProcessingState.buffering;
      case ProcessingState.ready:
        return AudioProcessingState.ready;
      case ProcessingState.completed:
        return AudioProcessingState.completed;
      default:
        throw Exception("Invalid state: ${_player.processingState}");
    }
  }
}

然后在我的状态管理课上我得到了一个流AudioService像这样:

Stream<AudioPlayerState> get mediaStateStream =>
    Rx.combineLatest2<Duration, MediaItem, AudioPlayerState>(
        AudioService.positionStream,
        AudioService.currentMediaItemStream,
        (position, mediaItem) => AudioPlayerState(position, mediaItem.duration));

AudioPlayerState 是哪里

class AudioPlayerState {
  const AudioPlayerState(this.currentTime, this.totalTime);
  final Duration currentTime;
  final Duration totalTime;

  const AudioPlayerState.initial() : this(Duration.zero, Duration.zero);
}

我用了一个StreamBuilder在 Flutter UI 中收听mediaStateStream并更新我的音频播放器搜索栏小部件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用audio_service和just_audio在Flutter中查找当前歌曲时长 的相关文章

随机推荐