feat: implement just_audio queue system
This commit is contained in:
parent
648cbf97a7
commit
052f89890b
5 changed files with 132 additions and 19 deletions
|
@ -20,23 +20,44 @@ class AudioPlayerService {
|
|||
/// The [AudioPlayer] instance
|
||||
final _player = AudioPlayer();
|
||||
|
||||
/// Check if listeners are set up
|
||||
var _setUp = false;
|
||||
|
||||
/// Registers listeners on the player
|
||||
void setup() {
|
||||
if (_setUp) return;
|
||||
_player.currentIndexStream.listen((index) {
|
||||
logger.d("Done fired");
|
||||
songNotifier.value = index == null ? null : _songList[index];
|
||||
_setColorScheme();
|
||||
});
|
||||
_setUp = true;
|
||||
}
|
||||
|
||||
/// True if [AudioPlayer] instance is playing
|
||||
bool get isPlaying => _player.playing;
|
||||
|
||||
/// Currently playing song
|
||||
///
|
||||
/// Null if no song is loaded
|
||||
Song? get song => _song;
|
||||
Song? get song => _player.currentIndex == null || _player.currentIndex == -1
|
||||
? null
|
||||
: _songList[_player.currentIndex!];
|
||||
|
||||
set song(Song? s) {
|
||||
_song = s;
|
||||
logger.d("CHANGE song");
|
||||
songNotifier.value = s;
|
||||
final _queue = ConcatenatingAudioSource(
|
||||
children: [],
|
||||
shuffleOrder: DefaultShuffleOrder(),
|
||||
);
|
||||
|
||||
_setColorScheme();
|
||||
/// Seeks to the next song
|
||||
Future<void> next() async {
|
||||
await _player.seekToNext();
|
||||
}
|
||||
|
||||
Song? _song;
|
||||
/// Previous
|
||||
Future<void> previous() async {
|
||||
await _player.seekToPrevious();
|
||||
}
|
||||
|
||||
/// Pauses playback
|
||||
Future<void> pause() async {
|
||||
|
@ -47,7 +68,7 @@ class AudioPlayerService {
|
|||
/// Resumes playback
|
||||
void resume() {
|
||||
_player.play();
|
||||
logger.d("Playing");
|
||||
logger.d("Playing ${_player.currentIndex}");
|
||||
}
|
||||
|
||||
/// Sets color scheme from image
|
||||
|
@ -63,15 +84,16 @@ class AudioPlayerService {
|
|||
logger.d(AudioPlayerService().song!.coverArtUrl);
|
||||
}
|
||||
|
||||
/// Plays the passed [Song] as a file
|
||||
Future<void> playFile({Song? song}) async {
|
||||
final List<Song> _songList = [];
|
||||
|
||||
/// Adds a selected song to the queue
|
||||
Future<void> addToQueue(Song song) async {
|
||||
final doCache = sp.getBool("doCache") ?? true;
|
||||
if (song == null && this.song == null) return;
|
||||
song ??= this.song;
|
||||
await _player.setAudioSource(
|
||||
_songList.add(song);
|
||||
await _queue.add(
|
||||
doCache
|
||||
? LockCachingAudioSource(
|
||||
Uri.parse(song!.streamUrl),
|
||||
Uri.parse(song.streamUrl),
|
||||
tag: MediaItem(
|
||||
id: song.id,
|
||||
title: song.title,
|
||||
|
@ -81,7 +103,7 @@ class AudioPlayerService {
|
|||
),
|
||||
)
|
||||
: AudioSource.uri(
|
||||
Uri.parse(song!.streamUrl),
|
||||
Uri.parse(song.streamUrl),
|
||||
tag: MediaItem(
|
||||
id: song.id,
|
||||
title: song.title,
|
||||
|
@ -91,7 +113,79 @@ class AudioPlayerService {
|
|||
),
|
||||
),
|
||||
);
|
||||
await _player.seek(Duration.zero);
|
||||
}
|
||||
|
||||
/// Plays the passed [Song], clearing the queue
|
||||
Future<void> playNow({
|
||||
List<Song> queueNext = const <Song>[],
|
||||
List<Song> queuePast = const <Song>[],
|
||||
}) async {
|
||||
if (queueNext.isEmpty) return;
|
||||
final doCache = sp.getBool("doCache") ?? true;
|
||||
await _queue.clear();
|
||||
_songList
|
||||
..clear()
|
||||
..addAll(queueNext);
|
||||
|
||||
final q = List<AudioSource>.generate(
|
||||
queuePast.length,
|
||||
(i) => doCache
|
||||
? LockCachingAudioSource(
|
||||
Uri.parse(queuePast[i].streamUrl),
|
||||
tag: MediaItem(
|
||||
id: queuePast[i].id,
|
||||
title: queuePast[i].title,
|
||||
album: queuePast[i].albumName,
|
||||
artist: queuePast[i].artistName,
|
||||
artUri: Uri.parse(queuePast[i].coverArtUrl),
|
||||
),
|
||||
)
|
||||
: AudioSource.uri(
|
||||
Uri.parse(queuePast[i].streamUrl),
|
||||
tag: MediaItem(
|
||||
id: queuePast[i].id,
|
||||
title: queuePast[i].title,
|
||||
album: queuePast[i].albumName,
|
||||
artist: queuePast[i].artistName,
|
||||
artUri: Uri.parse(queuePast[i].coverArtUrl),
|
||||
),
|
||||
),
|
||||
)..addAll(
|
||||
List<AudioSource>.generate(
|
||||
queueNext.length,
|
||||
(i) => doCache
|
||||
? LockCachingAudioSource(
|
||||
Uri.parse(queueNext[i].streamUrl),
|
||||
tag: MediaItem(
|
||||
id: queueNext[i].id,
|
||||
title: queueNext[i].title,
|
||||
album: queueNext[i].albumName,
|
||||
artist: queueNext[i].artistName,
|
||||
artUri: Uri.parse(queueNext[i].coverArtUrl),
|
||||
),
|
||||
)
|
||||
: AudioSource.uri(
|
||||
Uri.parse(queueNext[i].streamUrl),
|
||||
tag: MediaItem(
|
||||
id: queueNext[i].id,
|
||||
title: queueNext[i].title,
|
||||
album: queueNext[i].albumName,
|
||||
artist: queueNext[i].artistName,
|
||||
artUri: Uri.parse(queueNext[i].coverArtUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await _queue.addAll(
|
||||
q,
|
||||
);
|
||||
await _player.setAudioSource(
|
||||
_queue,
|
||||
initialIndex: queuePast.isEmpty ? 0 : queuePast.length - 1,
|
||||
initialPosition: Duration.zero,
|
||||
);
|
||||
|
||||
playerKey.currentState?.update();
|
||||
resume();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ void main() async {
|
|||
await JustAudioBackground.init(
|
||||
androidNotificationChannelId: 'wtf.caras.ocarina.bg_playback',
|
||||
androidNotificationChannelName: 'Ocarina playback',
|
||||
androidNotificationIcon: "mipmap/ic_launcher_monochrome",
|
||||
androidNotificationOngoing: true,
|
||||
);
|
||||
|
||||
|
|
|
@ -53,9 +53,11 @@ class _AlbumViewState extends State<AlbumView> {
|
|||
child: ListView.builder(
|
||||
itemBuilder: (c, i) => InkWell(
|
||||
onTap: () async {
|
||||
AudioPlayerService().song = _songs[i];
|
||||
playerKey.currentState?.update();
|
||||
await AudioPlayerService().playFile();
|
||||
await AudioPlayerService().playNow(
|
||||
queueNext: _songs.getRange(i, _songs.length).toList(),
|
||||
queuePast: (i == 0) ? [] : _songs.getRange(0, i).toList(),
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grouped_list/grouped_list.dart';
|
||||
import 'package:ocarina/api/audio/audioplayer_service.dart';
|
||||
import 'package:ocarina/api/login_manager.dart';
|
||||
import 'package:ocarina/api/subsonic/artistindex.dart';
|
||||
import 'package:ocarina/api/subsonic/subsonic.dart';
|
||||
|
@ -21,6 +22,7 @@ class _HomeViewState extends State<HomeView> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
AudioPlayerService().setup();
|
||||
authenticate();
|
||||
}
|
||||
|
||||
|
|
|
@ -165,9 +165,17 @@ class PlayerState extends State<Player> {
|
|||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 30.w,
|
||||
width: 35.w,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
AudioPlayerService().previous();
|
||||
},
|
||||
icon: const Icon(Icons.skip_previous),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
if (AudioPlayerService().song ==
|
||||
|
@ -193,6 +201,12 @@ class PlayerState extends State<Player> {
|
|||
const Duration(milliseconds: 300),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
AudioPlayerService().next();
|
||||
},
|
||||
icon: const Icon(Icons.skip_next),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue