diff --git a/CHANGELOG.md b/CHANGELOG.md index 3beb9051..c95c09be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.1.0 (#unreleased) - Feature [#468](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/pull/468) - Add macOS support +- Feature [#472](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/pull/472) - Add web platform support for audio player ## 2.0.2 diff --git a/example/lib/main.dart b/example/lib/main.dart index 78a4df4c..8f4ac1fa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:audio_waveforms_example/chat_bubble.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; @@ -52,6 +53,20 @@ class _HomeState extends State { Future _init() async { recorderController = RecorderController(); + // TODO(vasu): This is a simplified implementation for demonstration purposes. + // On web, asset paths work differently than native platforms. For production + // web apps, consider: + // 1. Using proper web URLs (http://example.com/audio.mp3) + // 2. Converting assets to base64-encoded data URIs + // 3. Using Flutter's AssetBundle to properly load web assets + // 4. Implementing proper CORS handling for external audio sources + if (kIsWeb) { + paths.addAll(assetPaths); + isLoading = false; + setState(() {}); + return; + } + appDirectory = await getApplicationDocumentsDirectory(); for (final path in assetPaths) { final fileName = path.split('/').last; @@ -204,6 +219,13 @@ class _HomeState extends State { void _startOrStopRecording() async { try { + if (kIsWeb) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Recording not supported on web')), + ); + return; + } + if (isRecording) { recorderController.reset(); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f35aa59b..f8ed9030 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ path_provider: ^2.0.11 - file_picker: 8.0.7 + file_picker: 10.3.8 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..9f2a731c --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + audio_waveforms_example + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..f7140d9e --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "audio_waveforms_example", + "short_name": "audio_waveforms_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/lib/audio_waveforms_web.dart b/lib/audio_waveforms_web.dart new file mode 100644 index 00000000..372fb33f --- /dev/null +++ b/lib/audio_waveforms_web.dart @@ -0,0 +1,12 @@ +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'web/audio_waveforms_plugin_web.dart'; + +/// Entry point for web platform +/// Delegates to AudioWaveformsPluginWeb for actual implementation +class AudioWaveformsPlugin { + /// Registers this class as the web plugin implementation + static void registerWith(Registrar registrar) { + AudioWaveformsPluginWeb.registerWith(registrar); + } +} diff --git a/lib/src/base/constants.dart b/lib/src/base/constants.dart index 9bdd7924..d7518fd0 100644 --- a/lib/src/base/constants.dart +++ b/lib/src/base/constants.dart @@ -58,4 +58,36 @@ class Constants { static const String normalisedRms = 'normalisedRms'; static const String bytes = 'bytes'; static const String recordedDuration = 'recordedDuration'; + + // Error codes + static const String audioWaveforms = 'audio_waveforms'; + static const String unsupported = 'UNSUPPORTED'; + static const String unimplemented = 'Unimplemented'; + + // Error messages + static const String cannotPreparePlayer = 'Cannot prepare player'; + static const String cannotStartPlayer = 'Cannot start player'; + static const String cannotPausePlayer = 'Cannot pause player'; + static const String cannotStopPlayer = 'Cannot stop player'; + static const String cannotReleasePlayer = 'Cannot release player'; + static const String cannotGetDuration = 'Cannot get duration'; + static const String cannotSeekToPosition = 'Cannot seek to position'; + static const String cannotSetVolume = 'Cannot set volume'; + static const String cannotSetRate = 'Cannot set rate'; + static const String cannotSetFinishMode = 'Cannot set finish mode'; + static const String playerKeyIsNull = 'Player key is null'; + static const String playerKeyOrDurationTypeIsNull = + 'Player key or duration type is null'; + static const String playerKeyOrProgressIsNull = + 'Player key or progress is null'; + static const String playerKeyOrVolumeIsNull = 'Player key or volume is null'; + static const String playerKeyOrRateIsNull = 'Player key or rate is null'; + static const String recordingNotSupportedOnWeb = + 'Audio recording is not supported on web platform'; + static const String methodNotAvailableForWeb = + 'is not available for web. Recording functionality is only available on mobile platforms (iOS/Android).'; + static const String methodNotImplementedOnWeb = + 'is not implemented on web platform'; + static const String audioWaveformsDoesNotImplement = + 'audio_waveforms for web doesn\'t implement'; } diff --git a/lib/web/audio_player_web.dart b/lib/web/audio_player_web.dart new file mode 100644 index 00000000..22b156b8 --- /dev/null +++ b/lib/web/audio_player_web.dart @@ -0,0 +1,356 @@ +import 'dart:async'; +import 'dart:js_interop'; + +import 'package:web/web.dart'; + +import '../src/base/constants.dart'; +import 'base/constants_web.dart'; +import 'base/utils_web.dart'; + +/// Web implementation for audio player functionality +/// This class manages a single audio player instance +class AudioPlayerWeb { + /// Constructor that requires callbacks and player key + AudioPlayerWeb({ + required this.onDurationUpdate, + required this.onCompletion, + }); + + /// Callback for sending duration updates + final void Function(int currentTime) onDurationUpdate; + + /// Callback for sending completion events + final void Function() onCompletion; + + /// The HTML audio element for this player + HTMLAudioElement? _audioElement; + + /// Timer for sending duration updates + Timer? _durationTimer; + + /// Update frequency in milliseconds + int _updateFrequency = WebConstants.defaultUpdateFrequency; + + /// Track if the ended listener has been set up + bool _hasEndedListener = false; + + /// Track if the player has been disposed + bool _isDisposed = false; + + /// Check if the player is currently playing + bool get isPlaying { + if (_audioElement == null) return false; + return !_audioElement!.paused && !_audioElement!.ended; + } + + /// Prepares the audio player with the given path + Future preparePlayer(Map arguments) async { + try { + final dynamic rawPath = arguments[Constants.path]; + // Validate required parameters + if (rawPath is! String || rawPath.isEmpty) { + 'Invalid or missing audio path'.error(); + return false; + } + final String path = rawPath; + final int frequency = arguments[Constants.updateFrequency] ?? + WebConstants.defaultUpdateFrequency; + final double? volume = arguments[Constants.volume]; + + // Store update frequency + _updateFrequency = frequency; + + // Create a new audio element + final audioElement = HTMLAudioElement(); + audioElement.src = path; + audioElement.preload = WebConstants.propertyPreload; + + // Set volume if provided + if (volume != null) { + audioElement.volume = volume.clamp(0.0, 1.0); + } + + // Store the audio element + _audioElement = audioElement; + + // Wait for metadata to load + final completer = Completer(); + bool isCompleted = false; + + audioElement.addEventListener( + WebConstants.eventLoadedMetadata, + (Event event) { + if (!isCompleted) { + isCompleted = true; + completer.complete(true); + } + }.toJS, + ); + + audioElement.addEventListener( + WebConstants.eventError, + (Event event) { + if (!isCompleted) { + isCompleted = true; + completer.completeError('Failed to load audio file'); + } + }.toJS, + ); + + // Load the audio + audioElement.load(); + + return await completer.future + .timeout( + const Duration(seconds: WebConstants.defaultLoadTimeout), + onTimeout: () => false, + ) + .catchError((e) { + // Only return false if the completer wasn't already completed + if (!isCompleted && !completer.isCompleted) { + isCompleted = true; + return false; + } + // If already completed, just swallow the error + return false; + }); + } catch (e) { + 'Error preparing player: $e'.error(); + return false; + } + } + + /// Starts playing the audio + Future startPlayer() async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + // Start playing and handle potential promise rejection + final playPromise = _audioElement!.play(); + // Convert the JS promise to a Dart Future and attach an error handler + // to avoid unhandled promise rejections in the browser. + playPromise.toDart.catchError((dynamic err) { + 'Error starting playback: $err'.error(); + return null; + }); + + // Start duration update timer + _startDurationTimer(); + + // Setup ended listener for completion callback (only once per player) + if (!_hasEndedListener) { + _hasEndedListener = true; + _audioElement!.addEventListener( + WebConstants.eventEnded, + (Event event) { + _onAudioFinished(); + }.toJS, + ); + } + + return true; + } catch (e) { + 'Error starting player: $e'.error(); + return false; + } + } + + /// Pauses the currently playing audio + Future pausePlayer() async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + _audioElement!.pause(); + _stopDurationTimer(); + + return true; + } catch (e) { + 'Error pausing player: $e'.error(); + return false; + } + } + + /// Stops the audio playback + Future stopPlayer() async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + _audioElement!.pause(); + _audioElement!.currentTime = 0; + _stopDurationTimer(); + + return true; + } catch (e) { + 'Error stopping player: $e'.error(); + return false; + } + } + + /// Releases resources associated with the player + Future release() async { + try { + if (_isDisposed) return true; + + // Stop the player if it's currently playing + if (isPlaying) { + await stopPlayer(); + } + + if (_audioElement != null) { + _stopDurationTimer(); + _audioElement!.pause(); + _audioElement!.src = ''; + _audioElement = null; + } + + _hasEndedListener = false; + + return true; + } catch (e) { + 'Error releasing player: $e'.error(); + return false; + } + } + + /// Gets the duration of the audio + Future getDuration(int durationType) async { + try { + if (_isDisposed || _audioElement == null) { + return -1; + } + + // durationType: 0 = current, 1 = max + if (durationType == 0) { + return (_audioElement!.currentTime * 1000).round(); + } else { + final duration = _audioElement!.duration; + if (duration.isNaN || duration.isInfinite) { + return -1; + } + return (duration * 1000).round(); + } + } catch (e) { + 'Error getting duration: $e'.error(); + return -1; + } + } + + /// Seeks to a specific position in the audio + Future seekTo(int progress) async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + _audioElement!.currentTime = progress / 1000; + return true; + } catch (e) { + 'Error seeking: $e'.error(); + return false; + } + } + + /// Sets the volume for the player + Future setVolume(double volume) async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + _audioElement!.volume = volume.clamp(0.0, 1.0); + return true; + } catch (e) { + 'Error setting volume: $e'.error(); + return false; + } + } + + /// Sets the playback rate + Future setRate(double rate) async { + try { + if (_isDisposed || _audioElement == null) { + return false; + } + + _audioElement!.playbackRate = rate; + return true; + } catch (e) { + 'Error setting rate: $e'.error(); + return false; + } + } + + /// Sets the finish mode for when audio completes + Future setFinishMode(int? finishType) async { + // TODO(vasu): Handle proper finish mode + } + + /// Starts a timer to periodically send duration updates + void _startDurationTimer() { + _stopDurationTimer(); + + _durationTimer = Timer.periodic( + Duration(milliseconds: _updateFrequency), + (timer) { + if (_audioElement == null) { + // The audio element has been removed or released; stop this timer. + timer.cancel(); + return; + } + final currentTime = (_audioElement!.currentTime * 1000).round(); + _sendDurationUpdate(currentTime); + }, + ); + } + + /// Sends duration update through callback + void _sendDurationUpdate(int currentTime) { + onDurationUpdate(currentTime); + } + + /// Stops the duration update timer + void _stopDurationTimer() { + _durationTimer?.cancel(); + _durationTimer = null; + } + + /// Called when audio finishes playing + void _onAudioFinished() { + _stopDurationTimer(); + _sendCompletionEvent(); + } + + /// Sends a completion event through callback. + /// + /// Note: + /// - On web, finish modes configured via `setFinishMode` (e.g. loop, pause) + /// are not applied. The web implementation always reports the finish type + /// as `2` (stopped) when playback ends. + /// - This differs from native platforms, where `setFinishMode` controls the + /// value of [Constants.finishType]. + void _sendCompletionEvent() { + onCompletion(); + } + + /// Dispose all resources + /// Follows the pattern: stop player → release resources → cleanup → mark as disposed + Future dispose() async { + if (_isDisposed) return; + + // Stop the player if it's currently playing + if (isPlaying) { + await stopPlayer(); + } + + // Release all resources + await release(); + + _isDisposed = true; + } +} diff --git a/lib/web/audio_waveforms_plugin_web.dart b/lib/web/audio_waveforms_plugin_web.dart new file mode 100644 index 00000000..84a54674 --- /dev/null +++ b/lib/web/audio_waveforms_plugin_web.dart @@ -0,0 +1,253 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import '../src/base/constants.dart'; +import 'audio_player_web.dart'; +import 'waveform_extractor_web.dart'; + +/// Web implementation of the AudioWaveforms plugin +/// This class serves as the main entry point for web platform +/// and delegates functionality to specialized web implementations +class AudioWaveformsPluginWeb { + AudioWaveformsPluginWeb(this._channel); + + /// Waveform extractor implementation for web + final WaveformExtractorWeb _waveformExtractor = WaveformExtractorWeb(); + + /// Method channel for communication with Flutter + late final MethodChannel _channel; + + /// Map to store audio players for each key + final Map _audioPlayers = {}; + + /// Registers this class as the web plugin implementation + static void registerWith(Registrar registrar) { + final MethodChannel channel = MethodChannel( + Constants.methodChannelName, + const StandardMethodCodec(), + registrar, + ); + + final AudioWaveformsPluginWeb pluginInstance = + AudioWaveformsPluginWeb(channel); + channel.setMethodCallHandler(pluginInstance.handleMethodCall); + } + + /// Handles method calls from the platform interface + Future handleMethodCall(MethodCall call) async { + final args = call.arguments as Map?; + + switch (call.method) { + // Player methods - delegated to AudioPlayerWeb + case Constants.preparePlayer: + final key = args?[Constants.playerKey] as String?; + if (key != null) { + _initPlayer(key); + return _audioPlayers[key]?.preparePlayer(call.arguments); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotPreparePlayer, + details: Constants.playerKeyIsNull, + ); + } + + case Constants.startPlayer: + final key = args?[Constants.playerKey] as String?; + if (key != null) { + return _audioPlayers[key]?.startPlayer(); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotStartPlayer, + details: Constants.playerKeyIsNull, + ); + } + + case Constants.pausePlayer: + final key = args?[Constants.playerKey] as String?; + if (key != null) { + return _audioPlayers[key]?.pausePlayer(); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotPausePlayer, + details: Constants.playerKeyIsNull, + ); + } + + case Constants.stopPlayer: + final key = args?[Constants.playerKey] as String?; + if (key != null) { + return _audioPlayers[key]?.stopPlayer(); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotStopPlayer, + details: Constants.playerKeyIsNull, + ); + } + + case Constants.releasePlayer: + final key = args?[Constants.playerKey] as String?; + if (key != null) { + final player = _audioPlayers[key]; + if (player != null) { + // Dispose the player properly + await player.dispose(); + // Remove from the map + _audioPlayers.remove(key); + } + return true; + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotReleasePlayer, + details: Constants.playerKeyIsNull, + ); + } + + case Constants.getDuration: + final key = args?[Constants.playerKey] as String?; + final durationType = args?[Constants.durationType] as int?; + if (key != null && durationType != null) { + return _audioPlayers[key]?.getDuration(durationType); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotGetDuration, + details: Constants.playerKeyOrDurationTypeIsNull, + ); + } + + case Constants.seekTo: + final key = args?[Constants.playerKey] as String?; + final progress = args?[Constants.progress] as int?; + if (key != null && progress != null) { + return _audioPlayers[key]?.seekTo(progress); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotSeekToPosition, + details: Constants.playerKeyOrProgressIsNull, + ); + } + + case Constants.setVolume: + final key = args?[Constants.playerKey] as String?; + final volume = args?[Constants.volume] as double?; + if (key != null && volume != null) { + return _audioPlayers[key]?.setVolume(volume); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotSetVolume, + details: Constants.playerKeyOrVolumeIsNull, + ); + } + + case Constants.setRate: + final key = args?[Constants.playerKey] as String?; + final rate = args?[Constants.rate] as double?; + if (key != null && rate != null) { + return _audioPlayers[key]?.setRate(rate); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotSetRate, + details: Constants.playerKeyOrRateIsNull, + ); + } + + case Constants.stopAllPlayers: + for (final key in _audioPlayers.keys.toList()) { + await _audioPlayers[key]?.stopPlayer(); + } + return true; + + case Constants.pauseAllPlayers: + for (final player in _audioPlayers.values) { + await player.pausePlayer(); + } + return true; + + case Constants.finishMode: + final key = args?[Constants.playerKey] as String?; + final finishType = args?[Constants.finishType] as int?; + if (key != null) { + return _audioPlayers[key]?.setFinishMode(finishType); + } else { + throw PlatformException( + code: Constants.audioWaveforms, + message: Constants.cannotSetFinishMode, + details: Constants.playerKeyIsNull, + ); + } + + // Waveform extraction - delegated to WaveformExtractorWeb + case Constants.extractWaveformData: + return _waveformExtractor.extractWaveformData(call.arguments); + + // Recorder methods - not supported on web platform + case Constants.initRecorder: + case Constants.startRecording: + case Constants.stopRecording: + case Constants.pauseRecording: + case Constants.resumeRecording: + case Constants.getDecibel: + case Constants.checkPermission: + throw PlatformException( + code: Constants.unsupported, + message: Constants.recordingNotSupportedOnWeb, + details: + 'The method \'${call.method}\' ${Constants.methodNotAvailableForWeb}', + ); + + default: + throw PlatformException( + code: Constants.unimplemented, + message: + 'The method \'${call.method}\' ${Constants.methodNotImplementedOnWeb}', + details: + '${Constants.audioWaveformsDoesNotImplement} \'${call.method}\'', + ); + } + } + + /// Initializes a new audio player for the given key if it doesn't exist + void _initPlayer(String playerKey) { + if (_audioPlayers[playerKey] == null) { + _audioPlayers[playerKey] = AudioPlayerWeb( + onDurationUpdate: (currentTime) { + _channel.invokeMethod( + Constants.onCurrentDuration, + { + Constants.playerKey: playerKey, + Constants.current: currentTime, + }, + ); + }, + onCompletion: () { + _channel.invokeMethod( + Constants.onDidFinishPlayingAudio, + { + Constants.playerKey: playerKey, + Constants.finishType: + 2, // 2 = stopped (default finish mode on web) + }, + ); + }, + ); + } + } + + /// Dispose all resources + void dispose() { + for (final player in _audioPlayers.values) { + player.dispose(); + } + _audioPlayers.clear(); + } +} diff --git a/lib/web/base/constants_web.dart b/lib/web/base/constants_web.dart new file mode 100644 index 00000000..7b342ef2 --- /dev/null +++ b/lib/web/base/constants_web.dart @@ -0,0 +1,21 @@ +/// Web-specific constants for audio waveforms plugin +class WebConstants { + WebConstants._(); + + /// Default update frequency for duration updates in milliseconds + static const int defaultUpdateFrequency = 200; + + /// Default timeout for loading audio in seconds + static const int defaultLoadTimeout = 10; + + /// Default number of samples for waveform extraction + static const int defaultNoOfSamples = 100; + + /// Audio element events + static const String eventLoadedMetadata = 'loadedmetadata'; + static const String eventError = 'error'; + static const String eventEnded = 'ended'; + + /// Audio element properties + static const String propertyPreload = 'auto'; +} diff --git a/lib/web/base/utils_web.dart b/lib/web/base/utils_web.dart new file mode 100644 index 00000000..5ee26868 --- /dev/null +++ b/lib/web/base/utils_web.dart @@ -0,0 +1,13 @@ +import 'dart:js_interop'; + +import 'package:web/web.dart'; + +extension WebConsoleLogger on String { + void log() => console.log(toJS); + + void info() => console.info(toJS); + + void warn() => console.warn(toJS); + + void error() => console.error(toJS); +} diff --git a/lib/web/waveform_extractor_web.dart b/lib/web/waveform_extractor_web.dart new file mode 100644 index 00000000..a1a6ae54 --- /dev/null +++ b/lib/web/waveform_extractor_web.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +import '../src/base/constants.dart'; +import 'base/constants_web.dart'; + +/// Web implementation for waveform extraction +/// Note: This is a simplified implementation for web platform +/// For full waveform extraction, Web Audio API would need to be implemented +class WaveformExtractorWeb { + /// Extracts waveform data from audio file. + /// For web, this returns a simplified waveform with dummy data + /// since Web Audio API would require more complex implementation. + /// + /// This is a placeholder implementation: it does not analyze the input audio, + /// and the returned values do **not** represent the actual audio waveform. + Future> extractWaveformData( + Map arguments, + ) async { + try { + final int noOfSamples = + arguments[Constants.noOfSamples] ?? WebConstants.defaultNoOfSamples; + + // Return normalized dummy data for web + // TODO(vasu): Implement proper waveform extraction using Web Audio API + // This would involve: + // 1. Loading audio file into AudioContext + // 2. Decoding audio data + // 3. Analyzing the audio buffer + // 4. Extracting amplitude values at regular intervals + return List.generate( + noOfSamples, + (index) { + // Generate a simple sine wave pattern for demonstration + return (index % 2 == 0 ? 0.5 : 0.7); + }, + ); + } catch (e) { + debugPrint('Error extracting waveform data: $e'); + return []; + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 2815ce3f..6a9b2f8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,9 @@ environment: dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter + web: ^1.1.0 dev_dependencies: flutter_test: @@ -28,4 +31,7 @@ flutter: pluginClass: AudioWaveformsPlugin macos: pluginClass: AudioWaveformsPlugin + web: + pluginClass: AudioWaveformsPlugin + fileName: audio_waveforms_web.dart