✨ Endless playback
This commit is contained in:
parent
3ca01ef147
commit
d80a398a23
@ -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());
|
||||||
|
103
lib/providers/endless_playback.dart
Normal file
103
lib/providers/endless_playback.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user