nkbmusic/lib/audio/player_providers.dart
metatroncubeswdev 178d2d00ab Nanga Kathai: Flutter app, self-hosted backend and song tools
- Flutter app (Android/iOS): home, explore, albums, search, player with
  equalizer, mini player, sign up/sign in with email codes, profile,
  encrypted offline downloads, About page
- server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx
  (or Caddy), migrations, backups, email templates
- supabase/migrations: schema, row-level security, profiles
- tools/: song scanner and importer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:39:26 -04:00

107 lines
4.0 KiB
Dart

import 'package:audio_service/audio_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/models.dart';
import '../data/catalog_repository.dart';
import '../offline/download_store.dart';
import 'nk_audio_handler.dart';
/// Created in main() by AudioService.init and injected with an override.
final audioHandlerProvider = Provider<NkAudioHandler>(
(ref) => throw UnimplementedError('audioHandlerProvider must be overridden in main()'),
);
final mediaItemProvider = StreamProvider<MediaItem?>((ref) => ref.watch(audioHandlerProvider).mediaItem);
final playbackStateProvider =
StreamProvider<PlaybackState>((ref) => ref.watch(audioHandlerProvider).playbackState);
final queueProvider = StreamProvider<List<MediaItem>>((ref) => ref.watch(audioHandlerProvider).queue);
final positionProvider = StreamProvider<Duration>((ref) => ref.watch(audioHandlerProvider).positionStream);
/// The catalog track behind the current media item, if any.
final currentTrackProvider = Provider<Track?>((ref) {
final id = ref.watch(mediaItemProvider).value?.id;
final catalog = ref.watch(catalogProvider).value;
if (id == null || catalog == null) return null;
return catalog.trackById(id);
});
final playerControllerProvider = Provider<PlayerController>((ref) => PlayerController(ref));
class PlayerController {
PlayerController(this._ref);
final Ref _ref;
NkAudioHandler get _handler => _ref.read(audioHandlerProvider);
MediaItem toMediaItem(Track t) {
final catalog = _ref.read(catalogProvider).requireValue;
return MediaItem(
id: t.id,
title: t.titleLatin,
artist: catalog.creditLine(t),
album: t.occasion.label,
duration: t.duration == Duration.zero ? null : t.duration,
artUri: t.coverUrl == null ? null : Uri.parse(t.coverUrl!),
extras: {'url': t.audioUrl, 'coverAsset': t.coverAsset, 'occasion': t.occasion.id},
);
}
Future<void> playTracks(List<Track> tracks, {int index = 0, bool shuffle = false}) async {
await _handler.setShuffleMode(AudioServiceShuffleMode.none);
await _handler.playQueue(tracks.map(toMediaItem).toList(), index: index);
if (shuffle) await _handler.setShuffleMode(AudioServiceShuffleMode.all);
}
/// Plays downloaded songs from their saved details, so it works with no internet.
Future<void> playDownloads(List<DownloadEntry> entries, {int index = 0}) async {
await _handler.setShuffleMode(AudioServiceShuffleMode.none);
await _handler.playQueue([
for (final e in entries)
MediaItem(
id: e.trackId,
title: e.title,
artist: e.credit,
album: e.album,
duration: e.durationMs == 0 ? null : Duration(milliseconds: e.durationMs),
extras: {'url': '', 'occasion': e.occasion},
),
], index: index);
}
void download(Iterable<Track> tracks) {
final catalog = _ref.read(catalogProvider).requireValue;
final store = _ref.read(downloadStoreProvider);
for (final t in tracks) {
store.enqueue(t, credit: catalog.creditLine(t), album: catalog.albumOf(t)?.title);
}
}
Future<void> playNext(Track t) => _handler.playNext(toMediaItem(t));
Future<void> addToQueue(Track t) => _handler.addQueueItem(toMediaItem(t));
Future<void> togglePlay() {
final playing = _ref.read(playbackStateProvider).value?.playing ?? false;
return playing ? _handler.pause() : _handler.play();
}
Future<void> toggleShuffle() {
final on = _ref.read(playbackStateProvider).value?.shuffleMode != AudioServiceShuffleMode.none;
return _handler.setShuffleMode(on ? AudioServiceShuffleMode.none : AudioServiceShuffleMode.all);
}
/// Off -> all -> one -> off.
Future<void> cycleRepeat() {
final mode = _ref.read(playbackStateProvider).value?.repeatMode ?? AudioServiceRepeatMode.none;
return _handler.setRepeatMode(switch (mode) {
AudioServiceRepeatMode.none => AudioServiceRepeatMode.all,
AudioServiceRepeatMode.all || AudioServiceRepeatMode.group => AudioServiceRepeatMode.one,
AudioServiceRepeatMode.one => AudioServiceRepeatMode.none,
});
}
}