diff --git a/lib/views/widgets/audio_player.dart b/lib/views/widgets/audio_player.dart index 204f2284..039095d1 100644 --- a/lib/views/widgets/audio_player.dart +++ b/lib/views/widgets/audio_player.dart @@ -1,26 +1,28 @@ import 'dart:async'; -import 'dart:typed_data'; +import 'dart:io'; import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:audioplayers/audioplayers.dart'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:fluffychat/views/widgets/message_download_content.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sound_lite/flutter_sound.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../utils/ui_fake.dart' if (dart.library.html) 'dart:ui' as ui; import 'matrix.dart'; import '../../utils/event_extension.dart'; -class AudioPlayer extends StatefulWidget { +class AudioPlayerWidget extends StatefulWidget { final Color color; final Event event; static String currentId; - const AudioPlayer(this.event, {this.color = Colors.black, Key key}) + const AudioPlayerWidget(this.event, {this.color = Colors.black, Key key}) : super(key: key); @override @@ -29,18 +31,21 @@ class AudioPlayer extends StatefulWidget { enum AudioPlayerStatus { notDownloaded, downloading, downloaded } -class _AudioPlayerState extends State { +class _AudioPlayerState extends State { AudioPlayerStatus status = AudioPlayerStatus.notDownloaded; + final AudioPlayer audioPlayer = AudioPlayer(); - final FlutterSoundPlayer flutterSound = FlutterSoundPlayer(); - - StreamSubscription soundSubscription; - Uint8List audioFile; + StreamSubscription onAudioPositionChanged; + StreamSubscription onDurationChanged; + StreamSubscription onPlayerStateChanged; + StreamSubscription onPlayerError; String statusText = '00:00'; double currentPosition = 0; double maxPosition = 0; + File audioFile; + String webSrcUrl; @override @@ -59,13 +64,14 @@ class _AudioPlayerState extends State { @override void dispose() { - if (flutterSound.isPlaying) { - flutterSound.stopPlayer(); + if (audioPlayer.state == AudioPlayerState.PLAYING) { + audioPlayer.stop(); } - if (flutterSound.isOpen()) { - flutterSound.closeAudioSession(); - } - soundSubscription?.cancel(); + onAudioPositionChanged?.cancel(); + onDurationChanged?.cancel(); + onPlayerStateChanged?.cancel(); + onPlayerError?.cancel(); + super.dispose(); } @@ -75,8 +81,15 @@ class _AudioPlayerState extends State { try { final matrixFile = await widget.event.downloadAndDecryptAttachmentCached(); + final tempDir = await getTemporaryDirectory(); + final fileName = matrixFile.name.contains('.') + ? matrixFile.name + : '${matrixFile.name}.mp3'; + final file = File('${tempDir.path}/$fileName'); + await file.writeAsBytes(matrixFile.bytes); + setState(() { - audioFile = matrixFile.bytes; + audioFile = file; status = AudioPlayerStatus.downloaded; }); _playAction(); @@ -91,50 +104,42 @@ class _AudioPlayerState extends State { } void _playAction() async { - if (AudioPlayer.currentId != widget.event.eventId) { - if (AudioPlayer.currentId != null) { - if (!flutterSound.isStopped) { - await flutterSound.stopPlayer(); + if (AudioPlayerWidget.currentId != widget.event.eventId) { + if (AudioPlayerWidget.currentId != null) { + if (audioPlayer.state != AudioPlayerState.STOPPED) { + await audioPlayer.stop(); setState(() => null); } } - AudioPlayer.currentId = widget.event.eventId; + AudioPlayerWidget.currentId = widget.event.eventId; } - switch (flutterSound.playerState) { - case PlayerState.isPlaying: - await flutterSound.pausePlayer(); + switch (audioPlayer.state) { + case AudioPlayerState.PLAYING: + await audioPlayer.pause(); break; - case PlayerState.isPaused: - await flutterSound.resumePlayer(); + case AudioPlayerState.PAUSED: + await audioPlayer.resume(); break; - case PlayerState.isStopped: + case AudioPlayerState.STOPPED: default: - if (!flutterSound.isOpen()) { - await flutterSound.openAudioSession( - focus: AudioFocus.requestFocusAndStopOthers, - category: SessionCategory.playback); - } - - await flutterSound.setSubscriptionDuration(Duration(milliseconds: 100)); - await flutterSound.startPlayer(fromDataBuffer: audioFile); - soundSubscription ??= flutterSound.onProgress.listen((e) { - if (AudioPlayer.currentId != widget.event.eventId) { - soundSubscription?.cancel()?.then((f) => soundSubscription = null); - setState(() { - currentPosition = 0; - statusText = '00:00'; - }); - AudioPlayer.currentId = null; - } else if (e != null) { - final txt = - '${e.position.inMinutes.toString().padLeft(2, '0')}:${(e.position.inSeconds % 60).toString().padLeft(2, '0')}'; - setState(() { - maxPosition = e.duration.inMilliseconds.toDouble(); - currentPosition = e.position.inMilliseconds.toDouble(); - statusText = txt; - }); - } + onAudioPositionChanged ??= + audioPlayer.onAudioPositionChanged.listen((state) { + setState(() => currentPosition = state.inMilliseconds.toDouble()); }); + onDurationChanged ??= audioPlayer.onDurationChanged.listen((max) => + setState(() => maxPosition = max.inMilliseconds.toDouble())); + onPlayerStateChanged ??= audioPlayer.onPlayerStateChanged + .listen((_) => setState(() => null)); + onPlayerError ??= audioPlayer.onPlayerError.listen((e) { + AdaptivePageLayout.of(context).showSnackBar( + SnackBar( + content: Text(L10n.of(context).oopsSomethingWentWrong), + ), + ); + SentryController.captureException(e, StackTrace.current); + }); + + await audioPlayer.play(audioFile.path); break; } } @@ -163,12 +168,12 @@ class _AudioPlayerState extends State { ? CircularProgressIndicator(strokeWidth: 2) : IconButton( icon: Icon( - flutterSound.isPlaying + audioPlayer.state == AudioPlayerState.PLAYING ? Icons.pause_outlined : Icons.play_arrow_outlined, color: widget.color, ), - tooltip: flutterSound.isPlaying + tooltip: audioPlayer.state == AudioPlayerState.PLAYING ? L10n.of(context).audioPlayerPause : L10n.of(context).audioPlayerPlay, onPressed: () { @@ -183,8 +188,8 @@ class _AudioPlayerState extends State { Expanded( child: Slider( value: currentPosition, - onChanged: (double position) => flutterSound - .seekToPlayer(Duration(milliseconds: position.toInt())), + onChanged: (double position) => + audioPlayer.seek(Duration(milliseconds: position.toInt())), max: status == AudioPlayerStatus.downloaded ? maxPosition : 0, min: 0, ), diff --git a/lib/views/widgets/message_content.dart b/lib/views/widgets/message_content.dart index ab57aa41..cd680eb5 100644 --- a/lib/views/widgets/message_content.dart +++ b/lib/views/widgets/message_content.dart @@ -83,7 +83,7 @@ class MessageContent extends StatelessWidget { } return MessageDownloadContent(event, textColor); case MessageTypes.Audio: - return AudioPlayer( + return AudioPlayerWidget( event, color: textColor, ); diff --git a/pubspec.lock b/pubspec.lock index 0e74f4f8..4a6a61df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -71,6 +71,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.5.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.3" base58check: dependency: transitive description: @@ -407,34 +414,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.0" - flutter_sound_lite: - dependency: "direct main" - description: - name: flutter_sound_lite - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.1" - flutter_sound_platform_interface: - dependency: transitive - description: - name: flutter_sound_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.1" - flutter_sound_web: - dependency: transitive - description: - name: flutter_sound_web - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.1" - flutter_spinkit: - dependency: transitive - description: - name: flutter_spinkit - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.0" flutter_svg: dependency: "direct main" description: @@ -867,13 +846,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - recase: - dependency: transitive - description: - name: recase - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" receive_sharing_intent: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 03ff22b8..7f1e2e03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: adaptive_page_layout: ^0.2.3 adaptive_theme: ^2.2.0 android_path_provider: ^0.2.1 + audioplayers: ^0.18.3 cached_network_image: ^3.0.0 cupertino_icons: any # desktop_notifications: ^0.0.0-dev.4 // Currently blocked by: https://github.com/canonical/desktop_notifications.dart/issues/5 @@ -39,7 +40,6 @@ dependencies: flutter_screen_lock: ^4.0.3 flutter_secure_storage: ^4.1.0 flutter_slidable: ^0.6.0 - flutter_sound_lite: ^8.1.1 flutter_svg: ^0.21.0+1 flutter_typeahead: ^3.1.1 future_loading_dialog: ^0.1.2