diff --git a/lib/main.dart b/lib/main.dart index 5d4826e3..ec967be2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,8 +62,8 @@ import 'package:path/path.dart' as path; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:unity_video_player/unity_video_player.dart'; -import 'package:unity_video_player_flutter/unity_video_player_flutter.dart'; -import 'package:unity_video_player_main/unity_video_player_main.dart'; +// import 'package:unity_video_player_flutter/unity_video_player_flutter.dart'; +// import 'package:unity_video_player_main/unity_video_player_main.dart'; import 'package:window_manager/window_manager.dart'; final navigatorKey = GlobalKey(); @@ -85,13 +85,13 @@ Future main(List args) async { DevHttpOverrides.configureCertificates(); API.initialize(); await UnityVideoPlayerInterface.instance.initialize(); - if (isDesktopPlatform && Platform.isLinux) { - if (isEmbedded) { - UnityVideoPlayerFlutterInterface.registerWith(); - } else { - UnityVideoPlayerMediaKitInterface.registerWith(); - } - } + // if (isDesktopPlatform && Platform.isLinux) { + // if (isEmbedded) { + // UnityVideoPlayerFlutterInterface.registerWith(); + // } else { + // UnityVideoPlayerMediaKitInterface.registerWith(); + // } + // } debugPrint(UnityVideoPlayerInterface.instance.runtimeType.toString()); await configureStorage(); diff --git a/lib/models/server.dart b/lib/models/server.dart index 0baf136b..52d10807 100644 --- a/lib/models/server.dart +++ b/lib/models/server.dart @@ -310,9 +310,14 @@ class Server { port: json['port'], login: json['login'], password: json['password'], - devices: List>.from(json['devices'] as List) - .map(Device.fromJson) - .toList(), + devices: () { + if (json['devices'] == null) return []; + if ((json['devices'] as List).isEmpty) return []; + return List?>.from(json['devices'] as List) + .where((element) => element != null) + .map((device) => Device.fromJson(device!)) + .toList(); + }(), rtspPort: json['rtspPort'], serverUUID: json['serverUUID'], cookie: json['cookie'], diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 8965b6bd..a5ad099f 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -31,6 +31,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:unity_video_player/unity_video_player.dart'; +import 'package:unity_video_player_main/unity_video_player_main.dart'; enum NetworkUsage { auto, @@ -407,8 +408,23 @@ class SettingsProvider extends UnityProvider { loadFrom: (value) => MatrixType.values[int.parse(value)], saveAs: (value) => value.index.toString(), ); + static bool get isHardwareZoomSupported { + if (Platform.isMacOS || kIsWeb || UpdateManager.isEmbedded) { + return false; + } + + try { + if (UnityVideoPlayerInterface.instance.runtimeType != + UnityVideoPlayerMediaKitInterface) { + return false; + } + } catch (_) {} + + return true; + } + final kSoftwareZooming = _SettingsOption( - def: Platform.isMacOS || kIsWeb || UpdateManager.isEmbedded ? true : false, + def: isHardwareZoomSupported ? true : false, key: 'other.software_zoom', onChanged: (value) { for (final player in UnityPlayers.players.values) { @@ -417,9 +433,7 @@ class SettingsProvider extends UnityProvider { ..zoom.softwareZoom = value; } }, - valueOverrider: Platform.isMacOS || kIsWeb || UpdateManager.isEmbedded - ? () => true - : null, + valueOverrider: isHardwareZoomSupported ? () => true : null, ); final kEventsMatrixedZoom = _SettingsOption( def: true, diff --git a/lib/screens/events_timeline/desktop/timeline_card.dart b/lib/screens/events_timeline/desktop/timeline_card.dart index c5a73e37..e45fa658 100644 --- a/lib/screens/events_timeline/desktop/timeline_card.dart +++ b/lib/screens/events_timeline/desktop/timeline_card.dart @@ -72,6 +72,12 @@ class _TimelineCardState extends State { fit: _fit ?? device.server.additionalSettings.videoFit ?? settings.kVideoFit.value, + // videoBuilder: (context, video) { + // return AspectRatio( + // aspectRatio: 16 / 9, + // child: video, + // ); + // }, paneBuilder: (context, controller) { if (currentEvent == null) { return RepaintBoundary( diff --git a/lib/screens/layouts/desktop/sidebar.dart b/lib/screens/layouts/desktop/sidebar.dart index 879b3b61..9badc158 100644 --- a/lib/screens/layouts/desktop/sidebar.dart +++ b/lib/screens/layouts/desktop/sidebar.dart @@ -35,7 +35,7 @@ class DesktopSidebar extends StatefulWidget { class _DesktopSidebarState extends State { var isSidebarHovering = false; var searchQuery = ''; - final _servers = >{}; + var _servers = >{}; @override void didChangeDependencies() { @@ -46,14 +46,12 @@ class _DesktopSidebarState extends State { void _updateServers() { final servers = context.read(); - _servers.clear(); + _servers = {}; for (final server in servers.servers) { final devices = server.devices.sorted( searchQuery: searchQuery, ); - if (devices.isNotEmpty) { - _servers[server] = devices; - } + _servers[server] = devices; } } @@ -92,145 +90,142 @@ class _DesktopSidebarState extends State { child: Material( type: MaterialType.transparency, child: CustomScrollView(slivers: [ - for (final entry in _servers.entries) - () { - final server = entry.key; - final devices = entry.value; - final isLoading = servers.isServerLoading(server); - if (!isLoading && - devices.isEmpty && - searchQuery.isNotEmpty) { - return const SliverToBoxAdapter( - child: SizedBox.shrink(), - ); - } - - /// Whether all the online devices are in the current view. - final isAllInView = devices - .where((d) => d.status) - .every( - (d) => view.currentLayout.devices.contains(d)); - - return MultiSliver(pushPinnedChildren: true, children: [ - SliverPinnedHeader( - child: SubHeader( - server.name, - materialType: MaterialType.canvas, - subtext: () { - if (!settings.checkServerCertificates(server)) { - return loc.certificateNotPassed; - } else if (server.online) { - return loc.nDevices(devices.length); - } else { - return loc.offline; - } - }(), - subtextStyle: TextStyle( - color: !server.online - ? theme.colorScheme.error - : null, - ), - trailing: Builder(builder: (context) { - if (isLoading) { - // wrap in an icon button to ensure ui consistency - return const SquaredIconButton( - onPressed: null, - icon: SizedBox( - height: 16.0, - width: 16.0, - child: CircularProgressIndicator.adaptive( - strokeWidth: 1.5, - ), - ), - ); - } else if (!server.online && - isSidebarHovering) { - return SquaredIconButton( - icon: const Icon(Icons.refresh), - tooltip: loc.refreshServer, - onPressed: () => servers - .refreshDevices(ids: [server.id]), - ); - } else if (isSidebarHovering && - devices.isNotEmpty) { - return SquaredIconButton( - icon: Icon( - isAllInView - ? Icons.playlist_remove - : Icons.playlist_add, + ..._servers.entries.map((entry) { + final server = entry.key; + final devices = entry.value; + final isLoading = servers.isServerLoading(server); + if (!isLoading && + devices.isEmpty && + searchQuery.isNotEmpty) { + return const SliverToBoxAdapter( + child: SizedBox.shrink(), + ); + } + + /// Whether all the online devices are in the current view. + final isAllInView = devices + .where((d) => d.status) + .every((d) => view.currentLayout.devices.contains(d)); + + return MultiSliver(pushPinnedChildren: true, children: [ + SliverPinnedHeader( + child: SubHeader( + server.name, + materialType: MaterialType.canvas, + subtext: () { + if (!settings.checkServerCertificates(server)) { + return loc.certificateNotPassed; + } else if (server.online) { + return loc.nDevices(devices.length); + } else { + return loc.offline; + } + }(), + subtextStyle: TextStyle( + color: !server.online + ? theme.colorScheme.error + : null, + ), + trailing: Builder(builder: (context) { + if (isLoading) { + // wrap in an icon button to ensure ui consistency + return const SquaredIconButton( + onPressed: null, + icon: SizedBox( + height: 16.0, + width: 16.0, + child: CircularProgressIndicator.adaptive( + strokeWidth: 1.5, ), - tooltip: isAllInView - ? loc.removeAllFromView - : loc.addAllToView, - onPressed: () { - if (isAllInView) { - view.removeDevicesFromCurrentLayout( - devices, - ); - } else { - for (final device in devices) { - if (device.status && - !view.currentLayout.devices - .contains(device)) { - view.add(device); - } + ), + ); + } else if (!server.online && isSidebarHovering) { + return SquaredIconButton( + icon: const Icon(Icons.refresh), + tooltip: loc.refreshServer, + onPressed: () => + servers.refreshDevices(ids: [server.id]), + ); + } else if (isSidebarHovering && + devices.isNotEmpty) { + return SquaredIconButton( + icon: Icon( + isAllInView + ? Icons.playlist_remove + : Icons.playlist_add, + ), + tooltip: isAllInView + ? loc.removeAllFromView + : loc.addAllToView, + onPressed: () { + if (isAllInView) { + view.removeDevicesFromCurrentLayout( + devices, + ); + } else { + for (final device in devices) { + if (device.status && + !view.currentLayout.devices + .contains(device)) { + view.add(device); } } - }, - ); - } else { - return const SizedBox.shrink(); - } - }), - ), - ), - if (server.online && !isLoading) - SliverList.builder( - itemCount: devices.length, - itemBuilder: (context, index) { - final device = devices.elementAt(index); - final selected = - view.currentLayout.devices.contains(device); - - final tile = DeviceSelectorTile( - device: device, - selected: selected, + } + }, ); - - if (!device.status && - !settings.kListOfflineDevices.value) { - return const SizedBox.shrink(); - } - if (selected || !device.status) return tile; - - final isBlocked = view.currentLayout.type == - DesktopLayoutType.singleView && - view.currentLayout.devices.isNotEmpty; - - return Draggable( - data: device, - feedback: Card( - child: SizedBox( - height: kDeviceSelectorTileHeight, - width: kSidebarConstraints.maxWidth, - child: Row(children: [ - Expanded(child: tile), - if (isBlocked) - Icon( - Icons.block, - color: theme.colorScheme.error, - size: 18.0, - ), - const SizedBox(width: 16.0), - ]), - ), + } else { + return const SizedBox.shrink(); + } + }), + ), + ), + if (server.online && !isLoading) + SliverList.builder( + itemCount: devices.length, + itemBuilder: (context, index) { + final device = devices.elementAt(index); + final selected = + view.currentLayout.devices.contains(device); + + final tile = DeviceSelectorTile( + device: device, + selected: selected, + ); + + if (!device.status && + !settings.kListOfflineDevices.value) { + return const SizedBox.shrink(); + } + if (selected || !device.status) return tile; + + final isBlocked = view.currentLayout.type == + DesktopLayoutType.singleView && + view.currentLayout.devices.isNotEmpty; + + return Draggable( + data: device, + feedback: Card( + child: SizedBox( + height: kDeviceSelectorTileHeight, + width: kSidebarConstraints.maxWidth, + child: Row(children: [ + Expanded(child: tile), + if (isBlocked) + Icon( + Icons.block, + color: theme.colorScheme.error, + size: 18.0, + ), + const SizedBox(width: 16.0), + ]), ), - child: tile, - ); - }, - ), - ]); - }(), + ), + child: tile, + ); + }, + ), + ]); + }), ]), ), ), diff --git a/lib/screens/servers/configure_dvr_server.dart b/lib/screens/servers/configure_dvr_server.dart index c51f36e2..7500c243 100644 --- a/lib/screens/servers/configure_dvr_server.dart +++ b/lib/screens/servers/configure_dvr_server.dart @@ -68,7 +68,11 @@ class _ConfigureDVRServerScreenState extends State { final finishFocusNode = FocusNode(); String getServerHostname(String text) { - if (Uri.parse(text).scheme.isEmpty) text = 'https://$text'; + try { + if (Uri.parse(text).scheme.isEmpty) text = 'https://$text'; + } catch (e) { + text = 'https://$text'; + } return Uri.parse(text).host; } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c4ad27ae..6dc847c4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) awesome_notifications_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AwesomeNotificationsPlugin"); awesome_notifications_plugin_register_with_registrar(awesome_notifications_registrar); + g_autoptr(FlPluginRegistrar) fvp_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FvpPlugin"); + fvp_plugin_register_with_registrar(fvp_registrar); g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 3b009456..06e9c034 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST awesome_notifications + fvp gtk media_kit_libs_linux media_kit_video diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 6dbc1bb5..5398a142 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import connectivity_plus import device_info_plus import firebase_core import firebase_messaging +import fvp import media_kit_libs_macos_video import media_kit_video import package_info_plus @@ -30,6 +31,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FvpPlugin.register(with: registry.registrar(forPlugin: "FvpPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/packages/pub_resolver.dart b/packages/pub_resolver.dart new file mode 100644 index 00000000..a3ceee9c --- /dev/null +++ b/packages/pub_resolver.dart @@ -0,0 +1,66 @@ +// A script that resolves the dependencies of all unity packages. + +// ignore_for_file: avoid_print + +import 'dart:convert'; +import 'dart:io'; + +void main() async { + print('Resolving dependencies'); + + final packagePaths = [ + '.', + 'packages/unity_multi_window', + 'packages/unity_video_player/unity_video_player', + 'packages/unity_video_player/unity_video_player_flutter', + 'packages/unity_video_player/unity_video_player_fvp', + 'packages/unity_video_player/unity_video_player_main', + 'packages/unity_video_player/unity_video_player_platform_interface', + ]; + + print('Running pub get in all packages:'); + await Future.wait(packagePaths.map(_runPubGet)); + + print('Running pub upgrade in all packages:'); + await Future.wait(packagePaths.map(_runPubUpgrade)); + + print('All done'); +} + +Future _runPubGet(String packagePath) async { + void printLine(String line) { + print('[$packagePath] $line'); + } + + final process = await Process.start( + 'flutter', + ['pub', 'get'], + workingDirectory: packagePath, + runInShell: true, + ); + process.stdout.transform(utf8.decoder).listen(printLine); + process.stderr.transform(utf8.decoder).listen(printLine); + final exitCode = await process.exitCode; + if (exitCode != 0) { + throw Exception('Failed to run pub get in $packagePath'); + } +} + +Future _runPubUpgrade(String packagePath) async { + void printLine(String line) { + print('[$packagePath] $line'); + } + + final process = await Process.start( + 'flutter', + ['pub', 'upgrade'], + workingDirectory: packagePath, + runInShell: true, + ); + process.stdout.transform(utf8.decoder).listen(printLine); + process.stderr.transform(utf8.decoder).listen(printLine); + final exitCode = await process.exitCode; + if (exitCode != 0) { + throw Exception('Failed to run pub upgrade in $packagePath'); + } +} diff --git a/packages/unity_video_player/unity_video_player/README.md b/packages/unity_video_player/unity_video_player/README.md deleted file mode 100644 index 02fe8eca..00000000 --- a/packages/unity_video_player/unity_video_player/README.md +++ /dev/null @@ -1,39 +0,0 @@ - - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. - -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - -```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. diff --git a/packages/unity_video_player/unity_video_player/pubspec.yaml b/packages/unity_video_player/unity_video_player/pubspec.yaml index f8c8ac98..2d3f03b4 100644 --- a/packages/unity_video_player/unity_video_player/pubspec.yaml +++ b/packages/unity_video_player/unity_video_player/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: path: ../unity_video_player_main unity_video_player_flutter: path: ../unity_video_player_flutter + # unity_video_player_fvp: + # path: ../unity_video_player_fvp dev_dependencies: flutter_test: @@ -36,4 +38,4 @@ flutter: windows: default_package: unity_video_player_main web: - default_package: unity_video_player_main + default_package: unity_video_player_flutter diff --git a/packages/unity_video_player/unity_video_player_flutter/README.md b/packages/unity_video_player/unity_video_player_flutter/README.md deleted file mode 100644 index 6d8f7f72..00000000 --- a/packages/unity_video_player/unity_video_player_flutter/README.md +++ /dev/null @@ -1,3 +0,0 @@ -`unity_video_player` imeplementation with `video_player`. - -This was created to be used in the Raspberry Pi, but it should work on any platform. \ No newline at end of file diff --git a/packages/unity_video_player/unity_video_player_flutter/lib/unity_video_player_flutter.dart b/packages/unity_video_player/unity_video_player_flutter/lib/unity_video_player_flutter.dart index 13dd0943..7cf326b6 100644 --- a/packages/unity_video_player/unity_video_player_flutter/lib/unity_video_player_flutter.dart +++ b/packages/unity_video_player/unity_video_player_flutter/lib/unity_video_player_flutter.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutterpi_gstreamer_video_player/flutterpi_gstreamer_video_player.dart'; import 'package:video_player/video_player.dart'; import 'package:unity_video_player_platform_interface/unity_video_player_platform_interface.dart'; +import 'package:fvp/fvp.dart' as fvp; class UnityVideoPlayerFlutterInterface extends UnityVideoPlayerInterface { /// Registers this class as the default instance of [UnityVideoPlayerInterface]. @@ -21,7 +22,19 @@ class UnityVideoPlayerFlutterInterface extends UnityVideoPlayerInterface { @override Future initialize() async { - FlutterpiVideoPlayer.registerWith(); + if ('pi' case const String.fromEnvironment('linux_environment')) { + FlutterpiVideoPlayer.registerWith(); + } else { + fvp.registerWith(options: { + 'player': { + 'avformat.analyzeduration': '10000', + 'avformat.probesize': '1000', + 'avformat.fpsprobesize': '0', + 'avformat.fflags': '+nobuffer', + 'avformat.avioflags': 'direct', + } + }); + } } @override @@ -58,6 +71,7 @@ class UnityVideoPlayerFlutterInterface extends UnityVideoPlayerInterface { return Builder(builder: (context) { return Stack(children: [ + const SizedBox.expand(), Positioned.fill( child: videoBuilder!( context, @@ -86,13 +100,17 @@ class UnityVideoPlayerFlutter extends UnityVideoPlayer { final _videoStream = StreamController.broadcast(); + RTSPProtocol? rtspProtocol; + UnityVideoPlayerFlutter({ super.width, super.height, bool enableCache = false, - RTSPProtocol? rtspProtocol, + this.rtspProtocol, String? title, - }); + }) { + if (title != null) this.title = title; + } @override String? get dataSource => player?.dataSource; @@ -106,74 +124,102 @@ class UnityVideoPlayerFlutter extends UnityVideoPlayer { Duration get duration => player?.value.duration ?? Duration.zero; @override - Stream get onDurationUpdate => - _videoStream.stream.map((_) => duration); + Stream get onDurationUpdate => _videoStream.stream + .where((value) => value.duration != Duration.zero) + .map((value) => value.duration); @override Duration get currentPos => player?.value.position ?? Duration.zero; @override Stream get onCurrentPosUpdate => - _videoStream.stream.map((_) => currentPos); + _videoStream.stream.map((value) => value.position); @override - bool get isBuffering => player?.value.isBuffering ?? false; - - @override - Duration get currentBuffer => - player?.value.buffered.last.end ?? Duration.zero; + Duration get currentBuffer { + if (player == null) return Duration.zero; + if (player!.value.buffered.isEmpty) return Duration.zero; + return player!.value.buffered.last.end; + } @override - Stream get onBufferUpdate => - _videoStream.stream.map((_) => currentBuffer); + Stream get onBufferUpdate => _videoStream.stream + .where((value) => value.buffered.isNotEmpty) + .map((value) => value.buffered.last.end); @override bool get isSeekable => duration > Duration.zero; + @override + bool get isBuffering => player?.value.isBuffering ?? false; @override Stream get onBufferStateUpdate => - _videoStream.stream.map((_) => isBuffering); + _videoStream.stream.map((value) => value.isBuffering); @override bool get isPlaying => player?.value.isPlaying ?? false; @override Stream get onPlayingStateUpdate => - _videoStream.stream.map((_) => isPlaying); + _videoStream.stream.map((value) => value.isPlaying); @override Future setDataSource(String url, {bool autoPlay = true}) async { if (url == dataSource) return Future.value(); - debugPrint('Playing $url'); if (player != null) { + debugPrint('Disposing player for $dataSource'); await player?.dispose(); + player = null; } + debugPrint('Playing $url'); - player = VideoPlayerController.networkUrl(Uri.parse(url)); - await player!.initialize(); - notifyListeners(); - player!.addListener(() { - _videoStream.add(player!.value); - }); - if (autoPlay) { - await player!.play(); + final uri = Uri.parse(url); + + // check if the url is a file + if (uri.scheme == 'file') { + player = VideoPlayerController.file(File.fromUri(uri)); + } else { + player = VideoPlayerController.networkUrl(uri); + } + + try { + await player!.initialize(); + player!.addListener(() { + if (_videoStream.isClosed) return; + _videoStream.add(player!.value); + }); + onReady(); + if (autoPlay) { + await player!.play(); + } + } catch (e, _) { + error = e.toString(); + notifyListeners(); + + rethrow; } } + var _multipleDataSources = []; + @override - Future setMultipleDataSource(Iterable url, - {bool autoPlay = true}) { - throw UnsupportedError( - 'setMultipleDataSource is not supported on this platform', - ); + Future setMultipleDataSource( + Iterable url, { + bool autoPlay = true, + int startIndex = 0, + }) { + _multipleDataSources = url.toList(); + return setDataSource(url.elementAt(startIndex), autoPlay: autoPlay); } @override Future jumpToIndex(int index) { - throw UnsupportedError( - 'jumpToIndex is not supported on this platform', - ); + if (index < 0 || index >= _multipleDataSources.length) { + return Future.error('Index out of range'); + } + + return setDataSource(_multipleDataSources[index]); } // Volume in media kit goes from 0 to 100 @@ -190,11 +236,10 @@ class UnityVideoPlayerFlutter extends UnityVideoPlayer { @override double get fps => 0.0; @override - Stream get fpsStream => - throw UnsupportedError('Fps is not implemented on this platform'); + Stream get fpsStream => Stream.value(fps); @override - double get aspectRatio => player?.value.aspectRatio ?? 1.0; + double get aspectRatio => player?.value.aspectRatio ?? 0; @override Future getProperty(String propertyName) { @@ -235,9 +280,13 @@ class UnityVideoPlayerFlutter extends UnityVideoPlayer { @override Future dispose() async { + debugPrint('Disposing player for $dataSource'); await release(); await super.dispose(); await _videoStream.close(); + player + ?..pause() + ..dispose(); UnityVideoPlayerInterface.unregisterPlayer(this); } } diff --git a/packages/unity_video_player/unity_video_player_flutter/pubspec.yaml b/packages/unity_video_player/unity_video_player_flutter/pubspec.yaml index 46ceff25..29153665 100644 --- a/packages/unity_video_player/unity_video_player_flutter/pubspec.yaml +++ b/packages/unity_video_player/unity_video_player_flutter/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: path: ../unity_video_player_main video_player: ^2.8.1 flutterpi_gstreamer_video_player: ^0.1.1+1 + fvp: ^0.24.1 dev_dependencies: flutter_test: @@ -25,3 +26,19 @@ dev_dependencies: flutter_lints: ^3.0.0 flutter: + plugin: + implements: unity_video_player + platforms: + android: + dartPluginClass: UnityVideoPlayerFlutterInterface + ios: + dartPluginClass: UnityVideoPlayerFlutterInterface + windows: + dartPluginClass: UnityVideoPlayerFlutterInterface + macos: + dartPluginClass: UnityVideoPlayerFlutterInterface + linux: + dartPluginClass: UnityVideoPlayerFlutterInterface + web: + pluginClass: UnityVideoPlayerMediaKitInterface + fileName: unity_video_player_flutter.dart \ No newline at end of file diff --git a/packages/unity_video_player/unity_video_player_main/README.md b/packages/unity_video_player/unity_video_player_main/README.md deleted file mode 100644 index b63191bb..00000000 --- a/packages/unity_video_player/unity_video_player_main/README.md +++ /dev/null @@ -1 +0,0 @@ -`unity_video_player` implementation with `media_kit` \ No newline at end of file diff --git a/packages/unity_video_player/unity_video_player_platform_interface/README.md b/packages/unity_video_player/unity_video_player_platform_interface/README.md deleted file mode 100644 index 02fe8eca..00000000 --- a/packages/unity_video_player/unity_video_player_platform_interface/README.md +++ /dev/null @@ -1,39 +0,0 @@ - - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. - -## Features - -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - -```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. diff --git a/packages/unity_video_player/unity_video_player_platform_interface/lib/unity_video_player_platform_interface.dart b/packages/unity_video_player/unity_video_player_platform_interface/lib/unity_video_player_platform_interface.dart index 4ff5c7df..899f2340 100644 --- a/packages/unity_video_player/unity_video_player_platform_interface/lib/unity_video_player_platform_interface.dart +++ b/packages/unity_video_player/unity_video_player_platform_interface/lib/unity_video_player_platform_interface.dart @@ -230,6 +230,10 @@ abstract class UnityVideoPlayer with ChangeNotifier { UnityVideoQuality? quality; VideoZoom zoom = VideoZoom(); + /// Called when the video source is ready to be listened to. + /// + /// Implementations must call this when the video source is ready to be + /// listened to. late final VoidCallback onReady; UnityVideoPlayer({this.width, this.height}) { @@ -434,11 +438,15 @@ abstract class UnityVideoPlayer with ChangeNotifier { @mustCallSuper @override Future dispose() async { - _onDurationUpdateSubscription.cancel(); - _onErrorSubscription.cancel(); - _onPositionUpdateSubscription.cancel(); - _fpsSubscription.cancel(); - _oldImageTimer?.cancel(); + try { + _onDurationUpdateSubscription.cancel(); + _onErrorSubscription.cancel(); + _onPositionUpdateSubscription.cancel(); + _fpsSubscription.cancel(); + _oldImageTimer?.cancel(); + } catch (error, stack) { + debugPrint('Tried to cancel subscriptions but failed: $error, $stack'); + } _lastImageTime = null; _isImageOld = false; diff --git a/pubspec.lock b/pubspec.lock index 0a0aebb4..f5409666 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -335,10 +335,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.21" + version: "2.0.22" flutter_simple_treeview: dependency: "direct main" description: @@ -373,6 +373,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.1+1" + fvp: + dependency: transitive + description: + name: fvp + sha256: "6462fd078de4478a0990d437463897036cff98aff3f0ac9efbbf817c99654c87" + url: "https://pub.dev" + source: hosted + version: "0.24.1" get_it: dependency: transitive description: @@ -989,6 +997,13 @@ packages: relative: true source: path version: "0.0.1" + unity_video_player_fvp: + dependency: transitive + description: + path: "packages/unity_video_player/unity_video_player_fvp" + relative: true + source: path + version: "0.0.1" unity_video_player_main: dependency: transitive description: @@ -1119,10 +1134,10 @@ packages: dependency: transitive description: name: video_player_android - sha256: "4de50df9ee786f5891d3281e1e633d7b142ef1acf47392592eb91cba5d355849" + sha256: "101028b643a3b43ced72107aacdbc4d30d55365487751de001a36cf0d86038d1" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.2" video_player_avfoundation: dependency: transitive description: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 3de2ce8d..9d1d583d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FvpPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FvpPluginCApi")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 566b17f2..f7b0a094 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links awesome_notifications connectivity_plus + fvp media_kit_libs_windows_video media_kit_video permission_handler_windows