【问题标题】:How to pass and play specific queue position media item from playlist in audio_service flutter?如何在audio_service颤动中从播放列表中传递和播放特定的队列位置媒体项目?
【发布时间】:2021-05-06 08:35:53
【问题描述】:

我正在为音乐播放器使用颤振 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}");
    }
  }
}

【问题讨论】:

    标签: flutter audio just-audio


    【解决方案1】:

    在 audio_service 0.17 中,传递给 start()params 仅用于简单数据类型,不适用于 MediaItems 列表。实际上,API 中还有其他专门为此设计的方法。

    我建议改为以下启动顺序:

    // Set the playlist
    await AudioService.updateQueue(playlist);
    // Jump to the right item
    await AudioService.skipToQueueItem(...);
    // Play
    AudioService.play(); // don't await!
    

    注意:如果您使用的是 0.18.0 或更高版本,请将 AudioService. 替换为 audioHandler.

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

    在您的后台音频任务 (0.17) 或音频处理程序 (0.18) 中,添加 updateQueue 的回调:

    // 0.17 solution:
    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(),
    ));
    // 0.18 solution:
    Future<void> updateQueue(List<MediaItem> queue) async {
      this.queue.add(_queue = queue);
      await _player.setAudioSource(ConcatenatingAudioSource(
        children:
            queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
    ));
    }
    

    您已经有一个onStart,但请记住,使用上面建议的启动顺序,队列将在后面的步骤中设置,播放器将在后面的步骤中跳到正确的队列项,因此您可以删除onStart 中的那些部分,只保留初始化事件侦听器的代码。 (在 0.18 中,该逻辑将进入您的音频处理程序构造函数)。

    【讨论】:

    • 工作。但有一件事,当我调用 AudioService.skipToNext 或 AudioService.skipToPrevious 时,它并没有跳到那个位置。
    • 这些方法的默认实现假定您的onSkipToQueueItem 实现将通过AudioServiceBackground.setMediaItem 广播当前媒体项,我刚刚注意到您的实现没有这样做。我相信如果您添加它应该可以工作。
    • 我通过取消注释我在onStart() 上写的_player.currentIndexStream.listen() 修复了AudioService.skipToNextAudioService.skipToPreviou 问题。一切都很好,但为什么启动 AudioService 需要 3/4 秒的时间?他们有更好的方法来更快地启动服务吗?
    • 启动服务本身会有一些延迟,但很可能大部分延迟发生在您开始播放音频之后,因为它需要从 URL 获取数据并缓冲足够的时间才能开始播放开始播放。如果是这种情况,您可以通过确保媒体针对流式传输进行优化来改善延迟。在 iOS 上,缓冲音频的延迟可能会更长。如果您遇到这种情况,请考虑在 just_audio 上打开功能请求,以提供控制缓冲行为的选项。
    • 增加播放列表中的项目,增加第一次播放项目的延迟。我觉得在服务中加载播放列表需要花费很多时间。
    猜你喜欢
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-10
    • 2015-10-12
    相关资源
    最近更新 更多