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