mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-03 01:28:37 +02:00
198 lines
5.9 KiB
Dart
198 lines
5.9 KiB
Dart
import 'package:audio_service/audio_service.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
|
import 'package:spotiflac_android/services/library_database.dart';
|
|
import 'package:spotiflac_android/services/music_player_service.dart';
|
|
|
|
final currentMediaItemProvider = StreamProvider<MediaItem?>((ref) {
|
|
return musicPlayerMediaItemEvents();
|
|
});
|
|
|
|
final playbackStateProvider = StreamProvider<PlaybackState>((ref) {
|
|
return musicPlayerPlaybackStateEvents();
|
|
});
|
|
|
|
/// Small derived providers keep position ticks from rebuilding widgets that
|
|
/// only care about transport state (and vice versa).
|
|
final playbackPositionProvider = Provider<Duration>((ref) {
|
|
return ref.watch(
|
|
playbackStateProvider.select(
|
|
(state) => state.value?.position ?? Duration.zero,
|
|
),
|
|
);
|
|
});
|
|
|
|
final playbackPlayingProvider = Provider<bool>((ref) {
|
|
return ref.watch(
|
|
playbackStateProvider.select((state) => state.value?.playing ?? false),
|
|
);
|
|
});
|
|
|
|
final playbackLoadingProvider = Provider<bool>((ref) {
|
|
return ref.watch(
|
|
playbackStateProvider.select((state) {
|
|
final processingState = state.value?.processingState;
|
|
return processingState == AudioProcessingState.loading ||
|
|
processingState == AudioProcessingState.buffering;
|
|
}),
|
|
);
|
|
});
|
|
|
|
final playQueueProvider = StreamProvider<List<MediaItem>>((ref) {
|
|
return musicPlayerQueueEvents();
|
|
});
|
|
|
|
class MusicPlayerController {
|
|
const MusicPlayerController();
|
|
|
|
MusicPlayerHandler? get _handler => musicPlayerHandler;
|
|
|
|
bool get isAvailable => _handler != null;
|
|
|
|
Future<MusicPlayerHandler?> ensureInitialized() async {
|
|
try {
|
|
return await initMusicPlayer();
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> playAll(
|
|
List<PlayableMedia> items, {
|
|
int initialIndex = 0,
|
|
}) async {
|
|
final handler = await ensureInitialized();
|
|
await handler?.setQueueAndPlay(items, initialIndex: initialIndex);
|
|
}
|
|
|
|
Future<void> playSingle(PlayableMedia item) => playAll([item]);
|
|
|
|
Future<void> playHistory(
|
|
List<DownloadHistoryItem> items, {
|
|
int initialIndex = 0,
|
|
}) async {
|
|
final media = items
|
|
.where((i) => i.filePath.trim().isNotEmpty)
|
|
.map(playableFromHistory)
|
|
.toList();
|
|
if (media.isEmpty) return;
|
|
await playAll(media, initialIndex: initialIndex.clamp(0, media.length - 1));
|
|
}
|
|
|
|
Future<void> playLocal(
|
|
List<LocalLibraryItem> items, {
|
|
int initialIndex = 0,
|
|
}) async {
|
|
final media = items
|
|
.where((i) => i.filePath.trim().isNotEmpty)
|
|
.map(playableFromLocal)
|
|
.toList();
|
|
if (media.isEmpty) return;
|
|
await playAll(media, initialIndex: initialIndex.clamp(0, media.length - 1));
|
|
}
|
|
|
|
Future<void> play() async => _handler?.play();
|
|
Future<void> pause() async => _handler?.pause();
|
|
Future<void> stop() async => _handler?.stop();
|
|
Future<void> seek(Duration position) async => _handler?.seek(position);
|
|
Future<void> next() async => _handler?.skipToNext();
|
|
Future<void> previous() async => _handler?.skipToPrevious();
|
|
|
|
Future<void> togglePlayPause(bool isPlaying) async {
|
|
final handler = _handler;
|
|
if (handler == null) return;
|
|
final processingState = handler.playbackState.value.processingState;
|
|
if (processingState == AudioProcessingState.loading ||
|
|
processingState == AudioProcessingState.buffering) {
|
|
return;
|
|
}
|
|
if (isPlaying) {
|
|
await handler.pause();
|
|
} else {
|
|
await handler.play();
|
|
}
|
|
}
|
|
|
|
Future<void> setShuffle(bool enabled) async {
|
|
await _handler?.setShuffleMode(
|
|
enabled ? AudioServiceShuffleMode.all : AudioServiceShuffleMode.none,
|
|
);
|
|
}
|
|
|
|
Future<void> setRepeatMode(AudioServiceRepeatMode mode) async =>
|
|
_handler?.setRepeatMode(mode);
|
|
|
|
Future<void> playNext(PlayableMedia item) async =>
|
|
(await ensureInitialized())?.enqueue(item, playNext: true);
|
|
|
|
Future<void> addToQueue(PlayableMedia item) async =>
|
|
(await ensureInitialized())?.enqueue(item);
|
|
|
|
Future<void> playNextHistory(DownloadHistoryItem item) async =>
|
|
playNext(playableFromHistory(item));
|
|
|
|
Future<void> addToQueueHistory(DownloadHistoryItem item) async =>
|
|
addToQueue(playableFromHistory(item));
|
|
|
|
Future<void> playNextLocal(LocalLibraryItem item) async =>
|
|
playNext(playableFromLocal(item));
|
|
|
|
Future<void> addToQueueLocal(LocalLibraryItem item) async =>
|
|
addToQueue(playableFromLocal(item));
|
|
|
|
Future<void> jumpTo(int index) async => _handler?.skipToQueueItem(index);
|
|
|
|
void moveQueueItem(int oldIndex, int newIndex) {
|
|
_handler?.moveQueueItem(oldIndex, newIndex);
|
|
}
|
|
}
|
|
|
|
final musicPlayerControllerProvider = Provider<MusicPlayerController>(
|
|
(ref) => const MusicPlayerController(),
|
|
);
|
|
|
|
PlayableMedia playableFromHistory(DownloadHistoryItem item) {
|
|
return PlayableMedia(
|
|
id: item.id,
|
|
source: item.filePath,
|
|
title: item.trackName,
|
|
artist: item.artistName,
|
|
album: item.albumName,
|
|
artUri: (item.coverUrl != null && item.coverUrl!.trim().isNotEmpty)
|
|
? item.coverUrl
|
|
: null,
|
|
duration: (item.duration != null && item.duration! > 0)
|
|
? Duration(seconds: item.duration!)
|
|
: null,
|
|
bitDepth: item.bitDepth,
|
|
sampleRate: item.sampleRate,
|
|
bitrate: item.bitrate,
|
|
format: item.format,
|
|
);
|
|
}
|
|
|
|
PlayableMedia playableFromLocal(LocalLibraryItem item) {
|
|
String? art;
|
|
final cover = item.coverPath;
|
|
if (cover != null && cover.trim().isNotEmpty) {
|
|
art = cover.startsWith('http') || cover.startsWith('content://')
|
|
? cover
|
|
: Uri.file(cover).toString();
|
|
}
|
|
return PlayableMedia(
|
|
id: item.id,
|
|
source: item.filePath,
|
|
title: item.trackName,
|
|
artist: item.artistName,
|
|
album: item.albumName,
|
|
artUri: art,
|
|
duration: (item.duration != null && item.duration! > 0)
|
|
? Duration(seconds: item.duration!)
|
|
: null,
|
|
bitDepth: item.bitDepth,
|
|
sampleRate: item.sampleRate,
|
|
bitrate: item.bitrate,
|
|
format: item.format,
|
|
);
|
|
}
|