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 '../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 createState() => _AdMediaState(); } class _AdMediaState extends State { 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 Image.network( widget.ad.imageUrl, fit: widget.fit, width: double.infinity, height: double.infinity, errorBuilder: (_, _, _) => _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), ), ); }