little better fix audio 3/07/1403

This commit is contained in:
OkaykOrhmn 2024-09-24 16:14:46 +03:30
parent f72228b339
commit fbb9db5103
17 changed files with 271 additions and 138 deletions

View File

@ -99,20 +99,21 @@ class Prompts {
bool? error;
bool? audio;
FilesModel? fileLocal;
int? duration;
Prompts({
this.id,
this.chatId,
this.text,
this.file,
this.fileName,
this.role,
this.createdAt,
this.finished,
this.error,
this.fileLocal,
this.audio,
});
Prompts(
{this.id,
this.chatId,
this.text,
this.file,
this.fileName,
this.role,
this.createdAt,
this.finished,
this.error,
this.fileLocal,
this.audio,
this.duration});
Prompts.fromJson(Map<String, dynamic> json) {
id = json['id'];
@ -123,6 +124,7 @@ class Prompts {
role = json['role'];
createdAt = json['createdAt'];
audio = json['audio'];
duration = json['duration'];
}
Map<String, dynamic> toJson() {
@ -135,21 +137,24 @@ class Prompts {
data['role'] = role;
data['createdAt'] = createdAt;
data['audio'] = audio;
data['duration'] = duration;
return data;
}
Prompts copyWith(
{int? id,
int? chatId,
String? text,
String? file,
String? fileName,
FilesModel? fileLocal,
String? role,
String? createdAt,
bool? finished,
bool? error,
bool? audio}) {
Prompts copyWith({
int? id,
int? chatId,
String? text,
String? file,
String? fileName,
FilesModel? fileLocal,
String? role,
String? createdAt,
bool? finished,
bool? error,
bool? audio,
int? duration,
}) {
return Prompts(
id: id ?? this.id,
chatId: chatId ?? this.chatId,
@ -162,6 +167,7 @@ class Prompts {
finished: finished ?? this.finished,
error: error ?? this.error,
audio: audio ?? this.audio,
duration: duration ?? this.duration,
);
}
}

View File

@ -14,6 +14,7 @@ class FilesModel {
final bool? image;
final bool? network;
final Uint8List? bytes;
final Duration? duration;
FilesModel(
this.path, {
@ -23,6 +24,7 @@ class FilesModel {
this.image,
this.network,
this.bytes,
this.duration,
}) {
basename = name ?? p.basename(path);
extname = p.extension(path);

View File

@ -33,7 +33,11 @@ class AiApiService {
if (edite != null) {
request.fields['edit'] = edite.toString().toLowerCase();
}
if (file != null) {
if (file.duration != null) {
request.fields['duration'] = file.duration!.inSeconds.toString();
}
Uint8List bytes;
String filename;
String mimeType;
@ -45,6 +49,8 @@ class AiApiService {
final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':'));
final http.Response audioResponse = await http.get(audioUri);
bytes = audioResponse.bodyBytes;
// final f = File.fromUri(Uri.parse(file.path));
// bytes = await f.readAsBytes();
// Fetch the blob using JavaScript interop
// final blob = await html.window

View File

@ -34,7 +34,6 @@ class MediaService {
String tag;
tag =
'${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id';
print("tag: $tag");
if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) {
audioSource = '${StorageService.appDocsDir}/podcasts/podcast-$id.mp3';
isNetworkAudio = false;

View File

@ -1,4 +1,3 @@
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/foundation.dart';
@ -52,13 +51,19 @@ class VoiceService {
return duration;
}
static Future<void> voiceHelper({required String src}) async {
static Future<void> voiceHelper(
{required String src,
final Duration? duration,
final Function()? startTimer,
final Function()? stopTimer}) async {
try {
if (VoiceService.src == src) {
if (audioPlayer.playing) {
await audioPlayer.pause();
stopTimer?.call();
} else {
await audioPlayer.play();
startTimer?.call();
}
return;
}
@ -88,6 +93,8 @@ class VoiceService {
await audioPlayer.setFilePath(src);
}
await audioPlayer.play();
startTimer?.call();
} catch (e) {
resetVoicePlayer();
}

View File

@ -3,7 +3,6 @@
import 'dart:convert';
import 'dart:developer';
import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart';
// ignore: depend_on_referenced_packages
import 'package:http/http.dart' as http;

View File

@ -348,8 +348,6 @@ class ActionSheetUtils {
),
)
: ListView.builder(
padding:
const EdgeInsets.symmetric(vertical: 12),
itemCount: state.bots.length,
physics: const BouncingScrollPhysics(),
shrinkWrap: true,

View File

@ -1,5 +1,7 @@
// ignore_for_file: library_private_types_in_public_api
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
@ -9,6 +11,7 @@ import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/home/home.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
@ -83,38 +86,66 @@ class _AiState extends State<Ai> {
onTap: () => ActionSheetUtils(context)
.botsDialogSelect(
context: context, state: state),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(360),
border: Border.all(
width: 1,
color:
Theme.of(context).colorScheme.text)),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 18),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
DidvanIcons.caret_down_solid,
color:
Theme.of(context).colorScheme.title,
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Column(
children: [
Transform.rotate(
angle: 180 * pi / 180,
child: Icon(
DidvanIcons.caret_down_solid,
color: Theme.of(context)
.colorScheme
.title,
),
),
Icon(
DidvanIcons.caret_down_solid,
color: Theme.of(context)
.colorScheme
.title,
),
],
),
const SizedBox(
width: 12,
),
DidvanText(bot.name.toString(),
color: Theme.of(context)
.colorScheme
.title),
],
),
const SizedBox(
width: 12,
),
DidvanText(bot.name.toString(),
color: Theme.of(context)
.colorScheme
.title),
ClipOval(
child: CachedNetworkImage(
width: 46,
height: 46,
imageUrl: bot.image.toString(),
),
),
],
),
const SizedBox(
width: 12,
),
ClipOval(
child: CachedNetworkImage(
width: 46,
height: 46,
imageUrl: bot.image.toString(),
),
),
],
),
),
),
const SizedBox(
@ -161,43 +192,71 @@ class _AiState extends State<Ai> {
arguments: AiChatArgs(
bot: bot,
)),
child: Container(
decoration: BoxDecoration(
boxShadow: DesignConfig.defaultShadow,
color: Theme.of(context).colorScheme.surface,
border: Border.all(
color: Theme.of(context).colorScheme.border),
borderRadius: DesignConfig.highBorderRadius),
child: Row(
children: [
const SizedBox(
width: 8,
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
boxShadow: DesignConfig.defaultShadow,
color: Theme.of(context).colorScheme.surface,
border: Border.all(
color: Theme.of(context).colorScheme.border),
borderRadius: DesignConfig.highBorderRadius),
child: Row(
children: [
const SizedBox(
width: 8,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Form(
child: Row(
children: [
const MessageBarBtn(
enable: false,
icon: DidvanIcons.mic_regular),
const SizedBox(
width: 8,
),
Expanded(
child: TextFormField(
textInputAction:
TextInputAction.newline,
style: Theme.of(context)
.textTheme
.bodyMedium,
minLines: 1,
enabled: false,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'بنویسید...',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
),
),
),
],
))))
],
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Form(
child: TextFormField(
textInputAction: TextInputAction.newline,
style: Theme.of(context).textTheme.bodyMedium,
minLines: 1,
enabled: false,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'بنویسید...',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
),
))))
],
),
),
const SizedBox(
width: 18,
),
Icon(
Icons.attach_file_rounded,
color: Theme.of(context).colorScheme.focusedBorder,
),
],
)),
),
],

