Skip to content

Commit

Permalink
refactor: create audio player wrapper and remove just_audio (again)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kingkor Roy Tirtho committed Apr 30, 2023
1 parent 7df2a0d commit 12915f3
Show file tree
Hide file tree
Showing 17 changed files with 305 additions and 149 deletions.
11 changes: 7 additions & 4 deletions lib/collections/intents.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions lib/components/album/album_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions lib/components/player/player_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand All @@ -154,6 +155,7 @@ class PlayerControls extends HookConsumerWidget {
);
},
activeColor: sliderColor,
secondaryActiveColor: sliderColor.withOpacity(0.2),
inactiveColor: sliderColor.withOpacity(0.15),
),
),
Expand Down
4 changes: 2 additions & 2 deletions lib/components/player/player_overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions lib/components/playlist/playlist_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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}",
Expand Down
15 changes: 10 additions & 5 deletions lib/hooks/use_progress.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
);
}
4 changes: 2 additions & 2 deletions lib/hooks/use_synced_lyrics.dart
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
1 change: 1 addition & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Future<void> main(List<String> rawArgs) async {
}

WidgetsFlutterBinding.ensureInitialized();

await DesktopTools.ensureInitialized(
DesktopWindowOptions(
hideTitleBar: true,
Expand Down
64 changes: 10 additions & 54 deletions lib/provider/playlist_queue_provider.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -140,25 +139,21 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {

late AudioServices audioServices;

final StreamController<bool> _bufferingController;

static final provider =
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
(ref) => PlaylistQueueNotifier._(ref),
);

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);
Expand All @@ -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 &&
Expand All @@ -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
Expand All @@ -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
: [];
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion lib/provider/user_preferences_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 12915f3

Please sign in to comment.