feat: show loading when track metadata is being fetched, android, ios, macos enable shuffling

This commit is contained in:
Kingkor Roy Tirtho 2023-05-27 13:34:25 +06:00
parent a0744630ba
commit bf59570251
11 changed files with 449 additions and 434 deletions

View File

@ -82,9 +82,7 @@ Future<void> main(List<String> rawArgs) async {
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
if (DesktopTools.platform.isWindows || DesktopTools.platform.isLinux) { MediaKit.ensureInitialized();
MediaKit.ensureInitialized();
}
await DesktopTools.ensureInitialized( await DesktopTools.ensureInitialized(
DesktopWindowOptions( DesktopWindowOptions(

View File

@ -31,10 +31,10 @@ class PlayerView extends HookConsumerWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final auth = ref.watch(AuthenticationNotifier.provider); final auth = ref.watch(AuthenticationNotifier.provider);
final currentTrack = ref.watch(ProxyPlaylistNotifier.provider.select( final currentTrack = ref.watch(ProxyPlaylistNotifier.provider.select(
(value) => value?.activeTrack, (value) => value.activeTrack,
)); ));
final isLocalTrack = ref.watch(ProxyPlaylistNotifier.provider.select( final isLocalTrack = ref.watch(ProxyPlaylistNotifier.provider.select(
(value) => value?.activeTrack is LocalTrack, (value) => value.activeTrack is LocalTrack,
)); ));
final breakpoint = useBreakpoints(); final breakpoint = useBreakpoints();

View File

@ -20,12 +20,12 @@ import 'package:spotube/utils/type_conversion_utils.dart';
/// * [x] Prefetch next track as [SpotubeTrack] on 80% of current track /// * [x] Prefetch next track as [SpotubeTrack] on 80% of current track
/// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack] /// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack]
/// * [ ] Modification of the Queue /// * [ ] Modification of the Queue
/// * [ ] Add track at the end /// * [x] Add track at the end
/// * [ ] Add track at the beginning /// * [ ] Add track at the beginning
/// * [ ] Remove track /// * [x] Remove track
/// * [ ] Reorder track /// * [ ] Reorder track
/// * [ ] Caching and loading of cache of tracks /// * [ ] Caching and loading of cache of tracks
/// * [ ] Shuffling /// * [x] Shuffling
/// * [x] loop => playlist, track, none /// * [x] loop => playlist, track, none
/// * [ ] Alternative Track Source /// * [ ] Alternative Track Source
/// * [x] Blacklisting of tracks and artist /// * [x] Blacklisting of tracks and artist
@ -65,30 +65,19 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
return; return;
} }
print('=============== Active Track Changed ===============');
print('Current tracks: ${state.tracks.map((e) => e.name).toList()}');
print('newIndexedTrack: ${newActiveTrack.name}');
notificationService.addTrack(newActiveTrack); notificationService.addTrack(newActiveTrack);
state = state.copyWith( state = state.copyWith(
active: state.tracks active: state.tracks
.toList() .toList()
.indexWhere((element) => element.id == newActiveTrack.id), .indexWhere((element) => element.id == newActiveTrack.id),
); );
print('New active: ${state.active}');
print('=============== ----- ===============');
if (preferences.albumColorSync) { if (preferences.albumColorSync) {
updatePalette(); updatePalette();
} }
}); });
audioPlayer.shuffledStream.listen((event) { audioPlayer.shuffledStream.listen((event) {
print('=============== Shuffled ===============');
print('oldTracks: ${state.tracks.map((e) => e.name).toList()}');
final newlyOrderedTracks = mapSourcesToTracks(audioPlayer.sources); final newlyOrderedTracks = mapSourcesToTracks(audioPlayer.sources);
print( print(
@ -98,10 +87,6 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
(element) => element.id == state.activeTrack?.id, (element) => element.id == state.activeTrack?.id,
); );
print('newActiveIndex $newActiveIndex');
print('=============== ----- ===============');
if (newActiveIndex == -1) return; if (newActiveIndex == -1) return;
state = state.copyWith( state = state.copyWith(
@ -157,14 +142,10 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
} }
Future<SpotubeTrack?> ensureSourcePlayable(String source) async { Future<SpotubeTrack?> ensureSourcePlayable(String source) async {
print("======== Ensure Source Playable =========");
print("source: $source");
if (isPlayable(source)) return null; if (isPlayable(source)) return null;
final track = mapSourcesToTracks([source]).firstOrNull; final track = mapSourcesToTracks([source]).firstOrNull;
print("nthTrack: ${track?.name}");
if (track == null || track is LocalTrack) { if (track == null || track is LocalTrack) {
return null; return null;
} }
@ -174,8 +155,6 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
_ => await SpotubeTrack.fetchFromTrack(track, preferences), _ => await SpotubeTrack.fetchFromTrack(track, preferences),
}; };
print("======== ----- =========");
await audioPlayer.replaceSource( await audioPlayer.replaceSource(
source, source,
nthFetchedTrack.ytUri, nthFetchedTrack.ytUri,
@ -256,9 +235,14 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
Future<void> jumpTo(int index) async { Future<void> jumpTo(int index) async {
final oldTrack = final oldTrack =
mapSourcesToTracks([audioPlayer.currentSource!]).firstOrNull; mapSourcesToTracks([audioPlayer.currentSource!]).firstOrNull;
state = state.copyWith(active: index);
await audioPlayer.pause();
final track = await ensureSourcePlayable(audioPlayer.sources[index]); final track = await ensureSourcePlayable(audioPlayer.sources[index]);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(
tracks: mergeTracks([track], state.tracks),
active: index,
);
} }
await audioPlayer.jumpTo(index); await audioPlayer.jumpTo(index);
@ -300,9 +284,20 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
Future<void> next() async { Future<void> next() async {
if (audioPlayer.nextSource == null) return; if (audioPlayer.nextSource == null) return;
final oldTrack = mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull; final oldTrack = mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
state = state.copyWith(
active: state.tracks
.toList()
.indexWhere((element) => element.id == oldTrack?.id),
);
await audioPlayer.pause();
final track = await ensureSourcePlayable(audioPlayer.nextSource!); final track = await ensureSourcePlayable(audioPlayer.nextSource!);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(
tracks: mergeTracks([track], state.tracks),
active: state.tracks
.toList()
.indexWhere((element) => element.id == track.id),
);
} }
await audioPlayer.skipToNext(); await audioPlayer.skipToNext();
@ -318,9 +313,20 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
if (audioPlayer.previousSource == null) return; if (audioPlayer.previousSource == null) return;
final oldTrack = final oldTrack =
mapSourcesToTracks([audioPlayer.previousSource!]).firstOrNull; mapSourcesToTracks([audioPlayer.previousSource!]).firstOrNull;
state = state.copyWith(
active: state.tracks
.toList()
.indexWhere((element) => element.id == oldTrack?.id),
);
await audioPlayer.pause();
final track = await ensureSourcePlayable(audioPlayer.previousSource!); final track = await ensureSourcePlayable(audioPlayer.previousSource!);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(
tracks: mergeTracks([track], state.tracks),
active: state.tracks
.toList()
.indexWhere((element) => element.id == track.id),
);
} }
await audioPlayer.skipToPrevious(); await audioPlayer.skipToPrevious();
if (oldTrack != null && track != null) { if (oldTrack != null && track != null) {

View File

@ -1,7 +1,6 @@
import 'package:catcher/catcher.dart'; import 'package:catcher/catcher.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:spotube/services/audio_player/mk_state_player.dart'; import 'package:spotube/services/audio_player/mk_state_player.dart';
import 'package:just_audio/just_audio.dart' as ja; // import 'package:just_audio/just_audio.dart' as ja;
import 'dart:async'; import 'dart:async';
import 'package:media_kit/media_kit.dart' as mk; import 'package:media_kit/media_kit.dart' as mk;
@ -14,37 +13,38 @@ part 'audio_players_streams_mixin.dart';
part 'audio_player_impl.dart'; part 'audio_player_impl.dart';
abstract class AudioPlayerInterface { abstract class AudioPlayerInterface {
final MkPlayerWithState? _mkPlayer; final MkPlayerWithState _mkPlayer;
final ja.AudioPlayer? _justAudio; // final ja.AudioPlayer? _justAudio;
AudioPlayerInterface() AudioPlayerInterface() : _mkPlayer = MkPlayerWithState()
: _mkPlayer = _mkSupportedPlatform ? MkPlayerWithState() : null, // _mkPlayer = _mkSupportedPlatform ? MkPlayerWithState() : null,
_justAudio = !_mkSupportedPlatform ? ja.AudioPlayer() : null { // _justAudio = !_mkSupportedPlatform ? ja.AudioPlayer() : null
_mkPlayer?.streams.error.listen((event) { {
_mkPlayer.streams.error.listen((event) {
Catcher.reportCheckedError(event, StackTrace.current); Catcher.reportCheckedError(event, StackTrace.current);
}); });
} }
/// Whether the current platform supports the audioplayers plugin /// Whether the current platform supports the audioplayers plugin
static final bool _mkSupportedPlatform = static const bool _mkSupportedPlatform = true;
DesktopTools.platform.isWindows || DesktopTools.platform.isLinux; // DesktopTools.platform.isWindows || DesktopTools.platform.isLinux;
bool get mkSupportedPlatform => _mkSupportedPlatform; bool get mkSupportedPlatform => _mkSupportedPlatform;
Future<Duration?> get duration async { Future<Duration?> get duration async {
if (mkSupportedPlatform) { return _mkPlayer.state.duration;
return _mkPlayer!.state.duration; // if (mkSupportedPlatform) {
} else { // } else {
return _justAudio!.duration; // return _justAudio!.duration;
} // }
} }
Future<Duration?> get position async { Future<Duration?> get position async {
if (mkSupportedPlatform) { return _mkPlayer.state.position;
return _mkPlayer!.state.position; // if (mkSupportedPlatform) {
} else { // } else {
return _justAudio!.position; // return _justAudio!.position;
} // }
} }
Future<Duration?> get bufferedPosition async { Future<Duration?> get bufferedPosition async {
@ -57,78 +57,87 @@ abstract class AudioPlayerInterface {
} }
bool get hasSource { bool get hasSource {
if (mkSupportedPlatform) { return _mkPlayer.playlist.medias.isNotEmpty;
return _mkPlayer!.playlist.medias.isNotEmpty; // if (mkSupportedPlatform) {
} else { // return _mkPlayer.playlist.medias.isNotEmpty;
return _justAudio!.audioSource != null; // } else {
} // return _justAudio!.audioSource != null;
// }
} }
// states // states
bool get isPlaying { bool get isPlaying {
if (mkSupportedPlatform) { return _mkPlayer.state.playing;
return _mkPlayer!.state.playing; // if (mkSupportedPlatform) {
} else { // return _mkPlayer.state.playing;
return _justAudio!.playing; // } else {
} // return _justAudio!.playing;
// }
} }
bool get isPaused { bool get isPaused {
if (mkSupportedPlatform) { return !_mkPlayer.state.playing;
return !_mkPlayer!.state.playing; // if (mkSupportedPlatform) {
} else { // return !_mkPlayer.state.playing;
return !isPlaying; // } else {
} // return !isPlaying;
// }
} }
bool get isStopped { bool get isStopped {
if (mkSupportedPlatform) { return !hasSource;
return !hasSource; // if (mkSupportedPlatform) {
} else { // return !hasSource;
return _justAudio!.processingState == ja.ProcessingState.idle; // } else {
} // return _justAudio!.processingState == ja.ProcessingState.idle;
// }
} }
Future<bool> get isCompleted async { Future<bool> get isCompleted async {
if (mkSupportedPlatform) { return _mkPlayer.state.completed;
return _mkPlayer!.state.completed; // if (mkSupportedPlatform) {
} else { // return _mkPlayer.state.completed;
return _justAudio!.processingState == ja.ProcessingState.completed; // } else {
} // return _justAudio!.processingState == ja.ProcessingState.completed;
// }
} }
Future<bool> get isShuffled async { Future<bool> get isShuffled async {
if (mkSupportedPlatform) { return _mkPlayer.shuffled;
return _mkPlayer!.shuffled; // if (mkSupportedPlatform) {
} else { // return _mkPlayer.shuffled;
return _justAudio!.shuffleModeEnabled; // } else {
} // return _justAudio!.shuffleModeEnabled;
// }
} }
Future<PlaybackLoopMode> get loopMode async { Future<PlaybackLoopMode> get loopMode async {
if (mkSupportedPlatform) { return PlaybackLoopMode.fromPlaylistMode(_mkPlayer.loopMode);
return PlaybackLoopMode.fromPlaylistMode(_mkPlayer!.loopMode); // if (mkSupportedPlatform) {
} else { // return PlaybackLoopMode.fromPlaylistMode(_mkPlayer.loopMode);
return PlaybackLoopMode.fromLoopMode(_justAudio!.loopMode); // } else {
} // return PlaybackLoopMode.fromLoopMode(_justAudio!.loopMode);
// }
} }
/// Returns the current volume of the player, between 0 and 1 /// Returns the current volume of the player, between 0 and 1
double get volume { double get volume {
if (mkSupportedPlatform) { return _mkPlayer.state.volume / 100;
return _mkPlayer!.state.volume / 100; // if (mkSupportedPlatform) {
} else { // return _mkPlayer.state.volume / 100;
return _justAudio!.volume; // } else {
} // return _justAudio!.volume;
// }
} }
bool get isBuffering { bool get isBuffering {
if (mkSupportedPlatform) { return false;
// audioplayers doesn't have the capability to get buffering state // if (mkSupportedPlatform) {
return false; // // audioplayers doesn't have the capability to get buffering state
} else { // return false;
return _justAudio!.processingState == ja.ProcessingState.buffering || // } else {
_justAudio!.processingState == ja.ProcessingState.loading; // return _justAudio!.processingState == ja.ProcessingState.buffering ||
} // _justAudio!.processingState == ja.ProcessingState.loading;
// }
} }
} }

View File

@ -5,15 +5,15 @@ final audioPlayer = SpotubeAudioPlayer();
class SpotubeAudioPlayer extends AudioPlayerInterface class SpotubeAudioPlayer extends AudioPlayerInterface
with SpotubeAudioPlayersStreams { with SpotubeAudioPlayersStreams {
Object _resolveUrlType(String url) { Object _resolveUrlType(String url) {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return mk.Media(url); return mk.Media(url);
} else { // } else {
if (url.startsWith("https")) { // if (url.startsWith("https")) {
return ja.AudioSource.uri(Uri.parse(url)); // return ja.AudioSource.uri(Uri.parse(url));
} else { // } else {
return ja.AudioSource.file(url); // return ja.AudioSource.file(url);
} // }
} // }
} }
Future<void> preload(String url) async { Future<void> preload(String url) async {
@ -29,63 +29,63 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
Future<void> play(String url) async { Future<void> play(String url) async {
final urlType = _resolveUrlType(url); final urlType = _resolveUrlType(url);
if (mkSupportedPlatform && urlType is mk.Media) { // if (mkSupportedPlatform && urlType is mk.Media) {
await _mkPlayer?.open(urlType, play: true); await _mkPlayer.open(urlType as mk.Media, play: true);
} else { // } else {
if (_justAudio?.audioSource is ja.ProgressiveAudioSource && // if (_justAudio?.audioSource is ja.ProgressiveAudioSource &&
(_justAudio?.audioSource as ja.ProgressiveAudioSource) // (_justAudio?.audioSource as ja.ProgressiveAudioSource)
.uri // .uri
.toString() == // .toString() ==
url) { // url) {
await _justAudio?.play(); // await _justAudio?.play();
} else { // } else {
await _justAudio?.stop(); // await _justAudio?.stop();
await _justAudio?.setAudioSource( // await _justAudio?.setAudioSource(
urlType as ja.AudioSource, // urlType as ja.AudioSource,
preload: true, // preload: true,
); // );
await _justAudio?.play(); // await _justAudio?.play();
} // }
} // }
} }
Future<void> pause() async { Future<void> pause() async {
await _mkPlayer?.pause(); await _mkPlayer.pause();
await _justAudio?.pause(); // await _justAudio?.pause();
} }
Future<void> resume() async { Future<void> resume() async {
await _mkPlayer?.play(); await _mkPlayer.play();
await _justAudio?.play(); // await _justAudio?.play();
} }
Future<void> stop() async { Future<void> stop() async {
await _mkPlayer?.stop(); await _mkPlayer.stop();
await _justAudio?.stop(); // await _justAudio?.stop();
await _justAudio?.setShuffleModeEnabled(false); // await _justAudio?.setShuffleModeEnabled(false);
await _justAudio?.setLoopMode(ja.LoopMode.off); // await _justAudio?.setLoopMode(ja.LoopMode.off);
} }
Future<void> seek(Duration position) async { Future<void> seek(Duration position) async {
await _mkPlayer?.seek(position); await _mkPlayer.seek(position);
await _justAudio?.seek(position); // await _justAudio?.seek(position);
} }
/// Volume is between 0 and 1 /// Volume is between 0 and 1
Future<void> setVolume(double volume) async { Future<void> setVolume(double volume) async {
assert(volume >= 0 && volume <= 1); assert(volume >= 0 && volume <= 1);
await _mkPlayer?.setVolume(volume * 100); await _mkPlayer.setVolume(volume * 100);
await _justAudio?.setVolume(volume); // await _justAudio?.setVolume(volume);
} }
Future<void> setSpeed(double speed) async { Future<void> setSpeed(double speed) async {
await _mkPlayer?.setRate(speed); await _mkPlayer.setRate(speed);
await _justAudio?.setSpeed(speed); // await _justAudio?.setSpeed(speed);
} }
Future<void> dispose() async { Future<void> dispose() async {
await _mkPlayer?.dispose(); await _mkPlayer.dispose();
await _justAudio?.dispose(); // await _justAudio?.dispose();
} }
// Playlist related // Playlist related
@ -97,28 +97,28 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
}) async { }) async {
assert(tracks.isNotEmpty); assert(tracks.isNotEmpty);
assert(initialIndex <= tracks.length - 1); assert(initialIndex <= tracks.length - 1);
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.open( await _mkPlayer.open(
mk.Playlist( mk.Playlist(
tracks.map(mk.Media.new).toList(), tracks.map(mk.Media.new).toList(),
index: initialIndex, index: initialIndex,
), ),
play: autoPlay, play: autoPlay,
); );
} else { // } else {
await _justAudio!.setAudioSource( // await _justAudio!.setAudioSource(
ja.ConcatenatingAudioSource( // ja.ConcatenatingAudioSource(
useLazyPreparation: true, // useLazyPreparation: true,
children: // children:
tracks.map((e) => ja.AudioSource.uri(Uri.parse(e))).toList(), // tracks.map((e) => ja.AudioSource.uri(Uri.parse(e))).toList(),
), // ),
preload: true, // preload: true,
initialIndex: initialIndex, // initialIndex: initialIndex,
); // );
if (autoPlay) { // if (autoPlay) {
await _justAudio!.play(); // await _justAudio!.play();
} // }
} // }
} }
List<SpotubeTrack> resolveTracksForSource(List<SpotubeTrack> tracks) { List<SpotubeTrack> resolveTracksForSource(List<SpotubeTrack> tracks) {
@ -130,108 +130,108 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
} }
List<String> get sources { List<String> get sources {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.playlist.medias.map((e) => e.uri).toList(); return _mkPlayer.playlist.medias.map((e) => e.uri).toList();
} else { // } else {
return _justAudio!.sequenceState?.effectiveSequence // return _justAudio!.sequenceState?.effectiveSequence
.map((e) => (e as ja.UriAudioSource).uri.toString()) // .map((e) => (e as ja.UriAudioSource).uri.toString())
.toList() ?? // .toList() ??
<String>[]; // <String>[];
} // }
} }
String? get currentSource { String? get currentSource {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.playlist.medias return _mkPlayer.playlist.medias
.elementAtOrNull(_mkPlayer!.playlist.index) .elementAtOrNull(_mkPlayer.playlist.index)
?.uri; ?.uri;
} else { // } else {
return (_justAudio?.sequenceState?.effectiveSequence // return (_justAudio?.sequenceState?.effectiveSequence
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex) // .elementAtOrNull(_justAudio!.sequenceState!.currentIndex)
as ja.UriAudioSource?) // as ja.UriAudioSource?)
?.uri // ?.uri
.toString(); // .toString();
} // }
} }
String? get nextSource { String? get nextSource {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.playlist.medias return _mkPlayer.playlist.medias
.elementAtOrNull(_mkPlayer!.playlist.index + 1) .elementAtOrNull(_mkPlayer.playlist.index + 1)
?.uri; ?.uri;
} else { // } else {
return (_justAudio?.sequenceState?.effectiveSequence // return (_justAudio?.sequenceState?.effectiveSequence
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex + 1) // .elementAtOrNull(_justAudio!.sequenceState!.currentIndex + 1)
as ja.UriAudioSource?) // as ja.UriAudioSource?)
?.uri // ?.uri
.toString(); // .toString();
} // }
} }
String? get previousSource { String? get previousSource {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.playlist.medias return _mkPlayer.playlist.medias
.elementAtOrNull(_mkPlayer!.playlist.index - 1) .elementAtOrNull(_mkPlayer.playlist.index - 1)
?.uri; ?.uri;
} else { // } else {
return (_justAudio?.sequenceState?.effectiveSequence // return (_justAudio?.sequenceState?.effectiveSequence
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex - 1) // .elementAtOrNull(_justAudio!.sequenceState!.currentIndex - 1)
as ja.UriAudioSource?) // as ja.UriAudioSource?)
?.uri // ?.uri
.toString(); // .toString();
} // }
} }
Future<void> skipToNext() async { Future<void> skipToNext() async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.next(); await _mkPlayer.next();
} else { // } else {
await _justAudio!.seekToNext(); // await _justAudio!.seekToNext();
} // }
} }
Future<void> skipToPrevious() async { Future<void> skipToPrevious() async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.previous(); await _mkPlayer.previous();
} else { // } else {
await _justAudio!.seekToPrevious(); // await _justAudio!.seekToPrevious();
} // }
} }
Future<void> jumpTo(int index) async { Future<void> jumpTo(int index) async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.jump(index); await _mkPlayer.jump(index);
} else { // } else {
await _justAudio!.seek(Duration.zero, index: index); // await _justAudio!.seek(Duration.zero, index: index);
} // }
} }
Future<void> addTrack(String url) async { Future<void> addTrack(String url) async {
final urlType = _resolveUrlType(url); final urlType = _resolveUrlType(url);
if (mkSupportedPlatform && urlType is mk.Media) { // if (mkSupportedPlatform && urlType is mk.Media) {
await _mkPlayer!.add(urlType); await _mkPlayer.add(urlType as mk.Media);
} else { // } else {
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource) // await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
.add(urlType as ja.AudioSource); // .add(urlType as ja.AudioSource);
} // }
} }
Future<void> removeTrack(int index) async { Future<void> removeTrack(int index) async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.remove(index); await _mkPlayer.remove(index);
} else { // } else {
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource) // await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
.removeAt(index); // .removeAt(index);
} // }
} }
Future<void> moveTrack(int from, int to) async { Future<void> moveTrack(int from, int to) async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.move(from, to); await _mkPlayer.move(from, to);
} else { // } else {
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource) // await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
.move(from, to); // .move(from, to);
} // }
} }
Future<void> replaceSource( Future<void> replaceSource(
@ -242,54 +242,54 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
final oldSourceIndex = sources.indexOf(oldSource); final oldSourceIndex = sources.indexOf(oldSource);
if (oldSourceIndex == -1) return; if (oldSourceIndex == -1) return;
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
_mkPlayer!.replace(oldSource, newSource); _mkPlayer.replace(oldSource, newSource);
} else { // } else {
final playlist = _justAudio!.audioSource as ja.ConcatenatingAudioSource; // final playlist = _justAudio!.audioSource as ja.ConcatenatingAudioSource;
print('oldSource: $oldSource'); // print('oldSource: $oldSource');
print('newSource: $newSource'); // print('newSource: $newSource');
final oldSourceIndexInPlaylist = // final oldSourceIndexInPlaylist =
_justAudio?.sequenceState?.effectiveSequence.indexWhere( // _justAudio?.sequenceState?.effectiveSequence.indexWhere(
(e) => (e as ja.UriAudioSource).uri.toString() == oldSource, // (e) => (e as ja.UriAudioSource).uri.toString() == oldSource,
); // );
print('oldSourceIndexInPlaylist: $oldSourceIndexInPlaylist'); // print('oldSourceIndexInPlaylist: $oldSourceIndexInPlaylist');
// ignores non existing source // // ignores non existing source
if (oldSourceIndexInPlaylist == null || oldSourceIndexInPlaylist == -1) { // if (oldSourceIndexInPlaylist == null || oldSourceIndexInPlaylist == -1) {
return; // return;
} // }
await playlist.removeAt(oldSourceIndexInPlaylist); // await playlist.removeAt(oldSourceIndexInPlaylist);
await playlist.insert( // await playlist.insert(
oldSourceIndexInPlaylist, // oldSourceIndexInPlaylist,
ja.AudioSource.uri(Uri.parse(newSource)), // ja.AudioSource.uri(Uri.parse(newSource)),
); // );
} // }
} }
Future<void> clearPlaylist() async { Future<void> clearPlaylist() async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
_mkPlayer!.stop(); _mkPlayer.stop();
} else { // } else {
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource).clear(); // await (_justAudio!.audioSource as ja.ConcatenatingAudioSource).clear();
} // }
} }
Future<void> setShuffle(bool shuffle) async { Future<void> setShuffle(bool shuffle) async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.setShuffle(shuffle); await _mkPlayer.setShuffle(shuffle);
} else { // } else {
await _justAudio!.setShuffleModeEnabled(shuffle); // await _justAudio!.setShuffleModeEnabled(shuffle);
} // }
} }
Future<void> setLoopMode(PlaybackLoopMode loop) async { Future<void> setLoopMode(PlaybackLoopMode loop) async {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer!.setPlaylistMode(loop.toPlaylistMode()); await _mkPlayer.setPlaylistMode(loop.toPlaylistMode());
} else { // } else {
await _justAudio!.setLoopMode(loop.toLoopMode()); // await _justAudio!.setLoopMode(loop.toLoopMode());
} // }
} }
} }