View File

@ -232,23 +232,29 @@ class _AiChatPageState extends State<AiChatPage> {
const SizedBox(
height: 24,
),
SizedBox(
width: MediaQuery.sizeOf(context).height / 5,
height: MediaQuery.sizeOf(context).height / 5,
child: ClipOval(
child: CachedNetworkImage(
imageUrl: widget.args.bot.image.toString(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DidvanText(widget.args.bot.name.toString()),
const SizedBox(
width: 12,
),
),
ClipOval(
child: CachedNetworkImage(
width: 46,
height: 46,
imageUrl: widget.args.bot.image.toString(),
),
),
],
),
const SizedBox(
height: 24,
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20.0),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Text(
"به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.\n دریافت پاسخ از: ${widget.args.bot.name}",
"به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.",
textAlign: TextAlign.center,
),
)
@ -324,7 +330,12 @@ class _AiChatPageState extends State<AiChatPage> {
Widget messageBubble(Prompts message, BuildContext context, AiChatState state,
int index, int mIndex) {
FilesModel? file = message.fileLocal ??
(message.file == null ? null : FilesModel(message.file.toString()));
(message.file == null
? null
: FilesModel(message.file.toString(),
duration: message.duration != null
? Duration(seconds: message.duration!)
: null));
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
pPadding: const EdgeInsets.all(0.8),
@ -427,7 +438,10 @@ class _AiChatPageState extends State<AiChatPage> {
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: AudioWave(file: file.path),
child: AudioWave(
file: file.path,
totalDuration: file.duration,
),
)
: file.isImage()
? Padding(

View File

@ -9,6 +9,7 @@ import 'package:didvan/models/ai/chats_model.dart';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/models/ai/messages_model.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/media/voice.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/ai_chat_state.dart';
@ -337,13 +338,21 @@ class _AiMessageBarState extends State<AiMessageBar> {
path = await record
.stop();
Duration? duration =
await VoiceService
.getDuration(
src: path ??
'');
state.file = FilesModel(
path.toString(),
name:
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a',
isRecorded: true,
audio: true,
image: false);
image: false,
duration:
duration);
_timer.cancel();
_countTimer.value = 0;
state.update();
@ -371,7 +380,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
.hasPermission()) {
try {
String path =
'';
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a';
//
if (!kIsWeb) {
Directory?

View File

@ -6,7 +6,6 @@ import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/media/voice.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:flutter/foundation.dart';
@ -17,7 +16,12 @@ import 'package:just_audio/just_audio.dart';
class AudioWave extends StatefulWidget {
final String file;
final double loadingPaddingSize;
const AudioWave({Key? key, required this.file, this.loadingPaddingSize = 0})
final Duration? totalDuration;
const AudioWave(
{Key? key,
required this.file,
this.loadingPaddingSize = 0,
this.totalDuration})
: super(key: key);
@override
@ -35,14 +39,18 @@ class _AudioWaveState extends State<AudioWave> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await VoiceService.getDuration(src: widget.file).then((duration) {
setState(() {
totalDuration = duration;
if (kDebugMode) {
print(totalDuration!.inSeconds);
}
if (widget.totalDuration == null) {
await VoiceService.getDuration(src: widget.file).then((duration) {
setState(() {
totalDuration = duration;
if (kDebugMode) {
print(totalDuration!.inSeconds);
}
});
});
});
} else {
totalDuration = widget.totalDuration;
}
});
// listeners();
}
@ -63,18 +71,18 @@ class _AudioWaveState extends State<AudioWave> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StreamBuilder<PlayerState>(
stream: VoiceService.audioPlayer.playerStateStream,
StreamBuilder<PlaybackEvent>(
stream: VoiceService.audioPlayer.playbackEventStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
if (snapshot.data!.processingState ==
ProcessingState.completed) {
VoiceService.audioPlayer.pause();
VoiceService.audioPlayer.seek(Duration.zero);
}
print(snapshot.data);
if ((snapshot.data!.processingState ==
ProcessingState.loading ||
snapshot.data!.processingState ==
@ -91,7 +99,7 @@ class _AudioWaveState extends State<AudioWave> {
}
return MessageBarBtn(
enable: true,
icon: snapshot.data!.playing &&
icon: VoiceService.audioPlayer.playing &&
VoiceService.src == widget.file
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
@ -107,7 +115,12 @@ class _AudioWaveState extends State<AudioWave> {
if (!snapshot.hasData) {
return const SizedBox();
}
return totalDuration == null
return totalDuration == null ||
(totalDuration != null &&
VoiceService.audioPlayer.playing &&
VoiceService.audioPlayer.duration!.inSeconds !=
totalDuration!.inSeconds &&
VoiceService.src == widget.file)
? Padding(
padding: EdgeInsets.symmetric(
vertical: widget.loadingPaddingSize),

View File

@ -1,5 +1,4 @@
import 'package:didvan/config/theme_data.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -44,7 +43,6 @@ class MessageBarBtn extends StatelessWidget {
child: Padding(
padding: EdgeInsets.all(8.0),
child: SpinKitCircle(
size: 24,
color: Colors.white,
),
))

View File

@ -17,12 +17,14 @@ class AudioWidget extends StatelessWidget {
final File? audioFile;
final int id;
final StudioDetailsData? audioMetaData;
final Duration? duration;
const AudioWidget({
Key? key,
this.audioUrl,
this.audioFile,
required this.id,
this.audioMetaData,
this.duration,
}) : super(key: key);
@override
@ -43,7 +45,8 @@ class AudioWidget extends StatelessWidget {
tag: audioMetaData != null
? '${audioMetaData!.type}-$id'
: 'message-$id',
duration: audioMetaData?.duration,
duration:
audioMetaData?.duration ?? duration?.inSeconds,
showTimer: true,
),
),
@ -88,6 +91,11 @@ class _AudioControllerButton extends StatelessWidget {
return StreamBuilder<PlayerState>(
stream: MediaService.audioPlayer.playerStateStream,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data!.processingState == ProcessingState.completed) {
MediaService.audioPlayer.pause();
MediaService.audioPlayer.seek(Duration.zero);
}
return DidvanIconButton(
icon: MediaService.audioPlayer.playing && _nowPlaying
? DidvanIcons.pause_circle_solid

View File

@ -2,6 +2,7 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/services/media/voice.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/direct/direct_state.dart';
import 'package:didvan/views/direct/widgets/audio_widget.dart';
@ -9,6 +10,7 @@ import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
@ -84,11 +86,34 @@ class Message extends StatelessWidget {
children: [
if (message.text != null) DidvanText(message.text!),
if (message.audio != null || message.audioFile != null)
AudioWidget(
audioFile: message.audioFile,
audioUrl: message.audio,
id: message.id,
),
FutureBuilder(
future: VoiceService.getDuration(
src: message.audioFile == null
? message.audio!
: message.audioFile!.path),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: List.generate(
5,
(index) => SpinKitWave(
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.4),
size: 32,
itemCount: 10,
)));
}
return AudioWidget(
audioFile: message.audioFile,
audioUrl: message.audio,
id: message.id,
duration: snapshot.data,
);
}),
if (message.radar != null || message.news != null)
const DidvanDivider(),
if (message.radar != null || message.news != null)

View File

@ -21,7 +21,6 @@ class AudioSlider extends StatelessWidget {
@override
Widget build(BuildContext context) {
print(MediaService.audioPlayerTag);
return IgnorePointer(
ignoring: !_isPlaying,
child: Directionality(

View File

@ -73,14 +73,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.2"
azblob:
dependency: "direct main"
description:
name: azblob
sha256: e9e8689280bd8fa19c1c21a476547de80d2a770c81d2da9da68173c7f5479c7b
url: "https://pub.dev"
source: hosted
version: "4.0.0"
boolean_selector:
dependency: transitive
description:

View File

@ -95,7 +95,6 @@ dependencies:
flutter_local_notifications: ^17.2.2
flutter_background_service: ^5.0.10
js: ^0.6.7
azblob: ^4.0.0
# url_launcher: ^6.3.0
dev_dependencies: