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
|
/// 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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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(() {});
|
||||||
},
|
},
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue