mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat(player): add playlist related methods to audio player
This commit is contained in:
parent
06f6adc69c
commit
f1080e1675
@ -6,7 +6,7 @@ import 'package:spotube/components/player/player_controls.dart';
|
|||||||
import 'package:spotube/collections/routes.dart';
|
import 'package:spotube/collections/routes.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import 'package:spotube/components/shared/playbutton_card.dart';
|
|||||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import 'package:spotube/extensions/context.dart';
|
|||||||
import 'package:spotube/hooks/use_progress.dart';
|
import 'package:spotube/hooks/use_progress.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
|
|
||||||
class PlayerControls extends HookConsumerWidget {
|
class PlayerControls extends HookConsumerWidget {
|
||||||
|
@ -10,7 +10,7 @@ import 'package:spotube/components/player/player_track_details.dart';
|
|||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
import 'package:spotube/hooks/use_progress.dart';
|
import 'package:spotube/hooks/use_progress.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
class PlayerOverlay extends HookConsumerWidget {
|
class PlayerOverlay extends HookConsumerWidget {
|
||||||
|
@ -6,7 +6,7 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
import 'package:spotube/components/shared/playbutton_card.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
@ -2,7 +2,7 @@ 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/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
Tuple4<double, Duration, Duration, double> useProgress(WidgetRef ref) {
|
Tuple4<double, Duration, Duration, double> useProgress(WidgetRef ref) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
|
||||||
int useSyncedLyrics(
|
int useSyncedLyrics(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
|
@ -24,7 +24,7 @@ import 'package:spotube/models/logger.dart';
|
|||||||
import 'package:spotube/provider/downloader_provider.dart';
|
import 'package:spotube/provider/downloader_provider.dart';
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/pocketbase.dart';
|
import 'package:spotube/services/pocketbase.dart';
|
||||||
import 'package:spotube/services/youtube.dart';
|
import 'package:spotube/services/youtube.dart';
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
|
@ -11,7 +11,7 @@ import 'package:spotube/extensions/track.dart';
|
|||||||
import 'package:spotube/provider/blacklist_provider.dart';
|
import 'package:spotube/provider/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/audio_services/audio_services.dart';
|
import 'package:spotube/services/audio_services/audio_services.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
@ -1,37 +1,16 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
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:just_audio/just_audio.dart' as ja;
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
|
import 'package:spotube/models/spotube_track.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';
|
||||||
|
|
||||||
final audioPlayer = SpotubeAudioPlayer();
|
final audioPlayer = SpotubeAudioPlayer();
|
||||||
|
|
||||||
enum AudioPlaybackState {
|
|
||||||
playing,
|
|
||||||
paused,
|
|
||||||
completed,
|
|
||||||
buffering,
|
|
||||||
stopped;
|
|
||||||
|
|
||||||
static AudioPlaybackState fromJaPlayerState(ja.PlayerState state) {
|
|
||||||
if (state.playing) {
|
|
||||||
return AudioPlaybackState.playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state.processingState) {
|
|
||||||
case ja.ProcessingState.idle:
|
|
||||||
return AudioPlaybackState.stopped;
|
|
||||||
case ja.ProcessingState.ready:
|
|
||||||
return AudioPlaybackState.paused;
|
|
||||||
case ja.ProcessingState.completed:
|
|
||||||
return AudioPlaybackState.completed;
|
|
||||||
case ja.ProcessingState.loading:
|
|
||||||
case ja.ProcessingState.buffering:
|
|
||||||
return AudioPlaybackState.buffering;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpotubeAudioPlayer {
|
class SpotubeAudioPlayer {
|
||||||
final MkPlayerWithState? _mkPlayer;
|
final MkPlayerWithState? _mkPlayer;
|
||||||
final ja.AudioPlayer? _justAudio;
|
final ja.AudioPlayer? _justAudio;
|
||||||
@ -84,6 +63,18 @@ class SpotubeAudioPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stream that emits when the player is almost (80%) complete
|
||||||
|
Stream<void> get almostCompleteStream {
|
||||||
|
return positionStream
|
||||||
|
.asyncMap((event) async => [event, await duration])
|
||||||
|
.where((event) {
|
||||||
|
final position = event[0] as Duration;
|
||||||
|
final duration = event[1] as Duration;
|
||||||
|
|
||||||
|
return position.inSeconds > (duration.inSeconds * .8).toInt();
|
||||||
|
}).asBroadcastStream();
|
||||||
|
}
|
||||||
|
|
||||||
Stream<bool> get playingStream {
|
Stream<bool> get playingStream {
|
||||||
if (mkSupportedPlatform) {
|
if (mkSupportedPlatform) {
|
||||||
return _mkPlayer!.streams.playing.asBroadcastStream();
|
return _mkPlayer!.streams.playing.asBroadcastStream();
|
||||||
@ -250,6 +241,8 @@ class SpotubeAudioPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
|
_mkLooped = PlaybackLoopMode.none;
|
||||||
|
_mkShuffled = false;
|
||||||
await _mkPlayer?.pause();
|
await _mkPlayer?.pause();
|
||||||
await _justAudio?.stop();
|
await _justAudio?.stop();
|
||||||
}
|
}
|
||||||
@ -273,45 +266,164 @@ class SpotubeAudioPlayer {
|
|||||||
await _mkPlayer?.dispose();
|
await _mkPlayer?.dispose();
|
||||||
await _justAudio?.dispose();
|
await _justAudio?.dispose();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// MediaKit [mk.Player] by default doesn't have a state stream.
|
// Playlist related
|
||||||
class MkPlayerWithState extends mk.Player {
|
|
||||||
final StreamController<AudioPlaybackState> _playerStateStream;
|
|
||||||
|
|
||||||
late final List<StreamSubscription> _subscriptions;
|
Future<void> openPlaylist(
|
||||||
|
List<String> tracks, {
|
||||||
MkPlayerWithState({super.configuration})
|
bool autoPlay = true,
|
||||||
: _playerStateStream = StreamController.broadcast() {
|
int initialIndex = 0,
|
||||||
_subscriptions = [
|
}) async {
|
||||||
streams.buffering.listen((event) {
|
assert(tracks.isNotEmpty);
|
||||||
_playerStateStream.add(AudioPlaybackState.buffering);
|
assert(initialIndex <= tracks.length - 1);
|
||||||
}),
|
if (mkSupportedPlatform) {
|
||||||
streams.playing.listen((playing) {
|
await _mkPlayer!.open(
|
||||||
if (playing) {
|
mk.Playlist(
|
||||||
_playerStateStream.add(AudioPlaybackState.playing);
|
tracks.map((e) => mk.Media(e)).toList(),
|
||||||
} else {
|
index: initialIndex,
|
||||||
_playerStateStream.add(AudioPlaybackState.paused);
|
),
|
||||||
}
|
play: autoPlay,
|
||||||
}),
|
);
|
||||||
streams.completed.listen((event) {
|
} else {
|
||||||
_playerStateStream.add(AudioPlaybackState.completed);
|
await _justAudio!.setAudioSource(
|
||||||
}),
|
ja.ConcatenatingAudioSource(
|
||||||
streams.playlist.listen((event) {
|
useLazyPreparation: true,
|
||||||
if (event.medias.isEmpty) {
|
children:
|
||||||
_playerStateStream.add(AudioPlaybackState.stopped);
|
tracks.map((e) => ja.AudioSource.uri(Uri.parse(e))).toList(),
|
||||||
}
|
),
|
||||||
}),
|
preload: true,
|
||||||
];
|
initialIndex: initialIndex,
|
||||||
}
|
);
|
||||||
|
if (autoPlay) {
|
||||||
Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
|
await _justAudio!.play();
|
||||||
|
}
|
||||||
@override
|
}
|
||||||
FutureOr<void> dispose({int code = 0}) {
|
}
|
||||||
for (var element in _subscriptions) {
|
|
||||||
element.cancel();
|
List<SpotubeTrack> resolveTracksForSource(List<SpotubeTrack> tracks) {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
final urls = _mkPlayer!.state.playlist.medias.map((e) => e.uri).toList();
|
||||||
|
return tracks.where((e) => urls.contains(e.ytUri)).toList();
|
||||||
|
} else {
|
||||||
|
final urls = (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
|
||||||
|
.children
|
||||||
|
.map((e) => (e as ja.UriAudioSource).uri.toString())
|
||||||
|
.toList();
|
||||||
|
return tracks.where((e) => urls.contains(e.ytUri)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tracksExistsInPlaylist(List<SpotubeTrack> tracks) {
|
||||||
|
return resolveTracksForSource(tracks).length == tracks.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get currentIndex {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkPlayer!.state.playlist.index;
|
||||||
|
} else {
|
||||||
|
return _justAudio!.sequenceState!.currentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> skipToIndex(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> clearPlaylist() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await Future.wait(
|
||||||
|
_mkPlayer!.state.playlist.medias.mapIndexed(
|
||||||
|
(i, e) async => await _mkPlayer!.remove(i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await (_justAudio!.audioSource as ja.ConcatenatingAudioSource).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _mkShuffled = false;
|
||||||
|
|
||||||
|
Future<void> setShuffle(bool shuffle) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.setShuffle(shuffle);
|
||||||
|
_mkShuffled = shuffle;
|
||||||
|
} else {
|
||||||
|
await _justAudio!.setShuffleModeEnabled(shuffle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isShuffled() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkShuffled;
|
||||||
|
} else {
|
||||||
|
return _justAudio!.shuffleModeEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaybackLoopMode _mkLooped = PlaybackLoopMode.none;
|
||||||
|
|
||||||
|
Future<void> setLoopMode(PlaybackLoopMode loop) async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
await _mkPlayer!.setPlaylistMode(loop.toPlaylistMode());
|
||||||
|
_mkLooped = loop;
|
||||||
|
} else {
|
||||||
|
await _justAudio!.setLoopMode(loop.toLoopMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PlaybackLoopMode> getLoopMode() async {
|
||||||
|
if (mkSupportedPlatform) {
|
||||||
|
return _mkLooped;
|
||||||
|
} else {
|
||||||
|
return PlaybackLoopMode.fromLoopMode(_justAudio!.loopMode);
|
||||||
}
|
}
|
||||||
return super.dispose(code: code);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
53
lib/services/audio_player/loop_mode.dart
Normal file
53
lib/services/audio_player/loop_mode.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
/// An unified loop mode for both [LoopMode] and [PlaylistMode]
|
||||||
|
enum PlaybackLoopMode {
|
||||||
|
all,
|
||||||
|
one,
|
||||||
|
none;
|
||||||
|
|
||||||
|
static PlaybackLoopMode fromLoopMode(LoopMode loopMode) {
|
||||||
|
switch (loopMode) {
|
||||||
|
case LoopMode.all:
|
||||||
|
return PlaybackLoopMode.all;
|
||||||
|
case LoopMode.one:
|
||||||
|
return PlaybackLoopMode.one;
|
||||||
|
case LoopMode.off:
|
||||||
|
return PlaybackLoopMode.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoopMode toLoopMode() {
|
||||||
|
switch (this) {
|
||||||
|
case PlaybackLoopMode.all:
|
||||||
|
return LoopMode.all;
|
||||||
|
case PlaybackLoopMode.one:
|
||||||
|
return LoopMode.one;
|
||||||
|
case PlaybackLoopMode.none:
|
||||||
|
return LoopMode.off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PlaybackLoopMode fromPlaylistMode(PlaylistMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case PlaylistMode.single:
|
||||||
|
return PlaybackLoopMode.one;
|
||||||
|
case PlaylistMode.loop:
|
||||||
|
return PlaybackLoopMode.all;
|
||||||
|
case PlaylistMode.none:
|
||||||
|
return PlaybackLoopMode.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaylistMode toPlaylistMode() {
|
||||||
|
switch (this) {
|
||||||
|
case PlaybackLoopMode.all:
|
||||||
|
return PlaylistMode.loop;
|
||||||
|
case PlaybackLoopMode.one:
|
||||||
|
return PlaylistMode.single;
|
||||||
|
case PlaybackLoopMode.none:
|
||||||
|
return PlaylistMode.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
lib/services/audio_player/mk_state_player.dart
Normal file
46
lib/services/audio_player/mk_state_player.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||||
|
|
||||||
|
/// MediaKit [Player] by default doesn't have a state stream.
|
||||||
|
/// This class adds a state stream to the [Player] class.
|
||||||
|
class MkPlayerWithState extends Player {
|
||||||
|
final StreamController<AudioPlaybackState> _playerStateStream;
|
||||||
|
|
||||||
|
late final List<StreamSubscription> _subscriptions;
|
||||||
|
|
||||||
|
MkPlayerWithState({super.configuration})
|
||||||
|
: _playerStateStream = StreamController.broadcast() {
|
||||||
|
_subscriptions = [
|
||||||
|
streams.buffering.listen((event) {
|
||||||
|
_playerStateStream.add(AudioPlaybackState.buffering);
|
||||||
|
}),
|
||||||
|
streams.playing.listen((playing) {
|
||||||
|
if (playing) {
|
||||||
|
_playerStateStream.add(AudioPlaybackState.playing);
|
||||||
|
} else {
|
||||||
|
_playerStateStream.add(AudioPlaybackState.paused);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
streams.completed.listen((event) {
|
||||||
|
_playerStateStream.add(AudioPlaybackState.completed);
|
||||||
|
}),
|
||||||
|
streams.playlist.listen((event) {
|
||||||
|
if (event.medias.isEmpty) {
|
||||||
|
_playerStateStream.add(AudioPlaybackState.stopped);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose({int code = 0}) {
|
||||||
|
for (var element in _subscriptions) {
|
||||||
|
element.cancel();
|
||||||
|
}
|
||||||
|
return super.dispose(code: code);
|
||||||
|
}
|
||||||
|
}
|
28
lib/services/audio_player/playback_state.dart
Normal file
28
lib/services/audio_player/playback_state.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
/// An unified playback state enum
|
||||||
|
enum AudioPlaybackState {
|
||||||
|
playing,
|
||||||
|
paused,
|
||||||
|
completed,
|
||||||
|
buffering,
|
||||||
|
stopped;
|
||||||
|
|
||||||
|
static AudioPlaybackState fromJaPlayerState(PlayerState state) {
|
||||||
|
if (state.playing) {
|
||||||
|
return AudioPlaybackState.playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state.processingState) {
|
||||||
|
case ProcessingState.idle:
|
||||||
|
return AudioPlaybackState.stopped;
|
||||||
|
case ProcessingState.ready:
|
||||||
|
return AudioPlaybackState.paused;
|
||||||
|
case ProcessingState.completed:
|
||||||
|
return AudioPlaybackState.completed;
|
||||||
|
case ProcessingState.loading:
|
||||||
|
case ProcessingState.buffering:
|
||||||
|
return AudioPlaybackState.buffering;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,8 @@ import 'package:mpris_service/mpris_service.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class LinuxAudioService {
|
class LinuxAudioService {
|
||||||
|
@ -3,7 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:audio_session/audio_session.dart';
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
|
||||||
class MobileAudioService extends BaseAudioHandler {
|
class MobileAudioService extends BaseAudioHandler {
|
||||||
AudioSession? session;
|
AudioSession? session;
|
||||||
|
@ -4,7 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:smtc_windows/smtc_windows.dart';
|
import 'package:smtc_windows/smtc_windows.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class WindowsAudioService {
|
class WindowsAudioService {
|
||||||
|
Loading…
Reference in New Issue
Block a user