From d693624b6d04c5346567b5c760727a679d6eb13f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 26 Mar 2024 13:36:35 +0600 Subject: [PATCH] feat: add volume control support --- lib/components/player/player.dart | 21 +++++++++++---- lib/components/player/player_queue.dart | 2 ++ lib/components/player/volume_slider.dart | 34 +++++++++++++----------- lib/components/root/bottom_player.dart | 14 ++++++++-- lib/models/connect/ws_event.dart | 13 +++++++++ lib/pages/connect/control/control.dart | 17 ++++++++++++ lib/provider/connect/connect.dart | 12 +++++++++ lib/provider/connect/server.dart | 14 ++++++++++ 8 files changed, 104 insertions(+), 23 deletions(-) diff --git a/lib/components/player/player.dart b/lib/components/player/player.dart index 3439bc65..6dbd9b11 100644 --- a/lib/components/player/player.dart +++ b/lib/components/player/player.dart @@ -26,6 +26,7 @@ import 'package:spotube/models/local_track.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -378,11 +379,21 @@ class PlayerView extends HookConsumerWidget { enabledThumbRadius: 8, ), ), - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: VolumeSlider( - fullWidth: true, - ), + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16), + child: Consumer(builder: (context, ref, _) { + final volume = ref.watch(volumeProvider); + return VolumeSlider( + fullWidth: true, + value: volume, + onChanged: (value) { + ref + .read(volumeProvider.notifier) + .setVolume(value); + }, + ); + }), ), ), ], diff --git a/lib/components/player/player_queue.dart b/lib/components/player/player_queue.dart index fff10b9f..86c543b0 100644 --- a/lib/components/player/player_queue.dart +++ b/lib/components/player/player_queue.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; +import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; @@ -298,6 +299,7 @@ class PlayerQueue extends HookConsumerWidget { ), ), ), + const Gap(100), ], ), ), diff --git a/lib/components/player/volume_slider.dart b/lib/components/player/volume_slider.dart index 7596a347..102bbef6 100644 --- a/lib/components/player/volume_slider.dart +++ b/lib/components/player/volume_slider.dart @@ -3,37 +3,39 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/provider/volume_provider.dart'; class VolumeSlider extends HookConsumerWidget { final bool fullWidth; + + final double value; + final ValueChanged onChanged; + const VolumeSlider({ super.key, this.fullWidth = false, + required this.value, + required this.onChanged, }); @override Widget build(BuildContext context, ref) { - final volume = ref.watch(volumeProvider); - final volumeNotifier = ref.watch(volumeProvider.notifier); - var slider = Listener( onPointerSignal: (event) async { if (event is PointerScrollEvent) { if (event.scrollDelta.dy > 0) { - final value = volume - .2; - volumeNotifier.setVolume(value < 0 ? 0 : value); + final newValue = value - .2; + onChanged(newValue < 0 ? 0 : newValue); } else { - final value = volume + .2; - volumeNotifier.setVolume(value > 1 ? 1 : value); + final newValue = value + .2; + onChanged(newValue > 1 ? 1 : newValue); } } }, child: Slider( min: 0, max: 1, - value: volume, - onChanged: volumeNotifier.setVolume, + value: value, + onChanged: onChanged, ), ); return Row( @@ -42,20 +44,20 @@ class VolumeSlider extends HookConsumerWidget { children: [ IconButton( icon: Icon( - volume == 0 + value == 0 ? SpotubeIcons.volumeMute - : volume <= 0.2 + : value <= 0.2 ? SpotubeIcons.volumeLow - : volume <= 0.6 + : value <= 0.6 ? SpotubeIcons.volumeMedium : SpotubeIcons.volumeHigh, size: 16, ), onPressed: () { - if (volume == 0) { - volumeNotifier.setVolume(1); + if (value == 0) { + onChanged(1); } else { - volumeNotifier.setVolume(0); + onChanged(0); } }, ), diff --git a/lib/components/root/bottom_player.dart b/lib/components/root/bottom_player.dart index 89fea296..e15e3717 100644 --- a/lib/components/root/bottom_player.dart +++ b/lib/components/root/bottom_player.dart @@ -19,10 +19,11 @@ import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/models/logger.dart'; import 'package:flutter/material.dart'; import 'package:spotube/provider/authentication_provider.dart'; -import 'package:spotube/provider/connect/connect.dart'; +import 'package:spotube/provider/connect/connect.dart' hide volumeProvider; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; +import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/utils/platform.dart'; class BottomPlayer extends HookConsumerWidget { @@ -125,7 +126,16 @@ class BottomPlayer extends HookConsumerWidget { Container( height: 40, constraints: const BoxConstraints(maxWidth: 250), - child: const VolumeSlider(), + child: Consumer(builder: (context, ref, _) { + final volume = ref.watch(volumeProvider); + return VolumeSlider( + fullWidth: true, + value: volume, + onChanged: (value) { + ref.read(volumeProvider.notifier).setVolume(value); + }, + ); + }), ) ], ) diff --git a/lib/models/connect/ws_event.dart b/lib/models/connect/ws_event.dart index 54cfaad0..1093e92b 100644 --- a/lib/models/connect/ws_event.dart +++ b/lib/models/connect/ws_event.dart @@ -2,6 +2,7 @@ part of 'connect.dart'; enum WsEvent { error, + volume, removeTrack, addTrack, reorder, @@ -209,6 +210,14 @@ class WebSocketEvent { WebSocketReorderEvent.fromJson(data as Map)); } } + + Future onVolume( + EventCallback callback, + ) async { + if (type == WsEvent.volume) { + await callback(WebSocketVolumeEvent(data as double)); + } + } } class WebSocketLoopEvent extends WebSocketEvent { @@ -355,3 +364,7 @@ class WebSocketReorderEvent extends WebSocketEvent { }); } } + +class WebSocketVolumeEvent extends WebSocketEvent { + WebSocketVolumeEvent(double data) : super(WsEvent.volume, data); +} diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index f1798dbc..d8af9cb5 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/player/player_queue.dart'; +import 'package:spotube/components/player/volume_slider.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/links/anchor_button.dart'; import 'package:spotube/components/shared/links/artist_link.dart'; @@ -263,6 +264,22 @@ class ConnectControlPage extends HookConsumerWidget { ), ), const SliverGap(30), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 20), + sliver: SliverToBoxAdapter( + child: Consumer(builder: (context, ref, _) { + final volume = ref.watch(volumeProvider); + return VolumeSlider( + fullWidth: true, + value: volume, + onChanged: (value) { + ref.read(volumeProvider.notifier).state = value; + }, + ); + }), + ), + ), + const SliverGap(30), if (constrains.mdAndDown) SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 20), diff --git a/lib/provider/connect/connect.dart b/lib/provider/connect/connect.dart index 4f9d0275..65daaf55 100644 --- a/lib/provider/connect/connect.dart +++ b/lib/provider/connect/connect.dart @@ -34,6 +34,10 @@ final queueProvider = StateProvider( (ref) => ProxyPlaylist({}), ); +final volumeProvider = StateProvider( + (ref) => 1.0, +); + class ConnectNotifier extends AsyncNotifier { @override build() async { @@ -85,6 +89,10 @@ class ConnectNotifier extends AsyncNotifier { event.onLoop((event) { ref.read(loopModeProvider.notifier).state = event.data; }); + + event.onVolume((event) { + ref.read(volumeProvider.notifier).state = event.data; + }); }, onError: (error) { Catcher2.reportCheckedError( @@ -164,6 +172,10 @@ class ConnectNotifier extends AsyncNotifier { Future reorder(ReorderData data) async { emit(WebSocketReorderEvent(data)); } + + Future setVolume(double value) async { + emit(WebSocketVolumeEvent(value)); + } } final connectProvider = diff --git a/lib/provider/connect/server.dart b/lib/provider/connect/server.dart index 41b62011..db170131 100644 --- a/lib/provider/connect/server.dart +++ b/lib/provider/connect/server.dart @@ -74,6 +74,9 @@ final connectServerProvider = FutureProvider((ref) async { channel.sink.add( WebSocketLoopEvent(audioPlayer.loopMode).toJson(), ); + channel.sink.add( + WebSocketVolumeEvent(audioPlayer.volume).toJson(), + ); subscriptions.addAll([ audioPlayer.positionStream.listen( @@ -111,6 +114,13 @@ final connectServerProvider = FutureProvider((ref) async { ); }, ), + audioPlayer.volumeStream.listen( + (volume) { + channel.sink.add( + WebSocketVolumeEvent(volume).toJson(), + ); + }, + ), channel.stream.listen( (message) { try { @@ -181,6 +191,10 @@ final connectServerProvider = FutureProvider((ref) async { event.data.newIndex, ); }); + + event.onVolume((event) async { + await audioPlayer.setVolume(event.data); + }); } catch (e, stackTrace) { Catcher2.reportCheckedError(e, stackTrace); channel.sink.add(WebSocketErrorEvent(e.toString()).toJson());