✨ Endless playback
This commit is contained in:
		@@ -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/auth.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/palette.dart';
 | 
			
		||||
import 'package:rhythm_box/providers/scrobbler.dart';
 | 
			
		||||
@@ -92,6 +93,7 @@ class MyApp extends StatelessWidget {
 | 
			
		||||
    Get.put(AudioPlayerProvider());
 | 
			
		||||
    Get.put(ActiveSourcedTrackProvider());
 | 
			
		||||
    Get.put(AudioPlayerStreamProvider());
 | 
			
		||||
    Get.put(EndlessPlaybackProvider());
 | 
			
		||||
 | 
			
		||||
    Get.put(PlaybackHistoryProvider());
 | 
			
		||||
    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),
 | 
			
		||||
              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(
 | 
			
		||||
                () => SwitchListTile(
 | 
			
		||||
                  contentPadding: const EdgeInsets.symmetric(horizontal: 24),
 | 
			
		||||
 
 | 
			
		||||
@@ -63,8 +63,16 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
 | 
			
		||||
          idx,
 | 
			
		||||
          preferPosition: AutoScrollPosition.middle,
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (_lyric!.lyrics.isNotEmpty) {
 | 
			
		||||
      _autoScrollController.scrollToIndex(
 | 
			
		||||
        0,
 | 
			
		||||
        preferPosition: AutoScrollPosition.begin,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -77,7 +85,9 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
 | 
			
		||||
      _playback.state.listen((value) {
 | 
			
		||||
        if (value.activeTrack == null) return;
 | 
			
		||||
        if (value.activeTrack!.id != _activeTrackId) {
 | 
			
		||||
          _pullLyrics();
 | 
			
		||||
          _pullLyrics().then((_) {
 | 
			
		||||
            _syncLyricsProgress();
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }),
 | 
			
		||||
    ];
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user