From 62e1ed336cd099e4b1f99799281677c8b4499e50 Mon Sep 17 00:00:00 2001 From: benjamin cauchebrais Date: Wed, 30 Apr 2025 18:35:57 +0200 Subject: [PATCH 1/5] feat: implement flexible waveform extraction and expose waveformData directly - Added `WaveformExtractionType` to choose between different waveform extraction modes (no extraction, asynchronous extraction, and synchronous extraction). - Fixed waveformData mutation issue by exposing the internal `_waveformData` list directly instead of a copy (`toList()`), enabling in-place modifications. - Updated `preparePlayer` method to integrate these changes with improved waveform extraction handling. --- example/lib/chat_bubble.dart | 13 +++++++++--- lib/audio_waveforms.dart | 1 + lib/src/controllers/player_controller.dart | 21 +++++++++---------- .../waveform_extraction_controller.dart | 2 +- lib/src/enums/waveform_extraction_type.dart | 10 +++++++++ 5 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 lib/src/enums/waveform_extraction_type.dart diff --git a/example/lib/chat_bubble.dart b/example/lib/chat_bubble.dart index faf1f1d3..fa2aafc8 100644 --- a/example/lib/chat_bubble.dart +++ b/example/lib/chat_bubble.dart @@ -102,11 +102,18 @@ class _WaveBubbleState extends State { if (widget.index == null && widget.path == null && file?.path == null) { return; } + late final WaveformExtractionType waveformExtractionType; + if (widget.index != null) { + waveformExtractionType = widget.index!.isEven + ? WaveformExtractionType.extractAsync + : WaveformExtractionType.noExtraction; + } else { + waveformExtractionType = WaveformExtractionType.extractAsync; + } // Prepare player with extracting waveform if index is even. controller.preparePlayer( - path: widget.path ?? file!.path, - shouldExtractWaveform: widget.index?.isEven ?? true, - ); + path: widget.path ?? file!.path, + waveformExtractionType: waveformExtractionType); // Extracting waveform separately if index is odd. if (widget.index?.isOdd ?? false) { controller.waveformExtraction diff --git a/lib/audio_waveforms.dart b/lib/audio_waveforms.dart index cf60644a..fdfd1211 100644 --- a/lib/audio_waveforms.dart +++ b/lib/audio_waveforms.dart @@ -10,3 +10,4 @@ export 'src/controllers/recorder_controller.dart'; export 'src/models/android_encoder_settings.dart'; export 'src/models/ios_encoder_setting.dart'; export 'src/models/recorder_settings.dart'; +export 'src/enums/waveform_extraction_type.dart'; diff --git a/lib/src/controllers/player_controller.dart b/lib/src/controllers/player_controller.dart index 55d349d0..dff91a17 100644 --- a/lib/src/controllers/player_controller.dart +++ b/lib/src/controllers/player_controller.dart @@ -127,7 +127,8 @@ class PlayerController extends ChangeNotifier { Future preparePlayer({ required String path, double? volume, - bool shouldExtractWaveform = true, + WaveformExtractionType waveformExtractionType = + WaveformExtractionType.noExtraction, int noOfSamples = 100, }) async { path = Uri.parse(path).path; @@ -143,20 +144,18 @@ class PlayerController extends ChangeNotifier { _setPlayerState(PlayerState.initialized); } - if (shouldExtractWaveform) { - waveformExtraction - .extractWaveformData( + if (waveformExtractionType != WaveformExtractionType.noExtraction) { + var extraction = waveformExtraction.extractWaveformData( path: path, noOfSamples: noOfSamples, - ) - .then( - (value) { + )..then((values) { waveformExtraction.waveformData ..clear() - ..addAll(value); - notifyListeners(); - }, - ); + ..addAll(values); + }); + if (waveformExtractionType == WaveformExtractionType.extractSync) { + await extraction; + } } notifyListeners(); } diff --git a/lib/src/controllers/waveform_extraction_controller.dart b/lib/src/controllers/waveform_extraction_controller.dart index a92b08b9..7225e90f 100644 --- a/lib/src/controllers/waveform_extraction_controller.dart +++ b/lib/src/controllers/waveform_extraction_controller.dart @@ -29,7 +29,7 @@ class WaveformExtractionController { /// This returns waveform data which can be used by [AudioFileWaveforms] /// to display waveforms. - List get waveformData => _waveformData.toList(); + List get waveformData => _waveformData; /// A stream to get current extracted waveform data. This stream will emit /// list of doubles which are waveform data point. diff --git a/lib/src/enums/waveform_extraction_type.dart b/lib/src/enums/waveform_extraction_type.dart new file mode 100644 index 00000000..2dfc13d7 --- /dev/null +++ b/lib/src/enums/waveform_extraction_type.dart @@ -0,0 +1,10 @@ +enum WaveformExtractionType { + /// No waveform extraction will be performed. + noExtraction, + + /// Extract waveform data asynchronously without waiting for the result. + extractAsync, + + /// Extract waveform data and wait until it's completed before continuing. + extractSync, +} From 07b9a8ac5e77c09e01695274f28436104e0383cc Mon Sep 17 00:00:00 2001 From: benjamin cauchebrais Date: Tue, 29 Apr 2025 20:08:02 +0200 Subject: [PATCH 2/5] feat: disable grow animation when animationDuration is zero --- lib/src/audio_file_waveforms.dart | 37 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/src/audio_file_waveforms.dart b/lib/src/audio_file_waveforms.dart index 6a56f82a..6ced6d01 100644 --- a/lib/src/audio_file_waveforms.dart +++ b/lib/src/audio_file_waveforms.dart @@ -46,7 +46,8 @@ class AudioFileWaveforms extends StatefulWidget { /// If decoration is used then use color in it. final Color? backgroundColor; - /// Duration for animation. Defaults to 500 milliseconds. + /// Duration for animation. If set to Duration.zero, no animation will occur. + /// Defaults to 500 milliseconds. final Duration animationDuration; /// Curve for animation. Defaults to Curves.easeIn @@ -144,18 +145,24 @@ class _AudioFileWaveformsState extends State void initState() { super.initState(); _initialiseVariables(); - _growingWaveController = AnimationController( - vsync: this, - duration: widget.animationDuration, - ); - _growAnimation = CurvedAnimation( - parent: _growingWaveController, - curve: widget.animationCurve, - ); - _growingWaveController - ..forward() - ..addListener(_updateGrowAnimationProgress); + if (widget.animationDuration == Duration.zero) { + _growAnimationProgress = 1.0; + } else { + _growingWaveController = AnimationController( + vsync: this, + duration: widget.animationDuration, + ); + _growAnimation = CurvedAnimation( + parent: _growingWaveController, + curve: widget.animationCurve, + ); + + _growingWaveController + ..forward() + ..addListener(_updateGrowAnimationProgress); + } + onCurrentDurationSubscription = playerController.onCurrentDurationChanged.listen((event) { _seekProgress.value = event; @@ -188,7 +195,11 @@ class _AudioFileWaveformsState extends State onCurrentExtractedWaveformData?.cancel(); onCompletionSubscription.cancel(); playerController.removeListener(_addWaveformDataFromController); - _growingWaveController.dispose(); + + if (widget.animationDuration != Duration.zero) { + _growingWaveController.dispose(); + } + super.dispose(); } From 0b6cc37de191acab6f40f10ea00a8406ea958f43 Mon Sep 17 00:00:00 2001 From: benjamin cauchebrais Date: Thu, 1 May 2025 17:43:30 +0200 Subject: [PATCH 3/5] feat: Add didUpdateWidget to update state when waveformData changes Implement didUpdateWidget method in _AudioFileWaveformsState to detect changes in waveformData and other important properties. This modification allows the widget to update correctly when its properties are modified without having to completely rebuild it. --- lib/src/audio_file_waveforms.dart | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/src/audio_file_waveforms.dart b/lib/src/audio_file_waveforms.dart index 6ced6d01..2d65eb1c 100644 --- a/lib/src/audio_file_waveforms.dart +++ b/lib/src/audio_file_waveforms.dart @@ -203,6 +203,42 @@ class _AudioFileWaveformsState extends State super.dispose(); } + @override + void didUpdateWidget(AudioFileWaveforms oldWidget) { + super.didUpdateWidget(oldWidget); + + // Vérifier si les waveformData ont changé + if (widget.waveformData != oldWidget.waveformData) { + if (widget.waveformData.isNotEmpty) { + _addWaveformData(widget.waveformData); + } + } + + // Vérifier si d'autres propriétés importantes ont changé + if (widget.playerWaveStyle != oldWidget.playerWaveStyle || + widget.size != oldWidget.size || + widget.waveformType != oldWidget.waveformType) { + if (mounted) setState(() {}); + } + + // Mettre à jour les variables si nécessaire + if (widget.margin != oldWidget.margin || + widget.padding != oldWidget.padding || + widget.decoration != oldWidget.decoration || + widget.backgroundColor != oldWidget.backgroundColor || + widget.animationDuration != oldWidget.animationDuration || + widget.animationCurve != oldWidget.animationCurve || + widget.clipBehavior != oldWidget.clipBehavior) { + margin = widget.margin; + padding = widget.padding; + decoration = widget.decoration; + backgroundColor = widget.backgroundColor; + animationDuration = widget.animationDuration; + animationCurve = widget.animationCurve; + clipBehavior = widget.clipBehavior; + } + } + double _audioProgress = 0.0; double _cachedAudioProgress = 0.0; From 4bd064640c3d9d1a19e36074349bd71c2c7649a3 Mon Sep 17 00:00:00 2001 From: benjamin cauchebrais Date: Fri, 2 May 2025 02:14:11 +0200 Subject: [PATCH 4/5] fix: Initialize audio progress when recreating widget with paused controller This commit fixes an issue where waveforms would only display in the fixed color when a controller was paused before the widget was destroyed and then recreated. The fix adds a new _initializeAudioProgress method that properly initializes the audio progress value based on the current position of a paused controller, ensuring that the waveform colors (live vs fixed) are correctly applied when the widget is recreated. --- lib/src/audio_file_waveforms.dart | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/src/audio_file_waveforms.dart b/lib/src/audio_file_waveforms.dart index 2d65eb1c..550c16c8 100644 --- a/lib/src/audio_file_waveforms.dart +++ b/lib/src/audio_file_waveforms.dart @@ -163,6 +163,8 @@ class _AudioFileWaveformsState extends State ..addListener(_updateGrowAnimationProgress); } + _initializeAudioProgress(); + onCurrentDurationSubscription = playerController.onCurrentDurationChanged.listen((event) { _seekProgress.value = event; @@ -206,21 +208,21 @@ class _AudioFileWaveformsState extends State @override void didUpdateWidget(AudioFileWaveforms oldWidget) { super.didUpdateWidget(oldWidget); - + // Vérifier si les waveformData ont changé if (widget.waveformData != oldWidget.waveformData) { if (widget.waveformData.isNotEmpty) { _addWaveformData(widget.waveformData); } } - + // Vérifier si d'autres propriétés importantes ont changé if (widget.playerWaveStyle != oldWidget.playerWaveStyle || widget.size != oldWidget.size || widget.waveformType != oldWidget.waveformType) { if (mounted) setState(() {}); } - + // Mettre à jour les variables si nécessaire if (widget.margin != oldWidget.margin || widget.padding != oldWidget.padding || @@ -453,4 +455,16 @@ class _AudioFileWaveformsState extends State } }); } + + Future _initializeAudioProgress() async { + if (playerController.playerState == PlayerState.paused && + playerController.maxDuration > 0) { + final currentPosition = + await playerController.getDuration(DurationType.current); + if (currentPosition > 0) { + _seekProgress.value = currentPosition; + _updatePlayerPercent(); + } + } + } } From 5b4c0cb3a358b2f0509ae116fa2538bc3cc6c049 Mon Sep 17 00:00:00 2001 From: benjamin cauchebrais Date: Tue, 20 May 2025 18:53:24 +0200 Subject: [PATCH 5/5] fix: Fixed bug in waveform extraction that didn't work on iOS --- ios/Classes/WaveformExtractor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/WaveformExtractor.swift b/ios/Classes/WaveformExtractor.swift index c9d1e565..f74985e8 100644 --- a/ios/Classes/WaveformExtractor.swift +++ b/ios/Classes/WaveformExtractor.swift @@ -110,7 +110,7 @@ public class WaveformExtractor { ) } - let progress = Float(i - startIndex + 1) / Float(endIndex - startIndex) + progress = Float(i - startIndex + 1) / Float(endIndex - startIndex) await sendWaveformDataToFlutter( waveformStorage: waveformStorage, progress: progress,