fixup video playback on apple platforms

This commit is contained in:
Henry Hiles 2026-11-12 14:42:50 -05:00
commit 0e29690134
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
5 changed files with 144 additions and 68 deletions

View file

@ -32,7 +32,7 @@ class const AudioPlayer(
// 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);
try { try {
// AVFoundation infers the format from the extension, which the cached file lacks // AVFoundation needs the format, since gomuks caches files without an extension
await player.setSource( await player.setSource(
audioplayers.DeviceFileSource(audio.path, mimeType: info?.mimeType), audioplayers.DeviceFileSource(audio.path, mimeType: info?.mimeType),
); );

View file

@ -5,7 +5,9 @@ import "package:material_ui/material_ui.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client.dart";
import "package:nexus/helpers/extensions/save_attachment.dart"; import "package:nexus/helpers/extensions/save_attachment.dart";
import "package:mime/mime.dart";
import "package:nexus/models/info/video.dart"; import "package:nexus/models/info/video.dart";
import "package:nexus/widgets/common/error_dialog.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:nexus/widgets/players/player_controls.dart"; import "package:nexus/widgets/players/player_controls.dart";
@ -43,6 +45,8 @@ class const VideoPlayer(
}, [controller]); }, [controller]);
final playing = value.value?.isPlaying ?? false; final playing = value.value?.isPlaying ?? false;
final ready = isLoaded && (value.value?.initialized ?? false);
final error = value.value?.errorDescription;
// better_player doesn't use material_ui, so this Material is needed. // better_player doesn't use material_ui, so this Material is needed.
return Material( return Material(
@ -52,13 +56,55 @@ class const VideoPlayer(
Positioned.fill( Positioned.fill(
child: GestureDetector( child: GestureDetector(
behavior: .opaque, behavior: .opaque,
onTap: isLoaded onTap: ready
? playing ? playing
? controller.pause ? controller.pause
: controller.play : controller.play
: null, : null,
), ),
), ),
if (error != null)
Positioned.fill(
child: ColoredBox(
color: Colors.black54,
child: Center(
child: Column(
mainAxisSize: .min,
children: [
Padding(
padding: .all(12),
child: Icon(
Icons.videocam_off,
color: Theme.of(context)
.colorScheme
.error,
),
),
Flexible(
child: Text(
"Can't play this video",
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: Theme.of(context)
.colorScheme
.error,
),
),
),
TextButton(
onPressed: () => showDialog(
context: context,
builder: (_) => ErrorDialog(error, null),
),
child: Text("Show Error"),
),
],
),
),
),
),
Positioned( Positioned(
top: 4, top: 4,
right: 4, right: 4,
@ -77,50 +123,51 @@ class const VideoPlayer(
icon: Icon(Icons.download), icon: Icon(Icons.download),
), ),
), ),
Positioned( if (error == null)
left: 0, Positioned(
right: 0, left: 0,
bottom: 0, right: 0,
child: DecoratedBox( bottom: 0,
decoration: const BoxDecoration( child: DecoratedBox(
gradient: LinearGradient( decoration: const BoxDecoration(
begin: .bottomCenter, gradient: LinearGradient(
end: .topCenter, begin: .bottomCenter,
colors: [Colors.black54, Colors.transparent], end: .topCenter,
colors: [Colors.black54, Colors.transparent],
),
), ),
), child: Padding(
child: Padding( padding: const .only(left: 4, right: 4, top: 16),
padding: const .only(left: 4, right: 4, top: 16), child: PlayerControls(
child: PlayerControls( playing: playing,
playing: playing, buffering: value.value?.isBuffering ?? false,
buffering: value.value?.isBuffering ?? false, position: value.value?.position ?? .zero,
position: value.value?.position ?? .zero, duration: value.value?.duration ?? .zero,
duration: value.value?.duration ?? .zero, onPlayPause: playing
onPlayPause: playing ? controller.pause
? controller.pause : controller.play,
: controller.play, onSeek: controller.seekTo,
onSeek: controller.seekTo, loading: !ready,
loading: !isLoaded, size: info?.size,
size: info?.size, color: Colors.white,
color: Colors.white, actions: .new([
actions: .new([ IconButton(
IconButton( onPressed: controller.toggleFullScreen,
onPressed: controller.toggleFullScreen, color: Colors.white,
color: Colors.white, tooltip: controller.isFullScreen
tooltip: controller.isFullScreen ? "Exit fullscreen"
? "Exit fullscreen" : "Fullscreen",
: "Fullscreen", icon: Icon(
icon: Icon( controller.isFullScreen
controller.isFullScreen ? Icons.fullscreen_exit
? Icons.fullscreen_exit : Icons.fullscreen,
: Icons.fullscreen, ),
), ),
), ]),
]), ),
), ),
), ),
), ),
),
], ],
), ),
); );
@ -137,7 +184,15 @@ class const VideoPlayer(
.read(ClientController.provider.notifier) .read(ClientController.provider.notifier)
.downloadMedia(.new(mxc: uri, encrypted: encrypted)); .downloadMedia(.new(mxc: uri, encrypted: encrypted));
if (!context.mounted) return; if (!context.mounted) return;
await controller.setupDataSource(.file(video.path)); await controller.setupDataSource(
.file(
video.path,
videoExtension: switch (info?.mimeType) {
final mimeType? => extensionFromMime(mimeType),
null => null,
},
),
);
// better_player's mpv backend (Linux and Windows) starts playing as // better_player's mpv backend (Linux and Windows) starts playing as
// soon as it loads a file, despite autoPlay being off // soon as it loads a file, despite autoPlay being off
await controller.pause(); await controller.pause();

View file

@ -55,6 +55,9 @@ flutter.buildFlutterApplication {
customSourceBuilders.avif_native = callPackage ./avif_native.nix { }; customSourceBuilders.avif_native = callPackage ./avif_native.nix { };
gitHashes = { gitHashes = {
better_player = "sha256-7OoRdtbswGcP97Vn+Jkj3lBU8h2slJ/KPz98G3XOyNQ=";
better_player_ios = "sha256-7OoRdtbswGcP97Vn+Jkj3lBU8h2slJ/KPz98G3XOyNQ=";
better_player_macos = "sha256-7OoRdtbswGcP97Vn+Jkj3lBU8h2slJ/KPz98G3XOyNQ=";
linkify = "sha256-TpMD6+0zyY6i9l+6d8ErnVufmepCv362rCtnbOht/z4="; linkify = "sha256-TpMD6+0zyY6i9l+6d8ErnVufmepCv362rCtnbOht/z4=";
location = "sha256-zWnffXiK1LD4Ln23coPozBjYfTlhZmBhC5Z8mCdAgwQ="; location = "sha256-zWnffXiK1LD4Ln23coPozBjYfTlhZmBhC5Z8mCdAgwQ=";
fab_m3e = "sha256-5V9m6yY0jimx2gh7lQHYg4HDKQ4Wrl0ATmBhw5DkQMI="; fab_m3e = "sha256-5V9m6yY0jimx2gh7lQHYg4HDKQ4Wrl0ATmBhw5DkQMI=";

View file

@ -164,43 +164,46 @@ packages:
better_player: better_player:
dependency: "direct main" dependency: "direct main"
description: description:
name: better_player path: "packages/better_player"
sha256: bde9cc4c35f8ccca5e75465934b51ac11db5cbfca7b221f2122d546bb5c5b324 ref: "quad/feat/mimetypes"
url: "https://pub.dev" resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687"
source: hosted url: "https://github.com/Henry-Hiles/betterplayer"
version: "1.36.0" source: git
version: "1.37.0"
better_player_android: better_player_android:
dependency: transitive dependency: transitive
description: description:
name: better_player_android name: better_player_android
sha256: "2a1eb33ff1f67155f8840224836a6c90e95b335263986bcce78e89bf837be04e" sha256: "24d3ce6c357186e0ec1eed63519e47a552bdc497ad48556f32593f5d6b406672"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.13.0" version: "1.13.1"
better_player_ios: better_player_ios:
dependency: transitive dependency: "direct overridden"
description: description:
name: better_player_ios path: "packages/better_player_ios"
sha256: d26bc4b7d6d8c8047772f498fcdfe7cd896aecf156842d0fca9072d13eb4864b ref: "quad/feat/mimetypes"
url: "https://pub.dev" resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687"
source: hosted url: "https://github.com/Henry-Hiles/betterplayer"
version: "1.13.0" source: git
version: "1.14.0"
better_player_linux: better_player_linux:
dependency: transitive dependency: transitive
description: description:
name: better_player_linux name: better_player_linux
sha256: "0e73f56852f7bb16b35b059f1ee40cc1b9cb84c9883dccd1e7766fcd26b3f40f" sha256: "97d9eb16a587df4d714c1906ef2c49b825c1483e7d4c9050d63e696e26998f6e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
better_player_macos: better_player_macos:
dependency: transitive dependency: "direct overridden"
description: description:
name: better_player_macos path: "packages/better_player_macos"
sha256: "9e5154bc33c1b1af22da851829b658eab7d318a33c9037c4da98bc37091dc7b7" ref: "quad/feat/mimetypes"
url: "https://pub.dev" resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687"
source: hosted url: "https://github.com/Henry-Hiles/betterplayer"
version: "1.8.0" source: git
version: "1.9.0"
better_player_platform_interface: better_player_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -213,18 +216,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: better_player_web name: better_player_web
sha256: "2faf05dc31ec9082dbd5f90cee7777887c282c2af17bb54efab4ebceae9c25f4" sha256: d7b365221f7724f22d3fef7840e45af2493a784e2429a30e980f807bcb7c9539
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.0" version: "1.6.1"
better_player_windows: better_player_windows:
dependency: transitive dependency: transitive
description: description:
name: better_player_windows name: better_player_windows
sha256: "4bfdce8c0f9c55c362b2f931edfb2cd7cebdac757217ad09d4804ea21ec95f35" sha256: eddc817dce6af08e74de456ad711ffd987a641c45f73998cbe9ccbf6ab5ebf22
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.0" version: "1.5.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:

View file

@ -47,6 +47,21 @@ dependency_overrides:
git: git:
url: https://github.com/appelladev/linkify url: https://github.com/appelladev/linkify
ref: fix/consecutive-periods-loose-url ref: fix/consecutive-periods-loose-url
better_player:
git:
url: https://github.com/Henry-Hiles/betterplayer
ref: quad/feat/mimetypes
path: packages/better_player
better_player_ios:
git:
url: https://github.com/Henry-Hiles/betterplayer
ref: quad/feat/mimetypes
path: packages/better_player_ios
better_player_macos:
git:
url: https://github.com/Henry-Hiles/betterplayer
ref: quad/feat/mimetypes
path: packages/better_player_macos
dependencies: dependencies:
flutter: flutter:
@ -119,7 +134,7 @@ dependencies:
url: https://github.com/Henry-Hiles/flutterlocation # Adds an option to leave out proprietary Google Play services, unreleased url: https://github.com/Henry-Hiles/flutterlocation # Adds an option to leave out proprietary Google Play services, unreleased
ref: feat/optional-play-services ref: feat/optional-play-services
path: packages/location path: packages/location
better_player: 1.36.0 better_player: 1.37.0
audioplayers: 6.8.1 audioplayers: 6.8.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: