From 052f89890b453b464cd6e7c18af9327e9c8135f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Mon, 27 May 2024 23:20:10 +0200 Subject: [PATCH] feat: implement just_audio queue system --- lib/api/audio/audioplayer_service.dart | 126 +++++++++++++++++++++---- lib/main.dart | 1 + lib/views/album_view.dart | 6 +- lib/views/home_view.dart | 2 + lib/widgets/player.dart | 16 +++- 5 files changed, 132 insertions(+), 19 deletions(-) diff --git a/lib/api/audio/audioplayer_service.dart b/lib/api/audio/audioplayer_service.dart index 3cd21bb..ca6ab99 100644 --- a/lib/api/audio/audioplayer_service.dart +++ b/lib/api/audio/audioplayer_service.dart @@ -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 next() async { + await _player.seekToNext(); } - Song? _song; + /// Previous + Future previous() async { + await _player.seekToPrevious(); + } /// Pauses playback Future 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 playFile({Song? song}) async { + final List _songList = []; + + /// Adds a selected song to the queue + Future 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 playNow({ + List queueNext = const [], + List queuePast = const [], + }) async { + if (queueNext.isEmpty) return; + final doCache = sp.getBool("doCache") ?? true; + await _queue.clear(); + _songList + ..clear() + ..addAll(queueNext); + + final q = List.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.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(); } diff --git a/lib/main.dart b/lib/main.dart index 4ee5e23..17f0771 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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, ); diff --git a/lib/views/album_view.dart b/lib/views/album_view.dart index 489bc9c..0fb2877 100644 --- a/lib/views/album_view.dart +++ b/lib/views/album_view.dart @@ -53,9 +53,11 @@ class _AlbumViewState extends State { 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(() {}); }, diff --git a/lib/views/home_view.dart b/lib/views/home_view.dart index 5accb30..6aba11f 100644 --- a/lib/views/home_view.dart +++ b/lib/views/home_view.dart @@ -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 { @override void initState() { super.initState(); + AudioPlayerService().setup(); authenticate(); } diff --git a/lib/widgets/player.dart b/lib/widgets/player.dart index ebc5acd..101aa5b 100644 --- a/lib/widgets/player.dart +++ b/lib/widgets/player.dart @@ -165,9 +165,17 @@ class PlayerState extends State { ), ), 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 { const Duration(milliseconds: 300), ), ), + IconButton( + onPressed: () { + AudioPlayerService().next(); + }, + icon: const Icon(Icons.skip_next), + ), ], ), ),