From cffef4554a067675237480671885f345c94c6aeb Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 18:15:47 -0300 Subject: [PATCH 01/11] feat: Add kEventsMatrixedZoom settings option --- lib/providers/settings_provider.dart | 15 +++++++++++---- lib/screens/layouts/desktop/external_stream.dart | 3 +-- lib/screens/layouts/desktop/multicast_view.dart | 2 +- lib/screens/layouts/desktop/stream_data.dart | 2 +- lib/screens/settings/advanced_options.dart | 4 ++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 3ee67bf4..d252e996 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -387,7 +387,7 @@ class SettingsProvider extends UnityProvider { ); // Other - final kDefaultBetaMatrixedZoomEnabled = _SettingsOption( + final kMatrixedZoomEnabled = _SettingsOption( def: false, key: 'other.matrixed_zoom_enabled', ); @@ -411,6 +411,10 @@ class SettingsProvider extends UnityProvider { ? () => true : null, ); + final kEventsMatrixedZoom = _SettingsOption( + def: true, + key: 'other.zoom_matrixed_zoom_enabled', + ); final kShowDebugInfo = _SettingsOption( def: kDebugMode, key: 'other.show_debug_info', @@ -477,9 +481,10 @@ class SettingsProvider extends UnityProvider { kAllowCrashReports.loadData(data), kAutoUpdate.loadData(data), kShowReleaseNotes.loadData(data), - kDefaultBetaMatrixedZoomEnabled.loadData(data), + kMatrixedZoomEnabled.loadData(data), kMatrixSize.loadData(data), kSoftwareZooming.loadData(data), + kEventsMatrixedZoom.loadData(data), kShowDebugInfo.loadData(data), kShowNetworkUsage.loadData(data), ]); @@ -559,10 +564,12 @@ class SettingsProvider extends UnityProvider { kAutoUpdate.key: kAutoUpdate.saveAs(kAutoUpdate.value), kShowReleaseNotes.key: kShowReleaseNotes.saveAs(kShowReleaseNotes.value), - kDefaultBetaMatrixedZoomEnabled.key: kDefaultBetaMatrixedZoomEnabled - .saveAs(kDefaultBetaMatrixedZoomEnabled.value), + kMatrixedZoomEnabled.key: + kMatrixedZoomEnabled.saveAs(kMatrixedZoomEnabled.value), kMatrixSize.key: kMatrixSize.saveAs(kMatrixSize.value), kSoftwareZooming.key: kSoftwareZooming.saveAs(kSoftwareZooming.value), + kEventsMatrixedZoom.key: + kEventsMatrixedZoom.saveAs(kEventsMatrixedZoom.value), kShowDebugInfo.key: kShowDebugInfo.saveAs(kShowDebugInfo.value), kShowNetworkUsage.key: kShowNetworkUsage.saveAs(kShowNetworkUsage.value), diff --git a/lib/screens/layouts/desktop/external_stream.dart b/lib/screens/layouts/desktop/external_stream.dart index 5bb67fc2..41ff1f47 100644 --- a/lib/screens/layouts/desktop/external_stream.dart +++ b/lib/screens/layouts/desktop/external_stream.dart @@ -223,8 +223,7 @@ class _AddExternalStreamDialogState extends State { ), ), ), - if (settings.kDefaultBetaMatrixedZoomEnabled.value && - showMoreOptions) ...[ + if (settings.kMatrixedZoomEnabled.value && showMoreOptions) ...[ const SizedBox(height: 16.0), Text(loc.matrixType, style: theme.textTheme.headlineSmall), const SizedBox(height: 6.0), diff --git a/lib/screens/layouts/desktop/multicast_view.dart b/lib/screens/layouts/desktop/multicast_view.dart index ecf7d54b..62655667 100644 --- a/lib/screens/layouts/desktop/multicast_view.dart +++ b/lib/screens/layouts/desktop/multicast_view.dart @@ -94,7 +94,7 @@ class _MulticastViewportState extends State { if (view == null || view.lastImageUpdate == null || - !settings.kDefaultBetaMatrixedZoomEnabled.value) { + !settings.kMatrixedZoomEnabled.value) { return const SizedBox.shrink(); } diff --git a/lib/screens/layouts/desktop/stream_data.dart b/lib/screens/layouts/desktop/stream_data.dart index da859331..dfa95204 100644 --- a/lib/screens/layouts/desktop/stream_data.dart +++ b/lib/screens/layouts/desktop/stream_data.dart @@ -278,7 +278,7 @@ class _StreamDataState extends State { }, ), ], - if (settings.kDefaultBetaMatrixedZoomEnabled.value) ...[ + if (settings.kMatrixedZoomEnabled.value) ...[ const SizedBox(height: 16.0), Text(loc.matrixType, style: theme.textTheme.headlineSmall), const SizedBox(height: 6.0), diff --git a/lib/screens/settings/advanced_options.dart b/lib/screens/settings/advanced_options.dart index 2596c5aa..037b1c98 100644 --- a/lib/screens/settings/advanced_options.dart +++ b/lib/screens/settings/advanced_options.dart @@ -84,10 +84,10 @@ class _AdvancedOptionsSettingsState extends State { contentPadding: DesktopSettings.horizontalPadding, title: Text(loc.matrixedViewZoom), subtitle: Text(loc.matrixedViewZoomDescription), - value: settings.kDefaultBetaMatrixedZoomEnabled.value, + value: settings.kMatrixedZoomEnabled.value, onChanged: (value) { if (value != null) { - settings.kDefaultBetaMatrixedZoomEnabled.value = value; + settings.kMatrixedZoomEnabled.value = value; } }, ), From 1afce1856c504954e16f1e76165fad97f6056f79 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 18:29:02 -0300 Subject: [PATCH 02/11] feat: Add "Event Magnification" to the "Advanced Options" screen --- lib/l10n/app_en.arb | 14 +++++---- lib/l10n/app_fr.arb | 14 +++++---- lib/l10n/app_pl.arb | 14 +++++---- lib/l10n/app_pt.arb | 14 +++++---- lib/screens/settings/advanced_options.dart | 33 ++++++++++++++++------ 5 files changed, 57 insertions(+), 32 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2768e2b6..0b09b53d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -547,14 +547,16 @@ "privacyPolicy": "Privacy Policy", "termsOfService": "Terms of Service", "@@ADVANCED_OPTIONS": {}, - "matrixZoom": "Area Magnification", - "matrixedViewZoom": "Area Magnification enabled", - "matrixedViewZoomDescription": "Magnify a area of the matrix view when selected. This is useful when you have a lot of cameras and want to see a specific area in more detail, or when a multicast stream is provided.", + "matrixMagnification": "Area Magnification", + "matrixedViewMagnification": "Area Magnification enabled", + "matrixedViewMagnificationDescription": "Magnify a area of the matrix view when selected. This is useful when you have a lot of cameras and want to see a specific area in more detail, or when a multicast stream is provided.", "matrixType": "Matrix type", "defaultMatrixSize": "Default Magnification Proportion", - "softwareZoom": "Software zoom", - "softwareZoomDescription": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly.", - "softwareZoomDescriptionMacOS": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly. On macOS, this can not be disabled.", + "softwareMagnification": "Software Magnification", + "softwareMagnificationDescription": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly.", + "softwareMagnificationDescriptionMacOS": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly. On macOS, this can not be disabled.", + "eventMagnification": "Event Magnification", + "eventMagnificationDescription": "Magnify the event video when selected. This is useful when you want to see the event in more detail.", "developerOptions": "Developer options", "openLogFile": "Open log file", "openAppDataDirectory": "Open app data directory", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 515555e3..a6fa81aa 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -523,14 +523,16 @@ "privacyPolicy": "Politique de confidentialité", "termsOfService": "Conditions 'utilisations", "@@ADVANCED_OPTIONS": {}, - "matrixZoom": "Zoom aatriciel", - "matrixedViewZoom": "Vue zoom matriciel", - "matrixedViewZoomDescription": "Zoomer sur une zone matricielle de 16x16 pour voir plus de détails.", + "matrixMagnification": "Area Magnification", + "matrixedViewMagnification": "Area Magnification enabled", + "matrixedViewMagnificationDescription": "Magnify a area of the matrix view when selected. This is useful when you have a lot of cameras and want to see a specific area in more detail, or when a multicast stream is provided.", "matrixType": "Type de matrice", "defaultMatrixSize": "Taille de matrice par défaut", - "softwareZoom": "Zoom logiciel", - "softwareZoomDescription": "Lorsque activé, le zoom matriciel n'utilisera pas le GPU. Cette option est utile lorsque le zoom matériel ne fonctionne pas correctement.", - "softwareZoomDescriptionMacOS": "Lorsque activé, le zoom matriciel n'utilisera pas le GPU. Cette option est utile lorsque le zoom matériel ne fonctionne pas correctement. Sur macOS, cette option ne peut être désactivée.", + "softwareMagnification": "Software Magnification", + "softwareMagnificationDescription": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly.", + "softwareMagnificationDescriptionMacOS": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly. On macOS, this can not be disabled.", + "eventMagnification": "Event Magnification", + "eventMagnificationDescription": "Magnify the event video when selected. This is useful when you want to see the event in more detail.", "developerOptions": "Options de développement", "openLogFile": "Ovrir les fichiers logs", "openAppDataDirectory": "Ouvrir le répertoir de l'application", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 441ffee8..e8b50e14 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -547,14 +547,16 @@ "privacyPolicy": "Privacy Policy", "termsOfService": "Terms of Service", "@@ADVANCED_OPTIONS": {}, - "matrixZoom": "Matrix Zoom", - "matrixedViewZoom": "Matrixed view zoom", - "matrixedViewZoomDescription": "Zoom in on a 16x16 matrixed view to see more detail.", + "matrixMagnification": "Area Magnification", + "matrixedViewMagnification": "Area Magnification enabled", + "matrixedViewMagnificationDescription": "Magnify a area of the matrix view when selected. This is useful when you have a lot of cameras and want to see a specific area in more detail, or when a multicast stream is provided.", "matrixType": "Matrix type", "defaultMatrixSize": "Default Matrix Size", - "softwareZoom": "Software zoom", - "softwareZoomDescription": "When enabled, the matrix zoom will not happen in the GPU. This is useful when the hardware zoom is not working properly.", - "softwareZoomDescriptionMacOS": "When enabled, the matrix zoom will not happen in the GPU. This is useful when the hardware zoom is not working properly. On macOS, this can not be disabled.", + "softwareMagnification": "Software Magnification", + "softwareMagnificationDescription": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly.", + "softwareMagnificationDescriptionMacOS": "When enabled, the magnification will not happen in the GPU. This is useful when the hardware magnification is not working properly. On macOS, this can not be disabled.", + "eventMagnification": "Event Magnification", + "eventMagnificationDescription": "Magnify the event video when selected. This is useful when you want to see the event in more detail.", "developerOptions": "Developer options", "openLogFile": "Open log file", "openAppDataDirectory": "Open app data directory", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index efd73b04..6a73cf88 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -547,14 +547,16 @@ "privacyPolicy": "Política de Privacidade", "termsOfService": "Termos de Serviço", "@@ADVANCED_OPTIONS": {}, - "matrixZoom": "Zoom da Matriz", - "matrixedViewZoom": "Zoom em Visualização Matrixial", - "matrixedViewZoomDescription": "Dê zoom em uma visualização matrixial 16x16 para ver mais detalhes.", + "matrixMagnification": "Ampliar", + "matrixedViewMagnification": "Ampliação de Área ativada", + "matrixedViewMagnificationDescription": "Ampliar a área da visualização da matriz quando selecionado. Isso é útil quando você tem muitas câmeras e deseja ver uma área específica com mais detalhes, ou quando uma stream multicast é usada.", "matrixType": "Tipo de Matrix", "defaultMatrixSize": "Tamanho Padrão da Matriz", - "softwareZoom": "Zoom do Software", - "softwareZoomDescription": "Quando ativado, o zoom não ocorrerá na GPU. Isso é útil quando o zoom do hardware não está funcionando corretamente.", - "softwareZoomDescriptionMacOS": "Quando ativado, o zoom da matriz não ocorrerá na GPU. Isso é útil quando o zoom do hardware não está funcionando corretamente. No macOS, isso não pode ser desativado.", + "softwareMagnification": "Ampliação de Software", + "softwareMagnificationDescription": "Quando ativado, a ampliação não ocorrerá na GPU. Isso é útil quando a ampliação no hardware não está funcionando corretamente.", + "softwareMagnificationDescriptionMacOS": "Quando ativado, a ampliação não ocorrerá na GPU. Isso é útil quando a ampliação no hardware não está funcionando corretamente. No macOS, isso não pode ser desativado.", + "eventMagnification": "Ampliar Evento", + "eventMagnificationDescription": "Ampliar o vídeo do evento quando selecionado. Isso é útil quando você deseja ver o evento em mais detalhes.", "developerOptions": "Opções de Desenvolvedor", "openLogFile": "Abrir Arquivo de Log", "openAppDataDirectory": "Abrir Diretório de Dados do Aplicativo", diff --git a/lib/screens/settings/advanced_options.dart b/lib/screens/settings/advanced_options.dart index 037b1c98..12aa8284 100644 --- a/lib/screens/settings/advanced_options.dart +++ b/lib/screens/settings/advanced_options.dart @@ -74,7 +74,7 @@ class _AdvancedOptionsSettingsState extends State { final settings = context.watch(); return ListView(children: [ - SubHeader(loc.matrixZoom), + SubHeader(loc.matrixMagnification), CheckboxListTile.adaptive( secondary: CircleAvatar( backgroundColor: Colors.transparent, @@ -82,8 +82,8 @@ class _AdvancedOptionsSettingsState extends State { child: const Icon(Icons.crop), ), contentPadding: DesktopSettings.horizontalPadding, - title: Text(loc.matrixedViewZoom), - subtitle: Text(loc.matrixedViewZoomDescription), + title: Text(loc.matrixedViewMagnification), + subtitle: Text(loc.matrixedViewMagnificationDescription), value: settings.kMatrixedZoomEnabled.value, onChanged: (value) { if (value != null) { @@ -110,15 +110,15 @@ class _AdvancedOptionsSettingsState extends State { secondary: CircleAvatar( backgroundColor: Colors.transparent, foregroundColor: theme.iconTheme.color, - child: const Icon(Icons.center_focus_strong), + child: const Icon(Icons.zoom_in_map), ), - title: Text(loc.softwareZoom), + title: Text(loc.softwareMagnification), subtitle: Text( Platform.isMacOS - ? loc.softwareZoomDescriptionMacOS - : loc.softwareZoomDescription, + ? loc.softwareMagnificationDescriptionMacOS + : loc.softwareMagnificationDescription, ), - value: Platform.isMacOS ? true : settings.kSoftwareZooming.value, + value: settings.kSoftwareZooming.value, onChanged: Platform.isMacOS ? null : (v) { @@ -128,6 +128,23 @@ class _AdvancedOptionsSettingsState extends State { }, dense: false, ), + CheckboxListTile.adaptive( + contentPadding: DesktopSettings.horizontalPadding, + secondary: CircleAvatar( + backgroundColor: Colors.transparent, + foregroundColor: theme.iconTheme.color, + child: const Icon(Icons.center_focus_weak), + ), + title: Text(loc.eventMagnification), + subtitle: Text(loc.eventMagnificationDescription), + value: settings.kEventsMatrixedZoom.value, + onChanged: (v) { + if (v != null) { + settings.kEventsMatrixedZoom.value = v; + } + }, + dense: false, + ), SubHeader(loc.developerOptions), if (!kIsWeb) ...[ FutureBuilder( From 03091e735a5680814ed74037e7c4d65ce3f2080f Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 18:53:36 -0300 Subject: [PATCH 03/11] feat: Add Multicast Viewport to Event Player --- .../layouts/desktop/multicast_view.dart | 19 ++++++++---- lib/screens/players/event_player_desktop.dart | 29 ++++++++++------- lib/utils/video_player.dart | 31 +++++++++++++++++++ ...unity_video_player_platform_interface.dart | 2 ++ .../lib/video_view.dart | 10 +++++- 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/lib/screens/layouts/desktop/multicast_view.dart b/lib/screens/layouts/desktop/multicast_view.dart index 62655667..c2b82fe3 100644 --- a/lib/screens/layouts/desktop/multicast_view.dart +++ b/lib/screens/layouts/desktop/multicast_view.dart @@ -32,15 +32,18 @@ import 'package:provider/provider.dart'; import 'package:unity_video_player/unity_video_player.dart'; class MulticastViewport extends StatefulWidget { - final Device device; + final Device? device; - const MulticastViewport({super.key, required this.device}); + const MulticastViewport({super.key, this.device}); @override State createState() => _MulticastViewportState(); } class _MulticastViewportState extends State { + final _placeholderDevice = Device.dump(); + Device get device => widget.device ?? _placeholderDevice; + Timer? _gap; (int row, int column)? currentZoom; @@ -98,7 +101,11 @@ class _MulticastViewportState extends State { return const SizedBox.shrink(); } - final matrixType = widget.device.matrixType ?? settings.kMatrixSize.value; + if (view.player.isRecorded && !settings.kMatrixedZoomEnabled.value) { + return const SizedBox.shrink(); + } + + final matrixType = device.matrixType ?? settings.kMatrixSize.value; final size = matrixType.size; if (view.player.isCropped) { return Listener( @@ -156,8 +163,8 @@ class _MulticastViewportState extends State { return HoverButton( onDoubleTap: () { views.updateDevice( - widget.device, - widget.device.copyWith(matrixType: matrixType.next), + device, + device.copyWith(matrixType: matrixType.next), ); }, onPressed: () { @@ -184,7 +191,7 @@ class _MulticastViewportState extends State { ), ), ), - for (final overlay in widget.device.overlays) + for (final overlay in device.overlays) if (overlay.visible) Positioned( left: constraints.maxWidth * (overlay.position.dx / 100), diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index a2384829..e48a9eae 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -26,9 +26,11 @@ import 'package:bluecherry_client/models/event.dart'; import 'package:bluecherry_client/providers/downloads_provider.dart'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/screens/downloads/indicators.dart'; +import 'package:bluecherry_client/screens/layouts/desktop/multicast_view.dart'; import 'package:bluecherry_client/screens/layouts/video_status_label.dart'; import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/extensions.dart'; +import 'package:bluecherry_client/utils/video_player.dart'; import 'package:bluecherry_client/widgets/collapsable_sidebar.dart'; import 'package:bluecherry_client/widgets/desktop_buttons.dart'; import 'package:bluecherry_client/widgets/error_warning.dart'; @@ -101,12 +103,7 @@ class _EventPlayerDesktopState extends State { void initState() { super.initState(); currentEvent = widget.event; - videoController = widget.player ?? - UnityVideoPlayer.create( - quality: UnityVideoQuality.p480, - enableCache: true, - title: title, - ); + videoController = widget.player ?? UnityPlayers.forEvent(widget.event); fit = device?.server.additionalSettings.videoFit ?? SettingsProvider.instance.kVideoFit.value; playingSubscription = @@ -199,10 +196,10 @@ class _EventPlayerDesktopState extends State { heroTag: currentEvent.mediaURL, player: videoController, fit: fit, - paneBuilder: (context, controller) { + paneBuilder: (context, player) { final video = UnityVideoView.of(context); - return Stack(children: [ + return Stack(clipBehavior: Clip.none, children: [ if (video.error != null) Center( child: ErrorWarning(message: video.error!), @@ -211,16 +208,24 @@ class _EventPlayerDesktopState extends State { Padding( padding: const EdgeInsets.all(8.0), child: Text( - 'source: ${controller.dataSource}' - '\nposition: ${controller.currentPos}' - '\nduration ${controller.duration}' - '\nbuffer ${controller.currentBuffer}', + 'source: ${player.dataSource}' + '\nposition: ${player.currentPos}' + '\nduration ${player.duration}' + '\nbuffer ${player.currentBuffer}', style: theme.textTheme.labelSmall?.copyWith( color: Colors.white, shadows: outlinedText(), ), ), ), + Positioned.fill( + child: Center( + child: Container( + // color: Colors.amber, + child: const MulticastViewport(), + ), + ), + ), PositionedDirectional( bottom: 8.0, end: 8.0, diff --git a/lib/utils/video_player.dart b/lib/utils/video_player.dart index c22d29cc..6137ebf3 100644 --- a/lib/utils/video_player.dart +++ b/lib/utils/video_player.dart @@ -20,6 +20,7 @@ import 'dart:async'; import 'package:bluecherry_client/models/device.dart'; +import 'package:bluecherry_client/models/event.dart'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/utils/logging.dart'; import 'package:flutter/foundation.dart'; @@ -139,6 +140,36 @@ class UnityPlayers with ChangeNotifier { return controller; } + static UnityVideoPlayer forEvent(Event event) { + SettingsProvider settings() => SettingsProvider.instance; + + final controller = UnityVideoPlayer.create( + // quality: settings().kRenderingQuality.value.playerQuality, + quality: UnityVideoQuality.p480, + enableCache: true, + title: event.title, + matrixType: settings().kMatrixSize.value, + softwareZoom: settings().kSoftwareZooming.value, + onLog: (message) { + logStreamToFile( + event.mediaURL?.toString() ?? 'Event ${event.title} (${event.id})', + message, + ); + }, + ) + ..setDataSource(event.mediaURL!.toString()) + ..setVolume(1.0) + ..setSpeed(1.0); + + controller.onError.listen((event) { + writeLogToFile( + 'An error ocurred when playing a video (${controller.dataSource}): $event\n', + ); + }); + + return controller; + } + /// Release the video player for the given [Device]. static Future releaseDevice(String deviceUUID) async { debugPrint('Releasing device $deviceUUID. ${players[deviceUUID]}'); 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 b0d78943..4ff5c7df 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 @@ -334,6 +334,8 @@ abstract class UnityVideoPlayer with ChangeNotifier { return source.contains('media/mjpeg') || source.contains('.m3u8'); } + bool get isRecorded => !isLive; + void _handleLateVideo() { switch (lateVideoBehavior) { case LateVideoBehavior.automatic: diff --git a/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart b/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart index ffa2fb7d..d489eda3 100644 --- a/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart +++ b/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart @@ -171,7 +171,15 @@ class UnityVideoViewState extends State { }); } - return widget.videoBuilder?.call(context, output) ?? output; + return Center( + child: AspectRatio( + aspectRatio: widget.player.aspectRatio == 0 || + widget.player.aspectRatio == double.infinity + ? 16 / 9 + : widget.player.aspectRatio, + child: widget.videoBuilder?.call(context, output) ?? output, + ), + ); }, paneBuilder: widget.paneBuilder, ), From 90225e00b3ad05f5c2cd8c36e5d192d8a8435679 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 19:01:08 -0300 Subject: [PATCH 04/11] fix: Correctly switch through matrix sizes on event multicast view --- lib/models/device.dart | 5 +++-- .../layouts/desktop/multicast_view.dart | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/models/device.dart b/lib/models/device.dart index 97c0cc6a..8293834e 100644 --- a/lib/models/device.dart +++ b/lib/models/device.dart @@ -140,11 +140,12 @@ class Device { this.resolutionY = 480, this.hasPTZ = false, this.url, - this.matrixType = MatrixType.t16, + MatrixType? matrixType, this.overlays = const [], this.preferredStreamingType, this.externalData, - }) : server = Server.dump(); + }) : server = Server.dump(), + matrixType = matrixType ?? SettingsProvider.instance.kMatrixSize.value; String get uri => 'live/$id'; diff --git a/lib/screens/layouts/desktop/multicast_view.dart b/lib/screens/layouts/desktop/multicast_view.dart index c2b82fe3..376c0ab6 100644 --- a/lib/screens/layouts/desktop/multicast_view.dart +++ b/lib/screens/layouts/desktop/multicast_view.dart @@ -41,7 +41,7 @@ class MulticastViewport extends StatefulWidget { } class _MulticastViewportState extends State { - final _placeholderDevice = Device.dump(); + var _placeholderDevice = Device.dump(); Device get device => widget.device ?? _placeholderDevice; Timer? _gap; @@ -162,10 +162,20 @@ class _MulticastViewportState extends State { return HoverButton( onDoubleTap: () { - views.updateDevice( - device, - device.copyWith(matrixType: matrixType.next), - ); + if (widget.device != null) { + views.updateDevice( + device, + device.copyWith(matrixType: matrixType.next), + ); + } else { + setState(() { + _placeholderDevice = _placeholderDevice.copyWith( + matrixType: matrixType.next, + ); + view.player.zoom.matrixType = + _placeholderDevice.matrixType!; + }); + } }, onPressed: () { view.player.crop(row, col); From 36c9c86a703b92eb5737503c43ba01cf4b605ce5 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 19:04:21 -0300 Subject: [PATCH 05/11] fix: Ensure a video view will not overlap the video size --- .../lib/video_view.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart b/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart index d489eda3..a980a620 100644 --- a/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart +++ b/packages/unity_video_player/unity_video_player_platform_interface/lib/video_view.dart @@ -181,7 +181,19 @@ class UnityVideoViewState extends State { ), ); }, - paneBuilder: widget.paneBuilder, + paneBuilder: widget.paneBuilder == null + ? null + : (context, player) { + return Center( + child: AspectRatio( + aspectRatio: widget.player.aspectRatio == 0 || + widget.player.aspectRatio == double.infinity + ? 16 / 9 + : widget.player.aspectRatio, + child: widget.paneBuilder?.call(context, player), + ), + ); + }, ), ); From 3326ee3595e88df7befe1d87c7514570452344b3 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 19:13:32 -0300 Subject: [PATCH 06/11] fix: Correctly reflect matrix size change --- lib/providers/desktop_view_provider.dart | 8 ++++++-- lib/screens/layouts/desktop/multicast_view.dart | 11 +++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/providers/desktop_view_provider.dart b/lib/providers/desktop_view_provider.dart index 834aa18e..9bef40cb 100644 --- a/lib/providers/desktop_view_provider.dart +++ b/lib/providers/desktop_view_provider.dart @@ -320,7 +320,9 @@ class DesktopViewProvider extends UnityProvider { /// Updates a device in all the layouts. /// /// If [reload] is `true`, the device player will be reloaded. - Future updateDevice( + /// + /// Return the new device. + Device updateDevice( Device previousDevice, Device device, { bool reload = false, @@ -343,6 +345,8 @@ class DesktopViewProvider extends UnityProvider { } notifyListeners(); - return save(notifyListeners: false); + save(notifyListeners: false); + + return device; } } diff --git a/lib/screens/layouts/desktop/multicast_view.dart b/lib/screens/layouts/desktop/multicast_view.dart index 376c0ab6..affb4759 100644 --- a/lib/screens/layouts/desktop/multicast_view.dart +++ b/lib/screens/layouts/desktop/multicast_view.dart @@ -41,8 +41,7 @@ class MulticastViewport extends StatefulWidget { } class _MulticastViewportState extends State { - var _placeholderDevice = Device.dump(); - Device get device => widget.device ?? _placeholderDevice; + late Device device = widget.device ?? Device.dump(); Timer? _gap; @@ -163,17 +162,17 @@ class _MulticastViewportState extends State { return HoverButton( onDoubleTap: () { if (widget.device != null) { - views.updateDevice( + device = views.updateDevice( device, device.copyWith(matrixType: matrixType.next), ); + setState(() {}); } else { setState(() { - _placeholderDevice = _placeholderDevice.copyWith( + device = device.copyWith( matrixType: matrixType.next, ); - view.player.zoom.matrixType = - _placeholderDevice.matrixType!; + view.player.zoom.matrixType = device.matrixType!; }); } }, From 31f9258e6f4f9d1f1328909c68dbf16e67768114 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 19:20:36 -0300 Subject: [PATCH 07/11] feat: Add Multicast View to Timeline --- lib/screens/events_timeline/desktop/timeline.dart | 2 ++ lib/screens/events_timeline/desktop/timeline_card.dart | 4 ++++ lib/screens/players/event_player_desktop.dart | 9 +-------- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/screens/events_timeline/desktop/timeline.dart b/lib/screens/events_timeline/desktop/timeline.dart index 953a185d..156502e1 100644 --- a/lib/screens/events_timeline/desktop/timeline.dart +++ b/lib/screens/events_timeline/desktop/timeline.dart @@ -73,6 +73,8 @@ class TimelineTile { quality: UnityVideoQuality.p480, enableCache: true, title: device.fullName, + softwareZoom: SettingsProvider.instance.kSoftwareZooming.value, + matrixType: SettingsProvider.instance.kMatrixSize.value, ); videoController.setMultipleDataSource( events.map((event) => event.videoUrl), diff --git a/lib/screens/events_timeline/desktop/timeline_card.dart b/lib/screens/events_timeline/desktop/timeline_card.dart index 4ea0fc73..ed901b9b 100644 --- a/lib/screens/events_timeline/desktop/timeline_card.dart +++ b/lib/screens/events_timeline/desktop/timeline_card.dart @@ -22,6 +22,7 @@ import 'package:bluecherry_client/providers/downloads_provider.dart'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/screens/downloads/indicators.dart'; import 'package:bluecherry_client/screens/events_timeline/desktop/timeline.dart'; +import 'package:bluecherry_client/screens/layouts/desktop/multicast_view.dart'; import 'package:bluecherry_client/screens/layouts/video_status_label.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/widgets/hover_button.dart'; @@ -168,6 +169,9 @@ class _TimelineCardState extends State { textAlign: TextAlign.end, ), ), + const Positioned.fill( + child: MulticastViewport(), + ), PositionedDirectional( end: 0, top: 0, diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index e48a9eae..fe6e63f4 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -218,14 +218,7 @@ class _EventPlayerDesktopState extends State { ), ), ), - Positioned.fill( - child: Center( - child: Container( - // color: Colors.amber, - child: const MulticastViewport(), - ), - ), - ), + const Positioned.fill(child: MulticastViewport()), PositionedDirectional( bottom: 8.0, end: 8.0, From 8092eca623a00873ea38aa1e36ab6186a50bd043 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 19:53:38 -0300 Subject: [PATCH 08/11] feat: Correct UI for Timeline --- .../events_timeline/desktop/timeline.dart | 83 +++++++++++------ .../desktop/timeline_card.dart | 88 ++++++++++--------- .../events_timeline/events_playback.dart | 7 ++ 3 files changed, 112 insertions(+), 66 deletions(-) diff --git a/lib/screens/events_timeline/desktop/timeline.dart b/lib/screens/events_timeline/desktop/timeline.dart index 156502e1..59b09c57 100644 --- a/lib/screens/events_timeline/desktop/timeline.dart +++ b/lib/screens/events_timeline/desktop/timeline.dart @@ -112,49 +112,58 @@ class TimelineEvent { return [ TimelineEvent( duration: const Duration(minutes: 1), - startTime: DateTime(2023).add( - Duration(hours: Random().nextInt(4), minutes: Random().nextInt(60)), - ), + startTime: DateTime.now() + .add( + Duration( + hours: Random().nextInt(4), minutes: Random().nextInt(60)), + ) + .toUtc(), event: Event.dump(), ), TimelineEvent( duration: const Duration(hours: 1), - startTime: DateTime(2023).add(Duration(hours: Random().nextInt(4) + 5)), + startTime: DateTime.now() + .add(Duration(hours: Random().nextInt(4) + 5)) + .toUtc(), event: Event.dump(), ), TimelineEvent( duration: const Duration(minutes: 1), - startTime: DateTime(2023).add(Duration(hours: Random().nextInt(4) + 9)), + startTime: DateTime.now() + .add(Duration(hours: Random().nextInt(4) + 9)) + .toUtc(), event: Event.dump(), ), TimelineEvent( duration: const Duration(minutes: 1), - startTime: DateTime(2023).add( - Duration( - hours: Random().nextInt(4) + 13, - minutes: Random().nextInt(60), - ), - ), + startTime: DateTime.now() + .add( + Duration( + hours: Random().nextInt(4) + 13, + minutes: Random().nextInt(60), + ), + ) + .toUtc(), event: Event.dump(), ), TimelineEvent( duration: const Duration(minutes: 1), - startTime: DateTime(2023).add( - Duration( - hours: Random().nextInt(4) + 14, - minutes: Random().nextInt(60), - ), - ), + startTime: DateTime.now() + .add(Duration( + hours: Random().nextInt(4) + 14, + minutes: Random().nextInt(60), + )) + .toUtc(), event: Event.dump(), ), TimelineEvent( duration: const Duration(minutes: 1), - startTime: DateTime(2023).add( - Duration( - hours: Random().nextInt(4) + 20, - minutes: Random().nextInt(60), - ), - ), + startTime: DateTime.now() + .add(Duration( + hours: Random().nextInt(4) + 20, + minutes: Random().nextInt(60), + )) + .toUtc(), event: Event.dump(), ), ]; @@ -181,13 +190,14 @@ class Timeline extends ChangeNotifier { final List tiles = []; /// All the events must have happened in the same day - final DateTime date; + late final DateTime date; Timeline({ required List tiles, - required this.date, + required DateTime date, Duration initialPosition = Duration.zero, }) { + this.date = DateTime(date.year, date.month, date.day).toLocal(); currentPosition = initialPosition; add(tiles.where((tile) => tile.events.isNotEmpty)); @@ -203,6 +213,29 @@ class Timeline extends ChangeNotifier { zoomController.addListener(notifyListeners); } + Timeline.dump() + : this( + tiles: [ + TimelineTile( + device: Device.dump(name: 'device1'), + events: TimelineEvent.fakeData, + ), + TimelineTile( + device: Device.dump(name: 'device2'), + events: TimelineEvent.fakeData, + ), + TimelineTile( + device: Device.dump(name: 'device3'), + events: TimelineEvent.fakeData, + ), + TimelineTile( + device: Device.dump(name: 'device4'), + events: TimelineEvent.fakeData, + ), + ], + date: DateTime.now(), + ); + void _eventCallback(TimelineTile tile, {bool notify = true}) { if (tile.videoController.duration <= Duration.zero) return; diff --git a/lib/screens/events_timeline/desktop/timeline_card.dart b/lib/screens/events_timeline/desktop/timeline_card.dart index ed901b9b..c5a73e37 100644 --- a/lib/screens/events_timeline/desktop/timeline_card.dart +++ b/lib/screens/events_timeline/desktop/timeline_card.dart @@ -66,7 +66,7 @@ class _TimelineCardState extends State { color: Colors.transparent, surfaceTintColor: Colors.transparent, child: UnityVideoView( - heroTag: device.streamURL, + // heroTag: device.streamURL, player: widget.tile.videoController, color: Colors.transparent, fit: _fit ?? @@ -110,54 +110,58 @@ class _TimelineCardState extends State { final video = UnityVideoView.of(context); + const paddingSize = 12.0; + return HoverButton( forceEnabled: true, - margin: const EdgeInsetsDirectional.all(16.0), builder: (_, states) => Stack(clipBehavior: Clip.none, children: [ - RichText( - text: TextSpan( - text: '', - style: theme.textTheme.labelLarge!.copyWith( - color: Colors.white, - shadows: outlinedText(strokeWidth: 0.75), - ), - children: [ - TextSpan( - text: device.name, - style: theme.textTheme.titleMedium!.copyWith( - color: Colors.white, - shadows: outlinedText(strokeWidth: 0.75), - ), + Padding( + padding: const EdgeInsetsDirectional.all(paddingSize), + child: RichText( + text: TextSpan( + text: '', + style: theme.textTheme.labelLarge!.copyWith( + color: Colors.white, + shadows: outlinedText(strokeWidth: 0.75), ), - const TextSpan(text: '\n'), - if (states.isHovering) - TextSpan( - text: settings.kShowDebugInfo.value - ? currentEvent - .position(widget.timeline.currentDate) - .toString() - : currentEvent - .position(widget.timeline.currentDate) - .humanReadableCompact(context), - ), - if (settings.kShowDebugInfo.value) ...[ - const TextSpan(text: '\ndebug: '), - TextSpan(text: controller.currentPos.toString()), - TextSpan( - text: - '\ndiff: ${currentEvent.position(widget.timeline.currentDate) - controller.currentPos}', - ), + children: [ TextSpan( - text: '\nindex: ${events.indexOf(currentEvent)}', + text: device.name, + style: theme.textTheme.titleMedium!.copyWith( + color: Colors.white, + shadows: outlinedText(strokeWidth: 0.75), + ), ), + const TextSpan(text: '\n'), + if (states.isHovering) + TextSpan( + text: settings.kShowDebugInfo.value + ? currentEvent + .position(widget.timeline.currentDate) + .toString() + : currentEvent + .position(widget.timeline.currentDate) + .humanReadableCompact(context), + ), + if (settings.kShowDebugInfo.value) ...[ + const TextSpan(text: '\ndebug: '), + TextSpan(text: controller.currentPos.toString()), + TextSpan( + text: + '\ndiff: ${currentEvent.position(widget.timeline.currentDate) - controller.currentPos}', + ), + TextSpan( + text: '\nindex: ${events.indexOf(currentEvent)}', + ), + ], ], - ], + ), ), ), if (settings.kShowDebugInfo.value) Positioned( top: 36.0, - right: 0.0, + right: paddingSize, child: Text( 'buffer: ' '${(widget.tile.videoController.currentBuffer.inMilliseconds / widget.tile.videoController.duration.inMilliseconds).toStringAsPrecision(2)}' @@ -173,8 +177,8 @@ class _TimelineCardState extends State { child: MulticastViewport(), ), PositionedDirectional( - end: 0, - top: 0, + end: paddingSize, + top: paddingSize, height: 24.0, width: 24.0, child: () { @@ -194,7 +198,8 @@ class _TimelineCardState extends State { }(), ), if (states.isHovering) - Align( + Container( + margin: const EdgeInsetsDirectional.all(paddingSize), alignment: AlignmentDirectional.bottomStart, child: RichText( text: TextSpan( @@ -216,7 +221,8 @@ class _TimelineCardState extends State { ), ), ), - Align( + Container( + margin: const EdgeInsetsDirectional.all(paddingSize), alignment: AlignmentDirectional.bottomEnd, child: SizedBox( height: 24.0, diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index dff4cbb6..c22725ef 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -30,6 +30,7 @@ import 'package:bluecherry_client/screens/events_timeline/desktop/timeline_sideb import 'package:bluecherry_client/screens/events_timeline/mobile/timeline_device_view.dart'; import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; @@ -83,6 +84,12 @@ class _EventsPlaybackState extends EventsScreenState { timeline?.dispose(); timeline = null; }); + + // if (kDebugMode && true) { + // setState(() => timeline = Timeline.dump()); + // return; + // } + await super.fetch( startDate: startDate, endDate: endDate, From a3149a878b38665595105b95b8a991e7c32e5a1e Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 20:06:00 -0300 Subject: [PATCH 09/11] feat: Collapse Timeline Tiles button --- .../events_timeline/desktop/timeline.dart | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/screens/events_timeline/desktop/timeline.dart b/lib/screens/events_timeline/desktop/timeline.dart index 59b09c57..5863dae2 100644 --- a/lib/screens/events_timeline/desktop/timeline.dart +++ b/lib/screens/events_timeline/desktop/timeline.dart @@ -546,6 +546,8 @@ class _TimelineEventsViewState extends State { final verticalScrollController = ScrollController(); + bool _isCollapsed = false; + @override void initState() { super.initState(); @@ -632,6 +634,16 @@ class _TimelineEventsViewState extends State { end: 8.0, ), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + SquaredIconButton( + icon: + Icon(_isCollapsed ? Icons.expand_more : Icons.expand_less), + onPressed: () { + setState(() { + _isCollapsed = !_isCollapsed; + }); + }, + tooltip: _isCollapsed ? loc.expand : loc.collapse, + ), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ if (timeline.pausedToBuffer.isNotEmpty) @@ -731,11 +743,16 @@ class _TimelineEventsViewState extends State { '${settings.kDateFormat.value.format(timeline.currentDate)} ' '${timelineTimeFormat.format(timeline.currentDate)}', ), - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: _kTimelineTileHeight * 5.0, + AnimatedContainer( + duration: const Duration(milliseconds: 300), + constraints: BoxConstraints( + maxHeight: _isCollapsed ? 0.0 : _kTimelineTileHeight * 5.0, ), child: LayoutBuilder(builder: (context, constraints) { + if (constraints.maxHeight < _kTimelineTileHeight / 1.9) { + return const SizedBox.shrink(); + } + final tileWidth = (constraints.maxWidth - _kDeviceNameWidth) * timeline.zoom; final hourWidth = tileWidth / 24; From 969b8eb8b20433f4595a9001d03120d2e260a9c1 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 20:08:59 -0300 Subject: [PATCH 10/11] fix: Correctly disable events area magnification --- lib/screens/layouts/desktop/multicast_view.dart | 2 +- lib/screens/layouts/video_status_label.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/layouts/desktop/multicast_view.dart b/lib/screens/layouts/desktop/multicast_view.dart index affb4759..27d6f92b 100644 --- a/lib/screens/layouts/desktop/multicast_view.dart +++ b/lib/screens/layouts/desktop/multicast_view.dart @@ -100,7 +100,7 @@ class _MulticastViewportState extends State { return const SizedBox.shrink(); } - if (view.player.isRecorded && !settings.kMatrixedZoomEnabled.value) { + if (view.player.isRecorded && !settings.kEventsMatrixedZoom.value) { return const SizedBox.shrink(); } diff --git a/lib/screens/layouts/video_status_label.dart b/lib/screens/layouts/video_status_label.dart index c5ab6cd5..6263a956 100644 --- a/lib/screens/layouts/video_status_label.dart +++ b/lib/screens/layouts/video_status_label.dart @@ -86,7 +86,7 @@ class _VideoStatusLabelState extends State { ? VideoLabel.error : isLoading ? VideoLabel.loading - : !widget.video.player.isLive + : widget.video.player.isRecorded ? VideoLabel.recorded : widget.video.player.isImageOld ? VideoLabel.timedOut From e07cfb397ddac0968e4ce93366f0c3dca4b19141 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Tue, 23 Jul 2024 20:09:16 -0300 Subject: [PATCH 11/11] chore: Update imports --- lib/screens/events_timeline/events_playback.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index c22725ef..e6eba832 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -30,7 +30,6 @@ import 'package:bluecherry_client/screens/events_timeline/desktop/timeline_sideb import 'package:bluecherry_client/screens/events_timeline/mobile/timeline_device_view.dart'; import 'package:bluecherry_client/utils/date.dart'; import 'package:bluecherry_client/utils/methods.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart';