Endless playback

This commit is contained in:
LittleSheep 2024-08-29 23:03:41 +08:00
parent 3ca01ef147
commit d80a398a23
4 changed files with 127 additions and 1 deletions

View File

@ -7,6 +7,7 @@ import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/audio_player_stream.dart'; import 'package:rhythm_box/providers/audio_player_stream.dart';
import 'package:rhythm_box/providers/auth.dart'; import 'package:rhythm_box/providers/auth.dart';
import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/endless_playback.dart';
import 'package:rhythm_box/providers/history.dart'; import 'package:rhythm_box/providers/history.dart';
import 'package:rhythm_box/providers/palette.dart'; import 'package:rhythm_box/providers/palette.dart';
import 'package:rhythm_box/providers/scrobbler.dart'; import 'package:rhythm_box/providers/scrobbler.dart';
@ -92,6 +93,7 @@ class MyApp extends StatelessWidget {
Get.put(AudioPlayerProvider()); Get.put(AudioPlayerProvider());
Get.put(ActiveSourcedTrackProvider()); Get.put(ActiveSourcedTrackProvider());
Get.put(AudioPlayerStreamProvider()); Get.put(AudioPlayerStreamProvider());
Get.put(EndlessPlaybackProvider());
Get.put(PlaybackHistoryProvider()); Get.put(PlaybackHistoryProvider());
Get.put(SegmentsProvider()); Get.put(SegmentsProvider());

View File

@ -0,0 +1,103 @@
import 'dart:async';
import 'dart:developer';
import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/auth.dart';
import 'package:rhythm_box/providers/spotify.dart';
import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:spotify/spotify.dart';
class EndlessPlaybackProvider extends GetxController {
late final _auth = Get.find<AuthenticationProvider>();
late final _playback = Get.find<AudioPlayerProvider>();
late final _spotify = Get.find<SpotifyProvider>().api;
late final _preferences = Get.find<UserPreferencesProvider>();
bool get isEndlessPlayback => _preferences.state.value.endlessPlayback;
late final StreamSubscription _subscription;
StreamSubscription? _idxSubscription;
@override
void onInit() {
super.onInit();
_initPlayback();
_subscription = _preferences.state.listen((value) {
if (value.endlessPlayback && _idxSubscription == null) {
_initPlayback();
} else if (!value.endlessPlayback && _idxSubscription != null) {
_idxSubscription!.cancel();
_idxSubscription = null;
}
});
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _initPlayback() {
if (!isEndlessPlayback || _auth.auth.value == null) return;
void listener(int index) async {
try {
final playState = _playback.state.value;
if (index != playState.tracks.length - 1) return;
final track = playState.tracks.last;
final query = '${track.name} Radio';
final pages = await _spotify.search
.get(query, types: [SearchType.playlist]).first();
final radios = pages
.expand((e) => e.items?.toList() ?? <PlaylistSimple>[])
.toList()
.cast<PlaylistSimple>();
final artists = track.artists!.map((e) => e.name);
final radio = radios.firstWhere(
(e) {
final validPlaylists =
artists.where((a) => e.description!.contains(a!));
return e.name == '${track.name} Radio' &&
(validPlaylists.length >= 2 ||
validPlaylists.length == artists.length) &&
e.owner?.displayName != 'Spotify';
},
orElse: () => radios.first,
);
final tracks =
await _spotify.playlists.getTracksByPlaylistId(radio.id!).all();
await _playback.addTracks(
tracks.toList()
..removeWhere((e) {
final isDuplicate =
_playback.state.value.tracks.any((t) => t.id == e.id);
return e.id == track.id || isDuplicate;
}),
);
} catch (e, stack) {
log('[EndlessPlayback] Error: $e; Trace:\n$stack');
}
}
if (_playback.state.value.playlist.index ==
_playback.state.value.playlist.medias.length - 1 &&
_playback.isPlaying.value) {
listener(_playback.state.value.playlist.index);
}
_idxSubscription = audioPlayer.currentIndexChangedStream.listen(listener);
}
}

View File

@ -100,6 +100,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
}), }),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Obx(
() => SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
secondary: const Icon(Icons.all_inclusive),
title: const Text('Endless Playback'),
subtitle: const Text(
'Automatically get more recommendation for you after your queue finish playing'),
value: _preferences.state.value.endlessPlayback,
onChanged: _preferences.setEndlessPlayback,
),
),
Obx( Obx(
() => SwitchListTile( () => SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),

View File

@ -63,8 +63,16 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
idx, idx,
preferPosition: AutoScrollPosition.middle, preferPosition: AutoScrollPosition.middle,
); );
return;
} }
} }
if (_lyric!.lyrics.isNotEmpty) {
_autoScrollController.scrollToIndex(
0,
preferPosition: AutoScrollPosition.begin,
);
}
} }
@override @override
@ -77,7 +85,9 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
_playback.state.listen((value) { _playback.state.listen((value) {
if (value.activeTrack == null) return; if (value.activeTrack == null) return;
if (value.activeTrack!.id != _activeTrackId) { if (value.activeTrack!.id != _activeTrackId) {
_pullLyrics(); _pullLyrics().then((_) {
_syncLyricsProgress();
});
} }
}), }),
]; ];