Files
jinzhi/lib/features/ads/presentation/ad_media.dart
T

124 lines
3.2 KiB
Dart

import 'dart:async';
import 'dart:io' show File;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import '../../../common/widgets/app_network_image.dart';
import '../data/ads_api_client.dart';
/// 统一渲染广告图片/GIF/视频;开屏可优先使用已缓存的本地素材。
class AdMedia extends StatefulWidget {
const AdMedia({
super.key,
required this.ad,
this.assetPath,
this.fit = BoxFit.cover,
});
final AdCreative ad;
final String? assetPath;
final BoxFit fit;
@override
State<AdMedia> createState() => _AdMediaState();
}
class _AdMediaState extends State<AdMedia> {
VideoPlayerController? player;
bool ready = false, failed = false;
@override
void initState() {
super.initState();
_load();
}
@override
void didUpdateWidget(covariant AdMedia old) {
super.didUpdateWidget(old);
if (old.ad.adId != widget.ad.adId || old.assetPath != widget.assetPath) {
unawaited(player?.dispose() ?? Future.value());
player = null;
ready = false;
failed = false;
_load();
}
}
void _load() {
if (widget.ad.mediaType != AdMediaType.video) return;
final uri = widget.ad.mediaUri;
if (uri == null) return;
final path = widget.assetPath;
final controller = !kIsWeb && path != null && path.isNotEmpty
? VideoPlayerController.file(File(path))
: VideoPlayerController.networkUrl(uri);
player = controller;
unawaited(
controller
.initialize()
.then((_) async {
await controller.setLooping(true);
await controller.setVolume(0);
await controller.play();
if (mounted) setState(() => ready = true);
})
.catchError((_) {
if (mounted) setState(() => failed = true);
}),
);
}
@override
void dispose() {
player?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.ad.mediaType != AdMediaType.video) {
final path = widget.assetPath;
if (!kIsWeb && path != null && path.isNotEmpty) {
return Image.file(
File(path),
fit: widget.fit,
width: double.infinity,
height: double.infinity,
errorBuilder: (_, _, _) => _error(),
);
}
return AppNetworkImage(
imageUrl: widget.ad.imageUrl,
fit: widget.fit,
width: double.infinity,
height: double.infinity,
errorWidget: _error(),
);
}
final c = player;
if (failed) return _error();
if (c == null || !ready) {
return const ColoredBox(
color: Color(0xFF161616),
child: Center(child: CircularProgressIndicator()),
);
}
return FittedBox(
fit: widget.fit,
clipBehavior: Clip.hardEdge,
child: SizedBox(
width: c.value.size.width,
height: c.value.size.height,
child: VideoPlayer(c),
),
);
}
Widget _error() => const ColoredBox(
color: Color(0xFF161616),
child: Center(
child: Icon(Icons.broken_image_outlined, color: Colors.white54),
),
);
}