如何在 audio_service flutter 中传递和播放播放列表中的特定队列位置媒体项?
How to pass and play specific queue position media item from playlist in audio_service flutter?
我正在为音乐播放器使用 flutter audio_service and just_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
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}");
}
}
}
在 audio_service 0.17 中,传入 start()
的 params
仅适用于简单数据类型,不适用于 MediaItem
列表。事实上,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 中,该逻辑将进入您的音频处理程序构造函数)。
我正在为音乐播放器使用 flutter audio_service and just_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
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}");
}
}
}
在 audio_service 0.17 中,传入 start()
的 params
仅适用于简单数据类型,不适用于 MediaItem
列表。事实上,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 中,该逻辑将进入您的音频处理程序构造函数)。