2024-05-27 21:46:18 +02:00
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
2024-05-24 00:10:11 +02:00
|
|
|
import 'package:flutter/material.dart';
|
2024-05-23 19:14:08 +02:00
|
|
|
import 'package:just_audio/just_audio.dart';
|
|
|
|
import 'package:just_audio_background/just_audio_background.dart';
|
|
|
|
import 'package:ocarina/api/subsonic/song.dart';
|
|
|
|
import 'package:ocarina/main.dart';
|
2024-05-24 00:10:11 +02:00
|
|
|
import 'package:ocarina/util/util.dart';
|
2024-05-23 19:14:08 +02:00
|
|
|
|
|
|
|
/// Service used to control the audio player
|
|
|
|
class AudioPlayerService {
|
|
|
|
/// Service used to control the audio player
|
|
|
|
factory AudioPlayerService() {
|
|
|
|
return _audioPlayerService;
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioPlayerService._internal();
|
|
|
|
static final AudioPlayerService _audioPlayerService =
|
|
|
|
AudioPlayerService._internal();
|
|
|
|
|
|
|
|
/// The [AudioPlayer] instance
|
2024-05-24 00:10:11 +02:00
|
|
|
final _player = AudioPlayer();
|
|
|
|
|
2024-05-27 23:20:10 +02:00
|
|
|
/// 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;
|
|
|
|
}
|
|
|
|
|
2024-05-24 00:10:11 +02:00
|
|
|
/// True if [AudioPlayer] instance is playing
|
|
|
|
bool get isPlaying => _player.playing;
|
2024-05-23 19:14:08 +02:00
|
|
|
|
|
|
|
/// Currently playing song
|
|
|
|
///
|
|
|
|
/// Null if no song is loaded
|
2024-05-27 23:20:10 +02:00
|
|
|
Song? get song => _player.currentIndex == null || _player.currentIndex == -1
|
|
|
|
? null
|
|
|
|
: _songList[_player.currentIndex!];
|
2024-05-24 00:10:11 +02:00
|
|
|
|
2024-05-27 23:20:10 +02:00
|
|
|
final _queue = ConcatenatingAudioSource(
|
|
|
|
children: [],
|
|
|
|
shuffleOrder: DefaultShuffleOrder(),
|
|
|
|
);
|
2024-05-24 00:10:11 +02:00
|
|
|
|
2024-05-27 23:20:10 +02:00
|
|
|
/// Seeks to the next song
|
|
|
|
Future<void> next() async {
|
|
|
|
await _player.seekToNext();
|
2024-05-24 00:10:11 +02:00
|
|
|
}
|
|
|
|
|
2024-05-27 23:20:10 +02:00
|
|
|
/// Previous
|
|
|
|
Future<void> previous() async {
|
|
|
|
await _player.seekToPrevious();
|
|
|
|
}
|
2024-05-24 00:10:11 +02:00
|
|
|
|
|
|
|
/// Pauses playback
|
|
|
|
Future<void> pause() async {
|
|
|
|
await _player.pause();
|
|
|
|
logger.d("Paused");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resumes playback
|
|
|
|
void resume() {
|
|
|
|
_player.play();
|
2024-05-27 23:20:10 +02:00
|
|
|
logger.d("Playing ${_player.currentIndex}");
|
2024-05-24 00:10:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets color scheme from image
|
|
|
|
Future<void> _setColorScheme() async {
|
|
|
|
if (AudioPlayerService().song == null) {
|
|
|
|
themeNotifier.value = ColorScheme.fromSeed(seedColor: Colors.deepPurple);
|
|
|
|
}
|
|
|
|
themeNotifier.value = await ColorScheme.fromImageProvider(
|
2024-05-27 21:46:18 +02:00
|
|
|
provider: CachedNetworkImageProvider(
|
2024-05-24 00:10:11 +02:00
|
|
|
AudioPlayerService().song!.coverArtUrl,
|
2024-05-28 18:54:05 +02:00
|
|
|
cacheKey: AudioPlayerService().song!.coverArtId,
|
2024-05-24 00:10:11 +02:00
|
|
|
),
|
|
|
|
);
|
|
|
|
logger.d(AudioPlayerService().song!.coverArtUrl);
|
|
|
|
}
|
2024-05-23 19:14:08 +02:00
|
|
|
|
2024-05-27 23:20:10 +02:00
|
|
|
final List<Song> _songList = [];
|
|
|
|
|
|
|
|
/// Adds a selected song to the queue
|
|
|
|
Future<void> addToQueue(Song song) async {
|
2024-05-23 19:14:08 +02:00
|
|
|
final doCache = sp.getBool("doCache") ?? true;
|
2024-05-27 23:20:10 +02:00
|
|
|
_songList.add(song);
|
|
|
|
await _queue.add(
|
2024-05-23 19:14:08 +02:00
|
|
|
doCache
|
|
|
|
? LockCachingAudioSource(
|
2024-05-27 23:20:10 +02:00
|
|
|
Uri.parse(song.streamUrl),
|
2024-05-23 19:14:08 +02:00
|
|
|
tag: MediaItem(
|
|
|
|
id: song.id,
|
|
|
|
title: song.title,
|
|
|
|
album: song.albumName,
|
|
|
|
artist: song.artistName,
|
|
|
|
artUri: Uri.parse(song.coverArtUrl),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
: AudioSource.uri(
|
2024-05-27 23:20:10 +02:00
|
|
|
Uri.parse(song.streamUrl),
|
2024-05-23 19:14:08 +02:00
|
|
|
tag: MediaItem(
|
|
|
|
id: song.id,
|
|
|
|
title: song.title,
|
|
|
|
album: song.albumName,
|
|
|
|
artist: song.artistName,
|
|
|
|
artUri: Uri.parse(song.coverArtUrl),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2024-05-27 23:20:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
2024-05-28 18:47:01 +02:00
|
|
|
initialIndex: queuePast.length,
|
2024-05-27 23:20:10 +02:00
|
|
|
initialPosition: Duration.zero,
|
|
|
|
);
|
|
|
|
|
2024-05-24 00:10:11 +02:00
|
|
|
playerKey.currentState?.update();
|
|
|
|
resume();
|
2024-05-23 19:14:08 +02:00
|
|
|
}
|
|
|
|
}
|