// ignore_for_file: deprecated_member_use_from_same_package import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:audioplayers/audioplayers.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hoshan/core/gen/assets.gen.dart'; import 'package:hoshan/core/services/api/dio_service.dart'; import 'package:hoshan/core/utils/date_time.dart'; import 'package:hoshan/ui/theme/colors.dart'; import 'package:hoshan/ui/theme/text.dart'; import 'package:hoshan/ui/widgets/components/button/circle_icon_btn.dart'; import 'package:string_validator/string_validator.dart'; class Player extends StatefulWidget { final String fileUrl; final Function()? onDelete; final bool inMessages; const Player( {super.key, required this.fileUrl, this.onDelete, this.inMessages = false}); @override State createState() => _PlayerState(); } enum DownloadAudioState { onDownloading, downloaded, initial } class _PlayerState extends State { final AudioPlayer audioPlayer = AudioPlayer(); bool isPlaying = false; bool isPaused = false; Duration duration = Duration.zero; DownloadAudioState downloadState = DownloadAudioState.initial; StreamSubscription? durationSubscription; StreamSubscription? positionSubscription; StreamSubscription? playerStateChangeSubscription; String? _audioFilePath; @override void initState() { super.initState(); try { setAudio(); } catch (e) { if (kDebugMode) { print("Audio Error is: $e"); } } playerStateChangeSubscription = audioPlayer.onPlayerStateChanged.listen( (state) { if (kDebugMode) { print("Player state changed: $state"); } setState(() { isPlaying = state == PlayerState.playing; isPaused = state == PlayerState.paused; }); }, ); durationSubscription = audioPlayer.onDurationChanged.listen( (newDuration) { if (kDebugMode) { print("Duration changed: $newDuration"); } setState(() { duration = newDuration; }); }, ); audioPlayer.onPlayerComplete.listen((event) { if (kDebugMode) { print("Player completed"); } }); audioPlayer.onLog.listen((msg) { if (kDebugMode) { print("AudioPlayer log: $msg"); } }); } Future setAudio() async { try { audioPlayer.setReleaseMode(ReleaseMode.stop); if (widget.fileUrl.isNotEmpty) { XFile? file; if (widget.fileUrl.isURL()) { setState(() { downloadState = DownloadAudioState.onDownloading; }); file = await DioService.downloadFile( widget.fileUrl, ); } else { file = XFile(widget.fileUrl); } if (file != null) { final fileExists = await File(file.path).exists(); if (!fileExists) { if (kDebugMode) { print("Audio file does not exist: ${file.path}"); } setState(() { downloadState = DownloadAudioState.initial; }); return; } await Future.delayed(const Duration(milliseconds: 100)); _audioFilePath = file.path; if (kDebugMode) { print("Setting audio source: ${file.path}"); final fileSize = await File(file.path).length(); print("File size: $fileSize bytes"); } try { await audioPlayer.setSource(DeviceFileSource(file.path)); setState(() { downloadState = DownloadAudioState.downloaded; }); if (kDebugMode) { print("Audio source set successfully"); } } catch (error) { if (kDebugMode) { print("Error setting audio source: $error"); } setState(() { downloadState = DownloadAudioState.initial; }); } } else { setState(() { downloadState = DownloadAudioState.initial; }); } } } catch (e) { if (kDebugMode) { print("Error in setAudio: $e"); } } } @override void dispose() { playerStateChangeSubscription?.cancel(); durationSubscription?.cancel(); positionSubscription?.cancel(); audioPlayer.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( children: [ if (widget.onDelete != null) Padding( padding: const EdgeInsets.only(right: 8), child: SizedBox( width: 24, height: 24, child: GestureDetector( onTap: widget.onDelete, child: Assets.icon.outline.trash.svg(), ), ), ), widget.inMessages ? downloadState == DownloadAudioState.initial ? CircleIconBtn( icon: Assets.icon.outline.download, size: 32, iconPadding: const EdgeInsets.all(6), iconColor: AppColors.secondryColor.defaultShade, onTap: () async { await setAudio(); }, ) : downloadState == DownloadAudioState.onDownloading ? Stack( children: [ Transform.rotate( angle: pi / 4, child: CircleIconBtn( icon: Assets.icon.outline.add, size: 32, iconPadding: const EdgeInsets.all(6), iconColor: AppColors.secondryColor.defaultShade, onTap: () async {}, ), ), Positioned.fill(child: Builder(builder: (context) { return const CircularProgressIndicator(); })) ], ) : CircleIconBtn( icon: isPlaying ? Assets.icon.outline.pause : Assets.icon.outline.play, size: 32, iconPadding: const EdgeInsets.all(6), iconColor: AppColors.secondryColor.defaultShade, onTap: () async { try { if (isPlaying) { await audioPlayer.pause(); } else if (isPaused) { await audioPlayer.resume(); } else { if (_audioFilePath != null) { await audioPlayer .play(DeviceFileSource(_audioFilePath!)); } } } catch (e) { if (kDebugMode) { print("Error playing audio: $e"); } } }, ) : SizedBox( width: 28, height: 28, child: GestureDetector( onTap: () async { try { if (isPlaying) { await audioPlayer.pause(); } else if (isPaused) { await audioPlayer.resume(); } else { if (_audioFilePath != null) { await audioPlayer .play(DeviceFileSource(_audioFilePath!)); } } } catch (e) { if (kDebugMode) { print("Error playing audio: $e"); } } }, child: (isPlaying ? Assets.icon.bold.pause : Assets.icon.bold.play) .svg(color: AppColors.primaryColor.defaultShade), ), ), Expanded( child: StreamBuilder( stream: audioPlayer.onPositionChanged, builder: (context, snapshot) { Duration position = Duration.zero; if (snapshot.hasData && snapshot.data != null) { position = snapshot.data!; } return Row( children: [ Expanded( child: Directionality( textDirection: TextDirection.ltr, child: Slider( activeColor: widget.inMessages ? AppColors.secondryColor[200] : null, inactiveColor: AppColors.secondryColor[50], thumbColor: widget.inMessages ? AppColors.secondryColor.defaultShade : null, min: 0, max: position < duration ? duration.inMilliseconds.toDouble() : 100, value: position < duration ? position.inMilliseconds.toDouble() : 10, onChanged: (value) async { if (downloadState != DownloadAudioState.downloaded) { return; } final position = Duration(milliseconds: value.toInt()); await audioPlayer.seek(position); // await audioPlayer.resume(); }, ), ), ), Text( DateTimeUtils.getTimeFromDuration( (isPlaying || isPaused ? position : duration) .inSeconds, ), style: AppTextStyles.body4.copyWith( color: widget.inMessages ? Colors.white : Theme.of(context).colorScheme.onSurface), ), ], ); })), ], ), ); } }