we use Getx State Management
pick file and generate wave data and store into local storage when we play the item then generate waves not generate showing data but not without stream builder
Future pickAndLoadAudio(BuildContext context) async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.audio,
);
if (result != null && result.files.single.path != null) {
final filePath = result.files.single.path!;
final fileName = result.files.single.name;
// --- Extract metadata ---
final metadata = await MetadataRetriever.fromFile(File(filePath));
Uint8List? albumArt = metadata.albumArt;
// --- Get duration ---
final audioSource = AudioSource.uri(Uri.file(filePath));
final d = await audioPlayer.setAudioSource(audioSource) ?? Duration.zero;
// --- Chunk map for resume support ---
Map<int, Duration> newChunkPositions = {};
final totalChunks = (d.inSeconds / chunksDurationSeconds).ceil();
for (int i = 0; i < totalChunks; i++) {
newChunkPositions[i] = Duration.zero;
}
// --- Generate waveform & store path ---
await _generateWaveform(filePath);
// --- Also load a downsized List<double> for small files ---
// List<double>? waveformData;
// try {
// final controller = PlayerController();
// await controller.preparePlayer(
// path: filePath,
// shouldExtractWaveform: true,
// );
//
// // Downsample aggressively for inline storage
// waveformData = _downsample(controller.waveformData, 1000);
// controller.dispose();
//
// debugPrint("Waveform data generated successfully for $fileName");
// } catch (e) {
// debugPrint("Error generating waveform: $e");
// }
// --- Build track ---
final newTrack = WaveAudioTrack(
filePath: filePath,
title: fileName,
totalDuration: d,
poster: albumArt,
chunkPositions: newChunkPositions,
waveform: currentWaveform.value,
chunkFilePaths: [],
lastPosition: position.value,
// ✅ Quick inline samples
);
// --- Avoid duplicates ---
final existingIndex = playlist.indexWhere(
(track) => track.title == newTrack.title,
);
if (existingIndex == -1) {
playlist.insert(0, newTrack);
debugPrint("New track added: ${newTrack.title}");
await saveState();
playTrack(0);
} else {
debugPrint("Track already exists. Playing existing one.");
playTrack(existingIndex);
Get.snackbar(
'File',
'File is already in the playlist',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green.shade400,
colorText: Colors.white,
);
}
}
}
-- ui display data--
Obx(() {
final dur = controller.duration.value;
final pos = controller.position.value;
final currentTrack = controller.getCurrentTrack();
if (currentTrack != null) {
// Check if waveform is available, if not, load it.
// This part should be handled in your controller logic
// so the waveform is ready before the widget is built.
if (controller.currentWaveform == null) {
// You may need to call a method here to load the waveform
// For example:
// controller.loadWaveformForTrack(currentTrack);
}
}
// Use the full Waveform object from the controller
final waveformData = controller.currentWaveform.value;
print("Before Update $waveformData");
if (waveformData == null) {
return LinearProgressIndicator();
} else {
return Container(
color: Colors.yellow,
height: 40,
child: LayoutBuilder(
builder: (context, constraints) {
final double waveformWidth = constraints.maxWidth;
print("Waves From Saves ${waveformData.duration}");
return GestureDetector(
onTapDown: (details) {
if (dur.inMilliseconds > 0) {
final double tapPositionRatio =
details.localPosition.dx / waveformWidth;
final int newMilliseconds =
(dur.inMilliseconds * tapPositionRatio).round();
final newPosition = Duration(
milliseconds: newMilliseconds,
);
controller.seek(newPosition);
}
},
onHorizontalDragStart: (details) {
controller.isDragging = true;
},
onHorizontalDragUpdate: (details) {
if (dur.inMilliseconds > 0) {
final double dragPositionRatio =
(details.localPosition.dx / waveformWidth).clamp(
0.0,
1.0,
);
final int newMilliseconds =
(dur.inMilliseconds * dragPositionRatio).round();
controller.dragPreviewPosition = Duration(
milliseconds: newMilliseconds,
);
controller.seek(controller.dragPreviewPosition!);
}
},
onHorizontalDragEnd: (details) {
controller.isDragging = false;
if (dur.inMilliseconds > 0 &&
controller.dragPreviewPosition != null) {
controller.seek(controller.dragPreviewPosition!);
final currentTrack = controller.getCurrentTrack();
if (currentTrack != null) {
currentTrack.lastPosition =
controller.dragPreviewPosition!;
controller.saveState();
}
controller.dragPreviewPosition = null;
}
},
onHorizontalDragCancel: () {
controller.isDragging = false;
controller.dragPreviewPosition = null;
},
child: AudioWaveformWidget(
waveform: waveformData,
start: pos,
duration: dur, // Use the full duration of the track
),
);
},
),
);
}
}),
we use Getx State Management
pick file and generate wave data and store into local storage when we play the item then generate waves not generate showing data but not without stream builder
Future pickAndLoadAudio(BuildContext context) async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.audio,
);
}
-- ui display data--
Obx(() {
final dur = controller.duration.value;
final pos = controller.position.value;
final currentTrack = controller.getCurrentTrack();
if (currentTrack != null) {
// Check if waveform is available, if not, load it.
// This part should be handled in your controller logic
// so the waveform is ready before the widget is built.
if (controller.currentWaveform == null) {
// You may need to call a method here to load the waveform
// For example:
// controller.loadWaveformForTrack(currentTrack);
}
}