import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; 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'; import 'package:ocarina/util/util.dart'; /// 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 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 => _player.currentIndex == null || _player.currentIndex == -1 ? null : _songList[_player.currentIndex!]; final _queue = ConcatenatingAudioSource( children: [], shuffleOrder: DefaultShuffleOrder(), ); /// Seeks to the next song Future next() async { await _player.seekToNext(); } /// Previous Future previous() async { await _player.seekToPrevious(); } /// Pauses playback Future pause() async { await _player.pause(); logger.d("Paused"); } /// Resumes playback void resume() { _player.play(); logger.d("Playing ${_player.currentIndex}"); } /// Sets color scheme from image Future _setColorScheme() async { if (AudioPlayerService().song == null) { themeNotifier.value = ColorScheme.fromSeed(seedColor: Colors.deepPurple); } themeNotifier.value = await ColorScheme.fromImageProvider( provider: CachedNetworkImageProvider( AudioPlayerService().song!.coverArtUrl, ), ); logger.d(AudioPlayerService().song!.coverArtUrl); } final List _songList = []; /// Adds a selected song to the queue Future addToQueue(Song song) async { final doCache = sp.getBool("doCache") ?? true; _songList.add(song); await _queue.add( doCache ? LockCachingAudioSource( Uri.parse(song.streamUrl), tag: MediaItem( id: song.id, title: song.title, album: song.albumName, artist: song.artistName, artUri: Uri.parse(song.coverArtUrl), ), ) : AudioSource.uri( Uri.parse(song.streamUrl), tag: MediaItem( id: song.id, title: song.title, album: song.albumName, artist: song.artistName, artUri: Uri.parse(song.coverArtUrl), ), ), ); } /// 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(); } }