Houshan-Basa/lib/ui/widgets/components/audio/player.dart

327 lines
11 KiB
Dart

// 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<Player> createState() => _PlayerState();
}
enum DownloadAudioState { onDownloading, downloaded, initial }
class _PlayerState extends State<Player> {
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<Duration>(
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),
),
],
);
})),
],
),
);
}
}