step 11 : play and pause rekaman dan slicing edit screen

This commit is contained in:
sindhu
2025-02-22 12:37:52 +07:00
parent c9cc76fbe7
commit 1206d7de24
11 changed files with 440 additions and 78 deletions

View File

@@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import '../../model/edit_voice_to_text_model.dart';
@@ -25,4 +26,12 @@ final selectedEdit = StateProvider<EditVoiceToTextModel>(
//
final barcodeX = StateProvider<Barcode>(
(ref) => Barcode(),
);
);
final noteCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final textCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttervoice2text/app/route.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../model/edit_voice_to_text_model.dart';
import '../../model/voice_to_text_model.dart';
@@ -62,9 +63,9 @@ class CardRiwayatRekaman extends HookConsumerWidget {
Voice2text_Updated: data.Voice2text_Updated,
Voice2text_User_ID: data.Voice2text_User_ID,
);
// Navigator.of(context).pushNamed(
// editRekamanRoute,
// );
Navigator.of(context).pushNamed(
editRekamRoute,
);
},
child: Icon(
Icons.edit,

View File

@@ -1,13 +1,115 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:toastification/toastification.dart';
import '../../app/constant.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/voice_to_text_provider.dart';
class EditRekamScreen extends HookConsumerWidget {
const EditRekamScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom],
);
final currentUser = ref.watch(currentUserProvider);
final host = currentUser?.host ?? "";
final userId = currentUser?.model.userId ?? "";
final qrCodeStr = useState("");
final editData = ref.watch(selectedEdit);
final baseUrl = "https://$host/";
final player = useState(AudioPlayer());
final urlRekaman = baseUrl + editData.Voice2text_Url;
final isPlaying = useState(false);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
ref.read(noteCtr.notifier).state = TextEditingController(
text: editData.Voice2text_Note,
);
ref.read(textCtr.notifier).state = TextEditingController(
text: editData.Voice2text_Text,
);
String afterVoice = editData.Voice2text_Url.split("voice-")[1];
String qrCodeExtract = afterVoice.split(".mp3")[0];
qrCodeStr.value = "Qrcode : $qrCodeExtract";
});
return () {};
}, []);
void showLongToast(
String title,
String message,
String typeToast,
Duration waktu,
) {
if (typeToast == "success") {
toastification.show(
context: context,
title: Text(title),
description: Text(message),
autoCloseDuration: waktu,
type: ToastificationType.success,
style: ToastificationStyle.fillColored,
);
} else if (typeToast == "error") {
toastification.show(
context: context,
title: Text(title),
description: Text(message),
autoCloseDuration: waktu,
type: ToastificationType.error,
style: ToastificationStyle.fillColored,
);
} else if (typeToast == "warning") {
toastification.show(
context: context,
title: Text(title),
description: Text(message),
autoCloseDuration: waktu,
type: ToastificationType.warning,
style: ToastificationStyle.fillColored,
);
}
}
Future<void> playRekaman() async {
if (urlRekaman.isNotEmpty) {
try {
if (isPlaying.value) {
await player.value.pause(); // Pause jika sedang diputar
} else {
await player.value.setSourceUrl(urlRekaman);
await player.value.resume(); // Putar jika tidak sedang diputar
}
isPlaying.value = !isPlaying.value;
} catch (e) {
print("Error saat memutar rekaman: ${e.toString()}");
showLongToast(
'Error',
"Error saat memutar rekaman: ${e.toString()}",
'error',
Duration(seconds: 3),
);
}
} else {
print("URL rekaman kosong");
showLongToast(
'Error',
"URL rekaman kosong",
'error',
Duration(seconds: 3),
);
}
}
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
@@ -15,6 +117,56 @@ class EditRekamScreen extends HookConsumerWidget {
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Constant.bgGrey,
bottomNavigationBar: BottomAppBar(
color: Colors.blueGrey.shade100,
height: Constant.getActualYPhone(context: context, y: 100),
shape: const CircularNotchedRectangle(),
child: Row(
children: [
ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
},
icon:
Icon(Icons.arrow_back, size: 17, color: Constant.textWhite),
label: Text(
'Kembali',
style: Constant.cardText(context: context)
.copyWith(color: Constant.textWhite),
),
style: ElevatedButton.styleFrom(
backgroundColor: Constant.textBlack,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
),
const Spacer(),
ElevatedButton(
onPressed: playRekaman,
style: ElevatedButton.styleFrom(
backgroundColor: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'SAVE',
style: Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
],
),
),
],
),
),
body: Column(
children: [
// atas
@@ -23,6 +175,125 @@ class EditRekamScreen extends HookConsumerWidget {
width: double.infinity,
fit: BoxFit.cover,
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 50,
),
),
// qrcode
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20,
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 10),
color: Constant.inputanGrey,
child: Text(
qrCodeStr.value,
style: TextStyle(color: Colors.white),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
// button play record
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20,
),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: playRekaman,
style: ElevatedButton.styleFrom(
backgroundColor: Constant.textRed,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
child: Text(
isPlaying.value ? 'STOP' : 'PUTAR REKAMAN',
style: Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
// note
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20,
),
child: TextField(
controller: ref.read(noteCtr),
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
label: Text('Note'),
hintText: "Note",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
// hasil transcribe
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20,
),
child: TextField(
controller: ref.read(textCtr),
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
label: Text('Text'),
hintText: "Text",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
],
),
),

View File

@@ -84,78 +84,6 @@ class RekamScreen extends HookConsumerWidget {
}
}
Future<void> jalankanRekaman() async {
try {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/myFile.aac';
// final filePath = '${dir.path}/myFile.mp3';
audioPath.value = filePath;
await recorder.value.startRecorder(
toFile: filePath,
);
judulTombol.value = "SELESAI";
isSelesaiRekam.value = true;
} catch (e) {
print("Error start record ${e.toString()}");
}
}
Future<String?> convertToMp3(String inputPath) async {
final dir = await getApplicationDocumentsDirectory();
final outputPath = "${dir.path}/output.mp3";
// Jalankan perintah FFmpeg untuk konversi
await FFmpegKit.execute(
'-i $inputPath -codec:a libmp3lame -qscale:a 2 $outputPath');
// Pastikan file MP3 berhasil dibuat
if (File(outputPath).existsSync()) {
return outputPath;
} else {
return null;
}
}
Future<void> playRecording() async {
try {
if (audioPath.value.isNotEmpty && File(audioPath.value).existsSync()) {
await player.value.startPlayer(fromURI: audioPath.value);
} else {
print("Error: Path rekaman kosong atau tidak ditemukan.");
}
} catch (e) {
print("Error saat memutar rekaman: ${e.toString()}");
}
}
Future<void> berhentiRekaman() async {
try {
await recorder.value.stopRecorder();
isSelesaiRekam.value = false;
// panggil fungsi untuk kirim ke BE
if (audioPath.value.isNotEmpty) {
String? mp3Path = await convertToMp3(audioPath.value);
if (mp3Path != null) {
// await uploadAudioFile(mp3Path);
ref.read(uploadRekamProvider.notifier).uploadRekam(
host: host,
filePath: mp3Path,
userId: userId,
qrCodeStr: qrCodeStr.value,
);
print('mp3 convert $mp3Path');
} else {
print("Konversi gagal!");
}
}
} catch (e) {
print("Error stop record ${e.toString()}");
}
}
void showLongToast(
String title,
String message,
@@ -192,6 +120,83 @@ class RekamScreen extends HookConsumerWidget {
}
}
Future<void> jalankanRekaman() async {
try {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/myFile.aac';
// final filePath = '${dir.path}/myFile.mp3';
audioPath.value = filePath;
await recorder.value.startRecorder(
toFile: filePath,
);
judulTombol.value = "SELESAI";
isSelesaiRekam.value = true;
} catch (e) {
print("Error start record ${e.toString()}");
showLongToast(
'Error',
"Error start record ${e.toString()}",
'error',
Duration(seconds: 3),
);
}
}
Future<String?> convertToMp3(String inputPath) async {
final dir = await getApplicationDocumentsDirectory();
final outputPath = "${dir.path}/output.mp3";
// Jalankan perintah FFmpeg untuk konversi
await FFmpegKit.execute(
'-i $inputPath -codec:a libmp3lame -qscale:a 2 $outputPath');
// Pastikan file MP3 berhasil dibuat
if (File(outputPath).existsSync()) {
return outputPath;
} else {
return null;
}
}
Future<void> berhentiRekaman() async {
try {
await recorder.value.stopRecorder();
isSelesaiRekam.value = false;
// panggil fungsi untuk kirim ke BE
if (audioPath.value.isNotEmpty) {
String? mp3Path = await convertToMp3(audioPath.value);
if (mp3Path != null) {
// await uploadAudioFile(mp3Path);
ref.read(uploadRekamProvider.notifier).uploadRekam(
host: host,
filePath: mp3Path,
userId: userId,
qrCodeStr: qrCodeStr.value,
);
} else {
print("Konversi gagal!");
showLongToast(
'Error',
"Konversi gagal!",
'error',
Duration(seconds: 3),
);
}
}
} catch (e) {
print("Error stop record ${e.toString()}");
showLongToast(
'Error',
"Error stop record ${e.toString()}",
'error',
Duration(seconds: 3),
);
}
}
// list Riwayat Rekam
ref.listen(listRiwayatRekamanProvider, (prev, next) {
if (next is ListRiwayatRekamanStateLoading) {
@@ -307,7 +312,7 @@ class RekamScreen extends HookConsumerWidget {
.copyWith(color: Constant.textWhite),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green.shade400,
backgroundColor: Constant.textBlack,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -370,7 +375,7 @@ class RekamScreen extends HookConsumerWidget {
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
if (awalan.value == "Info :")
if (awalan.value == "Info :")
SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(

View File

@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -5,12 +5,14 @@
import FlutterMacOS
import Foundation
import audioplayers_darwin
import ffmpeg_kit_flutter_audio
import mobile_scanner
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@@ -9,6 +9,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: b3d02a23918b980073d4a76c4e5659eaf5127251ca91a6a804869ba88ef1c8bf
url: "https://pub.dev"
source: hosted
version: "6.2.0"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: "56830454da1a943f33c686cdbfca52a8d24f4c6fd6ce88c6b3501020dbaaf48b"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: "34e1fb3a88ba02566615c6ca7173aa93b39cf61a38a05a729b60ba14c4899169"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: ce8383c1ff0db1ba93b5b0b8ef5b260ec2d0ce2598ad37dc8b946b773dd2c5fa
url: "https://pub.dev"
source: hosted
version: "4.1.0"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "3609bdf0e05e66a3d9750ee40b1e37f2a622c4edb796cc600b53a90a30a2ace4"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "52b57c706a20fc85daa911be67381b3a5c9d03f8e27cb9fdb226de5bddf293b1"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
boolean_selector:
dependency: transitive
description:
@@ -216,6 +272,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
http:
dependency: transitive
description:
name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
url: "https://pub.dev"
source: hosted
version: "1.3.0"
http_parser:
dependency: transitive
description:

View File

@@ -50,6 +50,7 @@ dependencies:
mobile_scanner: ^6.0.6
flutter_sound: ^9.23.1
ffmpeg_kit_flutter_audio: ^6.0.3
audioplayers: ^6.2.0
dev_dependencies:
flutter_test:

View File

@@ -6,9 +6,12 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
permission_handler_windows
)