feat: implement just_audio queue system

This commit is contained in:
Matyáš Caras 2024-05-27 23:20:10 +02:00
parent 648cbf97a7
commit 052f89890b
Signed by: hernik
GPG key ID: 2A3175F98820C5C6
5 changed files with 132 additions and 19 deletions

View file

@ -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();
}

View file

@ -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,
);

View file

@ -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(() {});
},

View file

@ -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();
}

View file

@ -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),
),
],
),
),