From 22e63ff254b3a730f79af9f8f3383130b366a874 Mon Sep 17 00:00:00 2001 From: nnra6864 Date: Fri, 18 Sep 2026 15:11:35 +0200 Subject: [PATCH 01/12] feat: Implemented persistent volume control for the audio player fix: Saving the preMuteVolume instead of regular volume to prefs style: Adjusted naming style: Reordered func for consistency --- .../molecules/audio_player/audio_player.dart | 221 ++++++++++++------ 1 file changed, 149 insertions(+), 72 deletions(-) diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index 25bd22983..d3d86ee67 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -5,10 +5,11 @@ import 'package:commet/utils/text_utils.dart'; import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import 'package:tiamat/tiamat.dart' as tiamat; +import 'package:shared_preferences/shared_preferences.dart'; class AudioPlayer extends StatefulWidget { const AudioPlayer( - {required this.file, this.fileName, this.fileSize, super.key}); + {required this.file, this.fileName, this.fileSize, super.key}); final String? fileName; final int? fileSize; @@ -32,9 +33,12 @@ class _AudioPlayerState extends State { void initState() { super.initState(); + _loadPreferences(); + subs = [ player.stream.playing.listen(onPlayingChanged), player.stream.position.listen(onPositionChanged), + player.stream.volume.listen(onVolumeChanged), if (widget.file.onProgressChanged != null) widget.file.onProgressChanged!.listen(onDownloadProgressChanged), ]; @@ -58,44 +62,69 @@ class _AudioPlayerState extends State { double displayPosition = 0; double? downloadProgress; + bool isMuted = false; + double volume = 50; + double preMuteVolume = 50; + + Future _savePreferences() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('audio_muted', isMuted); + await prefs.setDouble('audio_volume', preMuteVolume); + } + + Future _loadPreferences() async { + final prefs = await SharedPreferences.getInstance(); + final savedMuted = prefs.getBool('audio_muted') ?? false; + final savedVolume = prefs.getDouble('audio_volume') ?? 100.0; + + setState(() { + volume = savedVolume; + preMuteVolume = savedVolume; + }); + player.setVolume(savedVolume); + if (savedMuted) { + toggleMute(); + } + } + @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(8)), + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(8)), child: SizedBox( width: 500, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.fileName != null) - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 0, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon( - Icons.audio_file, - size: 20, - ), - SizedBox( - width: 8, - ), - tiamat.Text.labelLow(widget.fileName!), - ], - ), - if (widget.fileSize != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 12, 0), - child: tiamat.Text.labelLow( - TextUtils.readableFileSize(widget.fileSize!)), + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 0, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.audio_file, + size: 20, ), - ], - ), + SizedBox( + width: 8, + ), + tiamat.Text.labelLow(widget.fileName!), + ], + ), + if (widget.fileSize != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 12, 0), + child: tiamat.Text.labelLow( + TextUtils.readableFileSize(widget.fileSize!)), + ), + ], ), + ), Padding( padding: const EdgeInsets.fromLTRB(0, 8, 0, 4), child: tiamat.Seperator( @@ -108,40 +137,56 @@ class _AudioPlayerState extends State { width: 8, ), SizedBox( - width: 40, - height: 40, - child: state == AudioPlayerState.loading - ? Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - value: downloadProgress, - )), - ) - : tiamat.IconButton( - icon: state == AudioPlayerState.paused - ? Icons.play_arrow - : Icons.pause, - onPressed: onPlayButtonPressed, - )), + width: 40, + height: 40, + child: state == AudioPlayerState.loading + ? Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + value: downloadProgress, + )), + ) + : tiamat.IconButton( + icon: state == AudioPlayerState.paused + ? Icons.play_arrow + : Icons.pause, + onPressed: onPlayButtonPressed, + )), Expanded( - child: tiamat.Slider( - value: displayPosition, - onChangeStart: (value) { - dragging = true; - }, - onChangeEnd: (value) { - dragging = false; - - var position = player.state.duration * value; - - player.seek(position); - }, - onChanged: (value) => setState(() { - displayPosition = value; + child: tiamat.Slider( + value: displayPosition, + onChangeStart: (value) { + dragging = true; + }, + onChangeEnd: (value) { + dragging = false; + + var position = player.state.duration * value; + + player.seek(position); + }, + onChanged: (value) => setState(() { + displayPosition = value; + }), + )), + tiamat.IconButton( + icon: isMuted ? Icons.volume_mute : volume == 0 ? Icons.volume_off : Icons.volume_up, + onPressed: (() { + toggleMute(); + _savePreferences(); }), - )) + ), + SizedBox( + width: 100, + child: tiamat.Slider( + value: volume / 100, + onChanged: (value) => setVolume(value * 100), + onChangeEnd: (value) => _savePreferences(), + ), + ), + const SizedBox(width: 8), ], ), ], @@ -170,6 +215,18 @@ class _AudioPlayerState extends State { } } + void onPlayingChanged(bool event) { + if (event) { + setState(() { + state = AudioPlayerState.playing; + }); + } else { + setState(() { + state = AudioPlayerState.paused; + }); + } + } + void loadAudio() async { var uri = await widget.file.resolve(); @@ -183,16 +240,11 @@ class _AudioPlayerState extends State { }); } - void onPlayingChanged(bool event) { - if (event) { - setState(() { - state = AudioPlayerState.playing; - }); - } else { - setState(() { - state = AudioPlayerState.paused; - }); - } + void onDownloadProgressChanged(DownloadProgress event) { + print(event); + setState(() { + downloadProgress = event.downloaded.toDouble() / event.total.toDouble(); + }); } void onPositionChanged(Duration event) { @@ -208,10 +260,35 @@ class _AudioPlayerState extends State { } } - void onDownloadProgressChanged(DownloadProgress event) { - print(event); + void toggleMute() { + if (!isMuted) { + setState(() { + isMuted = true; + preMuteVolume = volume; + volume = 0; + }); + player.setVolume(0.0); + } else { + setState(() { + isMuted = false; + volume = preMuteVolume; + }); + player.setVolume(volume); + } + } + + void setVolume(double value) { setState(() { - downloadProgress = event.downloaded.toDouble() / event.total.toDouble(); + volume = value; + preMuteVolume = value; + isMuted = false; + }); + player.setVolume(value); + } + + void onVolumeChanged(double event) { + setState(() { + volume = event; }); } } From fdb05a2d12d4ef2b53e4faa9f7d19da92b690b06 Mon Sep 17 00:00:00 2001 From: nnra6864 Date: Tue, 22 Sep 2026 05:29:54 +0200 Subject: [PATCH 02/12] feat: Switched from SharedPreferences package to commet/lib/config/preferences.dart --- commet/lib/config/preferences.dart | 3 ++ .../molecules/audio_player/audio_player.dart | 38 +++++-------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/commet/lib/config/preferences.dart b/commet/lib/config/preferences.dart index 106220c73..d279189f8 100644 --- a/commet/lib/config/preferences.dart +++ b/commet/lib/config/preferences.dart @@ -560,4 +560,7 @@ class Preferences { StringListPreference expandedSpaceGroups = StringListPreference("expanded_space_groups", defaultValue: []); + + DoublePreference playerVolume = DoublePreference("player_volume", defaultValue: 100); + BoolPreference isPlayerMuted = BoolPreference("is_player_muted", defaultValue: false); } diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index d3d86ee67..d930868c6 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -5,7 +5,7 @@ import 'package:commet/utils/text_utils.dart'; import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import 'package:tiamat/tiamat.dart' as tiamat; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:commet/main.dart'; class AudioPlayer extends StatefulWidget { const AudioPlayer( @@ -33,7 +33,8 @@ class _AudioPlayerState extends State { void initState() { super.initState(); - _loadPreferences(); + setVolume(preferences.playerVolume.value); + if (preferences.isPlayerMuted.value) toggleIsMuted(); subs = [ player.stream.playing.listen(onPlayingChanged), @@ -63,29 +64,8 @@ class _AudioPlayerState extends State { double? downloadProgress; bool isMuted = false; - double volume = 50; - double preMuteVolume = 50; - - Future _savePreferences() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool('audio_muted', isMuted); - await prefs.setDouble('audio_volume', preMuteVolume); - } - - Future _loadPreferences() async { - final prefs = await SharedPreferences.getInstance(); - final savedMuted = prefs.getBool('audio_muted') ?? false; - final savedVolume = prefs.getDouble('audio_volume') ?? 100.0; - - setState(() { - volume = savedVolume; - preMuteVolume = savedVolume; - }); - player.setVolume(savedVolume); - if (savedMuted) { - toggleMute(); - } - } + double volume = 100; + double preMuteVolume = 100; @override Widget build(BuildContext context) { @@ -174,8 +154,8 @@ class _AudioPlayerState extends State { tiamat.IconButton( icon: isMuted ? Icons.volume_mute : volume == 0 ? Icons.volume_off : Icons.volume_up, onPressed: (() { - toggleMute(); - _savePreferences(); + toggleIsMuted(); + preferences.isPlayerMuted.set(isMuted); }), ), SizedBox( @@ -183,7 +163,7 @@ class _AudioPlayerState extends State { child: tiamat.Slider( value: volume / 100, onChanged: (value) => setVolume(value * 100), - onChangeEnd: (value) => _savePreferences(), + onChangeEnd: (value) => preferences.playerVolume.set(preMuteVolume), ), ), const SizedBox(width: 8), @@ -260,7 +240,7 @@ class _AudioPlayerState extends State { } } - void toggleMute() { + void toggleIsMuted() { if (!isMuted) { setState(() { isMuted = true; From 963aa9575eaa88620f45fc9914f10c5c2ba9fadf Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:30:20 +0930 Subject: [PATCH 03/12] add hover menu --- commet/lib/ui/atoms/hover_menu.dart | 96 ++++++++++ .../molecules/audio_player/audio_player.dart | 170 ++++++++++-------- 2 files changed, 196 insertions(+), 70 deletions(-) create mode 100644 commet/lib/ui/atoms/hover_menu.dart diff --git a/commet/lib/ui/atoms/hover_menu.dart b/commet/lib/ui/atoms/hover_menu.dart new file mode 100644 index 000000000..6b84c905f --- /dev/null +++ b/commet/lib/ui/atoms/hover_menu.dart @@ -0,0 +1,96 @@ +import 'package:commet/utils/debounce.dart'; +import 'package:flutter/material.dart'; + +class HoverMenu extends StatefulWidget { + const HoverMenu( + {super.key, + this.menuAlignment = Alignment.topLeft, + this.parentAlignment = Alignment.topLeft, + required this.builder, + required this.child}); + final Widget child; + final Widget Function(BuildContext context) builder; + final Alignment menuAlignment; + final Alignment parentAlignment; + @override + State createState() => HoverMenuState(); +} + +class HoverMenuState extends State { + OverlayEntry? entry; + LayerLink link = LayerLink(); + + bool overlayHovered = false; + + Debouncer overlayRemoveDebounder = + Debouncer(delay: Duration(milliseconds: 20)); + +// @override +// void didUpdateWidget(covariant HoverMenu oldWidget) { + +// WidgetsBinding.instance.addPostFrameCallback((_) { +// entry?.markNeedsBuild(); +// }); +// super.didUpdateWidget(oldWidget); +// } + + void addOverlay() { + if (entry != null) { + entry?.remove(); + entry = null; + } + + entry = OverlayEntry( + canSizeOverlay: true, + builder: (context) { + return Row( + children: [ + CompositedTransformFollower( + link: link, + followerAnchor: widget.menuAlignment, + targetAnchor: widget.parentAlignment, + child: MouseRegion( + onEnter: (event) { + overlayRemoveDebounder.cancel(); + }, + onExit: (event) { + overlayRemoveDebounder.run(removeOverlay); + }, + child: widget.builder(context))), + ], + ); + }, + ); + + Overlay.of( + context, + ).insert(entry!); + } + + void removeOverlay() { + if (entry != null) { + entry?.remove(); + entry = null; + } + } + + @override + Widget build(BuildContext context) { + return CompositedTransformTarget( + link: link, + child: MouseRegion( + onEnter: (event) { + overlayRemoveDebounder.cancel(); + if (entry == null) { + addOverlay(); + } + }, + onExit: (event) { + if (!overlayHovered) { + overlayRemoveDebounder.run(removeOverlay); + } + }, + child: widget.child), + ); + } +} diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index d930868c6..dfa0caf60 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:commet/cache/file_provider.dart'; +import 'package:commet/ui/atoms/hover_menu.dart'; import 'package:commet/utils/text_utils.dart'; import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; @@ -67,6 +68,8 @@ class _AudioPlayerState extends State { double volume = 100; double preMuteVolume = 100; + final GlobalKey menuKey = GlobalKey(); + @override Widget build(BuildContext context) { return Container( @@ -79,32 +82,32 @@ class _AudioPlayerState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.fileName != null) - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 0, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon( - Icons.audio_file, - size: 20, - ), - SizedBox( - width: 8, + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 0, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.audio_file, + size: 20, + ), + SizedBox( + width: 8, + ), + tiamat.Text.labelLow(widget.fileName!), + ], + ), + if (widget.fileSize != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 12, 0), + child: tiamat.Text.labelLow( + TextUtils.readableFileSize(widget.fileSize!)), ), - tiamat.Text.labelLow(widget.fileName!), - ], - ), - if (widget.fileSize != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 12, 0), - child: tiamat.Text.labelLow( - TextUtils.readableFileSize(widget.fileSize!)), - ), - ], + ], + ), ), - ), Padding( padding: const EdgeInsets.fromLTRB(0, 8, 0, 4), child: tiamat.Seperator( @@ -117,56 +120,78 @@ class _AudioPlayerState extends State { width: 8, ), SizedBox( - width: 40, - height: 40, - child: state == AudioPlayerState.loading - ? Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - value: downloadProgress, - )), - ) - : tiamat.IconButton( - icon: state == AudioPlayerState.paused - ? Icons.play_arrow - : Icons.pause, - onPressed: onPlayButtonPressed, - )), + width: 40, + height: 40, + child: state == AudioPlayerState.loading + ? Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + value: downloadProgress, + )), + ) + : tiamat.IconButton( + icon: state == AudioPlayerState.paused + ? Icons.play_arrow + : Icons.pause, + onPressed: onPlayButtonPressed, + )), Expanded( - child: tiamat.Slider( - value: displayPosition, - onChangeStart: (value) { - dragging = true; - }, - onChangeEnd: (value) { - dragging = false; - - var position = player.state.duration * value; - - player.seek(position); - }, - onChanged: (value) => setState(() { - displayPosition = value; - }), - )), - tiamat.IconButton( - icon: isMuted ? Icons.volume_mute : volume == 0 ? Icons.volume_off : Icons.volume_up, - onPressed: (() { - toggleIsMuted(); - preferences.isPlayerMuted.set(isMuted); + child: tiamat.Slider( + value: displayPosition, + onChangeStart: (value) { + dragging = true; + }, + onChangeEnd: (value) { + dragging = false; + + var position = player.state.duration * value; + + player.seek(position); + }, + onChanged: (value) => setState(() { + displayPosition = value; }), - ), - SizedBox( - width: 100, - child: tiamat.Slider( - value: volume / 100, - onChanged: (value) => setVolume(value * 100), - onChangeEnd: (value) => preferences.playerVolume.set(preMuteVolume), + )), + const SizedBox(width: 8), + HoverMenu( + key: menuKey, + menuAlignment: Alignment.bottomCenter, + parentAlignment: Alignment.topCenter, + child: tiamat.IconButton( + icon: isMuted + ? Icons.volume_mute + : volume == 0 + ? Icons.volume_off + : Icons.volume_up, + onPressed: (() { + toggleIsMuted(); + preferences.isPlayerMuted.set(isMuted); + }), ), + builder: (context) { + return RotatedBox( + quarterTurns: 3, + child: Container( + decoration: BoxDecoration( + color: + ColorScheme.of(context).surfaceContainer, + borderRadius: BorderRadius.circular(8)), + child: SizedBox( + width: 200, + height: 50, + child: tiamat.Slider( + value: volume / 100, + onChanged: (value) => setVolume(value * 100), + onChangeEnd: (value) => + preferences.playerVolume.set(preMuteVolume), + ), + ), + )); + }, ), - const SizedBox(width: 8), + const SizedBox(width: 12), ], ), ], @@ -263,6 +288,11 @@ class _AudioPlayerState extends State { preMuteVolume = value; isMuted = false; }); + + if (menuKey.currentState case HoverMenuState state) { + state.entry?.markNeedsBuild(); + } + player.setVolume(value); } From 3f995f1a4fb32203aa44a1a1395588b79aa8ca29 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:32:55 +0930 Subject: [PATCH 04/12] Update audio_player.dart --- commet/lib/ui/molecules/audio_player/audio_player.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index dfa0caf60..0f62511e5 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -280,6 +280,11 @@ class _AudioPlayerState extends State { }); player.setVolume(volume); } + + if (menuKey.currentState case HoverMenuState state) { + state.entry?.markNeedsBuild(); + } + } void setVolume(double value) { From 97ec4a9bc86fde510bd63dd369308736035c5be4 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:18:58 +0930 Subject: [PATCH 05/12] Update hover_menu.dart --- commet/lib/ui/atoms/hover_menu.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/commet/lib/ui/atoms/hover_menu.dart b/commet/lib/ui/atoms/hover_menu.dart index 6b84c905f..6b8a02711 100644 --- a/commet/lib/ui/atoms/hover_menu.dart +++ b/commet/lib/ui/atoms/hover_menu.dart @@ -25,15 +25,6 @@ class HoverMenuState extends State { Debouncer overlayRemoveDebounder = Debouncer(delay: Duration(milliseconds: 20)); -// @override -// void didUpdateWidget(covariant HoverMenu oldWidget) { - -// WidgetsBinding.instance.addPostFrameCallback((_) { -// entry?.markNeedsBuild(); -// }); -// super.didUpdateWidget(oldWidget); -// } - void addOverlay() { if (entry != null) { entry?.remove(); From d85cc00aa5ee98b5c9fce0bd8a45a32880fc8186 Mon Sep 17 00:00:00 2001 From: nnra6864 Date: Thu, 8 Oct 2026 06:07:46 +0200 Subject: [PATCH 06/12] feat: Implemented a very rough version of the video player volume control --- .../molecules/video_player/video_player.dart | 131 +++++++++++++++--- .../video_player/video_player_controller.dart | 26 +++- .../video_player_implementation.dart | 14 +- 3 files changed, 148 insertions(+), 23 deletions(-) diff --git a/commet/lib/ui/molecules/video_player/video_player.dart b/commet/lib/ui/molecules/video_player/video_player.dart index 9749ab142..2e8ff129d 100644 --- a/commet/lib/ui/molecules/video_player/video_player.dart +++ b/commet/lib/ui/molecules/video_player/video_player.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:commet/cache/file_provider.dart'; import 'package:commet/config/build_config.dart'; +import 'package:commet/ui/atoms/hover_menu.dart'; import 'package:commet/ui/atoms/tiny_pill.dart'; import 'package:commet/ui/molecules/video_player/video_player_implementation.dart'; import 'package:commet/utils/text_utils.dart'; +import 'package:commet/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:tiamat/tiamat.dart' as tiamat; @@ -52,6 +54,11 @@ class VideoPlayerState extends State { double videoProgress = 0; bool updateSlider = true; Timer? uiHideTimer; + bool isMuted = false; + double volume = 100; + double appliedVolume = 100; + + final GlobalKey menuKey = GlobalKey(); late List subscriptions; @@ -61,6 +68,8 @@ class VideoPlayerState extends State { controller = widget.controller ?? VideoPlayerController(); + setVolume(preferences.playerVolume.value); + subscriptions = [ controller.isBuffering.listen((isBuffering) { setState(() { @@ -93,7 +102,8 @@ class VideoPlayerState extends State { 1); }); } - }) + }), + controller.onVolumeChanged.listen(onVolumeChanged) ]; super.initState(); @@ -228,27 +238,65 @@ class VideoPlayerState extends State { child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (widget.showProgressBar) - Expanded( - child: tiamat.Slider( - value: videoProgress, - min: 0, - max: 1, - onChangeEnd: (value) { - updateSlider = true; - seekPercent(value); - }, - onChanged: (value) { - setState(() { - videoProgress = value; - }); - }, - onChangeStart: (value) { - updateSlider = false; - }, - ), + children: [ + if (widget.showProgressBar) + Expanded( + child: tiamat.Slider( + value: videoProgress, + min: 0, + max: 1, + onChangeEnd: (value) { + updateSlider = true; + seekPercent(value); + }, + onChanged: (value) { + setState(() { + videoProgress = value; + }); + }, + onChangeStart: (value) { + updateSlider = false; + }, ), + ), + if (widget.showProgressBar) + Expanded( + child: HoverMenu( + key: menuKey, + menuAlignment: Alignment.bottomCenter, + parentAlignment: Alignment.topCenter, + child: tiamat.IconButton( + icon: isMuted + ? Icons.volume_mute + : appliedVolume == 0 + ? Icons.volume_off + : Icons.volume_up, + onPressed: (() { + toggleIsMuted(); + preferences.isPlayerMuted.set(isMuted); + }), + ), + builder: (context) { + return RotatedBox( + quarterTurns: 3, + child: Container( + decoration: BoxDecoration( + color: + ColorScheme.of(context).surfaceContainer, + borderRadius: BorderRadius.circular(8)), + child: SizedBox( + width: 200, + height: 50, + child: tiamat.Slider( + value: appliedVolume / 100, + onChanged: (value) => setVolume(value * 100), + onChangeEnd: (value) => + preferences.playerVolume.set(volume), + ), + ), + )); + }, + )), if (widget.canGoFullscreen) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), @@ -324,6 +372,47 @@ class VideoPlayerState extends State { }); } + void toggleIsMuted() { + if (!isMuted) { + setState(() { + isMuted = true; + appliedVolume = 0; + }); + controller.setVolume(0.0); + } else { + setState(() { + isMuted = false; + appliedVolume = volume; + }); + controller.setVolume(appliedVolume); + } + + if (menuKey.currentState case HoverMenuState state) { + state.entry?.markNeedsBuild(); + } + } + + void setVolume(double value) { + setState(() { + volume = value; + appliedVolume = value; + isMuted = false; + }); + + if (menuKey.currentState case HoverMenuState state) { + state.entry?.markNeedsBuild(); + } + + controller.setVolume(value); + } + + void onVolumeChanged(double event) { + setState(() { + volume = event; + appliedVolume = isMuted ? 0 : volume; + }); + } + Widget pickPlayer() { return VideoPlayerImplementation( controller: controller, diff --git a/commet/lib/ui/molecules/video_player/video_player_controller.dart b/commet/lib/ui/molecules/video_player/video_player_controller.dart index 88a9949a7..bb1e5f7f6 100644 --- a/commet/lib/ui/molecules/video_player/video_player_controller.dart +++ b/commet/lib/ui/molecules/video_player/video_player_controller.dart @@ -13,12 +13,16 @@ class VideoPlayerController { Future Function(Duration percent)? _seekTo; + Future Function(double volume)? _setVolume; + Future Function()? _screenshot; Future Function()? _getSize; Future Function()? _getLength; + Future Function()? _getVolume; + final StreamController _isBuffering = StreamController.broadcast(); final StreamController _downloadProgress = @@ -28,11 +32,15 @@ class VideoPlayerController { final StreamController _onProgressed = StreamController.broadcast(); + final StreamController _onVolumeChanged = StreamController.broadcast(); + Stream get isBuffering => _isBuffering.stream; Stream get isCompleted => _isCompleted.stream; Stream get onProgressed => _onProgressed.stream; + + Stream get onVolumeChanged => _onVolumeChanged.stream; Stream get onDownloadProgressed => _downloadProgress.stream; @@ -42,8 +50,10 @@ class VideoPlayerController { required Future Function() replay, required Future Function() getLength, required Future Function() getSize, + required Future Function() getVolume, Future Function()? screenshot, - required Future Function(Duration percent) seekTo}) { + required Future Function(Duration percent) seekTo, + required Future Function(double volume) setVolume}) { _onPause = pause; _onPlay = play; _onReplay = replay; @@ -51,6 +61,8 @@ class VideoPlayerController { _getLength = getLength; _screenshot = screenshot; _getSize = getSize; + _getVolume = getVolume; + _setVolume = setVolume; } Future pause() async { @@ -69,6 +81,10 @@ class VideoPlayerController { await _seekTo!.call(duration); } + Future setVolume(double volume) async { + await _setVolume?.call(volume); + } + Future screenshot() async { return _screenshot?.call(); } @@ -89,6 +105,10 @@ class VideoPlayerController { _onProgressed.add(progress); } + void handleInternalVolumeChange(double volume) { + _onVolumeChanged.add(volume); + } + Future getLength() async { return await _getLength!.call(); } @@ -96,4 +116,8 @@ class VideoPlayerController { Future getSize() async { return await _getSize!.call(); } + + Future getVolume() async { + return await _getVolume!.call(); + } } diff --git a/commet/lib/ui/molecules/video_player/video_player_implementation.dart b/commet/lib/ui/molecules/video_player/video_player_implementation.dart index 5b59410b7..d40d06c89 100644 --- a/commet/lib/ui/molecules/video_player/video_player_implementation.dart +++ b/commet/lib/ui/molecules/video_player/video_player_implementation.dart @@ -47,8 +47,10 @@ class _VideoPlayerImplementationState extends State { replay: replay, screenshot: screenshot, getSize: getSize, + getVolume: getVolume, seekTo: seekTo, - getLength: getLength); + getLength: getLength, + setVolume: setVolume); player.stream.position.listen((event) { widget.controller.setProgress(event); @@ -60,6 +62,8 @@ class _VideoPlayerImplementationState extends State { }, ); + player.stream.volume.listen(widget.controller.handleInternalVolumeChange); + controller = VideoController(player); if (widget.streamUrl == null) { @@ -152,6 +156,10 @@ class _VideoPlayerImplementationState extends State { await player.seek(duration); } + Future setVolume(double volume) async { + await player.setVolume(volume); + } + Future getLength() async { return player.state.duration; } @@ -164,4 +172,8 @@ class _VideoPlayerImplementationState extends State { return Size( player.state.width!.toDouble(), player.state.height!.toDouble()); } + + Future getVolume() async { + return player.state.volume; + } } From 7f11900b2befeb0915d805662dfdc91b5d63e387 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:28:23 +1030 Subject: [PATCH 07/12] fix volume hover menu disabling video player ui --- commet/lib/ui/atoms/hover_menu.dart | 10 ++ .../molecules/video_player/video_player.dart | 113 ++++++++++-------- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/commet/lib/ui/atoms/hover_menu.dart b/commet/lib/ui/atoms/hover_menu.dart index 6b8a02711..e8f809eb9 100644 --- a/commet/lib/ui/atoms/hover_menu.dart +++ b/commet/lib/ui/atoms/hover_menu.dart @@ -6,12 +6,15 @@ class HoverMenu extends StatefulWidget { {super.key, this.menuAlignment = Alignment.topLeft, this.parentAlignment = Alignment.topLeft, + this.onHoverStateChanged, required this.builder, required this.child}); final Widget child; final Widget Function(BuildContext context) builder; + final Function(bool hovered)? onHoverStateChanged; final Alignment menuAlignment; final Alignment parentAlignment; + @override State createState() => HoverMenuState(); } @@ -26,6 +29,9 @@ class HoverMenuState extends State { Debouncer(delay: Duration(milliseconds: 20)); void addOverlay() { + + widget.onHoverStateChanged?.call(true); + if (entry != null) { entry?.remove(); entry = null; @@ -59,6 +65,10 @@ class HoverMenuState extends State { } void removeOverlay() { + + + widget.onHoverStateChanged?.call(false); + if (entry != null) { entry?.remove(); entry = null; diff --git a/commet/lib/ui/molecules/video_player/video_player.dart b/commet/lib/ui/molecules/video_player/video_player.dart index 2e8ff129d..39c80c4fc 100644 --- a/commet/lib/ui/molecules/video_player/video_player.dart +++ b/commet/lib/ui/molecules/video_player/video_player.dart @@ -49,8 +49,10 @@ class VideoPlayerState extends State { bool buffering = false; DownloadProgress? downloadProgress; late bool showThumbnail; - bool shouldShowControls = true; + + bool currentlyHovered = true; bool isCompleted = false; + bool showingVolumeSlider = false; double videoProgress = 0; bool updateSlider = true; Timer? uiHideTimer; @@ -62,6 +64,8 @@ class VideoPlayerState extends State { late List subscriptions; + bool get shouldShowControls => currentlyHovered || isCompleted || showingVolumeSlider; + @override void initState() { showThumbnail = widget.doThumbnail; @@ -83,7 +87,6 @@ class VideoPlayerState extends State { controller.isCompleted.listen((event) { setState(() { isCompleted = event; - if (isCompleted) shouldShowControls = true; }); }), controller.onDownloadProgressed.listen((event) { @@ -180,18 +183,18 @@ class VideoPlayerState extends State { onTap: () { if (BuildConfig.MOBILE) { if (shouldShowControls) { - hideControls(); + onUnhovered(); } else { - showControls(); + onHovered(); } } }, child: MouseRegion( onEnter: (_) { - showControls(); + onHovered(); }, onExit: (_) { - hideControls(); + onUnhovered(); }, child: AnimatedOpacity( opacity: shouldShowControls ? 1.0 : 0.0, @@ -260,43 +263,52 @@ class VideoPlayerState extends State { ), ), if (widget.showProgressBar) - Expanded( - child: HoverMenu( - key: menuKey, - menuAlignment: Alignment.bottomCenter, - parentAlignment: Alignment.topCenter, - child: tiamat.IconButton( - icon: isMuted - ? Icons.volume_mute - : appliedVolume == 0 - ? Icons.volume_off - : Icons.volume_up, - onPressed: (() { - toggleIsMuted(); - preferences.isPlayerMuted.set(isMuted); + Padding( + padding: EdgeInsetsGeometry.fromLTRB(8, 0, 8, 0), + child: SizedBox( + height: 24, + width: 24, + child: HoverMenu( + key: menuKey, + menuAlignment: Alignment.bottomCenter, + parentAlignment: Alignment.topCenter, + onHoverStateChanged: (hovered) => setState(() { + showingVolumeSlider = hovered; }), - ), - builder: (context) { - return RotatedBox( - quarterTurns: 3, - child: Container( - decoration: BoxDecoration( - color: - ColorScheme.of(context).surfaceContainer, - borderRadius: BorderRadius.circular(8)), - child: SizedBox( - width: 200, - height: 50, - child: tiamat.Slider( - value: appliedVolume / 100, - onChanged: (value) => setVolume(value * 100), - onChangeEnd: (value) => - preferences.playerVolume.set(volume), + child: tiamat.IconButton( + icon: isMuted + ? Icons.volume_mute + : appliedVolume == 0 + ? Icons.volume_off + : Icons.volume_up, + onPressed: (() { + toggleIsMuted(); + preferences.isPlayerMuted.set(isMuted); + }), + ), + builder: (context) { + return RotatedBox( + quarterTurns: 3, + child: Container( + decoration: BoxDecoration( + color: + ColorScheme.of(context).surfaceContainer, + borderRadius: BorderRadius.circular(8)), + child: SizedBox( + width: 200, + height: 50, + child: tiamat.Slider( + value: appliedVolume / 100, + onChanged: (value) => setVolume(value * 100), + onChangeEnd: (value) => + preferences.playerVolume.set(volume), + ), ), - ), - )); - }, - )), + )); + }, + ), + ), + ), if (widget.canGoFullscreen) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), @@ -332,9 +344,10 @@ class VideoPlayerState extends State { setState(() { inited = true; playing = true; - shouldShowControls = false; + isCompleted = false; + currentlyHovered = false; controller.play(); - if (BuildConfig.MOBILE) hideControls(); + if (BuildConfig.MOBILE) onUnhovered(); }); } @@ -342,25 +355,25 @@ class VideoPlayerState extends State { setState(() { playing = true; controller.replay(); - shouldShowControls = false; - if (BuildConfig.MOBILE) hideControls(); + isCompleted = false; + if (BuildConfig.MOBILE) onUnhovered(); }); } - void showControls() { + void onHovered() { setState(() { - shouldShowControls = true; + currentlyHovered = true; }); if (BuildConfig.MOBILE) { uiHideTimer?.cancel(); - uiHideTimer = Timer(const Duration(seconds: 3), hideControls); + uiHideTimer = Timer(const Duration(seconds: 3), onUnhovered); } } - void hideControls() { + void onUnhovered() { setState(() { - shouldShowControls = false; + currentlyHovered = false; }); uiHideTimer?.cancel(); } From 07ac9a28ba01ab2503daea08e363cbc4a35dd362 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:39:37 +1030 Subject: [PATCH 08/12] few small fixes --- commet/lib/ui/atoms/message_attachment.dart | 6 ++++-- commet/lib/ui/molecules/video_player/video_player.dart | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/commet/lib/ui/atoms/message_attachment.dart b/commet/lib/ui/atoms/message_attachment.dart index ae1b584cb..ddcd841d1 100644 --- a/commet/lib/ui/atoms/message_attachment.dart +++ b/commet/lib/ui/atoms/message_attachment.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:commet/client/attachment.dart'; import 'package:commet/config/build_config.dart'; import 'package:commet/ui/atoms/lightbox.dart'; @@ -107,12 +109,12 @@ class _MessageAttachmentState extends State { bool showInfo = widget.attachment.name != null && attachment.fileSize != null; - double height = 160; + double height = 200; return ClipRRect( borderRadius: BorderRadius.circular(10), child: SizedBox( height: widget.constrainSize ? height + (showInfo ? 30 : 0) : null, - width: widget.constrainSize ? attachment.aspectRatio * height : null, + width: max(widget.constrainSize ? attachment.aspectRatio * height : 200, 200), child: Panel( mainAxisSize: MainAxisSize.min, header: showInfo diff --git a/commet/lib/ui/molecules/video_player/video_player.dart b/commet/lib/ui/molecules/video_player/video_player.dart index 39c80c4fc..e5f0c234b 100644 --- a/commet/lib/ui/molecules/video_player/video_player.dart +++ b/commet/lib/ui/molecules/video_player/video_player.dart @@ -264,7 +264,7 @@ class VideoPlayerState extends State { ), if (widget.showProgressBar) Padding( - padding: EdgeInsetsGeometry.fromLTRB(8, 0, 8, 0), + padding: EdgeInsetsGeometry.fromLTRB(0, 0, 8, 0), child: SizedBox( height: 24, width: 24, @@ -345,7 +345,6 @@ class VideoPlayerState extends State { inited = true; playing = true; isCompleted = false; - currentlyHovered = false; controller.play(); if (BuildConfig.MOBILE) onUnhovered(); }); From 75564b44fb8581b3c427e0d97fe76a2652786fc4 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:44:25 +1030 Subject: [PATCH 09/12] apply volume when video starts --- .../ui/molecules/video_player/video_player_implementation.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commet/lib/ui/molecules/video_player/video_player_implementation.dart b/commet/lib/ui/molecules/video_player/video_player_implementation.dart index d40d06c89..c9c691593 100644 --- a/commet/lib/ui/molecules/video_player/video_player_implementation.dart +++ b/commet/lib/ui/molecules/video_player/video_player_implementation.dart @@ -41,6 +41,8 @@ class _VideoPlayerImplementationState extends State { player = Player(); + player.setVolume(preferences.playerVolume.value); + widget.controller.attach( pause: pause, play: play, From f35eb2cd29dd7f2bd0eb6336f3a2faf623bed15c Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:47:20 +1030 Subject: [PATCH 10/12] fix handling of mute status --- commet/lib/ui/molecules/video_player/video_player.dart | 4 +++- .../molecules/video_player/video_player_implementation.dart | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/commet/lib/ui/molecules/video_player/video_player.dart b/commet/lib/ui/molecules/video_player/video_player.dart index e5f0c234b..089784c0d 100644 --- a/commet/lib/ui/molecules/video_player/video_player.dart +++ b/commet/lib/ui/molecules/video_player/video_player.dart @@ -74,6 +74,8 @@ class VideoPlayerState extends State { setVolume(preferences.playerVolume.value); + isMuted = preferences.isPlayerMuted.value; + subscriptions = [ controller.isBuffering.listen((isBuffering) { setState(() { @@ -394,7 +396,7 @@ class VideoPlayerState extends State { } else { setState(() { isMuted = false; - appliedVolume = volume; + appliedVolume = preferences.playerVolume.value; }); controller.setVolume(appliedVolume); } diff --git a/commet/lib/ui/molecules/video_player/video_player_implementation.dart b/commet/lib/ui/molecules/video_player/video_player_implementation.dart index c9c691593..3454ddb3b 100644 --- a/commet/lib/ui/molecules/video_player/video_player_implementation.dart +++ b/commet/lib/ui/molecules/video_player/video_player_implementation.dart @@ -41,7 +41,11 @@ class _VideoPlayerImplementationState extends State { player = Player(); - player.setVolume(preferences.playerVolume.value); + if(preferences.isPlayerMuted.value) { + player.setVolume(0); + } else { + player.setVolume(preferences.playerVolume.value); + } widget.controller.attach( pause: pause, From 079698ed8272befd8f2a8a09ed61aa9d7b27985d Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Thu, 8 Oct 2026 21:47:34 +1030 Subject: [PATCH 11/12] dart format --- commet/lib/config/preferences.dart | 6 +- commet/lib/ui/atoms/hover_menu.dart | 3 - commet/lib/ui/atoms/message_attachment.dart | 3 +- .../molecules/audio_player/audio_player.dart | 10 +- .../molecules/video_player/video_player.dart | 133 +++++++++--------- .../video_player/video_player_controller.dart | 5 +- .../video_player_implementation.dart | 2 +- 7 files changed, 83 insertions(+), 79 deletions(-) diff --git a/commet/lib/config/preferences.dart b/commet/lib/config/preferences.dart index d279189f8..ba6259230 100644 --- a/commet/lib/config/preferences.dart +++ b/commet/lib/config/preferences.dart @@ -561,6 +561,8 @@ class Preferences { StringListPreference expandedSpaceGroups = StringListPreference("expanded_space_groups", defaultValue: []); - DoublePreference playerVolume = DoublePreference("player_volume", defaultValue: 100); - BoolPreference isPlayerMuted = BoolPreference("is_player_muted", defaultValue: false); + DoublePreference playerVolume = + DoublePreference("player_volume", defaultValue: 100); + BoolPreference isPlayerMuted = + BoolPreference("is_player_muted", defaultValue: false); } diff --git a/commet/lib/ui/atoms/hover_menu.dart b/commet/lib/ui/atoms/hover_menu.dart index e8f809eb9..7405faa97 100644 --- a/commet/lib/ui/atoms/hover_menu.dart +++ b/commet/lib/ui/atoms/hover_menu.dart @@ -29,7 +29,6 @@ class HoverMenuState extends State { Debouncer(delay: Duration(milliseconds: 20)); void addOverlay() { - widget.onHoverStateChanged?.call(true); if (entry != null) { @@ -65,8 +64,6 @@ class HoverMenuState extends State { } void removeOverlay() { - - widget.onHoverStateChanged?.call(false); if (entry != null) { diff --git a/commet/lib/ui/atoms/message_attachment.dart b/commet/lib/ui/atoms/message_attachment.dart index ddcd841d1..b669ec834 100644 --- a/commet/lib/ui/atoms/message_attachment.dart +++ b/commet/lib/ui/atoms/message_attachment.dart @@ -114,7 +114,8 @@ class _MessageAttachmentState extends State { borderRadius: BorderRadius.circular(10), child: SizedBox( height: widget.constrainSize ? height + (showInfo ? 30 : 0) : null, - width: max(widget.constrainSize ? attachment.aspectRatio * height : 200, 200), + width: max( + widget.constrainSize ? attachment.aspectRatio * height : 200, 200), child: Panel( mainAxisSize: MainAxisSize.min, header: showInfo diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index 0f62511e5..62d065ade 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -10,7 +10,7 @@ import 'package:commet/main.dart'; class AudioPlayer extends StatefulWidget { const AudioPlayer( - {required this.file, this.fileName, this.fileSize, super.key}); + {required this.file, this.fileName, this.fileSize, super.key}); final String? fileName; final int? fileSize; @@ -74,8 +74,8 @@ class _AudioPlayerState extends State { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(8)), + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(8)), child: SizedBox( width: 500, child: Column( @@ -175,8 +175,7 @@ class _AudioPlayerState extends State { quarterTurns: 3, child: Container( decoration: BoxDecoration( - color: - ColorScheme.of(context).surfaceContainer, + color: ColorScheme.of(context).surfaceContainer, borderRadius: BorderRadius.circular(8)), child: SizedBox( width: 200, @@ -284,7 +283,6 @@ class _AudioPlayerState extends State { if (menuKey.currentState case HoverMenuState state) { state.entry?.markNeedsBuild(); } - } void setVolume(double value) { diff --git a/commet/lib/ui/molecules/video_player/video_player.dart b/commet/lib/ui/molecules/video_player/video_player.dart index 089784c0d..0fee443d9 100644 --- a/commet/lib/ui/molecules/video_player/video_player.dart +++ b/commet/lib/ui/molecules/video_player/video_player.dart @@ -64,7 +64,8 @@ class VideoPlayerState extends State { late List subscriptions; - bool get shouldShowControls => currentlyHovered || isCompleted || showingVolumeSlider; + bool get shouldShowControls => + currentlyHovered || isCompleted || showingVolumeSlider; @override void initState() { @@ -243,74 +244,78 @@ class VideoPlayerState extends State { child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (widget.showProgressBar) - Expanded( - child: tiamat.Slider( - value: videoProgress, - min: 0, - max: 1, - onChangeEnd: (value) { - updateSlider = true; - seekPercent(value); - }, - onChanged: (value) { - setState(() { - videoProgress = value; - }); - }, - onChangeStart: (value) { - updateSlider = false; - }, + children: [ + if (widget.showProgressBar) + Expanded( + child: tiamat.Slider( + value: videoProgress, + min: 0, + max: 1, + onChangeEnd: (value) { + updateSlider = true; + seekPercent(value); + }, + onChanged: (value) { + setState(() { + videoProgress = value; + }); + }, + onChangeStart: (value) { + updateSlider = false; + }, + ), ), - ), - if (widget.showProgressBar) - Padding( - padding: EdgeInsetsGeometry.fromLTRB(0, 0, 8, 0), - child: SizedBox( - height: 24, - width: 24, - child: HoverMenu( - key: menuKey, - menuAlignment: Alignment.bottomCenter, - parentAlignment: Alignment.topCenter, - onHoverStateChanged: (hovered) => setState(() { - showingVolumeSlider = hovered; - }), - child: tiamat.IconButton( - icon: isMuted - ? Icons.volume_mute - : appliedVolume == 0 - ? Icons.volume_off - : Icons.volume_up, - onPressed: (() { - toggleIsMuted(); - preferences.isPlayerMuted.set(isMuted); + if (widget.showProgressBar) + Padding( + padding: EdgeInsetsGeometry.fromLTRB(0, 0, 8, 0), + child: SizedBox( + height: 24, + width: 24, + child: HoverMenu( + key: menuKey, + menuAlignment: Alignment.bottomCenter, + parentAlignment: Alignment.topCenter, + onHoverStateChanged: (hovered) => + setState(() { + showingVolumeSlider = hovered; }), + child: tiamat.IconButton( + icon: isMuted + ? Icons.volume_mute + : appliedVolume == 0 + ? Icons.volume_off + : Icons.volume_up, + onPressed: (() { + toggleIsMuted(); + preferences.isPlayerMuted.set(isMuted); + }), + ), + builder: (context) { + return RotatedBox( + quarterTurns: 3, + child: Container( + decoration: BoxDecoration( + color: ColorScheme.of(context) + .surfaceContainer, + borderRadius: + BorderRadius.circular(8)), + child: SizedBox( + width: 200, + height: 50, + child: tiamat.Slider( + value: appliedVolume / 100, + onChanged: (value) => + setVolume(value * 100), + onChangeEnd: (value) => + preferences.playerVolume + .set(volume), + ), + ), + )); + }, ), - builder: (context) { - return RotatedBox( - quarterTurns: 3, - child: Container( - decoration: BoxDecoration( - color: - ColorScheme.of(context).surfaceContainer, - borderRadius: BorderRadius.circular(8)), - child: SizedBox( - width: 200, - height: 50, - child: tiamat.Slider( - value: appliedVolume / 100, - onChanged: (value) => setVolume(value * 100), - onChangeEnd: (value) => - preferences.playerVolume.set(volume), - ), - ), - )); - }, ), ), - ), if (widget.canGoFullscreen) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), diff --git a/commet/lib/ui/molecules/video_player/video_player_controller.dart b/commet/lib/ui/molecules/video_player/video_player_controller.dart index bb1e5f7f6..5381c9495 100644 --- a/commet/lib/ui/molecules/video_player/video_player_controller.dart +++ b/commet/lib/ui/molecules/video_player/video_player_controller.dart @@ -32,14 +32,15 @@ class VideoPlayerController { final StreamController _onProgressed = StreamController.broadcast(); - final StreamController _onVolumeChanged = StreamController.broadcast(); + final StreamController _onVolumeChanged = + StreamController.broadcast(); Stream get isBuffering => _isBuffering.stream; Stream get isCompleted => _isCompleted.stream; Stream get onProgressed => _onProgressed.stream; - + Stream get onVolumeChanged => _onVolumeChanged.stream; Stream get onDownloadProgressed => _downloadProgress.stream; diff --git a/commet/lib/ui/molecules/video_player/video_player_implementation.dart b/commet/lib/ui/molecules/video_player/video_player_implementation.dart index 3454ddb3b..c49f6c5bd 100644 --- a/commet/lib/ui/molecules/video_player/video_player_implementation.dart +++ b/commet/lib/ui/molecules/video_player/video_player_implementation.dart @@ -41,7 +41,7 @@ class _VideoPlayerImplementationState extends State { player = Player(); - if(preferences.isPlayerMuted.value) { + if (preferences.isPlayerMuted.value) { player.setVolume(0); } else { player.setVolume(preferences.playerVolume.value); From 2b3780f452917fc5308e5a3d5522b27129da3e2b Mon Sep 17 00:00:00 2001 From: nnra6864 Date: Thu, 8 Oct 2026 19:21:29 +0200 Subject: [PATCH 12/12] feat: Replaced preMuteVolume with appliedVolume in the AudioPlayer --- .../ui/molecules/audio_player/audio_player.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/commet/lib/ui/molecules/audio_player/audio_player.dart b/commet/lib/ui/molecules/audio_player/audio_player.dart index 62d065ade..db7df0be8 100644 --- a/commet/lib/ui/molecules/audio_player/audio_player.dart +++ b/commet/lib/ui/molecules/audio_player/audio_player.dart @@ -66,7 +66,7 @@ class _AudioPlayerState extends State { bool isMuted = false; double volume = 100; - double preMuteVolume = 100; + double appliedVolume = 100; final GlobalKey menuKey = GlobalKey(); @@ -184,7 +184,7 @@ class _AudioPlayerState extends State { value: volume / 100, onChanged: (value) => setVolume(value * 100), onChangeEnd: (value) => - preferences.playerVolume.set(preMuteVolume), + preferences.playerVolume.set(appliedVolume), ), ), )); @@ -268,16 +268,15 @@ class _AudioPlayerState extends State { if (!isMuted) { setState(() { isMuted = true; - preMuteVolume = volume; - volume = 0; + appliedVolume = 0; }); player.setVolume(0.0); } else { setState(() { isMuted = false; - volume = preMuteVolume; + appliedVolume = preferences.playerVolume.value; }); - player.setVolume(volume); + player.setVolume(appliedVolume); } if (menuKey.currentState case HoverMenuState state) { @@ -288,7 +287,7 @@ class _AudioPlayerState extends State { void setVolume(double value) { setState(() { volume = value; - preMuteVolume = value; + appliedVolume = value; isMuted = false; }); @@ -302,6 +301,7 @@ class _AudioPlayerState extends State { void onVolumeChanged(double event) { setState(() { volume = event; + appliedVolume = isMuted ? 0 : volume; }); } }