From 3286b1938732b76d0825ae3e7841474ad37e14c7 Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 5 Aug 2024 13:21:12 +0200 Subject: [PATCH 1/2] refactor: Recording dialog --- lib/pages/chat/chat.dart | 6 ++--- lib/pages/chat/recording_dialog.dart | 38 +++++++++++++--------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 129bcb4ef..6f4a83e9d 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -621,10 +621,10 @@ class ChatController extends State builder: (c) => const RecordingDialog(), ); if (result == null) return; - final audioFile = File(result.path); + final audioFile = XFile(result.path); final file = MatrixAudioFile( - bytes: audioFile.readAsBytesSync(), - name: audioFile.path, + bytes: await audioFile.readAsBytes(), + name: result.fileName ?? audioFile.path, ); await room.sendFileEvent( file, diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index be22ff308..37d3703d3 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:path/path.dart' as path_lib; import 'package:path_provider/path_provider.dart'; import 'package:record/record.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -26,10 +28,12 @@ class RecordingDialogState extends State { Duration _duration = Duration.zero; bool error = false; - String? _recordedPath; + final _audioRecorder = AudioRecorder(); final List amplitudeTimeline = []; + String? fileName; + static const int bitRate = 64000; static const int samplingRate = 44100; @@ -37,9 +41,13 @@ class RecordingDialogState extends State { try { final useOpus = await _audioRecorder.isEncoderSupported(AudioEncoder.opus); - final tempDir = await getTemporaryDirectory(); - final path = _recordedPath = - '${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + fileName = + 'recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + String? path; + if (!kIsWeb) { + final tempDir = await getTemporaryDirectory(); + path = path_lib.join(tempDir.path, fileName); + } final result = await _audioRecorder.hasPermission(); if (result != true) { @@ -58,7 +66,7 @@ class RecordingDialogState extends State { noiseSuppress: true, encoder: useOpus ? AudioEncoder.opus : AudioEncoder.aacLc, ), - path: path, + path: path ?? '', ); setState(() => _duration = Duration.zero); _recorderSubscription?.cancel(); @@ -94,8 +102,8 @@ class RecordingDialogState extends State { void _stopAndSend() async { _recorderSubscription?.cancel(); - await _audioRecorder.stop(); - final path = _recordedPath; + final path = await _audioRecorder.stop(); + if (path == null) throw ('Recording failed!'); const waveCount = AudioPlayerWidget.wavesCount; final step = amplitudeTimeline.length < waveCount @@ -110,6 +118,7 @@ class RecordingDialogState extends State { path: path, duration: _duration.inMilliseconds, waveform: waveform, + fileName: fileName, ), ); } @@ -220,23 +229,12 @@ class RecordingResult { final String path; final int duration; final List waveform; + final String? fileName; const RecordingResult({ required this.path, required this.duration, required this.waveform, + required this.fileName, }); - - factory RecordingResult.fromJson(Map json) => - RecordingResult( - path: json['path'], - duration: json['duration'], - waveform: List.from(json['waveform']), - ); - - Map toJson() => { - 'path': path, - 'duration': duration, - 'waveform': waveform, - }; } From 47481eb6769134b405c649e238d5db7472d7cf09 Mon Sep 17 00:00:00 2001 From: Krille Date: Tue, 6 Aug 2024 11:55:01 +0200 Subject: [PATCH 2/2] feat: Send voice messages from web --- lib/pages/chat/recording_dialog.dart | 34 ++++++++++++++++++++++++---- lib/utils/platform_infos.dart | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/recording_dialog.dart b/lib/pages/chat/recording_dialog.dart index 37d3703d3..6d8718854 100644 --- a/lib/pages/chat/recording_dialog.dart +++ b/lib/pages/chat/recording_dialog.dart @@ -39,10 +39,16 @@ class RecordingDialogState extends State { Future startRecording() async { try { - final useOpus = - await _audioRecorder.isEncoderSupported(AudioEncoder.opus); + final codec = kIsWeb + // Web seems to create webm instead of ogg when using opus encoder + // which does not play on iOS right now. So we use wav for now: + ? AudioEncoder.wav + // Everywhere else we use opus if supported by the platform: + : await _audioRecorder.isEncoderSupported(AudioEncoder.opus) + ? AudioEncoder.opus + : AudioEncoder.aacLc; fileName = - 'recording${DateTime.now().microsecondsSinceEpoch}.${useOpus ? 'ogg' : 'm4a'}'; + 'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}'; String? path; if (!kIsWeb) { final tempDir = await getTemporaryDirectory(); @@ -64,7 +70,7 @@ class RecordingDialogState extends State { autoGain: true, echoCancel: true, noiseSuppress: true, - encoder: useOpus ? AudioEncoder.opus : AudioEncoder.aacLc, + encoder: codec, ), path: path ?? '', ); @@ -238,3 +244,23 @@ class RecordingResult { required this.fileName, }); } + +extension on AudioEncoder { + String get fileExtension { + switch (this) { + case AudioEncoder.aacLc: + case AudioEncoder.aacEld: + case AudioEncoder.aacHe: + return 'm4a'; + case AudioEncoder.opus: + return 'ogg'; + case AudioEncoder.wav: + return 'wav'; + case AudioEncoder.amrNb: + case AudioEncoder.amrWb: + case AudioEncoder.flac: + case AudioEncoder.pcm16bits: + throw UnsupportedError('Not yet used'); + } + } +} diff --git a/lib/utils/platform_infos.dart b/lib/utils/platform_infos.dart index b96104df4..638c5bcc2 100644 --- a/lib/utils/platform_infos.dart +++ b/lib/utils/platform_infos.dart @@ -29,7 +29,7 @@ abstract class PlatformInfos { static bool get usesTouchscreen => !isMobile; - static bool get platformCanRecord => (isMobile || isMacOS); + static bool get platformCanRecord => (isMobile || isMacOS || isWeb); static String get clientName => '${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}';