Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
if (displayAsset.isImage && !isPlayingMotionVideo) {
final size = context.sizeData;
return PhotoView(
key: ValueKey(displayAsset.heroTag),
key: Key(displayAsset.heroTag),
index: widget.index,
imageProvider: getFullImageProvider(displayAsset, size: size),
heroAttributes: heroAttributes,
Expand Down Expand Up @@ -334,7 +334,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
}

return PhotoView.customChild(
key: ValueKey(displayAsset),
key: Key(displayAsset.heroTag),
onDragStart: _onDragStart,
onDragUpdate: _onDragUpdate,
onDragEnd: _onDragEnd,
Expand All @@ -350,12 +350,11 @@ class _AssetPageState extends ConsumerState<AssetPage> {
enablePanAlways: true,
backgroundDecoration: backgroundDecoration,
child: NativeVideoViewer(
key: ValueKey(displayAsset),
key: _NativeVideoViewerKey(displayAsset.heroTag),
asset: displayAsset,
scaleStateNotifier: _videoScaleStateNotifier,
disableScaleGestures: showingDetails,
image: Image(
key: ValueKey(displayAsset.heroTag),
image: getFullImageProvider(displayAsset, size: context.sizeData),
height: context.height,
width: context.width,
Expand Down Expand Up @@ -459,3 +458,25 @@ class _AssetPageState extends ConsumerState<AssetPage> {
);
}
}

// A global key is used for video viewers to prevent them from being
// unnecessarily recreated. They're quite expensive, and maintain internal
// state. This can cause videos to restart multiple times during normal usage,
// like a hero animation.
//
// A plain ValueKey is insufficient, as it does not allow widgets to reparent. A
// GlobalObjectKey is fragile, as it checks if the given objects are identical,
// rather than equal. Hero tags are created with string interpolation, which
// prevents Dart from interning them. As such, hero tags are not identical, even
// if they are equal.
class _NativeVideoViewerKey extends GlobalKey {
final String value;

const _NativeVideoViewerKey(this.value) : super.constructor();

@override
bool operator ==(Object other) => other is _NativeVideoViewerKey && other.value == value;

@override
int get hashCode => value.hashCode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -420,20 +420,18 @@ class NativeVideoViewer extends HookConsumerWidget {
child: Stack(
children: [
// Hide thumbnail once video is visible to avoid it showing in background when zooming out on video.
if (!isVisible.value || controller.value == null) Center(key: ValueKey(asset.heroTag), child: image),
if (!isVisible.value || controller.value == null) Center(child: image),
if (aspectRatio.value != null && !isCasting && isCurrent)
Visibility.maintain(
key: ValueKey(asset),
visible: isVisible.value,
child: PhotoView.customChild(
key: ValueKey(asset),
enableRotation: false,
disableScaleGestures: disableScaleGestures,
// Transparent to avoid a black flash when viewer becomes visible but video isn't loaded yet.
backgroundDecoration: const BoxDecoration(color: Colors.transparent),
scaleStateChangedCallback: (state) => scaleStateNotifier?.value = state,
childSize: videoContextSize(aspectRatio.value, context),
child: NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController),
child: NativeVideoPlayerView(onViewReady: initController),
),
),
if (showControls) const Center(child: VideoViewerControls()),
Expand Down
Loading