#flutter #audio #queue #just-audio
#флаттер #Аудио #очередь #просто-аудио
Вопрос:
Я использую flutter audio_service (https://pub.dev/packages/audio_service ) и just_audio (https://pub.dev/packages/just_audio ) для воспроизведения аудиофайлов на переднем плане и в фоновом режиме.
Я подкласс BackgroundAudioTask, добавил экземпляр AudioPlayer и переопределил необходимые методы. Например, я обновил режим повтора:
@override
Future<void> onSetRepeatMode(AudioServiceRepeatMode repeatMode) async {
super.onSetRepeatMode(repeatMode);
switch (repeatMode)
{
case AudioServiceRepeatMode.all:
_audioPlayer.setLoopMode(LoopMode.all);
break;
case AudioServiceRepeatMode.none:
_audioPlayer.setLoopMode(LoopMode.off);
break;
case AudioServiceRepeatMode.one:
_audioPlayer.setLoopMode(LoopMode.one);
break;
case AudioServiceRepeatMode.group:
_audioPlayer.setLoopMode(LoopMode.all);
break;
}
}
@override
Future<void> onPlayFromMediaId(String mediaId) async {
await _audioPlayer.stop();
// Get queue index by mediaId.
_queueIndex = _queue.indexWhere((test) => test.id == mediaId);
// Set url source to _audioPlayer.
downloadAndPlay(_mediaItem);
}
Я пытаюсь добавить список воспроизведения и создать цикл аудиофайлов, но, похоже, чего-то не хватает, и после воспроизведения дорожки № 1 проигрыватель пытается запустить ту же дорожку № 1 и воспроизводит ее снова.
Вот мой код (часть downloadAndPlay) :
var list=List<AudioSource>();
for (int t=0;t<_queue.length;t )
{
var mi=_queue[t];
var url = mi.extras['source'];
list.add(AudioSource.uri(Uri.parse(url)));
}
D("Loading list with ${list.length} items");
D("Seeking to index : $_queueIndex");
await _audioPlayer.load(ConcatenatingAudioSource(children: list),
initialIndex: _queueIndex, initialPosition: Duration.zero);
AudioService.updateQueue(_queue);
AudioService.setRepeatMode(AudioServiceRepeatMode.all);
_audioPlayer.play();
Я добавил это, чтобы проверить, сработал ли ProcessingState.completed , что означает, что он достиг конца дорожки, но не срабатывает:
playerEventSubscription = _audioPlayer.playbackEventStream.listen((event) {
D("audioPlayerTask: playbackEventStream : ${event.processingState}");
switch (event.processingState) {
case ProcessingState.ready:
_setState(state: AudioProcessingState.ready);
break;
case ProcessingState.buffering:
_setState(state: AudioProcessingState.buffering);
break;
case ProcessingState.completed:
_handlePlaybackCompleted();
break;
default:
break;
}
});
Комментарии:
1. Вы должны либо дождаться завершения «загрузки» перед поиском (используя await), либо, что еще лучше, передать начальный индекс поиска и позицию в метод «load» (подробности см. В документации). Все эти методы являются асинхронными, поэтому, если вы хотите, чтобы они выполнялись по порядку, вам следует дождаться их завершения. Единственным исключением является «воспроизведение», для завершения которого потребуется много времени, и вам, вероятно, не нужно ждать его завершения. Если вы в остальном пишете правильный код, но зацикливание все еще не работает, это ошибка, и вы должны сообщить об этом.
2. Я добавил await и обновил приведенный выше код. Но все равно он не переходит к следующему треку после завершения воспроизведения текущего трека. @RyanHeise
Ответ №1:
Изнутри вашего BackgroundAudioTask
подкласса вы должны переопределить onSetRepeatMode
метод.
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
class AudioPlayerTask extends BackgroundAudioTask {
AudioPlayer _player = new AudioPlayer();
...
@override
Future<void> onSetRepeatMode(AudioServiceRepeatMode repeatMode) async {
switch (repeatMode) {
case AudioServiceRepeatMode.none:
await _player.setLoopMode(LoopMode.off);
break;
case AudioServiceRepeatMode.one:
await _player.setLoopMode(LoopMode.one);
break;
case AudioServiceRepeatMode.all:
case AudioServiceRepeatMode.group:
await _player.setLoopMode(LoopMode.all);
break;
}
}
}
Эта конкретная реализация использует just_audio в качестве аудиоплеера. Обратите внимание, что AudioPlayerTask
это единственный класс, в котором AudioPlayer
отображается just_audio. Его не должно быть ни в одном другом классе вашего приложения. Везде вы будете использовать AudioService
класс audio_service.
Например, когда пользователь нажимает кнопку, вы можете запустить следующую строку кода:
AudioService.setRepeatMode(AudioServiceRepeatMode.none);
Это уведомит систему, а также позволит BackgroundAudioTask
выполнить onSetRepeatMode
, чтобы внутренний AudioPlayer
класс мог делать свое дело.
Комментарии:
1. Спасибо за простой пример!
Ответ №2:
Вы никогда не вызывали _audioPlayer.setLoopMode()
, поэтому он не будет зацикливаться.
Вы также используете audio_service, но ваш аудиокод, похоже, наполовину находится внутри, а наполовину вне audio_service. В частности, ваш экземпляр _audioPlayer
находится за пределами audio_service, в то время как ваша реализация setRepeatMode
находится внутри audio_service и не будет иметь доступа к вашему _audioPlayer
экземпляру, который находится за пределами audio_service.
Инкапсулируйте всю вашу звуковую логику в audio_service, чтобы ваша setRepeatMode
реализация могла ссылаться на ваш _audioPlayer
экземпляр и вызывать _audioPlayer.setLoopMode()
его. На самом деле это та самая причина, по которой мы инкапсулируем (связываем методы с данными, с которыми они должны работать), но в этом случае есть еще одна причина, по которой мы делаем это в audio_service, и это то, что все, что находится за пределами «капсулы» audio_service, может быть уничтожено в любое время. Поэтому, если ваше приложение переходит в фоновый режим и ваш пользовательский интерфейс разрушается, вы не хотите, чтобы половина вашей звуковой логики была уничтожена. Если вы инкапсулируете все это в audio_service, он сможет пережить разрушение пользовательского интерфейса и, поскольку он самодостаточен, будет иметь все необходимое для продолжения воспроизведения звука в фоновом режиме.
Кроме того, тот факт, что ваше приложение может также захотеть воспроизводить аудио на переднем плане, не означает, что вам нужно нарушать эту инкапсуляцию. Этот инкапсулированный аудиокод прекрасно может работать как с пользовательским интерфейсом, так и без него, и поэтому вам на самом деле не нужно менять свой стиль программирования, чтобы поддерживать регистр переднего плана.
Комментарии:
1. Я недостаточно поделился, но _audioPlayer находится в пределах и степени BackgroundAudioTask. И я переопределяю там onSetRepeatMode (обновлено в коде выше). Но все равно не работает!
2. Существует блок кода, в котором вы используете клиентский API (например, AudioService.updateQueue) для отправки сообщения «от» клиента «к » фоновой задаче. Тот же самый блок кода (который находится на стороне клиента) ссылается на переменную с именем _audioPlayer . Следовательно, этот аудиоплеер находится на стороне клиента, а не в фоновой задаче. Вы никогда не вызывали _audioPlayer.setLoopMode() на этом проигрывателе. Если этот блок на самом деле находится в фоновой задаче, это не похоже. Он использует клиентские API, и поэтому код также не будет иметь смысла в этой интерпретации. Вам нужно четко разделить два слоя.
3. Я думаю, что нашел проблему: AudioService.currentMediaItemStream не запускается, когда трек завершен, и он начинает воспроизведение следующего трека.
4. Как я объяснил выше, у вас, похоже, есть две разные переменные _audioPlayer, одна внутри фоновой задачи, а другая снаружи, и вы все смешиваете, загружая одну из них с источником мультимедиа, но настраивая другую в режиме цикла. Если это не так, вам нужно ответить на мой предыдущий комментарий и объяснить, находится ли рассматриваемый блок кода внутри или вне фоновой аудиозадачи, и если внутри, почему он использует клиентские API, которые следует использовать только извне.
5. Нет, сэр, я не использую два аудиоплеера, только один, который находится внутри подкласса BackgroundAudioTask, точно так, как вы заказали 🙂 Но AudioService.currentMediaItemStream ничего не добавляет, когда трек автоматически переходит к следующему.
Ответ №3:
Способ, которым я это сделал, заключался в том, чтобы просто ввести метод OnStart:
await _player.setLoopMode(LoopMode.all);
Это, конечно, не может быть изменено пользователем.