mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: create audio player wrapper and remove just_audio (again)
This commit is contained in:
parent
7df2a0daba
commit
12915f3e5a
@ -27,8 +27,12 @@ class PlayPauseAction extends Action<PlayPauseIntent> {
|
||||
final playlistNotifier = intent.ref.read(PlaylistQueueNotifier.notifier);
|
||||
if (playlist == null) {
|
||||
return null;
|
||||
} else if (!PlaylistQueueNotifier.isPlaying) {
|
||||
await playlistNotifier.play();
|
||||
} else if (!audioPlayer.isPlaying) {
|
||||
if (audioPlayer.hasSource && !audioPlayer.isCompleted) {
|
||||
await playlistNotifier.resume();
|
||||
} else {
|
||||
await playlistNotifier.play();
|
||||
}
|
||||
} else {
|
||||
await playlistNotifier.pause();
|
||||
}
|
||||
@ -103,8 +107,7 @@ class SeekAction extends Action<SeekIntent> {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final position =
|
||||
(await audioPlayer.getCurrentPosition() ?? Duration.zero).inSeconds;
|
||||
final position = (await audioPlayer.position ?? Duration.zero).inSeconds;
|
||||
await playlistNotifier.seek(
|
||||
Duration(
|
||||
seconds: intent.forward ? position + 5 : position - 5,
|
||||
|
@ -7,6 +7,7 @@ import 'package:spotube/components/shared/playbutton_card.dart';
|
||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
@ -41,8 +42,7 @@ class AlbumCard extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||
PlaylistQueueNotifier.isPlaying;
|
||||
final playing = useStream(audioPlayer.playingStream).data ?? false;
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
final queryClient = useQueryClient();
|
||||
final query = queryClient
|
||||
|
@ -10,6 +10,7 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/hooks/use_progress.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
class PlayerControls extends HookConsumerWidget {
|
||||
@ -43,9 +44,8 @@ class PlayerControls extends HookConsumerWidget {
|
||||
[]);
|
||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||
PlaylistQueueNotifier.isPlaying;
|
||||
final buffering = useStream(playlistNotifier.buffering).data ?? true;
|
||||
final playing = useStream(audioPlayer.playingStream).data ?? false;
|
||||
final buffering = useStream(audioPlayer.bufferingStream).data ?? true;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final isDominantColorDark = ThemeData.estimateBrightnessForColor(
|
||||
@ -141,6 +141,7 @@ class PlayerControls extends HookConsumerWidget {
|
||||
// there's an edge case for value being bigger
|
||||
// than total duration. Keeping it resolved
|
||||
value: progress.value.toDouble(),
|
||||
secondaryTrackValue: progressObj.item4,
|
||||
onChanged: playlist?.isLoading == true || buffering
|
||||
? null
|
||||
: (v) {
|
||||
@ -154,6 +155,7 @@ class PlayerControls extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
activeColor: sliderColor,
|
||||
secondaryActiveColor: sliderColor.withOpacity(0.2),
|
||||
inactiveColor: sliderColor.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
|
@ -10,6 +10,7 @@ import 'package:spotube/components/player/player_track_details.dart';
|
||||
import 'package:spotube/collections/intents.dart';
|
||||
import 'package:spotube/hooks/use_progress.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class PlayerOverlay extends HookConsumerWidget {
|
||||
@ -27,8 +28,7 @@ class PlayerOverlay extends HookConsumerWidget {
|
||||
);
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||
PlaylistQueueNotifier.isPlaying;
|
||||
final playing = useStream(audioPlayer.playingStream).data ?? false;
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final textColor = theme.colorScheme.primary;
|
||||
|
@ -6,6 +6,7 @@ import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
@ -20,8 +21,7 @@ class PlaylistCard extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, ref) {
|
||||
final playlistQueue = ref.watch(PlaylistQueueNotifier.provider);
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||
PlaylistQueueNotifier.isPlaying;
|
||||
final playing = useStream(audioPlayer.playingStream).data ?? false;
|
||||
final queryBowl = QueryClient.of(context);
|
||||
final query = queryBowl.getQuery<List<Track>, dynamic>(
|
||||
"playlist-tracks/${playlist.id}",
|
||||
|
@ -2,16 +2,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
Tuple3<double, Duration, Duration> useProgress(WidgetRef ref) {
|
||||
Tuple4<double, Duration, Duration, double> useProgress(WidgetRef ref) {
|
||||
ref.watch(PlaylistQueueNotifier.provider);
|
||||
|
||||
final bufferProgress =
|
||||
useStream(audioPlayer.bufferedPositionStream).data?.inSeconds ?? 0;
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
|
||||
final duration =
|
||||
useStream(PlaylistQueueNotifier.duration).data ?? Duration.zero;
|
||||
final positionSnapshot = useStream(PlaylistQueueNotifier.position);
|
||||
final duration = useStream(audioPlayer.durationStream).data ?? Duration.zero;
|
||||
final positionSnapshot = useStream(audioPlayer.positionStream);
|
||||
|
||||
final position = positionSnapshot.data ?? Duration.zero;
|
||||
|
||||
@ -31,9 +33,12 @@ Tuple3<double, Duration, Duration> useProgress(WidgetRef ref) {
|
||||
return null;
|
||||
}, [positionSnapshot.hasData, duration]);
|
||||
|
||||
return Tuple3(
|
||||
return Tuple4(
|
||||
sliderMax == 0 || sliderValue > sliderMax ? 0 : sliderValue / sliderMax,
|
||||
position,
|
||||
duration,
|
||||
sliderMax == 0 || bufferProgress > sliderMax
|
||||
? 0
|
||||
: bufferProgress / sliderMax,
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
|
||||
int useSyncedLyrics(
|
||||
WidgetRef ref,
|
||||
Map<int, String> lyricsMap,
|
||||
int delay,
|
||||
) {
|
||||
final stream = PlaylistQueueNotifier.position;
|
||||
final stream = audioPlayer.positionStream;
|
||||
|
||||
final currentTime = useState(0);
|
||||
|
||||
|
@ -71,6 +71,7 @@ Future<void> main(List<String> rawArgs) async {
|
||||
}
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await DesktopTools.ensureInitialized(
|
||||
DesktopWindowOptions(
|
||||
hideTitleBar: true,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
@ -140,8 +139,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
late AudioServices audioServices;
|
||||
|
||||
final StreamController<bool> _bufferingController;
|
||||
|
||||
static final provider =
|
||||
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
|
||||
(ref) => PlaylistQueueNotifier._(ref),
|
||||
@ -149,16 +146,14 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
static final notifier = provider.notifier;
|
||||
|
||||
PlaylistQueueNotifier._(this.ref)
|
||||
: _bufferingController = StreamController.broadcast(),
|
||||
super(null, "playlist") {
|
||||
PlaylistQueueNotifier._(this.ref) : super(null, "playlist") {
|
||||
configure();
|
||||
}
|
||||
|
||||
void configure() async {
|
||||
audioServices = await AudioServices.create(ref, this);
|
||||
|
||||
audioPlayer.onPlayerComplete.listen((event) async {
|
||||
audioPlayer.completedStream.listen((event) async {
|
||||
if (!isLoaded) return;
|
||||
if (state!.isLooping) {
|
||||
await audioPlayer.seek(Duration.zero);
|
||||
@ -170,10 +165,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
bool isPreSearching = false;
|
||||
|
||||
audioPlayer.onPositionChanged.listen((pos) async {
|
||||
audioPlayer.positionStream.listen((pos) async {
|
||||
if (!isLoaded) return;
|
||||
_bufferingController.add(false);
|
||||
final currentDuration = await audioPlayer.getDuration() ?? Duration.zero;
|
||||
final currentDuration = await audioPlayer.duration ?? Duration.zero;
|
||||
|
||||
// skip all the activeTrack.skipSegments
|
||||
if (state?.isLoading != true &&
|
||||
@ -199,18 +193,16 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
!isPreSearching) {
|
||||
isPreSearching = true;
|
||||
final tracks = state!.tracks.toList();
|
||||
tracks[state!.active + 1] = await SpotubeTrack.fetchFromTrack(
|
||||
final newTrack = await SpotubeTrack.fetchFromTrack(
|
||||
state!.tracks.elementAt(state!.active + 1),
|
||||
preferences,
|
||||
);
|
||||
tracks[state!.active + 1] = newTrack;
|
||||
await audioPlayer.preload(newTrack.ytUri);
|
||||
state = state!.copyWith(tracks: Set.from(tracks));
|
||||
isPreSearching = false;
|
||||
}
|
||||
});
|
||||
|
||||
audioPlayer.onSeekComplete.listen((event) {
|
||||
_bufferingController.add(false);
|
||||
});
|
||||
}
|
||||
|
||||
// properties
|
||||
@ -222,31 +214,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
bool get isLoaded => state != null;
|
||||
|
||||
Stream<bool> get buffering =>
|
||||
_bufferingController.stream.asyncMap((bufferEvent) async {
|
||||
final duration = await audioPlayer.getDuration();
|
||||
final position = await audioPlayer.getCurrentPosition();
|
||||
final isBuffering = state?.activeTrack is! SpotubeTrack &&
|
||||
audioPlayer.state == PlayerState.playing &&
|
||||
(bufferEvent || (duration == null && position == null));
|
||||
return isBuffering;
|
||||
});
|
||||
|
||||
Future<bool> get isBuffering => buffering.first;
|
||||
|
||||
// redirectors
|
||||
static bool get isPlaying => audioPlayer.state == PlayerState.playing;
|
||||
static bool get isPaused => audioPlayer.state == PlayerState.paused;
|
||||
static bool get isStopped => audioPlayer.state == PlayerState.stopped;
|
||||
|
||||
static Stream<Duration> get duration =>
|
||||
audioPlayer.onDurationChanged.asBroadcastStream();
|
||||
static Stream<Duration> get position =>
|
||||
audioPlayer.onPositionChanged.asBroadcastStream();
|
||||
static Stream<bool> get playing => audioPlayer.onPlayerStateChanged
|
||||
.map((event) => event == PlayerState.playing)
|
||||
.asBroadcastStream();
|
||||
|
||||
List<Video> get siblings => state?.isLoading == false
|
||||
? (state!.activeTrack as SpotubeTrack).siblings
|
||||
: [];
|
||||
@ -360,14 +327,10 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
Future<void> play() async {
|
||||
if (!isLoaded) return;
|
||||
_bufferingController.add(true);
|
||||
await pause();
|
||||
await audioServices.addTrack(state!.activeTrack);
|
||||
if (state!.activeTrack is LocalTrack) {
|
||||
await audioPlayer.play(
|
||||
DeviceFileSource((state!.activeTrack as LocalTrack).path),
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
await audioPlayer.play((state!.activeTrack as LocalTrack).path);
|
||||
return;
|
||||
}
|
||||
if (state!.activeTrack is! SpotubeTrack) {
|
||||
@ -392,15 +355,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
final cached =
|
||||
await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!);
|
||||
if (preferences.predownload && cached != null) {
|
||||
await audioPlayer.play(
|
||||
DeviceFileSource(cached.file.path),
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
await audioPlayer.play(cached.file.path);
|
||||
} else {
|
||||
await audioPlayer.play(
|
||||
UrlSource((state!.activeTrack as SpotubeTrack).ytUri),
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
await audioPlayer.play((state!.activeTrack as SpotubeTrack).ytUri);
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,7 +432,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
|
||||
Future<void> seek(Duration position) async {
|
||||
if (!isLoaded) return;
|
||||
_bufferingController.add(true);
|
||||
await audioPlayer.seek(position);
|
||||
await resume();
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
|
||||
showSystemTrayIcon = map["showSystemTrayIcon"] ?? showSystemTrayIcon;
|
||||
|
||||
final localeMap = jsonDecode(map["locale"]);
|
||||
final localeMap = map["locale"] != null ? jsonDecode(map["locale"]) : null;
|
||||
locale =
|
||||
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
||||
}
|
||||
|
@ -1,25 +1,244 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'dart:async';
|
||||
|
||||
final audioPlayer = (() {
|
||||
AudioPlayer.global.setAudioContext(
|
||||
const AudioContext(
|
||||
android: AudioContextAndroid(
|
||||
audioFocus: AndroidAudioFocus.gain,
|
||||
audioMode: AndroidAudioMode.inCall,
|
||||
contentType: AndroidContentType.music,
|
||||
stayAwake: true,
|
||||
usageType: AndroidUsageType.media,
|
||||
),
|
||||
iOS: AudioContextIOS(
|
||||
category: AVAudioSessionCategory.playback,
|
||||
options: [
|
||||
AVAudioSessionOptions.allowBluetooth,
|
||||
AVAudioSessionOptions.allowBluetoothA2DP,
|
||||
AVAudioSessionOptions.defaultToSpeaker,
|
||||
AVAudioSessionOptions.mixWithOthers,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return AudioPlayer();
|
||||
})();
|
||||
import 'package:audioplayers/audioplayers.dart' as ap;
|
||||
|
||||
final audioPlayer = SpotubeAudioPlayer();
|
||||
|
||||
enum PlayerState {
|
||||
playing,
|
||||
paused,
|
||||
completed,
|
||||
buffering,
|
||||
stopped;
|
||||
|
||||
static PlayerState fromApPlayerState(ap.PlayerState state) {
|
||||
switch (state) {
|
||||
case ap.PlayerState.playing:
|
||||
return PlayerState.playing;
|
||||
case ap.PlayerState.paused:
|
||||
return PlayerState.paused;
|
||||
case ap.PlayerState.stopped:
|
||||
return PlayerState.stopped;
|
||||
case ap.PlayerState.completed:
|
||||
return PlayerState.completed;
|
||||
}
|
||||
}
|
||||
|
||||
ap.PlayerState get asAudioPlayerPlayerState {
|
||||
switch (this) {
|
||||
case PlayerState.playing:
|
||||
return ap.PlayerState.playing;
|
||||
case PlayerState.paused:
|
||||
return ap.PlayerState.paused;
|
||||
case PlayerState.stopped:
|
||||
return ap.PlayerState.stopped;
|
||||
case PlayerState.completed:
|
||||
return ap.PlayerState.completed;
|
||||
case PlayerState.buffering:
|
||||
return ap.PlayerState.paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpotubeAudioPlayer {
|
||||
final ap.AudioPlayer? _audioPlayer;
|
||||
|
||||
SpotubeAudioPlayer()
|
||||
: _audioPlayer = apSupportedPlatform ? ap.AudioPlayer() : null;
|
||||
|
||||
/// Whether the current platform supports the audioplayers plugin
|
||||
static const bool apSupportedPlatform = true;
|
||||
|
||||
// stream getters
|
||||
Stream<Duration> get durationStream {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.onDurationChanged;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Duration> get positionStream {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.onPositionChanged;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Duration> get bufferedPositionStream {
|
||||
if (apSupportedPlatform) {
|
||||
// audioplayers doesn't have the capability to get buffered position
|
||||
return const Stream.empty();
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<void> get completedStream {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.onPlayerComplete;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<bool> get playingStream {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.onPlayerStateChanged.map((state) {
|
||||
return state == ap.PlayerState.playing;
|
||||
});
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<bool> get bufferingStream {
|
||||
if (apSupportedPlatform) {
|
||||
return const Stream.empty();
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<PlayerState> get playerStateStream =>
|
||||
_audioPlayer!.onPlayerStateChanged
|
||||
.map((state) => PlayerState.fromApPlayerState(state));
|
||||
|
||||
// regular info getter
|
||||
|
||||
Future<Duration?> get duration async {
|
||||
if (apSupportedPlatform) {
|
||||
return await _audioPlayer!.getDuration();
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Duration?> get position async {
|
||||
if (apSupportedPlatform) {
|
||||
return await _audioPlayer!.getCurrentPosition();
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Duration?> get bufferedPosition async {
|
||||
if (apSupportedPlatform) {
|
||||
// audioplayers doesn't have the capability to get buffered position
|
||||
return null;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasSource {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.source != null;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
// states
|
||||
bool get isPlaying {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.state == ap.PlayerState.playing;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isPaused {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.state == ap.PlayerState.paused;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isStopped {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.state == ap.PlayerState.stopped;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isCompleted {
|
||||
if (apSupportedPlatform) {
|
||||
return _audioPlayer!.state == ap.PlayerState.completed;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isBuffering {
|
||||
if (apSupportedPlatform) {
|
||||
// audioplayers doesn't have the capability to get buffering state
|
||||
return false;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Object _resolveUrlType(String url) {
|
||||
if (apSupportedPlatform) {
|
||||
if (url.startsWith("https")) {
|
||||
return ap.UrlSource(url);
|
||||
} else {
|
||||
return ap.DeviceFileSource(url);
|
||||
}
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> preload(String url) async {
|
||||
final urlType = _resolveUrlType(url);
|
||||
if (apSupportedPlatform && urlType is ap.Source) {
|
||||
// audioplayers doesn't have the capability to preload
|
||||
return;
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> play(String url) async {
|
||||
final urlType = _resolveUrlType(url);
|
||||
if (apSupportedPlatform && urlType is ap.Source) {
|
||||
await _audioPlayer?.play(urlType);
|
||||
} else {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pause() async {
|
||||
await _audioPlayer?.pause();
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<void> resume() async {
|
||||
await _audioPlayer?.resume();
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _audioPlayer?.stop();
|
||||
}
|
||||
|
||||
Future<void> seek(Duration position) async {
|
||||
await _audioPlayer?.seek(position);
|
||||
}
|
||||
|
||||
Future<void> setVolume(double volume) async {
|
||||
await _audioPlayer?.setVolume(volume);
|
||||
}
|
||||
|
||||
Future<void> setSpeed(double speed) async {
|
||||
await _audioPlayer?.setPlaybackRate(speed);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _audioPlayer?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:mpris_service/mpris_service.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
@ -39,7 +38,7 @@ class LinuxAudioService {
|
||||
pause: playlistNotifier.pause,
|
||||
play: playlistNotifier.resume,
|
||||
playPause: () async {
|
||||
if (PlaylistQueueNotifier.isPlaying) {
|
||||
if (audioPlayer.isPlaying) {
|
||||
await playlistNotifier.pause();
|
||||
} else {
|
||||
await playlistNotifier.resume();
|
||||
@ -61,8 +60,9 @@ class LinuxAudioService {
|
||||
));
|
||||
|
||||
final playerStateStream =
|
||||
audioPlayer.onPlayerStateChanged.listen((state) async {
|
||||
audioPlayer.playerStateStream.listen((state) async {
|
||||
switch (state) {
|
||||
case PlayerState.buffering:
|
||||
case PlayerState.playing:
|
||||
mpris.playbackStatus = MPRISPlaybackStatus.playing;
|
||||
break;
|
||||
@ -78,12 +78,12 @@ class LinuxAudioService {
|
||||
}
|
||||
});
|
||||
|
||||
final positionStream = audioPlayer.onPositionChanged.listen((pos) async {
|
||||
final positionStream = audioPlayer.positionStream.listen((pos) async {
|
||||
mpris.position = pos;
|
||||
});
|
||||
|
||||
final durationStream =
|
||||
audioPlayer.onDurationChanged.listen((duration) async {
|
||||
audioPlayer.durationStream.listen((duration) async {
|
||||
mpris.metadata = mpris.metadata.copyWith(length: duration);
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/audio_player.dart';
|
||||
|
||||
@ -29,11 +28,14 @@ class MobileAudioService extends BaseAudioHandler {
|
||||
}
|
||||
});
|
||||
});
|
||||
audioPlayer.onPlayerStateChanged.listen((state) async {
|
||||
audioPlayer.playerStateStream.listen((state) async {
|
||||
playbackState.add(await _transformEvent());
|
||||
});
|
||||
|
||||
audioPlayer.onPositionChanged.listen((pos) async {
|
||||
audioPlayer.positionStream.listen((pos) async {
|
||||
playbackState.add(await _transformEvent());
|
||||
});
|
||||
audioPlayer.bufferedPositionStream.listen((pos) async {
|
||||
playbackState.add(await _transformEvent());
|
||||
});
|
||||
}
|
||||
@ -93,18 +95,15 @@ class MobileAudioService extends BaseAudioHandler {
|
||||
@override
|
||||
Future<void> onTaskRemoved() async {
|
||||
await playlistNotifier.stop();
|
||||
await audioPlayer.release();
|
||||
return super.onTaskRemoved();
|
||||
}
|
||||
|
||||
Future<PlaybackState> _transformEvent() async {
|
||||
final position = (await audioPlayer.getCurrentPosition()) ?? Duration.zero;
|
||||
final position = (await audioPlayer.position) ?? Duration.zero;
|
||||
return PlaybackState(
|
||||
controls: [
|
||||
MediaControl.skipToPrevious,
|
||||
audioPlayer.state == PlayerState.playing
|
||||
? MediaControl.pause
|
||||
: MediaControl.play,
|
||||
audioPlayer.isPlaying ? MediaControl.pause : MediaControl.play,
|
||||
MediaControl.skipToNext,
|
||||
MediaControl.stop,
|
||||
],
|
||||
@ -112,9 +111,9 @@ class MobileAudioService extends BaseAudioHandler {
|
||||
MediaAction.seek,
|
||||
},
|
||||
androidCompactActionIndices: const [0, 1, 2],
|
||||
playing: audioPlayer.state == PlayerState.playing,
|
||||
playing: audioPlayer.isPlaying,
|
||||
updatePosition: position,
|
||||
bufferedPosition: position,
|
||||
bufferedPosition: await audioPlayer.bufferedPosition ?? Duration.zero,
|
||||
shuffleMode: playlist?.isShuffled == true
|
||||
? AudioServiceShuffleMode.all
|
||||
: AudioServiceShuffleMode.none,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:smtc_windows/smtc_windows.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
@ -41,7 +40,7 @@ class WindowsAudioService {
|
||||
});
|
||||
|
||||
final playerStateStream =
|
||||
audioPlayer.onPlayerStateChanged.listen((state) async {
|
||||
audioPlayer.playerStateStream.listen((state) async {
|
||||
switch (state) {
|
||||
case PlayerState.playing:
|
||||
await smtc.setPlaybackStatus(PlaybackStatus.Playing);
|
||||
@ -62,12 +61,11 @@ class WindowsAudioService {
|
||||
}
|
||||
});
|
||||
|
||||
final positionStream = audioPlayer.onPositionChanged.listen((pos) async {
|
||||
final positionStream = audioPlayer.positionStream.listen((pos) async {
|
||||
await smtc.setPosition(pos);
|
||||
});
|
||||
|
||||
final durationStream =
|
||||
audioPlayer.onDurationChanged.listen((duration) async {
|
||||
final durationStream = audioPlayer.durationStream.listen((duration) async {
|
||||
await smtc.setEndTime(duration);
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,6 @@ import audioplayers_darwin
|
||||
import catcher
|
||||
import device_info_plus
|
||||
import flutter_secure_storage_macos
|
||||
import just_audio
|
||||
import local_notifier
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
@ -31,7 +30,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
28
pubspec.lock
28
pubspec.lock
@ -998,30 +998,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
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"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1657,10 +1633,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b"
|
||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0"
|
||||
system_theme:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -55,7 +55,6 @@ dependencies:
|
||||
introduction_screen: ^3.0.2
|
||||
json_annotation: ^4.8.0
|
||||
json_serializable: ^6.6.0
|
||||
just_audio: ^0.9.32
|
||||
logger: ^1.1.0
|
||||
metadata_god: ^0.4.1
|
||||
mime: ^1.0.2
|
||||
|
Loading…
Reference in New Issue
Block a user