View File

@ -3,42 +3,42 @@ part of 'audio_player.dart';
mixin SpotubeAudioPlayersStreams on AudioPlayerInterface { mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
// stream getters // stream getters
Stream<Duration> get durationStream { Stream<Duration> get durationStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.streams.duration.asBroadcastStream(); return _mkPlayer.streams.duration.asBroadcastStream();
} else { // } else {
return _justAudio!.durationStream // return _justAudio!.durationStream
.where((event) => event != null) // .where((event) => event != null)
.map((event) => event!) // .map((event) => event!)
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
Stream<Duration> get positionStream { Stream<Duration> get positionStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.streams.position.asBroadcastStream(); return _mkPlayer.streams.position.asBroadcastStream();
} else { // } else {
return _justAudio!.positionStream.asBroadcastStream(); // return _justAudio!.positionStream.asBroadcastStream();
} // }
} }
Stream<Duration> get bufferedPositionStream { Stream<Duration> get bufferedPositionStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
// audioplayers doesn't have the capability to get buffered position // audioplayers doesn't have the capability to get buffered position
return _mkPlayer!.streams.buffer.asBroadcastStream(); return _mkPlayer.streams.buffer.asBroadcastStream();
} else { // } else {
return _justAudio!.bufferedPositionStream.asBroadcastStream(); // return _justAudio!.bufferedPositionStream.asBroadcastStream();
} // }
} }
Stream<void> get completedStream { Stream<void> get completedStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.streams.completed.asBroadcastStream(); return _mkPlayer.streams.completed.asBroadcastStream();
} else { // } else {
return _justAudio!.playerStateStream // return _justAudio!.playerStateStream
.where( // .where(
(event) => event.processingState == ja.ProcessingState.completed) // (event) => event.processingState == ja.ProcessingState.completed)
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
/// Stream that emits when the player is almost (%) complete /// Stream that emits when the player is almost (%) complete
@ -57,92 +57,92 @@ mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
} }
Stream<bool> get playingStream { Stream<bool> get playingStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.streams.playing.asBroadcastStream(); return _mkPlayer.streams.playing.asBroadcastStream();
} else { // } else {
return _justAudio!.playingStream.asBroadcastStream(); // return _justAudio!.playingStream.asBroadcastStream();
} // }
} }
Stream<bool> get shuffledStream { Stream<bool> get shuffledStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.shuffleStream.asBroadcastStream(); return _mkPlayer.shuffleStream.asBroadcastStream();
} else { // } else {
return _justAudio!.shuffleModeEnabledStream.asBroadcastStream(); // return _justAudio!.shuffleModeEnabledStream.asBroadcastStream();
} // }
} }
Stream<PlaybackLoopMode> get loopModeStream { Stream<PlaybackLoopMode> get loopModeStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.loopModeStream return _mkPlayer.loopModeStream
.map(PlaybackLoopMode.fromPlaylistMode) .map(PlaybackLoopMode.fromPlaylistMode)
.asBroadcastStream(); .asBroadcastStream();
} else { // } else {
return _justAudio!.loopModeStream // return _justAudio!.loopModeStream
.map(PlaybackLoopMode.fromLoopMode) // .map(PlaybackLoopMode.fromLoopMode)
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
Stream<double> get volumeStream { Stream<double> get volumeStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.streams.volume return _mkPlayer.streams.volume
.map((event) => event / 100) .map((event) => event / 100)
.asBroadcastStream(); .asBroadcastStream();
} else { // } else {
return _justAudio!.volumeStream.asBroadcastStream(); // return _justAudio!.volumeStream.asBroadcastStream();
} // }
} }
Stream<bool> get bufferingStream { Stream<bool> get bufferingStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return Stream.value(false).asBroadcastStream(); return Stream.value(false).asBroadcastStream();
} else { // } else {
return _justAudio!.playerStateStream // return _justAudio!.playerStateStream
.map( // .map(
(event) => // (event) =>
event.processingState == ja.ProcessingState.buffering || // event.processingState == ja.ProcessingState.buffering ||
event.processingState == ja.ProcessingState.loading, // event.processingState == ja.ProcessingState.loading,
) // )
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
Stream<AudioPlaybackState> get playerStateStream { Stream<AudioPlaybackState> get playerStateStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.playerStateStream.asBroadcastStream(); return _mkPlayer.playerStateStream.asBroadcastStream();
} else { // } else {
return _justAudio!.playerStateStream // return _justAudio!.playerStateStream
.map(AudioPlaybackState.fromJaPlayerState) // .map(AudioPlaybackState.fromJaPlayerState)
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
Stream<int> get currentIndexChangedStream { Stream<int> get currentIndexChangedStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.indexChangeStream; return _mkPlayer.indexChangeStream;
} else { // } else {
return _justAudio!.sequenceStateStream // return _justAudio!.sequenceStateStream
.map((event) => event?.currentIndex ?? -1) // .map((event) => event?.currentIndex ?? -1)
.asBroadcastStream(); // .asBroadcastStream();
} // }
} }
Stream<String> get activeSourceChangedStream { Stream<String> get activeSourceChangedStream {
if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
return _mkPlayer!.indexChangeStream return _mkPlayer.indexChangeStream
.map((event) { .map((event) {
return _mkPlayer!.playlist.medias.elementAtOrNull(event)?.uri; return _mkPlayer.playlist.medias.elementAtOrNull(event)?.uri;
}) })
.where((event) => event != null) .where((event) => event != null)
.cast<String>(); .cast<String>();
} else { // } else {
return _justAudio!.sequenceStateStream // return _justAudio!.sequenceStateStream
.map((event) { // .map((event) {
return (event?.currentSource as ja.UriAudioSource?)?.uri.toString(); // return (event?.currentSource as ja.UriAudioSource?)?.uri.toString();
}) // })
.where((event) => event != null) // .where((event) => event != null)
.cast<String>(); // .cast<String>();
} // }
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:just_audio/just_audio.dart'; // import 'package:just_audio/just_audio.dart';
import 'package:mpris_service/mpris_service.dart'; import 'package:mpris_service/mpris_service.dart';
/// An unified loop mode for both [LoopMode] and [PlaylistMode] /// An unified loop mode for both [LoopMode] and [PlaylistMode]
@ -9,27 +9,27 @@ enum PlaybackLoopMode {
one, one,
none; none;
static PlaybackLoopMode fromLoopMode(LoopMode loopMode) { // static PlaybackLoopMode fromLoopMode(LoopMode loopMode) {
switch (loopMode) { // switch (loopMode) {
case LoopMode.all: // case LoopMode.all:
return PlaybackLoopMode.all; // return PlaybackLoopMode.all;
case LoopMode.one: // case LoopMode.one:
return PlaybackLoopMode.one; // return PlaybackLoopMode.one;
case LoopMode.off: // case LoopMode.off:
return PlaybackLoopMode.none; // return PlaybackLoopMode.none;
} // }
} // }
LoopMode toLoopMode() { // LoopMode toLoopMode() {
switch (this) { // switch (this) {
case PlaybackLoopMode.all: // case PlaybackLoopMode.all:
return LoopMode.all; // return LoopMode.all;
case PlaybackLoopMode.one: // case PlaybackLoopMode.one:
return LoopMode.one; // return LoopMode.one;
case PlaybackLoopMode.none: // case PlaybackLoopMode.none:
return LoopMode.off; // return LoopMode.off;
} // }
} // }
static PlaybackLoopMode fromPlaylistMode(PlaylistMode mode) { static PlaybackLoopMode fromPlaylistMode(PlaylistMode mode) {
switch (mode) { switch (mode) {

View File

@ -1,4 +1,4 @@
import 'package:just_audio/just_audio.dart'; // import 'package:just_audio/just_audio.dart';
/// An unified playback state enum /// An unified playback state enum
enum AudioPlaybackState { enum AudioPlaybackState {
@ -8,21 +8,21 @@ enum AudioPlaybackState {
buffering, buffering,
stopped; stopped;
static AudioPlaybackState fromJaPlayerState(PlayerState state) { // static AudioPlaybackState fromJaPlayerState(PlayerState state) {
if (state.playing) { // if (state.playing) {
return AudioPlaybackState.playing; // return AudioPlaybackState.playing;
} // }
switch (state.processingState) { // switch (state.processingState) {
case ProcessingState.idle: // case ProcessingState.idle:
return AudioPlaybackState.stopped; // return AudioPlaybackState.stopped;
case ProcessingState.ready: // case ProcessingState.ready:
return AudioPlaybackState.paused; // return AudioPlaybackState.paused;
case ProcessingState.completed: // case ProcessingState.completed:
return AudioPlaybackState.completed; // return AudioPlaybackState.completed;
case ProcessingState.loading: // case ProcessingState.loading:
case ProcessingState.buffering: // case ProcessingState.buffering:
return AudioPlaybackState.buffering; // return AudioPlaybackState.buffering;
} // }
} // }
} }

View File

@ -11,8 +11,8 @@ import audio_session
import catcher import catcher
import device_info_plus import device_info_plus
import flutter_secure_storage_macos import flutter_secure_storage_macos
import just_audio
import local_notifier import local_notifier
import media_kit_libs_macos_audio
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import screen_retriever import screen_retriever
@ -32,8 +32,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin")) CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))

View File

@ -966,30 +966,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.2" version: "6.6.2"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: "7e6d31508dacd01a066e3889caf6282e5f1eb60707c230203b21a83af5c55586"
url: "https://pub.dev"
source: hosted
version: "0.9.32"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: eff112d5138bea3ba544b6338b1e0537a32b5e1425e4d0dc38f732771cda7c84
url: "https://pub.dev"
source: hosted
version: "4.2.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "89d8db6f19f3821bb6bf908c4bfb846079afb2ab575b783d781a6bf119e3abaf"
url: "https://pub.dev"
source: hosted
version: "0.4.7"
jwt_decode: jwt_decode:
dependency: transitive dependency: transitive
description: description:
@ -1062,6 +1038,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.7+1" version: "0.0.7+1"
media_kit_libs_android_audio:
dependency: "direct main"
description:
name: media_kit_libs_android_audio
sha256: e16292bbb1d737ce026fa68154b2fcf5c6f88d6fa752bdb4c0d079c98a82b523
url: "https://pub.dev"
source: hosted
version: "1.0.4"
media_kit_libs_ios_audio:
dependency: "direct main"
description:
name: media_kit_libs_ios_audio
sha256: bb18ed87e8a155fc1d2ee4bf59462ca04d02a0eeb38d0b454facee6d32441ce7
url: "https://pub.dev"
source: hosted
version: "1.0.4"
media_kit_libs_linux: media_kit_libs_linux:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1070,6 +1062,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
media_kit_libs_macos_audio:
dependency: "direct main"
description:
name: media_kit_libs_macos_audio
sha256: b9f4a2995396fd5cc243901e618c0586d4f1781eecf2302999b77d6faee8b250
url: "https://pub.dev"
source: hosted
version: "1.0.5"
media_kit_libs_windows_audio: media_kit_libs_windows_audio:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -55,12 +55,14 @@ dependencies:
intl: ^0.18.0 intl: ^0.18.0
introduction_screen: ^3.0.2 introduction_screen: ^3.0.2
json_annotation: ^4.8.1 json_annotation: ^4.8.1
just_audio: ^0.9.32
logger: ^1.1.0 logger: ^1.1.0
media_kit: ^0.0.7+1 media_kit: ^0.0.7+1
media_kit_libs_windows_audio: ^1.0.3
media_kit_libs_linux: ^1.0.2
media_kit_native_event_loop: ^1.0.3 media_kit_native_event_loop: ^1.0.3
media_kit_libs_android_audio: ^1.0.4
media_kit_libs_ios_audio: ^1.0.4
media_kit_libs_linux: ^1.0.2
media_kit_libs_macos_audio: ^1.0.5
media_kit_libs_windows_audio: ^1.0.3
metadata_god: ^0.4.1 metadata_god: ^0.4.1
mime: ^1.0.2 mime: ^1.0.2
mpris_service: mpris_service: