feat: integrate real AudioPlayer, real ImagePicker for Camera/Gallery, and on-the-fly OGG-to-MP3 converter on server
This commit is contained in:
@@ -47,5 +47,11 @@
|
||||
</array>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>This app requires contacts access to match phone numbers with your local address book names.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app requires camera access to take and send photos via WhatsApp.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app requires photo library access to choose and send photos via WhatsApp.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app requires microphone access to record and send audio messages via WhatsApp.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import '../controllers/chat_controller.dart';
|
||||
import '../models/conversation_model.dart';
|
||||
import '../models/message_model.dart';
|
||||
@@ -232,19 +234,21 @@ class ChatScreen extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildAttachmentItem(
|
||||
icon: Icons.photo,
|
||||
color: Colors.purple,
|
||||
label: 'Photo',
|
||||
icon: Icons.camera_alt,
|
||||
color: Colors.green,
|
||||
label: 'Camera',
|
||||
onTap: () {
|
||||
Get.back();
|
||||
// Real red dot 5x5 pixel PNG base64
|
||||
const base64Photo = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
||||
ctrl.sendMediaMessage(
|
||||
base64Photo,
|
||||
'image/png',
|
||||
'photo.png',
|
||||
caption: '📸 Photo sent from Mywhatsapp App!',
|
||||
);
|
||||
_pickAndSendImage(ctrl, ImageSource.camera);
|
||||
},
|
||||
),
|
||||
_buildAttachmentItem(
|
||||
icon: Icons.photo_library,
|
||||
color: Colors.purple,
|
||||
label: 'Gallery',
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickAndSendImage(ctrl, ImageSource.gallery);
|
||||
},
|
||||
),
|
||||
_buildAttachmentItem(
|
||||
@@ -272,6 +276,41 @@ class ChatScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _pickAndSendImage(ChatController ctrl, ImageSource source) async {
|
||||
try {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? image = await picker.pickImage(
|
||||
source: source,
|
||||
imageQuality: 75,
|
||||
);
|
||||
if (image == null) return;
|
||||
|
||||
final bytes = await image.readAsBytes();
|
||||
final base64String = base64Encode(bytes);
|
||||
|
||||
String mimetype = 'image/jpeg';
|
||||
if (image.path.toLowerCase().endsWith('.png')) {
|
||||
mimetype = 'image/png';
|
||||
} else if (image.path.toLowerCase().endsWith('.gif')) {
|
||||
mimetype = 'image/gif';
|
||||
}
|
||||
|
||||
await ctrl.sendMediaMessage(
|
||||
base64String,
|
||||
mimetype,
|
||||
image.name,
|
||||
caption: '📸 Photo sent via Mywhatsapp!',
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error picking image',
|
||||
e.toString(),
|
||||
backgroundColor: Colors.redAccent.withOpacity(0.8),
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAttachmentItem({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import '../models/message_model.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import '../services/whatsapp_service.dart';
|
||||
@@ -146,47 +147,81 @@ class _InteractiveMediaWidgetState extends State<InteractiveMediaWidget> {
|
||||
final WhatsAppService _svc = Get.find<WhatsAppService>();
|
||||
bool _isLoading = false;
|
||||
|
||||
// Audio simulation state
|
||||
// Audio player state
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
StreamSubscription? _posSub;
|
||||
StreamSubscription? _durSub;
|
||||
StreamSubscription? _stateSub;
|
||||
|
||||
bool _isPlaying = false;
|
||||
double _audioProgress = 0.0;
|
||||
int _audioDurationSeconds = 12;
|
||||
int _audioDurationSeconds = 1;
|
||||
int _audioCurrentSeconds = 0;
|
||||
Timer? _audioTimer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_audioTimer?.cancel();
|
||||
super.dispose();
|
||||
void initState() {
|
||||
super.initState();
|
||||
_posSub = _player.onPositionChanged.listen((p) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_audioCurrentSeconds = p.inSeconds;
|
||||
if (_audioDurationSeconds > 0) {
|
||||
_audioProgress = p.inMilliseconds / (_audioDurationSeconds * 1000);
|
||||
if (_audioProgress > 1.0) _audioProgress = 1.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
void _toggleAudioPlayback() {
|
||||
if (_isPlaying) {
|
||||
_audioTimer?.cancel();
|
||||
_durSub = _player.onDurationChanged.listen((d) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isPlaying = false;
|
||||
_audioDurationSeconds = d.inSeconds > 0 ? d.inSeconds : 1;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isPlaying = true;
|
||||
});
|
||||
const intervalMs = 100;
|
||||
_audioTimer = Timer.periodic(const Duration(milliseconds: intervalMs), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
_stateSub = _player.onPlayerStateChanged.listen((s) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_audioProgress += intervalMs / (_audioDurationSeconds * 1000);
|
||||
_audioCurrentSeconds = (_audioProgress * _audioDurationSeconds).floor();
|
||||
if (_audioProgress >= 1.0) {
|
||||
_isPlaying = s == PlayerState.playing;
|
||||
if (s == PlayerState.completed) {
|
||||
_audioProgress = 0.0;
|
||||
_audioCurrentSeconds = 0;
|
||||
_isPlaying = false;
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_posSub?.cancel();
|
||||
_durSub?.cancel();
|
||||
_stateSub?.cancel();
|
||||
_player.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleAudioPlayback(String base64Data) async {
|
||||
try {
|
||||
if (_isPlaying) {
|
||||
await _player.pause();
|
||||
} else {
|
||||
final bytes = base64Decode(base64Data);
|
||||
await _player.play(BytesSource(bytes));
|
||||
}
|
||||
} catch (e) {
|
||||
print('[AUDIO PLAYBACK ERROR] $e');
|
||||
Get.snackbar(
|
||||
'Playback Error',
|
||||
'Could not play audio message: $e',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.redAccent.withOpacity(0.8),
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -292,7 +327,7 @@ class _InteractiveMediaWidgetState extends State<InteractiveMediaWidget> {
|
||||
color: AppTheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
onPressed: _toggleAudioPlayback,
|
||||
onPressed: () => _toggleAudioPlayback(base64Data),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||
#include <file_selector_linux/file_selector_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);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_linux
|
||||
file_selector_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import audioplayers_darwin
|
||||
import file_selector_macos
|
||||
import firebase_core
|
||||
import firebase_messaging
|
||||
import flutter_local_notifications
|
||||
@@ -12,6 +14,8 @@ import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
|
||||
@@ -25,6 +25,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
audioplayers:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audioplayers
|
||||
sha256: a72dd459d1a48f61a6fb9c0134dba26597c9236af40639ff0eb70eb4e0baab70
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.0"
|
||||
audioplayers_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_android
|
||||
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
audioplayers_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_darwin
|
||||
sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
audioplayers_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_linux
|
||||
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
audioplayers_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_platform_interface
|
||||
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.1"
|
||||
audioplayers_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_web
|
||||
sha256: faa8fa6587f996a6f604433b53af44c57a1407d4fe8dff5766cf63d6875e8de9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.0"
|
||||
audioplayers_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_windows
|
||||
sha256: bafff2b38b6f6d331887558ba6e0a01c9c208d9dbb3ad0005234db065122a734
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -89,6 +145,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -137,6 +201,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -246,6 +342,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.34"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -296,6 +400,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+17"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.1"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -384,6 +552,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -21,6 +21,8 @@ dependencies:
|
||||
firebase_messaging: ^14.9.1
|
||||
flutter_local_notifications: ^17.1.2
|
||||
flutter_contacts: ^1.1.7
|
||||
image_picker: ^1.0.7
|
||||
audioplayers: ^6.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -6,9 +6,15 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
AudioplayersWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_windows
|
||||
file_selector_windows
|
||||
firebase_core
|
||||
)
|
||||
|
||||
|
||||
@@ -409,6 +409,19 @@ async function handleMessage(ws, raw) {
|
||||
return respond({ type: 'error', message: 'Failed to download media file from WhatsApp servers after multiple attempts' });
|
||||
}
|
||||
|
||||
// If the media is an Ogg/Opus audio file, convert it to MP3 on-the-fly
|
||||
if (media.mimetype && (media.mimetype.includes('audio/ogg') || media.mimetype.includes('ogg'))) {
|
||||
try {
|
||||
console.log(`[WS] Converting OGG audio file for message ${messageId} to MP3 for iOS compatibility...`);
|
||||
const mp3Data = await convertOggToMp3(media.data);
|
||||
media.data = mp3Data;
|
||||
media.mimetype = 'audio/mp3';
|
||||
media.filename = 'voice_note.mp3';
|
||||
} catch (err) {
|
||||
console.error(`[WS] Ogg to MP3 conversion failed (sending raw Ogg instead):`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return respond({
|
||||
type: 'media',
|
||||
messageId: messageId,
|
||||
@@ -561,3 +574,37 @@ server.listen(PORT, () => {
|
||||
console.log(`[SERVER] Standalone WhatsApp Bridge running on port ${PORT}`);
|
||||
initWhatsApp();
|
||||
});
|
||||
|
||||
// ─── OGG to MP3 base64 converter using ffmpeg child process ────────────────
|
||||
function convertOggToMp3(base64Ogg) {
|
||||
const { exec } = require('child_process');
|
||||
const tmp = require('os').tmpdir();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeId = Date.now();
|
||||
const inputPath = path.join(tmp, `input_${timeId}.ogg`);
|
||||
const outputPath = path.join(tmp, `output_${timeId}.mp3`);
|
||||
|
||||
fs.writeFileSync(inputPath, Buffer.from(base64Ogg, 'base64'));
|
||||
|
||||
exec(`ffmpeg -i "${inputPath}" -acodec libmp3lame -aq 2 "${outputPath}"`, (error, stdout, stderr) => {
|
||||
// Clean up input file
|
||||
try { fs.unlinkSync(inputPath); } catch(_) {}
|
||||
|
||||
if (error) {
|
||||
console.error('[FFMPEG ERROR]', error);
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const mp3Base64 = fs.readFileSync(outputPath).toString('base64');
|
||||
try { fs.unlinkSync(outputPath); } catch(_) {}
|
||||
resolve(mp3Base64);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user