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 /// The [AudioPlayer] instance
final _player = AudioPlayer(); 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 /// True if [AudioPlayer] instance is playing
bool get isPlaying => _player.playing; bool get isPlaying => _player.playing;
/// Currently playing song /// Currently playing song
/// ///
/// Null if no song is loaded /// 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) { final _queue = ConcatenatingAudioSource(
_song = s; children: [],
logger.d("CHANGE song"); shuffleOrder: DefaultShuffleOrder(),
songNotifier.value = s; );
_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 /// Pauses playback
Future<void> pause() async { Future<void> pause() async {
@ -47,7 +68,7 @@ class AudioPlayerService {
/// Resumes playback /// Resumes playback
void resume() { void resume() {
_player.play(); _player.play();
logger.d("Playing"); logger.d("Playing ${_player.currentIndex}");
} }
/// Sets color scheme from image /// Sets color scheme from image
@ -63,15 +84,16 @@ class AudioPlayerService {
logger.d(AudioPlayerService().song!.coverArtUrl); logger.d(AudioPlayerService().song!.coverArtUrl);
} }
/// Plays the passed [Song] as a file final List<Song> _songList = [];
Future<void> playFile({Song? song}) async {
/// Adds a selected song to the queue
Future<void> addToQueue(Song song) async {
final doCache = sp.getBool("doCache") ?? true; final doCache = sp.getBool("doCache") ?? true;
if (song == null && this.song == null) return; _songList.add(song);
song ??= this.song; await _queue.add(
await _player.setAudioSource(
doCache doCache
? LockCachingAudioSource( ? LockCachingAudioSource(
Uri.parse(song!.streamUrl), Uri.parse(song.streamUrl),
tag: MediaItem( tag: MediaItem(
id: song.id, id: song.id,
title: song.title, title: song.title,
@ -81,7 +103,7 @@ class AudioPlayerService {
), ),
) )
: AudioSource.uri( : AudioSource.uri(
Uri.parse(song!.streamUrl), Uri.parse(song.streamUrl),
tag: MediaItem( tag: MediaItem(
id: song.id, id: song.id,
title: song.title, 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(); playerKey.currentState?.update();
resume(); resume();
} }

View file

@ -14,6 +14,7 @@ void main() async {
await JustAudioBackground.init( await JustAudioBackground.init(
androidNotificationChannelId: 'wtf.caras.ocarina.bg_playback', androidNotificationChannelId: 'wtf.caras.ocarina.bg_playback',
androidNotificationChannelName: 'Ocarina playback', androidNotificationChannelName: 'Ocarina playback',
androidNotificationIcon: "mipmap/ic_launcher_monochrome",
androidNotificationOngoing: true, androidNotificationOngoing: true,
); );

View file

@ -53,9 +53,11 @@ class _AlbumViewState extends State<AlbumView> {
child: ListView.builder( child: ListView.builder(
itemBuilder: (c, i) => InkWell( itemBuilder: (c, i) => InkWell(
onTap: () async { onTap: () async {
AudioPlayerService().song = _songs[i];
playerKey.currentState?.update(); 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(() {}); setState(() {});
}, },

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:grouped_list/grouped_list.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/login_manager.dart';
import 'package:ocarina/api/subsonic/artistindex.dart'; import 'package:ocarina/api/subsonic/artistindex.dart';
import 'package:ocarina/api/subsonic/subsonic.dart'; import 'package:ocarina/api/subsonic/subsonic.dart';
@ -21,6 +22,7 @@ class _HomeViewState extends State<HomeView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
AudioPlayerService().setup();
authenticate(); authenticate();
} }

View file

@ -165,9 +165,17 @@ class PlayerState extends State<Player> {
), ),
), ),
SizedBox( SizedBox(
width: 30.w, width: 35.w,
child: Row( child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
IconButton(
onPressed: () {
AudioPlayerService().previous();
},
icon: const Icon(Icons.skip_previous),
),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
if (AudioPlayerService().song == if (AudioPlayerService().song ==
@ -193,6 +201,12 @@ class PlayerState extends State<Player> {
const Duration(milliseconds: 300), const Duration(milliseconds: 300),
), ),
), ),
IconButton(
onPressed: () {
AudioPlayerService().next();
},
icon: const Icon(Icons.skip_next),
),
], ],
), ),
), ),