Files
SpotiFLAC-Mobile/lib/widgets/mornye_player_artwork.dart
T

100 lines
3.2 KiB
Dart

import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/providers/player_artwork_video_provider.dart';
import 'package:spotiflac_android/widgets/motion_header_banner.dart';
import 'package:spotiflac_android/widgets/player_artwork.dart';
import 'package:video_player/video_player.dart';
class MornyePlayerArtwork extends ConsumerStatefulWidget {
const MornyePlayerArtwork({
super.key,
required this.mediaItem,
this.videoUrl,
this.onAspectRatioChanged,
this.onError,
this.resolvingVideo = false,
});
final MediaItem mediaItem;
final String? videoUrl;
final ValueChanged<double>? onAspectRatioChanged;
final VoidCallback? onError;
final bool resolvingVideo;
@override
ConsumerState<MornyePlayerArtwork> createState() =>
_MornyePlayerArtworkState();
}
class _MornyePlayerArtworkState extends ConsumerState<MornyePlayerArtwork> {
String? _displayedSource;
VideoPlayerController? _displayedController;
@override
Widget build(BuildContext context) {
final fallback = Align(
alignment: Alignment.topCenter,
child: AspectRatio(
aspectRatio: 1,
child: PlayerArtwork(
artUri: widget.mediaItem.artUri?.toString(),
colorScheme: Theme.of(context).colorScheme,
cacheWidth:
(MediaQuery.sizeOf(context).width *
MediaQuery.devicePixelRatioOf(context))
.round(),
),
),
);
if (MediaQuery.disableAnimationsOf(context)) {
_displayedSource = null;
_displayedController = null;
return fallback;
}
final videoUrl = widget.videoUrl;
// Keep one outgoing decoder alive until the next cover is ready. Rapid
// skips must not replace a visible frame with a loading placeholder.
if (_displayedSource != null && _displayedSource != videoUrl) {
ref.watch(playerArtworkVideoProvider(_displayedSource!));
}
final prepared = videoUrl == null
? null
: ref.watch(playerArtworkVideoProvider(videoUrl));
if (prepared?.hasError == true) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.onError?.call();
});
}
if (prepared?.value case final controller?) {
_displayedSource = videoUrl;
_displayedController = controller;
} else if (!widget.resolvingVideo &&
(videoUrl == null || prepared?.hasError == true)) {
_displayedSource = null;
_displayedController = null;
}
return Stack(
fit: StackFit.expand,
children: [
fallback,
if (_displayedController != null)
MotionHeaderBanner(
videoUrl: _displayedSource!,
fallback: const SizedBox.shrink(),
controller: _displayedController,
fadeDuration: Duration.zero,
onAspectRatioChanged: (ratio) {
if (_displayedSource == widget.videoUrl) {
widget.onAspectRatioChanged?.call(ratio);
}
},
onError: () {
if (_displayedSource == widget.videoUrl) widget.onError?.call();
},
),
],
);
}
}