-
Notifications
You must be signed in to change notification settings - Fork 44
TW-2796: Implement autoplay for voice messages and enhance audio even #2797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nqhhdev
wants to merge
26
commits into
main
Choose a base branch
from
TW-2796-Implement-autoplay-for-voice-messages
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
fff3951
TW-2766: Displayed audio player if user goes to list of chats or to a…
nqhhdev 89795de
fixup! fixup! TW-2766: Displayed audio player if user goes to list of…
nqhhdev 9bd409c
TW-2796: Implement autoplay for voice messages and enhance audio even…
nqhhdev e8b6891
fixup! TW-2796: Implement autoplay for voice messages and enhance aud…
nqhhdev 2fa6daa
fixup! fixup! TW-2796: Implement autoplay for voice messages and enha…
nqhhdev f7a7530
fixup! fixup! fixup! TW-2796: Implement autoplay for voice messages a…
nqhhdev b0b98e5
fixup! fixup! fixup! fixup! TW-2796: Implement autoplay for voice mes…
nqhhdev e838060
fixup! fixup! fixup! fixup! fixup! TW-2796: Implement autoplay for vo…
nqhhdev 9832fe6
fixup! fixup! fixup! fixup! fixup! fixup! TW-2796: Implement autoplay…
nqhhdev 242258e
fixup! fixup! fixup! fixup! fixup! fixup! fixup! TW-2796: Implement a…
nqhhdev 4f02ae7
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! TW-2796: Impl…
nqhhdev ca05e5d
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! TW-279…
nqhhdev 3efdf7e
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev 58e4ce7
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev bbd89a8
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev 8d24e67
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev a6b07d6
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev 2338f7f
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev a18d44b
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev 5a38912
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev cb5a40f
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev b7a6e5b
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev 2b6e3d5
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev d4dd228
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev f67fcd5
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev ff145e6
fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup!…
nqhhdev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import 'package:fluffychat/resource/image_paths.dart'; | |
| import 'package:fluffychat/utils/localized_exception_extension.dart'; | ||
| import 'package:fluffychat/utils/string_extension.dart'; | ||
| import 'package:fluffychat/widgets/matrix.dart'; | ||
| import 'package:fluffychat/presentation/mixins/audio_mixin.dart'; | ||
| import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart'; | ||
| import 'package:flutter/foundation.dart'; | ||
| import 'package:flutter/material.dart'; | ||
|
|
@@ -16,10 +17,9 @@ import 'package:fluffychat/generated/l10n/app_localizations.dart'; | |
| import 'package:just_audio/just_audio.dart'; | ||
| import 'package:linagora_design_flutter/linagora_design_flutter.dart'; | ||
| import 'package:matrix/matrix.dart'; | ||
| import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart'; | ||
| import 'package:path_provider/path_provider.dart'; | ||
|
|
||
| class ChatAudioPlayerWidget extends StatelessWidget { | ||
| class ChatAudioPlayerWidget extends StatefulWidget { | ||
| final MatrixState? matrix; | ||
| final bool enableBorder; | ||
|
|
||
|
|
@@ -29,29 +29,45 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| super.key, | ||
| }); | ||
|
|
||
| static final _defaultAudioStatus = ValueNotifier<AudioPlayerStatus>( | ||
| AudioPlayerStatus.notDownloaded, | ||
| ); | ||
| static final _defaultEvent = ValueNotifier<Event?>(null); | ||
|
|
||
| @override | ||
| State<ChatAudioPlayerWidget> createState() => _ChatAudioPlayerWidgetState(); | ||
| } | ||
|
|
||
| class _ChatAudioPlayerWidgetState extends State<ChatAudioPlayerWidget> | ||
| with AudioMixin { | ||
| @override | ||
| Widget build(BuildContext context) { | ||
| final defaultAudioStatus = ValueNotifier<AudioPlayerStatus>( | ||
| AudioPlayerStatus.notDownloaded, | ||
| ); | ||
| final defaultEvent = ValueNotifier<Event?>(null); | ||
| // Return empty if matrix is not available | ||
| if (widget.matrix == null) { | ||
| return const SizedBox.shrink(); | ||
| } | ||
|
|
||
| return ValueListenableBuilder( | ||
| valueListenable: matrix?.currentAudioStatus ?? defaultAudioStatus, | ||
| valueListenable: widget.matrix?.currentAudioStatus ?? | ||
| ChatAudioPlayerWidget._defaultAudioStatus, | ||
| builder: (context, status, _) { | ||
| return ValueListenableBuilder( | ||
| valueListenable: matrix?.voiceMessageEvent ?? defaultEvent, | ||
| valueListenable: widget.matrix?.voiceMessageEvent ?? | ||
| ChatAudioPlayerWidget._defaultEvent, | ||
| builder: (context, hasEvent, _) { | ||
| if (hasEvent == null) { | ||
| return const SizedBox.shrink(); | ||
| } | ||
| final audioPlayer = matrix?.audioPlayer; | ||
| final audioPlayer = widget.matrix?.audioPlayer; | ||
| return StreamBuilder<Object>( | ||
| stream: StreamGroup.merge([ | ||
| matrix?.audioPlayer.positionStream.asBroadcastStream() ?? | ||
| widget.matrix?.audioPlayer?.positionStream | ||
| .asBroadcastStream() ?? | ||
| Stream.value(Duration.zero), | ||
| matrix?.audioPlayer.playerStateStream.asBroadcastStream() ?? | ||
| widget.matrix?.audioPlayer?.playerStateStream | ||
| .asBroadcastStream() ?? | ||
| Stream.value(Duration.zero), | ||
| matrix?.audioPlayer.speedStream.asBroadcastStream() ?? | ||
| widget.matrix?.audioPlayer?.speedStream.asBroadcastStream() ?? | ||
| Stream.value(Duration.zero), | ||
| ]), | ||
| builder: (context, snapshot) { | ||
|
|
@@ -67,7 +83,7 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| constraints: const BoxConstraints(maxHeight: 40), | ||
| decoration: BoxDecoration( | ||
| color: LinagoraSysColors.material().onPrimary, | ||
| border: enableBorder | ||
| border: widget.enableBorder | ||
| ? Border( | ||
| top: BorderSide( | ||
| color: LinagoraStateLayer( | ||
|
|
@@ -150,31 +166,22 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| } | ||
|
|
||
| Future<void> _handleCloseAudioPlayer() async { | ||
| matrix?.voiceMessageEvent.value = null; | ||
| matrix?.cancelAudioPlayerAutoDispose(); | ||
| await matrix?.audioPlayer.stop(); | ||
| await matrix?.audioPlayer.dispose(); | ||
| matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| } | ||
|
|
||
| Future<File> _handleOggAudioFileIniOS(File file) async { | ||
| Logs().v('Convert ogg audio file for iOS...'); | ||
| final convertedFile = File('${file.path}.caf'); | ||
| if (await convertedFile.exists() == false) { | ||
| OpusCaf().convertOpusToCaf(file.path, convertedFile.path); | ||
| } | ||
| return convertedFile; | ||
| widget.matrix?.voiceMessageEvent.value = null; | ||
| widget.matrix?.cancelAudioPlayerAutoDispose(); | ||
| await widget.matrix?.audioPlayer?.stop(); | ||
| await widget.matrix?.audioPlayer?.dispose(); | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| } | ||
|
|
||
| Future<void> _handlePlayAudioAgain(BuildContext context) async { | ||
| File? file; | ||
| MatrixFile? matrixFile; | ||
| await matrix?.audioPlayer.stop(); | ||
| await matrix?.audioPlayer.dispose(); | ||
| matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| final currentEvent = matrix?.voiceMessageEvent.value; | ||
| await widget.matrix?.audioPlayer?.stop(); | ||
| await widget.matrix?.audioPlayer?.dispose(); | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| final currentEvent = widget.matrix?.voiceMessageEvent.value; | ||
|
|
||
| matrix?.currentAudioStatus.value = AudioPlayerStatus.downloading; | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.downloading; | ||
|
|
||
| try { | ||
| matrixFile = await currentEvent?.downloadAndDecryptAttachment(); | ||
|
|
@@ -194,23 +201,31 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
|
|
||
| if (Platform.isIOS && | ||
| matrixFile?.mimeType.toLowerCase() == 'audio/ogg') { | ||
| file = await _handleOggAudioFileIniOS(file); | ||
| final converted = await handleOggAudioFileIniOS(file); | ||
| if (converted == null) { | ||
| throw Exception('OGG to CAF conversion failed'); | ||
| } | ||
| file = converted; | ||
| } | ||
| } | ||
|
|
||
| matrix?.currentAudioStatus.value = AudioPlayerStatus.downloaded; | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.downloaded; | ||
| } catch (e, s) { | ||
| Logs().v('Could not download audio file', e, s); | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text(e.toLocalizedString(context)), | ||
| ), | ||
| ); | ||
| rethrow; | ||
| Logs().e('Could not download audio file', e, s); | ||
| if (context.mounted) { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text(e.toLocalizedString(context)), | ||
| ), | ||
| ); | ||
| } | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| return; | ||
| } | ||
| if (!context.mounted) return; | ||
|
|
||
| if (matrix == null) { | ||
| if (widget.matrix == null) { | ||
| if (!context.mounted) return; | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text(L10n.of(context)!.couldNotPlayAudioFile), | ||
|
|
@@ -219,13 +234,13 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| return; | ||
| } | ||
|
|
||
| matrix!.audioPlayer = AudioPlayer(); | ||
| widget.matrix!.audioPlayer = AudioPlayer(); | ||
|
|
||
| if (file != null) { | ||
| await matrix?.audioPlayer.setFilePath(file.path); | ||
| await widget.matrix?.audioPlayer?.setFilePath(file.path); | ||
| } else if (matrixFile != null) { | ||
| await matrix?.audioPlayer | ||
| .setAudioSource(MatrixFileAudioSource(matrixFile)); | ||
| await widget.matrix?.audioPlayer | ||
| ?.setAudioSource(MatrixFileAudioSource(matrixFile)); | ||
| } else { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
|
|
@@ -236,10 +251,13 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| } | ||
|
|
||
| // Set up auto-dispose listener managed globally in MatrixState | ||
| matrix?.setupAudioPlayerAutoDispose(); | ||
| widget.matrix?.setupAudioPlayerAutoDispose(context: context); | ||
|
|
||
| matrix?.audioPlayer.play().onError((e, s) { | ||
| widget.matrix?.audioPlayer?.play().onError((e, s) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not use try/catch instead of onError? |
||
| Logs().e('Could not play audio file', e, s); | ||
| widget.matrix?.voiceMessageEvent.value = null; | ||
| widget.matrix?.currentAudioStatus.value = AudioPlayerStatus.notDownloaded; | ||
| if (!context.mounted) return; | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text( | ||
|
|
@@ -252,7 +270,7 @@ class ChatAudioPlayerWidget extends StatelessWidget { | |
| } | ||
|
|
||
| Future<void> _handlePlayOrPauseAudioPlayer(BuildContext context) async { | ||
| final audioPlayer = matrix?.audioPlayer; | ||
| final audioPlayer = widget.matrix?.audioPlayer; | ||
| if (audioPlayer == null) return; | ||
| if (audioPlayer.isAtEndPosition) { | ||
| await _handlePlayAudioAgain(context); | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can
AudioMixinbe used withStatelessWidget? Why doesChatAudioPlayerWidgetneed to beStatefulWidgetThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it's mixing in AudioMixin which has mutable fields. This violates Flutter's immutability requirement.