mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
refactor: split audio player impl into multiple parts and mixins
This commit is contained in:
parent
0e98f1da55
commit
75b46e1a17
@ -23,7 +23,7 @@ void useInitSysTray(WidgetRef ref) {
|
|||||||
}
|
}
|
||||||
final enabled = !playlist.isFetching;
|
final enabled = !playlist.isFetching;
|
||||||
systemTray.value = await DesktopTools.createSystemTrayMenu(
|
systemTray.value = await DesktopTools.createSystemTrayMenu(
|
||||||
title: "Spotube",
|
title: DesktopTools.platform.isLinux ? "" : "Spotube",
|
||||||
iconPath: "assets/spotube-logo.png",
|
iconPath: "assets/spotube-logo.png",
|
||||||
windowsIconPath: "assets/spotube-logo.ico",
|
windowsIconPath: "assets/spotube-logo.ico",
|
||||||
items: [
|
items: [
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
|
@ -25,7 +25,8 @@ import 'package:spotube/utils/type_conversion_utils.dart';
|
|||||||
/// * [ ] Remove track
|
/// * [ ] Remove track
|
||||||
/// * [ ] Reorder track
|
/// * [ ] Reorder track
|
||||||
/// * [ ] Caching and loading of cache of tracks
|
/// * [ ] Caching and loading of cache of tracks
|
||||||
/// * [ ] Shuffling and loop => playlist, track, none
|
/// * [ ] Shuffling
|
||||||
|
/// * [x] loop => playlist, track, none
|
||||||
/// * [ ] Alternative Track Source
|
/// * [ ] Alternative Track Source
|
||||||
/// * [x] Blacklisting of tracks and artist
|
/// * [x] Blacklisting of tracks and artist
|
||||||
///
|
///
|
||||||
|
@ -1,161 +1,35 @@
|
|||||||
|
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:just_audio/just_audio.dart' as ja;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:catcher/catcher.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:media_kit/media_kit.dart' as mk;
|
import 'package:media_kit/media_kit.dart' as mk;
|
||||||
import 'package:just_audio/just_audio.dart' as ja;
|
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/services/audio_player/loop_mode.dart';
|
import 'package:spotube/services/audio_player/loop_mode.dart';
|
||||||
import 'package:spotube/services/audio_player/mk_state_player.dart';
|
|
||||||
import 'package:spotube/services/audio_player/playback_state.dart';
|
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||||
|
|
||||||
final audioPlayer = SpotubeAudioPlayer();
|
part 'audio_players_streams_mixin.dart';
|
||||||
|
part 'audio_player_impl.dart';
|
||||||
|
|
||||||
class SpotubeAudioPlayer {
|
abstract class AudioPlayerInterface {
|
||||||
final MkPlayerWithState? _mkPlayer;
|
final MkPlayerWithState? _mkPlayer;
|
||||||
final ja.AudioPlayer? _justAudio;
|
final ja.AudioPlayer? _justAudio;
|
||||||
|
|
||||||
SpotubeAudioPlayer()
|
AudioPlayerInterface()
|
||||||
: _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 final bool _mkSupportedPlatform =
|
||||||
DesktopTools.platform.isWindows || DesktopTools.platform.isLinux;
|
DesktopTools.platform.isWindows || DesktopTools.platform.isLinux;
|
||||||
|
|
||||||
// stream getters
|
bool get mkSupportedPlatform => _mkSupportedPlatform;
|
||||||
Stream<Duration> get durationStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.streams.duration.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.durationStream
|
|
||||||
.where((event) => event != null)
|
|
||||||
.map((event) => event!)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<Duration> get positionStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.streams.position.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.positionStream.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<Duration> get bufferedPositionStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
// audioplayers doesn't have the capability to get buffered position
|
|
||||||
return _mkPlayer!.streams.buffer.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.bufferedPositionStream.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<void> get completedStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.streams.completed.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.playerStateStream
|
|
||||||
.where(
|
|
||||||
(event) => event.processingState == ja.ProcessingState.completed)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stream that emits when the player is almost (%) complete
|
|
||||||
Stream<int> percentCompletedStream(double percent) {
|
|
||||||
return positionStream
|
|
||||||
.asyncMap(
|
|
||||||
(position) async => (await duration)?.inSeconds == 0
|
|
||||||
? 0
|
|
||||||
: (position.inSeconds /
|
|
||||||
((await duration)?.inSeconds ?? 100) *
|
|
||||||
100)
|
|
||||||
.toInt(),
|
|
||||||
)
|
|
||||||
.where((event) => event >= percent)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<bool> get playingStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.streams.playing.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.playingStream.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<bool> get shuffledStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.shuffleStream.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.shuffleModeEnabledStream.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<PlaybackLoopMode> get loopModeStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.loopModeStream
|
|
||||||
.map(PlaybackLoopMode.fromPlaylistMode)
|
|
||||||
.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.loopModeStream
|
|
||||||
.map(PlaybackLoopMode.fromLoopMode)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<double> get volumeStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.streams.volume
|
|
||||||
.map((event) => event / 100)
|
|
||||||
.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.volumeStream.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<bool> get bufferingStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return Stream.value(false).asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.playerStateStream
|
|
||||||
.map(
|
|
||||||
(event) =>
|
|
||||||
event.processingState == ja.ProcessingState.buffering ||
|
|
||||||
event.processingState == ja.ProcessingState.loading,
|
|
||||||
)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<AudioPlaybackState> get playerStateStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.playerStateStream.asBroadcastStream();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.playerStateStream
|
|
||||||
.map(AudioPlaybackState.fromJaPlayerState)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<int> get currentIndexChangedStream {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.indexChangeStream;
|
|
||||||
} else {
|
|
||||||
return _justAudio!.sequenceStateStream
|
|
||||||
.map((event) => event?.currentIndex ?? -1)
|
|
||||||
.asBroadcastStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// regular info getter
|
|
||||||
|
|
||||||
Future<Duration?> get duration async {
|
Future<Duration?> get duration async {
|
||||||
if (mkSupportedPlatform) {
|
if (mkSupportedPlatform) {
|
||||||
@ -257,251 +131,4 @@ class SpotubeAudioPlayer {
|
|||||||
_justAudio!.processingState == ja.ProcessingState.loading;
|
_justAudio!.processingState == ja.ProcessingState.loading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object _resolveUrlType(String url) {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return mk.Media(url);
|
|
||||||
} else {
|
|
||||||
if (url.startsWith("https")) {
|
|
||||||
return ja.AudioSource.uri(Uri.parse(url));
|
|
||||||
} else {
|
|
||||||
return ja.AudioSource.file(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> preload(String url) async {
|
|
||||||
throw UnimplementedError();
|
|
||||||
// final urlType = _resolveUrlType(url);
|
|
||||||
// if (mkSupportedPlatform && urlType is ap.Source) {
|
|
||||||
// // audioplayers doesn't have the capability to preload
|
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> play(String url) async {
|
|
||||||
final urlType = _resolveUrlType(url);
|
|
||||||
if (mkSupportedPlatform && urlType is mk.Media) {
|
|
||||||
await _mkPlayer?.open(urlType, play: true);
|
|
||||||
} else {
|
|
||||||
if (_justAudio?.audioSource is ja.ProgressiveAudioSource &&
|
|
||||||
(_justAudio?.audioSource as ja.ProgressiveAudioSource)
|
|
||||||
.uri
|
|
||||||
.toString() ==
|
|
||||||
url) {
|
|
||||||
await _justAudio?.play();
|
|
||||||
} else {
|
|
||||||
await _justAudio?.stop();
|
|
||||||
await _justAudio?.setAudioSource(
|
|
||||||
urlType as ja.AudioSource,
|
|
||||||
preload: true,
|
|
||||||
);
|
|
||||||
await _justAudio?.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pause() async {
|
|
||||||
await _mkPlayer?.pause();
|
|
||||||
await _justAudio?.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> resume() async {
|
|
||||||
await _mkPlayer?.play();
|
|
||||||
await _justAudio?.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> stop() async {
|
|
||||||
await _mkPlayer?.stop();
|
|
||||||
await _justAudio?.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> seek(Duration position) async {
|
|
||||||
await _mkPlayer?.seek(position);
|
|
||||||
await _justAudio?.seek(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Volume is between 0 and 1
|
|
||||||
Future<void> setVolume(double volume) async {
|
|
||||||
assert(volume >= 0 && volume <= 1);
|
|
||||||
await _mkPlayer?.setVolume(volume * 100);
|
|
||||||
await _justAudio?.setVolume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setSpeed(double speed) async {
|
|
||||||
await _mkPlayer?.setRate(speed);
|
|
||||||
await _justAudio?.setSpeed(speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> dispose() async {
|
|
||||||
await _mkPlayer?.dispose();
|
|
||||||
await _justAudio?.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Playlist related
|
|
||||||
|
|
||||||
Future<void> openPlaylist(
|
|
||||||
List<String> tracks, {
|
|
||||||
bool autoPlay = true,
|
|
||||||
int initialIndex = 0,
|
|
||||||
}) async {
|
|
||||||
assert(tracks.isNotEmpty);
|
|
||||||
assert(initialIndex <= tracks.length - 1);
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.open(
|
|
||||||
mk.Playlist(
|
|
||||||
tracks.map(mk.Media.new).toList(),
|
|
||||||
index: initialIndex,
|
|
||||||
),
|
|
||||||
play: autoPlay,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await _justAudio!.setAudioSource(
|
|
||||||
ja.ConcatenatingAudioSource(
|
|
||||||
useLazyPreparation: true,
|
|
||||||
children:
|
|
||||||
tracks.map((e) => ja.AudioSource.uri(Uri.parse(e))).toList(),
|
|
||||||
),
|
|
||||||
preload: true,
|
|
||||||
initialIndex: initialIndex,
|
|
||||||
);
|
|
||||||
if (autoPlay) {
|
|
||||||
await _justAudio!.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SpotubeTrack> resolveTracksForSource(List<SpotubeTrack> tracks) {
|
|
||||||
return tracks.where((e) => sources.contains(e.ytUri)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tracksExistsInPlaylist(List<SpotubeTrack> tracks) {
|
|
||||||
return resolveTracksForSource(tracks).length == tracks.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> get sources {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.playlist.medias.map((e) => e.uri).toList();
|
|
||||||
} else {
|
|
||||||
return _justAudio!.sequenceState?.effectiveSequence
|
|
||||||
.map((e) => (e as ja.UriAudioSource).uri.toString())
|
|
||||||
.toList() ??
|
|
||||||
<String>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int get currentIndex {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
return _mkPlayer!.playlist.index;
|
|
||||||
} else {
|
|
||||||
return _justAudio!.sequenceState?.currentIndex ?? -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> skipToNext() async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.next();
|
|
||||||
} else {
|
|
||||||
await _justAudio!.seekToNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> skipToPrevious() async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.previous();
|
|
||||||
} else {
|
|
||||||
await _justAudio!.seekToPrevious();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> jumpTo(int index) async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.jump(index);
|
|
||||||
} else {
|
|
||||||
await _justAudio!.seek(Duration.zero, index: index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addTrack(String url) async {
|
|
||||||
final urlType = _resolveUrlType(url);
|
|
||||||
if (mkSupportedPlatform && urlType is mk.Media) {
|
|
||||||
await _mkPlayer!.add(urlType);
|
|
||||||
} else {
|
|
||||||
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
|
||||||
.add(urlType as ja.AudioSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> removeTrack(int index) async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.remove(index);
|
|
||||||
} else {
|
|
||||||
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
|
||||||
.removeAt(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> moveTrack(int from, int to) async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.move(from, to);
|
|
||||||
} else {
|
|
||||||
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
|
||||||
.move(from, to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> replaceSource(
|
|
||||||
String oldSource,
|
|
||||||
String newSource, {
|
|
||||||
bool exclusive = false,
|
|
||||||
}) async {
|
|
||||||
final oldSourceIndex = sources.indexOf(oldSource);
|
|
||||||
if (oldSourceIndex == -1) return;
|
|
||||||
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
_mkPlayer!.replace(oldSource, newSource);
|
|
||||||
} else {
|
|
||||||
await addTrack(newSource);
|
|
||||||
await removeTrack(oldSourceIndex);
|
|
||||||
|
|
||||||
int newSourceIndex = sources.indexOf(newSource);
|
|
||||||
while (newSourceIndex == -1) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
newSourceIndex = sources.indexOf(newSource);
|
|
||||||
}
|
|
||||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
|
||||||
newSourceIndex = sources.indexOf(newSource);
|
|
||||||
while (newSourceIndex != oldSourceIndex) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
|
||||||
newSourceIndex = sources.indexOf(newSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearPlaylist() async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
_mkPlayer!.stop();
|
|
||||||
} else {
|
|
||||||
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource).clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setShuffle(bool shuffle) async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.setShuffle(shuffle);
|
|
||||||
} else {
|
|
||||||
await _justAudio!.setShuffleModeEnabled(shuffle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setLoopMode(PlaybackLoopMode loop) async {
|
|
||||||
if (mkSupportedPlatform) {
|
|
||||||
await _mkPlayer!.setPlaylistMode(loop.toPlaylistMode());
|
|
||||||
} else {
|
|
||||||
await _justAudio!.setLoopMode(loop.toLoopMode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
253
lib/services/audio_player/audio_player_impl.dart
Normal file
253
lib/services/audio_player/audio_player_impl.dart
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
part of 'audio_player.dart';
|
||||||
|
|
||||||
|
final audioPlayer = SpotubeAudioPlayer();
|
||||||
|
|
||||||
|
class SpotubeAudioPlayer extends AudioPlayerInterface
|
||||||
|
with SpotubeAudioPlayersStreams {
|
||||||
|
Object _resolveUrlType(String url) {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return mk.Media(url);
|
||||||
|
} else {
|
||||||
|
if (url.startsWith("https")) {
|
||||||
|
return ja.AudioSource.uri(Uri.parse(url));
|
||||||
|
} else {
|
||||||
|
return ja.AudioSource.file(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> preload(String url) async {
|
||||||
|
throw UnimplementedError();
|
||||||
|
// final urlType = _resolveUrlType(url);
|
||||||
|
// if (mkSupportedPlatform && urlType is ap.Source) {
|
||||||
|
// // audioplayers doesn't have the capability to preload
|
||||||
|
// return;
|
||||||
|
// } else {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> play(String url) async {
|
||||||
|
final urlType = _resolveUrlType(url);
|
||||||
|
if (mkSupportedPlatform && urlType is mk.Media) {
|
||||||
|
await _mkPlayer?.open(urlType, play: true);
|
||||||
|
} else {
|
||||||
|
if (_justAudio?.audioSource is ja.ProgressiveAudioSource &&
|
||||||
|
(_justAudio?.audioSource as ja.ProgressiveAudioSource)
|
||||||
|
.uri
|
||||||
|
.toString() ==
|
||||||
|
url) {
|
||||||
|
await _justAudio?.play();
|
||||||
|
} else {
|
||||||
|
await _justAudio?.stop();
|
||||||
|
await _justAudio?.setAudioSource(
|
||||||
|
urlType as ja.AudioSource,
|
||||||
|
preload: true,
|
||||||
|
);
|
||||||
|
await _justAudio?.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pause() async {
|
||||||
|
await _mkPlayer?.pause();
|
||||||
|
await _justAudio?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> resume() async {
|
||||||
|
await _mkPlayer?.play();
|
||||||
|
await _justAudio?.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
await _mkPlayer?.stop();
|
||||||
|
await _justAudio?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> seek(Duration position) async {
|
||||||
|
await _mkPlayer?.seek(position);
|
||||||
|
await _justAudio?.seek(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Volume is between 0 and 1
|
||||||
|
Future<void> setVolume(double volume) async {
|
||||||
|
assert(volume >= 0 && volume <= 1);
|
||||||
|
await _mkPlayer?.setVolume(volume * 100);
|
||||||
|
await _justAudio?.setVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setSpeed(double speed) async {
|
||||||
|
await _mkPlayer?.setRate(speed);
|
||||||
|
await _justAudio?.setSpeed(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await _mkPlayer?.dispose();
|
||||||
|
await _justAudio?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playlist related
|
||||||
|
|
||||||
|
Future<void> openPlaylist(
|
||||||
|
List<String> tracks, {
|
||||||
|
bool autoPlay = true,
|
||||||
|
int initialIndex = 0,
|
||||||
|
}) async {
|
||||||
|
assert(tracks.isNotEmpty);
|
||||||
|
assert(initialIndex <= tracks.length - 1);
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.open(
|
||||||
|
mk.Playlist(
|
||||||
|
tracks.map(mk.Media.new).toList(),
|
||||||
|
index: initialIndex,
|
||||||
|
),
|
||||||
|
play: autoPlay,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await _justAudio!.setAudioSource(
|
||||||
|
ja.ConcatenatingAudioSource(
|
||||||
|
useLazyPreparation: true,
|
||||||
|
children:
|
||||||
|
tracks.map((e) => ja.AudioSource.uri(Uri.parse(e))).toList(),
|
||||||
|
),
|
||||||
|
preload: true,
|
||||||
|
initialIndex: initialIndex,
|
||||||
|
);
|
||||||
|
if (autoPlay) {
|
||||||
|
await _justAudio!.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SpotubeTrack> resolveTracksForSource(List<SpotubeTrack> tracks) {
|
||||||
|
return tracks.where((e) => sources.contains(e.ytUri)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tracksExistsInPlaylist(List<SpotubeTrack> tracks) {
|
||||||
|
return resolveTracksForSource(tracks).length == tracks.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> get sources {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.playlist.medias.map((e) => e.uri).toList();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.sequenceState?.effectiveSequence
|
||||||
|
.map((e) => (e as ja.UriAudioSource).uri.toString())
|
||||||
|
.toList() ??
|
||||||
|
<String>[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int get currentIndex {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.playlist.index;
|
||||||
|
} else {
|
||||||
|
return _justAudio!.sequenceState?.currentIndex ?? -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> skipToNext() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.next();
|
||||||
|
} else {
|
||||||
|
await _justAudio!.seekToNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> skipToPrevious() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.previous();
|
||||||
|
} else {
|
||||||
|
await _justAudio!.seekToPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> jumpTo(int index) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.jump(index);
|
||||||
|
} else {
|
||||||
|
await _justAudio!.seek(Duration.zero, index: index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addTrack(String url) async {
|
||||||
|
final urlType = _resolveUrlType(url);
|
||||||
|
if (mkSupportedPlatform && urlType is mk.Media) {
|
||||||
|
await _mkPlayer!.add(urlType);
|
||||||
|
} else {
|
||||||
|
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
||||||
|
.add(urlType as ja.AudioSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeTrack(int index) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.remove(index);
|
||||||
|
} else {
|
||||||
|
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
||||||
|
.removeAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> moveTrack(int from, int to) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.move(from, to);
|
||||||
|
} else {
|
||||||
|
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
||||||
|
.move(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> replaceSource(
|
||||||
|
String oldSource,
|
||||||
|
String newSource, {
|
||||||
|
bool exclusive = false,
|
||||||
|
}) async {
|
||||||
|
final oldSourceIndex = sources.indexOf(oldSource);
|
||||||
|
if (oldSourceIndex == -1) return;
|
||||||
|
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
_mkPlayer!.replace(oldSource, newSource);
|
||||||
|
} else {
|
||||||
|
await addTrack(newSource);
|
||||||
|
await removeTrack(oldSourceIndex);
|
||||||
|
|
||||||
|
int newSourceIndex = sources.indexOf(newSource);
|
||||||
|
while (newSourceIndex == -1) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
|
}
|
||||||
|
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||||
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
|
while (newSourceIndex != oldSourceIndex) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||||
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearPlaylist() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
_mkPlayer!.stop();
|
||||||
|
} else {
|
||||||
|
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setShuffle(bool shuffle) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.setShuffle(shuffle);
|
||||||
|
} else {
|
||||||
|
await _justAudio!.setShuffleModeEnabled(shuffle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setLoopMode(PlaybackLoopMode loop) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.setPlaylistMode(loop.toPlaylistMode());
|
||||||
|
} else {
|
||||||
|
await _justAudio!.setLoopMode(loop.toLoopMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
lib/services/audio_player/audio_players_streams_mixin.dart
Normal file
130
lib/services/audio_player/audio_players_streams_mixin.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
part of 'audio_player.dart';
|
||||||
|
|
||||||
|
mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
|
||||||
|
// stream getters
|
||||||
|
Stream<Duration> get durationStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.streams.duration.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.durationStream
|
||||||
|
.where((event) => event != null)
|
||||||
|
.map((event) => event!)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Duration> get positionStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.streams.position.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.positionStream.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Duration> get bufferedPositionStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
// audioplayers doesn't have the capability to get buffered position
|
||||||
|
return _mkPlayer!.streams.buffer.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.bufferedPositionStream.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<void> get completedStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.streams.completed.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.playerStateStream
|
||||||
|
.where(
|
||||||
|
(event) => event.processingState == ja.ProcessingState.completed)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream that emits when the player is almost (%) complete
|
||||||
|
Stream<int> percentCompletedStream(double percent) {
|
||||||
|
return positionStream
|
||||||
|
.asyncMap(
|
||||||
|
(position) async => (await duration)?.inSeconds == 0
|
||||||
|
? 0
|
||||||
|
: (position.inSeconds /
|
||||||
|
((await duration)?.inSeconds ?? 100) *
|
||||||
|
100)
|
||||||
|
.toInt(),
|
||||||
|
)
|
||||||
|
.where((event) => event >= percent)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<bool> get playingStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.streams.playing.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.playingStream.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<bool> get shuffledStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.shuffleStream.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.shuffleModeEnabledStream.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<PlaybackLoopMode> get loopModeStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.loopModeStream
|
||||||
|
.map(PlaybackLoopMode.fromPlaylistMode)
|
||||||
|
.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.loopModeStream
|
||||||
|
.map(PlaybackLoopMode.fromLoopMode)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<double> get volumeStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.streams.volume
|
||||||
|
.map((event) => event / 100)
|
||||||
|
.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.volumeStream.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<bool> get bufferingStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return Stream.value(false).asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.playerStateStream
|
||||||
|
.map(
|
||||||
|
(event) =>
|
||||||
|
event.processingState == ja.ProcessingState.buffering ||
|
||||||
|
event.processingState == ja.ProcessingState.loading,
|
||||||
|
)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<AudioPlaybackState> get playerStateStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.playerStateStream.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _justAudio!.playerStateStream
|
||||||
|
.map(AudioPlaybackState.fromJaPlayerState)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int> get currentIndexChangedStream {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.indexChangeStream;
|
||||||
|
} else {
|
||||||
|
return _justAudio!.sequenceStateStream
|
||||||
|
.map((event) => event?.currentIndex ?? -1)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user