diff --git a/lib/widgets/players/audio.dart b/lib/widgets/players/audio.dart index 5d06d59..638564b 100644 --- a/lib/widgets/players/audio.dart +++ b/lib/widgets/players/audio.dart @@ -32,7 +32,7 @@ class const AudioPlayer( // Keeps the audio loaded after it finishes, so it can be replayed await player.setReleaseMode(.stop); 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( audioplayers.DeviceFileSource(audio.path, mimeType: info?.mimeType), ); diff --git a/lib/widgets/players/video.dart b/lib/widgets/players/video.dart index 0f09f88..053041a 100644 --- a/lib/widgets/players/video.dart +++ b/lib/widgets/players/video.dart @@ -5,7 +5,9 @@ import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/helpers/extensions/save_attachment.dart"; +import "package:mime/mime.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:nexus/widgets/players/player_controls.dart"; @@ -43,6 +45,8 @@ class const VideoPlayer( }, [controller]); 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. return Material( @@ -52,13 +56,55 @@ class const VideoPlayer( Positioned.fill( child: GestureDetector( behavior: .opaque, - onTap: isLoaded + onTap: ready ? playing ? controller.pause : controller.play : 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( top: 4, right: 4, @@ -77,50 +123,51 @@ class const VideoPlayer( icon: Icon(Icons.download), ), ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: DecoratedBox( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: .bottomCenter, - end: .topCenter, - colors: [Colors.black54, Colors.transparent], + if (error == null) + Positioned( + left: 0, + right: 0, + bottom: 0, + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: .bottomCenter, + end: .topCenter, + colors: [Colors.black54, Colors.transparent], + ), ), - ), - child: Padding( - padding: const .only(left: 4, right: 4, top: 16), - child: PlayerControls( - playing: playing, - buffering: value.value?.isBuffering ?? false, - position: value.value?.position ?? .zero, - duration: value.value?.duration ?? .zero, - onPlayPause: playing - ? controller.pause - : controller.play, - onSeek: controller.seekTo, - loading: !isLoaded, - size: info?.size, - color: Colors.white, - actions: .new([ - IconButton( - onPressed: controller.toggleFullScreen, - color: Colors.white, - tooltip: controller.isFullScreen - ? "Exit fullscreen" - : "Fullscreen", - icon: Icon( - controller.isFullScreen - ? Icons.fullscreen_exit - : Icons.fullscreen, + child: Padding( + padding: const .only(left: 4, right: 4, top: 16), + child: PlayerControls( + playing: playing, + buffering: value.value?.isBuffering ?? false, + position: value.value?.position ?? .zero, + duration: value.value?.duration ?? .zero, + onPlayPause: playing + ? controller.pause + : controller.play, + onSeek: controller.seekTo, + loading: !ready, + size: info?.size, + color: Colors.white, + actions: .new([ + IconButton( + onPressed: controller.toggleFullScreen, + color: Colors.white, + tooltip: controller.isFullScreen + ? "Exit fullscreen" + : "Fullscreen", + icon: Icon( + controller.isFullScreen + ? Icons.fullscreen_exit + : Icons.fullscreen, + ), ), - ), - ]), + ]), + ), ), ), ), - ), ], ), ); @@ -137,7 +184,15 @@ class const VideoPlayer( .read(ClientController.provider.notifier) .downloadMedia(.new(mxc: uri, encrypted: encrypted)); 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 // soon as it loads a file, despite autoPlay being off await controller.pause(); diff --git a/linux/nix/pkg/default.nix b/linux/nix/pkg/default.nix index 8a74f71..b6f9c2a 100644 --- a/linux/nix/pkg/default.nix +++ b/linux/nix/pkg/default.nix @@ -55,6 +55,9 @@ flutter.buildFlutterApplication { customSourceBuilders.avif_native = callPackage ./avif_native.nix { }; 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="; location = "sha256-zWnffXiK1LD4Ln23coPozBjYfTlhZmBhC5Z8mCdAgwQ="; fab_m3e = "sha256-5V9m6yY0jimx2gh7lQHYg4HDKQ4Wrl0ATmBhw5DkQMI="; diff --git a/pubspec.lock b/pubspec.lock index 9785051..e49b530 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -164,43 +164,46 @@ packages: better_player: dependency: "direct main" description: - name: better_player - sha256: bde9cc4c35f8ccca5e75465934b51ac11db5cbfca7b221f2122d546bb5c5b324 - url: "https://pub.dev" - source: hosted - version: "1.36.0" + path: "packages/better_player" + ref: "quad/feat/mimetypes" + resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687" + url: "https://github.com/Henry-Hiles/betterplayer" + source: git + version: "1.37.0" better_player_android: dependency: transitive description: name: better_player_android - sha256: "2a1eb33ff1f67155f8840224836a6c90e95b335263986bcce78e89bf837be04e" + sha256: "24d3ce6c357186e0ec1eed63519e47a552bdc497ad48556f32593f5d6b406672" url: "https://pub.dev" source: hosted - version: "1.13.0" + version: "1.13.1" better_player_ios: - dependency: transitive + dependency: "direct overridden" description: - name: better_player_ios - sha256: d26bc4b7d6d8c8047772f498fcdfe7cd896aecf156842d0fca9072d13eb4864b - url: "https://pub.dev" - source: hosted - version: "1.13.0" + path: "packages/better_player_ios" + ref: "quad/feat/mimetypes" + resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687" + url: "https://github.com/Henry-Hiles/betterplayer" + source: git + version: "1.14.0" better_player_linux: dependency: transitive description: name: better_player_linux - sha256: "0e73f56852f7bb16b35b059f1ee40cc1b9cb84c9883dccd1e7766fcd26b3f40f" + sha256: "97d9eb16a587df4d714c1906ef2c49b825c1483e7d4c9050d63e696e26998f6e" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" better_player_macos: - dependency: transitive + dependency: "direct overridden" description: - name: better_player_macos - sha256: "9e5154bc33c1b1af22da851829b658eab7d318a33c9037c4da98bc37091dc7b7" - url: "https://pub.dev" - source: hosted - version: "1.8.0" + path: "packages/better_player_macos" + ref: "quad/feat/mimetypes" + resolved-ref: "0f9d7f391973cf1e2096fd7f9a7c9d3c0586a687" + url: "https://github.com/Henry-Hiles/betterplayer" + source: git + version: "1.9.0" better_player_platform_interface: dependency: transitive description: @@ -213,18 +216,18 @@ packages: dependency: transitive description: name: better_player_web - sha256: "2faf05dc31ec9082dbd5f90cee7777887c282c2af17bb54efab4ebceae9c25f4" + sha256: d7b365221f7724f22d3fef7840e45af2493a784e2429a30e980f807bcb7c9539 url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.6.1" better_player_windows: dependency: transitive description: name: better_player_windows - sha256: "4bfdce8c0f9c55c362b2f931edfb2cd7cebdac757217ad09d4804ea21ec95f35" + sha256: eddc817dce6af08e74de456ad711ffd987a641c45f73998cbe9ccbf6ab5ebf22 url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index be53944..fb26036 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,21 @@ dependency_overrides: git: url: https://github.com/appelladev/linkify 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: flutter: @@ -119,7 +134,7 @@ dependencies: url: https://github.com/Henry-Hiles/flutterlocation # Adds an option to leave out proprietary Google Play services, unreleased ref: feat/optional-play-services path: packages/location - better_player: 1.36.0 + better_player: 1.37.0 audioplayers: 6.8.1 dev_dependencies: flutter_test: