diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 631770a48..9a78b309c 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -289,6 +289,18 @@ class AvesEntry { return isRotated ? Size(h, w) : Size(w, h); } + Size videoDisplaySize(double sar) { + final size = displaySize; + if (sar != 1) { + final dar = displayAspectRatio * sar; + final w = size.width; + final h = size.height; + if (w >= h) return Size(w, w / dar); + if (h > w) return Size(h * dar, h); + } + return size; + } + int get megaPixels => (width * height / 1000000).round(); DateTime? _bestDate; diff --git a/lib/widgets/viewer/overlay/minimap.dart b/lib/widgets/viewer/overlay/minimap.dart index a75c1699f..e8c2845de 100644 --- a/lib/widgets/viewer/overlay/minimap.dart +++ b/lib/widgets/viewer/overlay/minimap.dart @@ -1,9 +1,11 @@ import 'dart:math'; import 'package:aves/model/entry.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class Minimap extends StatelessWidget { final AvesEntry entry; @@ -28,16 +30,30 @@ class Minimap extends StatelessWidget { if (viewportSize == null) return const SizedBox.shrink(); return AnimatedBuilder( animation: entry.imageChangeNotifier, - builder: (context, child) => CustomPaint( - painter: MinimapPainter( - viewportSize: viewportSize, - entrySize: entry.displaySize, - viewCenterOffset: viewState.position, - viewScale: viewState.scale!, - minimapBorderColor: Colors.white30, - ), - size: size, - ), + builder: (context, child) { + Widget _builder(Size displaySize) => CustomPaint( + painter: MinimapPainter( + viewportSize: viewportSize, + entrySize: displaySize, + viewCenterOffset: viewState.position, + viewScale: viewState.scale!, + minimapBorderColor: Colors.white30, + ), + size: size, + ); + + if (entry.isVideo) { + final videoController = context.read().getController(entry); + if (videoController == null) return const SizedBox(); + return ValueListenableBuilder( + valueListenable: videoController.sarNotifier, + builder: (context, sar, child) { + return _builder(entry.videoDisplaySize(sar)); + }, + ); + } + return _builder(entry.displaySize); + }, ); }), ); diff --git a/lib/widgets/viewer/video/controller.dart b/lib/widgets/viewer/video/controller.dart index 73048e113..bdbfef65d 100644 --- a/lib/widgets/viewer/video/controller.dart +++ b/lib/widgets/viewer/video/controller.dart @@ -37,6 +37,8 @@ abstract class AvesVideoController { Stream get positionStream; + ValueNotifier get sarNotifier; + Widget buildPlayerWidget(BuildContext context); } diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index b2e830b26..41ff90954 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -9,11 +9,11 @@ import 'package:aves/model/video/metadata.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:collection/collection.dart'; + // ignore: import_of_legacy_library_into_null_safe import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class IjkPlayerAvesVideoController extends AvesVideoController { late FijkPlayer _instance; @@ -25,9 +25,11 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final ValueNotifier _selectedVideoStream = ValueNotifier(null); final ValueNotifier _selectedAudioStream = ValueNotifier(null); final ValueNotifier _selectedTextStream = ValueNotifier(null); - final ValueNotifier> _sar = ValueNotifier(const Tuple2(1, 1)); Timer? _initialPlayTimer; + @override + final ValueNotifier sarNotifier = ValueNotifier(1); + Stream get _valueStream => _valueStreamController.stream; static const initialPlayDelay = Duration(milliseconds: 100); @@ -59,7 +61,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { } Future _init({int startMillis = 0}) async { - _sar.value = const Tuple2(1, 1); + sarNotifier.value = 1; _applyOptions(startMillis); // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts @@ -165,7 +167,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { if (streamInfo != null) { final num = streamInfo[Keys.sarNum] ?? 0; final den = streamInfo[Keys.sarDen] ?? 0; - _sar.value = Tuple2(num != 0 ? num : 1, den != 0 ? den : 1); + sarNotifier.value = (num != 0 ? num : 1) / (den != 0 ? den : 1); } } } @@ -232,15 +234,12 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Widget buildPlayerWidget(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: _sar, + return ValueListenableBuilder( + valueListenable: sarNotifier, builder: (context, sar, child) { - final sarNum = sar.item1; - final sarDen = sar.item2; // derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 - final dar = entry.displayAspectRatio * sarNum / sarDen; - // TODO TLAD notify SAR to make the magnifier and minimap use the rendering DAR instead of entry DAR + final dar = entry.displayAspectRatio * sar; return FijkView( player: _instance, fit: FijkFit( diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 48f12d762..9a2e2091b 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -193,12 +193,17 @@ class _EntryPageViewState extends State { return Stack( fit: StackFit.expand, children: [ - _buildMagnifier( - child: VideoView( - entry: entry, - controller: videoController, - ), - ), + ValueListenableBuilder( + valueListenable: videoController.sarNotifier, + builder: (context, sar, child) { + return _buildMagnifier( + displaySize: entry.videoDisplaySize(sar), + child: VideoView( + entry: entry, + controller: videoController, + ), + ); + }), // fade out image to ease transition with the player StreamBuilder( stream: videoController.statusStream, @@ -231,13 +236,14 @@ class _EntryPageViewState extends State { ScaleLevel maxScale = maxScale, ScaleStateCycle scaleStateCycle = defaultScaleStateCycle, bool applyScale = true, + Size? displaySize, required Widget child, }) { return Magnifier( // key includes modified date to refresh when the image is modified by metadata (e.g. rotated) key: ValueKey('${entry.pageId}_${entry.dateModifiedSecs}'), controller: _magnifierController, - childSize: entry.displaySize, + childSize: displaySize ?? entry.displaySize, minScale: minScale, maxScale: maxScale, initialScale: initialScale,