微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在audio_service flutter中从播放列表传递和播放特定队列位置的媒体项目?

如何解决如何在audio_service flutter中从播放列表传递和播放特定队列位置的媒体项目?

我正在为音乐播放器使用 Flutter audio_servicejust_audio 包。我想在初始化音乐播放器时播放播放列表中特定队列位置的媒体项目。当我调用 AudioService.start() 方法时,它总是播放播放列表的第一项。启动音频服务时,如何从播放列表中传递和播放特定队列位置的媒体项目?

音频服务启动

AudioService.start(
        backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,androidNotificationChannelName: 'Zenmind',androidNotificationColor: 0xFF2196f3,androidNotificationIcon: 'mipmap/ic_launcher',androidEnableQueue: true,params: params); // [params contains playlist ] 

_audioPlayerTaskEntrypoint 代码

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

AudioPlayerTask 类

class AudioPlayerTask extends BackgroundAudioTask {
  var _queue = <MediaItem>[];
  AudioPlayer _player = new AudioPlayer();
  AudioProcessingState _skipState;
  Seeker _seeker;
  StreamSubscription<PlaybackEvent> _eventSubscription;

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

  @override
  Future<void> onStart(Map<String,dynamic> params) async {
    _queue.clear();
    List mediaItems = params['data'];
    // print(params['data']);
    for (int i = 0; i < mediaItems.length; i++) {
      MediaItem mediaItem = MediaItem.fromJson(mediaItems[i]);
      _queue.add(mediaItem);
    }

    _player.currentIndexStream.listen((index) {
      print("index value is $index");
      if (index != null) {
        AudioServiceBackground.setMediaItem(queue[index]);
      }
    });
    _eventSubscription = _player.playbackEventStream.listen((event) {
      _broadcastState();
    });

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

    AudioServiceBackground.setQueue(queue);
    try {
      await _player.setAudioSource(ConcatenatingAudioSource(
        children:
            queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),));
      onSkipToQueueItem(queue[1].id);
      onPlay();
    } catch (e) {
      print("Error: $e");
      onStop();
    }
  }

  @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);
    AudioServiceBackground.sendCustomEvent('skip to $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> onSeekForward(bool begin) async => _seekContinuously(begin,1);

  @override
  Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin,-1);

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

  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;
    // if (newPosition > _player.duration) newPosition = _player.duration;
    await _player.seek(newPosition);
  }

  void _seekContinuously(bool begin,int direction) {
    _seeker?.stop();
    if (begin) {
      _seeker = Seeker(
          _player,Duration(seconds: 10 * direction),// Duration(seconds: 1),mediaItem)
          Duration(seconds: 1),queue[_player.currentIndex])
        ..start();
    }
  }

  Future<void> _broadcastState() async {
    await AudioServiceBackground.setState(
      controls: [
        MediaControl.skipToPrevIoUs,if (_player.playing) MediaControl.pause else MediaControl.play,MediaControl.stop,MediaControl.skipToNext,],systemActions: [
        MediaAction.seekTo,MediaAction.seekForward,MediaAction.seekBackward,androidCompactActions: [0,1,3],processingState: _getProcessingState(),playing: _player.playing,position: _player.position,bufferedPosition: _player.bufferedPosition,speed: _player.speed,);
  }

  AudioProcessingState _getProcessingState() {
    if (_skipState != null) return _skipState;
    switch (_player.processingState) {
      case ProcessingState.idle:
        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}");
    }
  }
}

解决方法

paramsstart 参数仅限于简单数据类型,并且无法传入 MediaItem 列表。相反,您可以通过以下启动顺序启动音频服务:

// Just start the service
await AudioService.start(....);
// Set the playlist
await AudioService.updateQueue(playlist);
// Jump to the right item
await AudioService.skipToQueueItem(playlist[startIndex].id);
// Play
AudioService.play(); // don't await!

await 关键字很重要。这些方法是异步的,在前面的方法完成之前不应调用后面的方法。例如,在队列实际设置之后 之前,您不想跳到特定的队列项。但请注意最后一步缺少 await:除非您想等待播放完成,否则您不会等待 play 调用。

适当地定义您的后台音频任务回调:onStartonUpdateQueueonSkipToQueueItemonPlay。您的 onSkipToQueueItemonPlay 回调看起来不错。但你需要onUpdateQueue

Future<void> onUpdateQueue(List<MediaItem> queue) async {
  AudioServiceBackground.setQueue(_queue = queue);
  await _player.setAudioSource(ConcatenatingAudioSource(
    children:
        queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),));
}

您已经有了onStart,但请记住,使用上面建议的启动顺序,队列将在后面的步骤中设置,而玩家将在后面的步骤中跳到正确的队列项,因此您可以从您的 onStart 中删除这些部分,并保留初始化事件侦听器的代码。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。