more gracefully handle failed audio messages

This commit is contained in:
Henry Hiles 2026-11-12 14:09:13 -05:00
commit 84dd903bc4
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs

View file

@ -1,6 +1,7 @@
import "dart:async"; import "dart:async";
import "package:audioplayers/audioplayers.dart" as audioplayers; import "package:audioplayers/audioplayers.dart" as audioplayers;
import "package:flutter/services.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
@ -20,6 +21,7 @@ class const AudioPlayer(
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = useMemoized(audioplayers.AudioPlayer.new); final player = useMemoized(audioplayers.AudioPlayer.new);
final loaded = useState(false); final loaded = useState(false);
final unsupported = useState(false);
useEffect(() { useEffect(() {
scheduleMicrotask(() async { scheduleMicrotask(() async {
@ -29,7 +31,16 @@ class const AudioPlayer(
if (!context.mounted) return; if (!context.mounted) return;
// Keeps the audio loaded after it finishes, so it can be replayed // Keeps the audio loaded after it finishes, so it can be replayed
await player.setReleaseMode(.stop); await player.setReleaseMode(.stop);
await player.setSource(audioplayers.DeviceFileSource(audio.path)); try {
// AVFoundation infers the format from the extension, which the cached file lacks
await player.setSource(
audioplayers.DeviceFileSource(audio.path, mimeType: info?.mimeType),
);
} on PlatformException catch (_) {
// The platform can't play this format, like Ogg on Apple platforms
if (context.mounted) unsupported.value = true;
return;
}
if (context.mounted) loaded.value = true; if (context.mounted) loaded.value = true;
}); });
@ -50,32 +61,52 @@ class const AudioPlayer(
).requireData; ).requireData;
final playing = state == .playing; final playing = state == .playing;
final downloadButton = IconButton(
onPressed: () => context.saveAttachment(
ref,
uri,
encrypted: encrypted,
filename: filename,
),
tooltip: "Download",
icon: Icon(Icons.download),
);
return Card( return Card(
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Padding( child: Padding(
padding: .symmetric(horizontal: 8), padding: .symmetric(horizontal: 8),
child: PlayerControls( child: unsupported.value
playing: playing, ? Row(
position: position, children: [
duration: duration, Padding(
onPlayPause: playing ? player.pause : player.resume, padding: .all(12),
onSeek: player.seek, child: Icon(
loading: !loaded.value, Icons.music_off,
size: info?.size, color: Theme.of(context).colorScheme.error,
actions: .new([ ),
SizedBox(width: 4), ),
IconButton( Expanded(
onPressed: () => context.saveAttachment( child: Text(
ref, "Can't play this audio format",
uri, style: Theme.of(context).textTheme.bodySmall?.copyWith(
encrypted: encrypted, color: Theme.of(context).colorScheme.error,
filename: filename, ),
),
),
downloadButton,
],
)
: PlayerControls(
playing: playing,
position: position,
duration: duration,
onPlayPause: playing ? player.pause : player.resume,
onSeek: player.seek,
loading: !loaded.value,
size: info?.size,
actions: .new([SizedBox(width: 4), downloadButton]),
), ),
tooltip: "Download",
icon: Icon(Icons.download),
),
]),
),
), ),
); );
} }