From 8dc83a5d5a76b4b0504e1db760f2184a3d4fbe0a Mon Sep 17 00:00:00 2001 From: Rico Berger Date: Fri, 27 Oct 2023 15:22:27 +0200 Subject: [PATCH] [mastodon] Add Support for Videos (#51) It is now possible to play videos from toots within FeedDeck. For that we are using the "madia_kit" package, which is already used for the Podcast player on Windows and Linux. The videos from a toot are saved within the "options.videos" field of an item next to the "options.media" field. In the "ItemDetailsMastodon" widget we are then checking if this field is present and contains a list of video urls. These urls can then be played via the "ItemVideos" widget. --- app/ios/Podfile.lock | 32 +++- app/lib/main.dart | 21 ++- .../item/details/item_details_mastodon.dart | 14 +- .../item/details/utils/item_videos.dart | 93 ++++++++++++ .../item/preview/item_preview_mastodon.dart | 4 +- .../flutter/generated_plugin_registrant.cc | 4 + app/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 8 + app/macos/Podfile.lock | 26 +++- app/pubspec.lock | 137 +++++++++++++++++- app/pubspec.yaml | 11 +- .../flutter/generated_plugin_registrant.cc | 12 +- app/windows/flutter/generated_plugins.cmake | 4 +- supabase/functions/_shared/feed/mastodon.ts | 41 +++++- 14 files changed, 381 insertions(+), 27 deletions(-) create mode 100644 app/lib/widgets/item/details/utils/item_videos.dart diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index ab860e8..f9ced21 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -13,8 +13,12 @@ PODS: - FMDB/standard (2.7.5) - just_audio (0.0.1): - Flutter + - media_kit_libs_ios_video (1.0.4): + - Flutter - media_kit_native_event_loop (1.0.0): - Flutter + - media_kit_video (0.0.1): + - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -26,6 +30,8 @@ PODS: - PurchasesHybridCommon (7.0.0): - RevenueCat (= 4.27.0) - RevenueCat (4.27.0) + - screen_brightness_ios (0.1.0): + - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -36,6 +42,10 @@ PODS: - FMDB (>= 2.7.5) - url_launcher_ios (0.0.1): - Flutter + - volume_controller (0.0.1): + - Flutter + - wakelock_plus (0.0.1): + - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter @@ -46,14 +56,19 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) + - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) + - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`) + - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - volume_controller (from `.symlinks/plugins/volume_controller/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: @@ -75,14 +90,20 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_native_splash/ios" just_audio: :path: ".symlinks/plugins/just_audio/ios" + media_kit_libs_ios_video: + :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" media_kit_native_event_loop: :path: ".symlinks/plugins/media_kit_native_event_loop/ios" + media_kit_video: + :path: ".symlinks/plugins/media_kit_video/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" purchases_flutter: :path: ".symlinks/plugins/purchases_flutter/ios" + screen_brightness_ios: + :path: ".symlinks/plugins/screen_brightness_ios/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sign_in_with_apple: @@ -91,6 +112,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + volume_controller: + :path: ".symlinks/plugins/volume_controller/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" @@ -102,16 +127,21 @@ SPEC CHECKSUMS: flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - media_kit_native_event_loop: f1ee9f941ec0af371b245969a3e010901c375480 + media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 + media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a + media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 purchases_flutter: 549ccfbbaf5e7cd195043c714b69a35e278c00f1 PurchasesHybridCommon: af3b2413f9cb999bc1fdca44770bdaf39dfb89fa RevenueCat: 84fbe2eb9bbf63e1abf346ccd3ff9ee45d633e3b + screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 + wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a PODFILE CHECKSUM: ec83c31511fbc978a9918c6fda235238118483f5 diff --git a/app/lib/main.dart b/app/lib/main.dart index 24daedd..efb2e69 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:just_audio_background/just_audio_background.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -45,13 +46,23 @@ void main() async { }); } + /// Initialize the [media_kit] packages, so that we can play audio and video + /// files. + MediaKit.ensureInitialized(); + /// Initialize the [just_audio_background] package, so that we can play audio /// files in the background. - await JustAudioBackground.init( - androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', - androidNotificationChannelName: 'Audio playback', - androidNotificationOngoing: true, - ); + /// + /// We can not initialize the [just_audio_background] package on Windows and + /// Linux, because then the returned duration in the `_player.durationStream` + /// isn't working correctly in the [ItemAudioPlayer] widget. + if (kIsWeb || Platform.isAndroid || Platform.isIOS || Platform.isIOS) { + await JustAudioBackground.init( + androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', + androidNotificationChannelName: 'Audio playback', + androidNotificationOngoing: true, + ); + } /// For the ewb we have to use the path url strategy, so that the redirect /// within Supabase is working in all cases. On all other platforms this is a diff --git a/app/lib/widgets/item/details/item_details_mastodon.dart b/app/lib/widgets/item/details/item_details_mastodon.dart index 5b1a4bc..fff185c 100644 --- a/app/lib/widgets/item/details/item_details_mastodon.dart +++ b/app/lib/widgets/item/details/item_details_mastodon.dart @@ -6,6 +6,7 @@ import 'package:feeddeck/utils/constants.dart'; import 'package:feeddeck/widgets/item/details/utils/item_description.dart'; import 'package:feeddeck/widgets/item/details/utils/item_media_gallery.dart'; import 'package:feeddeck/widgets/item/details/utils/item_subtitle.dart'; +import 'package:feeddeck/widgets/item/details/utils/item_videos.dart'; class ItemDetailsMastodon extends StatelessWidget { const ItemDetailsMastodon({ @@ -36,12 +37,23 @@ class ItemDetailsMastodon extends StatelessWidget { height: Constants.spacingExtraSmall, ), ItemMediaGallery( - itemMedias: item.options != null && item.options!.containsKey('media') + itemMedias: item.options != null && + item.options!.containsKey('media') && + item.options!['media'] != null ? (item.options!['media'] as List) .map((item) => item as String) .toList() : null, ), + ItemVideos( + videos: item.options != null && + item.options!.containsKey('videos') && + item.options!['videos'] != null + ? (item.options!['videos'] as List) + .map((item) => item as String) + .toList() + : null, + ), ], ); } diff --git a/app/lib/widgets/item/details/utils/item_videos.dart b/app/lib/widgets/item/details/utils/item_videos.dart new file mode 100644 index 0000000..b8b2c8f --- /dev/null +++ b/app/lib/widgets/item/details/utils/item_videos.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; + +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; + +import 'package:feeddeck/utils/constants.dart'; + +/// The [ItemVideos] widget is used to display a list of videos, which can be +/// played by the user. If the [videos] list is empty or null, the widget will +/// not be displayed. +class ItemVideos extends StatelessWidget { + const ItemVideos({ + super.key, + required this.videos, + }); + + final List? videos; + + @override + Widget build(BuildContext context) { + if (videos == null || videos!.isEmpty) { + return Container(); + } + + return Container( + padding: const EdgeInsets.only( + bottom: Constants.spacingMiddle, + ), + child: ListView.separated( + shrinkWrap: true, + separatorBuilder: (context, index) { + return const SizedBox( + height: Constants.spacingMiddle, + ); + }, + itemCount: videos!.length, + itemBuilder: (context, index) { + return ItemVideoPlayer(video: videos![index]); + }, + ), + ); + } +} + +/// The [ItemVideoPlayer] widget is used to display a video, which can be played +/// by the user. It should be used in combination with the [ItemVideos] widget +/// and is responsible for the actual implementation of the video player. +class ItemVideoPlayer extends StatefulWidget { + const ItemVideoPlayer({ + super.key, + required this.video, + }); + + final String video; + + @override + State createState() => _ItemVideoPlayerState(); +} + +class _ItemVideoPlayerState extends State { + late final player = Player(); + late final controller = VideoController(player); + + @override + void initState() { + super.initState(); + player.open( + Media(widget.video), + play: false, + ); + } + + @override + void dispose() { + player.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return Center( + child: SizedBox( + width: constraints.maxWidth, + height: constraints.maxWidth * 9.0 / 16.0, + child: Video(controller: controller), + ), + ); + }, + ); + } +} diff --git a/app/lib/widgets/item/preview/item_preview_mastodon.dart b/app/lib/widgets/item/preview/item_preview_mastodon.dart index 6a8c476..084164c 100644 --- a/app/lib/widgets/item/preview/item_preview_mastodon.dart +++ b/app/lib/widgets/item/preview/item_preview_mastodon.dart @@ -38,7 +38,9 @@ class ItemPreviewMastodon extends StatelessWidget { tagetFormat: DescriptionFormat.markdown, ), ItemMediaGallery( - itemMedias: item.options != null && item.options!.containsKey('media') + itemMedias: item.options != null && + item.options!.containsKey('media') && + item.options!['media'] != null ? (item.options!['media'] as List) .map((item) => item as String) .toList() diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index a78a161..8e301f7 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); + g_autoptr(FlPluginRegistrar) media_kit_video_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); + media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index b955c9b..0d78474 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST media_kit_libs_linux + media_kit_video screen_retriever url_launcher_linux window_manager diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index b41f7fc..b1fc016 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,14 +9,18 @@ import app_links import audio_service import audio_session import just_audio +import media_kit_libs_macos_video +import media_kit_video import package_info_plus import path_provider_foundation import purchases_flutter +import screen_brightness_macos import screen_retriever import shared_preferences_foundation import sign_in_with_apple import sqflite import url_launcher_macos +import wakelock_plus import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -24,13 +28,17 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) + MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PurchasesFlutterPlugin.register(with: registry.registrar(forPlugin: "PurchasesFlutterPlugin")) + ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/app/macos/Podfile.lock b/app/macos/Podfile.lock index d8b31ca..0e8a41c 100644 --- a/app/macos/Podfile.lock +++ b/app/macos/Podfile.lock @@ -11,8 +11,12 @@ PODS: - FMDB/standard (2.7.5) - just_audio (0.0.1): - FlutterMacOS + - media_kit_libs_macos_video (1.0.4): + - FlutterMacOS - media_kit_native_event_loop (1.0.0): - FlutterMacOS + - media_kit_video (0.0.1): + - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): @@ -24,6 +28,8 @@ PODS: - PurchasesHybridCommon (7.0.0): - RevenueCat (= 4.27.0) - RevenueCat (4.27.0) + - screen_brightness_macos (0.1.0): + - FlutterMacOS - screen_retriever (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -36,6 +42,8 @@ PODS: - FMDB (>= 2.7.5) - url_launcher_macos (0.0.1): - FlutterMacOS + - wakelock_plus (0.0.1): + - FlutterMacOS - window_manager (0.2.0): - FlutterMacOS @@ -45,15 +53,19 @@ DEPENDENCIES: - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) + - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) + - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - purchases_flutter (from `Flutter/ephemeral/.symlinks/plugins/purchases_flutter/macos`) + - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) SPEC REPOS: @@ -73,14 +85,20 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral just_audio: :path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos + media_kit_libs_macos_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos media_kit_native_event_loop: :path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos + media_kit_video: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin purchases_flutter: :path: Flutter/ephemeral/.symlinks/plugins/purchases_flutter/macos + screen_brightness_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos screen_retriever: :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos shared_preferences_foundation: @@ -91,6 +109,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + wakelock_plus: + :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos @@ -101,17 +121,21 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489 - media_kit_native_event_loop: d20622d35dd6d06fe71223976bd70a2bcf595dce + media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 + media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 + media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 purchases_flutter: 9aad80bf27960c38fdeafc27ab066cb55615aed5 PurchasesHybridCommon: af3b2413f9cb999bc1fdca44770bdaf39dfb89fa RevenueCat: 84fbe2eb9bbf63e1abf346ccd3ff9ee45d633e3b + screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 PODFILE CHECKSUM: 8d40c19d3cbdb380d870685c3a564c989f1efa52 diff --git a/app/pubspec.lock b/app/pubspec.lock index 79e33d1..d5a5822 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" + source: hosted + version: "0.7.8" fake_async: dependency: transitive description: @@ -403,10 +411,11 @@ packages: just_audio_media_kit: dependency: "direct main" description: - name: just_audio_media_kit - sha256: d6288e898bc5ed499a938c3cf1ea99eeca4264f9b6ef7bdf92ace3e8b804e259 - url: "https://pub.dev" - source: hosted + path: "." + ref: HEAD + resolved-ref: "5980ceac7cc385baf2269eda2dcb024d3e5203cc" + url: "https://github.com/feeddeck/just_audio_media_kit.git" + source: git version: "1.0.0" just_audio_platform_interface: dependency: transitive @@ -473,13 +482,29 @@ packages: source: hosted version: "0.5.0" media_kit: - dependency: transitive + dependency: "direct main" description: name: media_kit sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" url: "https://pub.dev" source: hosted version: "1.1.10+1" + media_kit_libs_android_video: + dependency: transitive + description: + name: media_kit_libs_android_video + sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c" + url: "https://pub.dev" + source: hosted + version: "1.3.6" + media_kit_libs_ios_video: + dependency: transitive + description: + name: media_kit_libs_ios_video + sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991 + url: "https://pub.dev" + source: hosted + version: "1.1.4" media_kit_libs_linux: dependency: transitive description: @@ -488,11 +513,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.3" - media_kit_libs_windows_audio: + media_kit_libs_macos_video: dependency: transitive description: - name: media_kit_libs_windows_audio - sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53 + name: media_kit_libs_macos_video + sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + media_kit_libs_video: + dependency: "direct main" + description: + name: media_kit_libs_video + sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + media_kit_libs_windows_video: + dependency: transitive + description: + name: media_kit_libs_windows_video + sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122" url: "https://pub.dev" source: hosted version: "1.0.9" @@ -504,6 +545,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + media_kit_video: + dependency: "direct main" + description: + name: media_kit_video + sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 + url: "https://pub.dev" + source: hosted + version: "1.2.4" meta: dependency: transitive description: @@ -720,6 +769,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + screen_brightness: + dependency: transitive + description: + name: screen_brightness + sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + screen_brightness_android: + dependency: transitive + description: + name: screen_brightness_android + sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" + screen_brightness_ios: + dependency: transitive + description: + name: screen_brightness_ios + sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_macos: + dependency: transitive + description: + name: screen_brightness_macos + sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" + url: "https://pub.dev" + source: hosted + version: "0.1.0+1" + screen_brightness_platform_interface: + dependency: transitive + description: + name: screen_brightness_platform_interface + sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_windows: + dependency: transitive + description: + name: screen_brightness_windows + sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86" + url: "https://pub.dev" + source: hosted + version: "0.1.3" screen_retriever: dependency: transitive description: @@ -1053,6 +1150,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + volume_controller: + dependency: transitive + description: + name: volume_controller + sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "268e56b9c63f850406f54e9acb2a7d2ddf83c26c8ff9e7a125a96c3a513bf65f" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index c7dbf7a..f231eaa 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -47,7 +47,16 @@ dependencies: intl: ^0.18.1 just_audio: ^0.9.32 just_audio_background: ^0.0.1-beta.10 - just_audio_media_kit: ^1.0.0 + # We use our own fork of the "just_audio_media_kit" package, where we + # replaced the "media_kit_libs_windows_audio" with + # "media_kit_libs_windows_video" package, so that we can play video files on + # Windows via the "media_kit" package. + just_audio_media_kit: + git: + url: https://github.com/feeddeck/just_audio_media_kit.git + media_kit: ^1.1.10+1 + media_kit_video: ^1.2.4 + media_kit_libs_video: ^1.0.4 package_info_plus: ^4.1.0 provider: ^6.0.4 purchases_flutter: ^6.0.0 diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index 6a28fd1..142f529 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -7,7 +7,9 @@ #include "generated_plugin_registrant.h" #include -#include +#include +#include +#include #include #include #include @@ -15,8 +17,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AppLinksPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AppLinksPluginCApi")); - MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("MediaKitLibsWindowsAudioPluginCApi")); + MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); + MediaKitVideoPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); + ScreenBrightnessWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 71c21b9..b2dbd7e 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -4,7 +4,9 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links - media_kit_libs_windows_audio + media_kit_libs_windows_video + media_kit_video + screen_brightness_windows screen_retriever url_launcher_windows window_manager diff --git a/supabase/functions/_shared/feed/mastodon.ts b/supabase/functions/_shared/feed/mastodon.ts index e73e51a..06e63c8 100644 --- a/supabase/functions/_shared/feed/mastodon.ts +++ b/supabase/functions/_shared/feed/mastodon.ts @@ -114,10 +114,22 @@ export const getMastodonFeed = async ( /** * Create the item object and add it to the `items` array. Before the item is created we also try to get a list of - * media fils (images) and add it to the options. Since there could be multiple media files we add it to the options - * and not to the media field. + * media fils (images) and videos which will then be added to the `options`. Since there could be multiple media + * files we add it to the options and not to the media field. + * + * The implementation to generate the options field is not ideal, but is required to be compatible with older + * clients, where we just check if the options are defined and if it contains a media field, but we do not check if + * the media field is null. */ + const options: { media?: string[]; videos?: string[] } = {}; const media = getMedia(entry); + if (media && media.length > 0) { + options["media"] = media; + } + const videos = getVideos(entry); + if (videos && videos.length > 0) { + options["videos"] = videos; + } items.push({ id: itemId, @@ -126,7 +138,7 @@ export const getMastodonFeed = async ( sourceId: source.id, title: "", link: entry.links[0].href!, - options: media && media.length > 0 ? { media: media } : undefined, + options: Object.keys(options).length === 0 ? undefined : options, description: entry.description?.value ? unescape(entry.description.value) : undefined, @@ -187,8 +199,8 @@ const generateItemId = (sourceId: string, identifier: string): string => { }; /** - * `getMedia` returns an image for the provided feed entry from it's description. If we could not get an image from the - * description we return `undefined`. + * `getMedia` returns all images for the provided feed entry from it's `media:content` field. If we could not get an + * image we return `undefined`. */ const getMedia = (entry: FeedEntry): string[] | undefined => { if (entry["media:content"]) { @@ -205,6 +217,25 @@ const getMedia = (entry: FeedEntry): string[] | undefined => { return undefined; }; +/** + * `getVideos` returns all videos for the provided feed entry from it's `media:content` field. If we could not get a + * video we return `undefined`. + */ +const getVideos = (entry: FeedEntry): string[] | undefined => { + if (entry["media:content"]) { + const videos = []; + for (const media of entry["media:content"]) { + if (media.medium === "video" && media.url) { + videos.push(media.url); + } + } + + return videos; + } + + return undefined; +}; + /** * `getAuthor` returns the author for the provided feed entry based on the link to the entry. */