import 'package:flutter/material.dart'; import 'package:flutter_svga/flutter_svga.dart'; import 'dart:async'; import 'dart:typed_data'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart'; // 动画队列项 class AnimationQueueItem { final String resource; final Map? dynamicData; // 动态数据(文本、图片等) final VoidCallback? onCompleted; final Function(String)? onError; Completer? completer; AnimationQueueItem({ required this.resource, this.dynamicData, this.onCompleted, this.onError, }); } // 动态数据类型 class DynamicData { final Map? textReplacements; // 文本替换 final Map? imageReplacements; // 图片替换(二进制数据) final Map? imageAssetPaths; // 图片资源路径 DynamicData({ this.textReplacements, this.imageReplacements, this.imageAssetPaths, }); } // 动画队列管理器 class RoomEntranceQueueManager { static final RoomEntranceQueueManager _instance = RoomEntranceQueueManager._internal(); factory RoomEntranceQueueManager() => _instance; RoomEntranceQueueManager._internal(); // 队列列表 final List _queue = []; // 当前是否正在播放 bool _isPlaying = false; // 当前播放器实例 _RoomEntranceWidgetState? _currentPlayer; // 队列回调 void Function()? onQueueUpdated; // 最大队列长度限制 int maxQueueLength = 20; // 自动播放延迟时间(毫秒) int autoPlayDelay = 500; // 添加动画到队列 Future addToQueue(AnimationQueueItem item) { final completer = Completer(); item.completer = completer; // 检查队列长度限制 if (_queue.length >= maxQueueLength) { // 移除最早的动画 final removedItem = _queue.removeAt(0); removedItem.completer?.completeError('Queue overflow, item removed'); } _queue.add(item); // 通知队列更新 if (onQueueUpdated != null) { onQueueUpdated!(); } // 如果没有正在播放,则开始播放队列 if (!_isPlaying) { _startNextAnimation(); } return completer.future; } // 获取当前队列长度 int get queueLength => _queue.length; // 获取等待中的队列项 List get waitingItems => _queue.isNotEmpty && _isPlaying ? _queue.sublist(1) : List.from(_queue); // 设置当前播放器 void setCurrentPlayer(_RoomEntranceWidgetState player) { _currentPlayer = player; } // 清除当前播放器 void clearCurrentPlayer() { _currentPlayer = null; } // 开始播放下一个动画 void _startNextAnimation() { if (_queue.isEmpty) { _isPlaying = false; if (onQueueUpdated != null) { onQueueUpdated!(); } return; } _isPlaying = true; final nextItem = _queue.first; // 如果有延迟,则延迟播放 Future.delayed(Duration(milliseconds: autoPlayDelay), () { if (_currentPlayer != null && _currentPlayer!.mounted) { // 播放队列中的动画 _currentPlayer!.playFromQueue( resource: nextItem.resource, dynamicData: nextItem.dynamicData != null ? DynamicData( textReplacements: nextItem.dynamicData?['textReplacements'], imageReplacements: nextItem.dynamicData?['imageReplacements'], imageAssetPaths: nextItem.dynamicData?['imageAssetPaths'], ) : null, onCompleted: () { // 动画完成回调 if (nextItem.onCompleted != null) { nextItem.onCompleted!(); } // 完成Completer nextItem.completer?.complete(); // 从队列中移除当前项 _queue.removeAt(0); // 通知队列更新 if (onQueueUpdated != null) { onQueueUpdated!(); } // 播放下一个 _startNextAnimation(); }, onError: (error) { // 错误处理 if (nextItem.onError != null) { nextItem.onError!(error); } // 完成Completer(带错误) nextItem.completer?.completeError(error); // 从队列中移除当前项 _queue.removeAt(0); // 通知队列更新 if (onQueueUpdated != null) { onQueueUpdated!(); } // 播放下一个 _startNextAnimation(); }, ); } else { // 如果播放器不可用,跳过当前项 _queue.removeAt(0); _startNextAnimation(); } }); } // 清空队列 void clearQueue() { for (final item in _queue) { item.completer?.completeError('Queue cleared'); } _queue.clear(); _isPlaying = false; if (onQueueUpdated != null) { onQueueUpdated!(); } } // 移除指定位置的队列项 void removeAtIndex(int index) { if (index >= 0 && index < _queue.length) { final removedItem = _queue.removeAt(index); removedItem.completer?.completeError('Item removed from queue'); if (onQueueUpdated != null) { onQueueUpdated!(); } } } // 获取队列状态 Map get queueStatus { return { 'isPlaying': _isPlaying, 'queueLength': _queue.length, 'currentResource': _queue.isNotEmpty ? _queue.first.resource : null, 'waitingCount': _queue.length > (_isPlaying ? 1 : 0) ? _queue.length - (_isPlaying ? 1 : 0) : 0, }; } } class RoomEntranceWidget extends StatefulWidget { // 尺寸 final double? width; final double? height; // 动画控制参数 final int loops; // 循环次数(0表示无限循环) final bool clearsAfterStop; // 缓存控制 final bool useCache; // 队列相关参数 final bool useQueue; // 是否使用队列 final int queueDelay; // 队列播放延迟(毫秒) // 回调函数(不再需要onCompleted和onError,因为通过队列管理) final VoidCallback? onStartLoading; final VoidCallback? onFinishLoading; // 是否在初始化时自动开始监听队列 final bool autoStartQueue; const RoomEntranceWidget({ Key? key, this.width, this.height, this.loops = 0, this.clearsAfterStop = true, this.useCache = true, this.useQueue = true, this.queueDelay = 500, this.onStartLoading, this.onFinishLoading, this.autoStartQueue = true, }) : super(key: key); @override _RoomEntranceWidgetState createState() => _RoomEntranceWidgetState(); } class _RoomEntranceWidgetState extends State with SingleTickerProviderStateMixin { SVGAAnimationController? _animationController; bool _isLoading = false; bool _hasError = false; String? _currentResource; // 用于存储SVGAImage组件的key,以便刷新占位符 GlobalKey _svgaKey = GlobalKey(); // 队列管理器实例 final RoomEntranceQueueManager _queueManager = RoomEntranceQueueManager(); // 队列播放相关 VoidCallback? _queueCompletedCallback; Function(String)? _queueErrorCallback; // 动态数据 DynamicData? _currentDynamicData; @override void initState() { super.initState(); _animationController = SVGAAnimationController(vsync: this); // 设置队列延迟 _queueManager.autoPlayDelay = widget.queueDelay; // 设置当前播放器 _queueManager.setCurrentPlayer(this); // 添加动画状态监听 _animationController?.addStatusListener((status) { if (status == AnimationStatus.completed) { // 如果是在队列播放中,调用队列完成回调 if (_queueCompletedCallback != null) { _queueCompletedCallback!(); _queueCompletedCallback = null; _queueErrorCallback = null; } } }); // 如果需要自动开始队列,则检查队列 if (widget.autoStartQueue && widget.useQueue) { // 延迟一小段时间,确保组件已经挂载 Future.delayed(Duration(milliseconds: 100), () { _queueManager._startNextAnimation(); }); } } // 从队列播放(内部使用) void playFromQueue({ required String resource, DynamicData? dynamicData, VoidCallback? onCompleted, Function(String)? onError, }) { _queueCompletedCallback = onCompleted; _queueErrorCallback = onError; _currentDynamicData = dynamicData; _currentResource = resource; // 加载动画 _loadAnimation( resource: resource, dynamicData: dynamicData, isFromQueue: true, ); } // 加载动画 Future _loadAnimation({ required String resource, DynamicData? dynamicData, bool isFromQueue = false, }) async { if (widget.onStartLoading != null) { widget.onStartLoading!(); } setState(() { _isLoading = true; _hasError = false; }); try { final isNetworkResource = resource.startsWith('http'); MovieEntity? videoItem; if (widget.useCache) { videoItem = SCGiftVapSvgaManager().videoItemCache[resource]; } if (videoItem == null) { videoItem = isNetworkResource ? await SVGAParser.shared.decodeFromURL(resource) : await SVGAParser.shared.decodeFromAssets(resource); videoItem.autorelease = false; if (widget.useCache) { SCGiftVapSvgaManager().videoItemCache[resource] = videoItem; } } if (mounted) { // 应用动态数据(文本、图片替换) await _applyDynamicData(videoItem, dynamicData); setState(() { _animationController?.videoItem = videoItem; _isLoading = false; }); // 开始播放动画 _startAnimation(); if (widget.onFinishLoading != null) { widget.onFinishLoading!(); } } } catch (e) { if (mounted) { setState(() { _isLoading = false; _hasError = true; }); } // 队列错误回调 if (isFromQueue && _queueErrorCallback != null) { _queueErrorCallback!(e.toString()); _queueErrorCallback = null; _queueCompletedCallback = null; } // 显示错误信息 SCTts.show("SVGA加载失败: ${e.toString()}"); } } // 应用动态数据(文本、图片替换) Future _applyDynamicData( MovieEntity videoItem, DynamicData? dynamicData, ) async { if (dynamicData == null) return; // 1. 应用文本替换 if (dynamicData.textReplacements != null) { await _applyTextReplacements(videoItem, dynamicData.textReplacements!); } // 2. 应用图片替换(二进制数据) if (dynamicData.imageReplacements != null) { await _applyImageReplacements(videoItem, dynamicData.imageReplacements!); } // 3. 应用图片资源路径替换 if (dynamicData.imageAssetPaths != null) { await _applyImageAssetReplacements( videoItem, dynamicData.imageAssetPaths!, ); } } // 应用文本替换 Future _applyTextReplacements( MovieEntity videoItem, Map textReplacements, ) async { // 这里需要根据具体的SVGA插件API来替换文本 // 示例代码,具体实现取决于插件支持 if (videoItem.dynamicItem != null) { textReplacements.forEach((key, value) { try { // 根据插件API设置文本 // 注意:flutter_svga插件的API可能会有所不同 videoItem.dynamicItem!.setText( TextPainter( text: TextSpan( text: "Hello, World!", style: TextStyle( fontSize: 28, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), key, ); } catch (e) { print('设置文本占位符 $key 失败: $e'); } }); } } // 应用图片替换(二进制数据) Future _applyImageReplacements( MovieEntity videoItem, Map imageReplacements, ) async { // 检查是否支持图片替换 if (videoItem.images != null) { imageReplacements.forEach((key, imageData) { try { // 替换图片数据 // 注意:flutter_svga插件的API可能会有所不同 if (videoItem.images!.containsKey(key)) { videoItem.images![key] = imageData; } } catch (e) { print('设置图片占位符 $key 失败: $e'); } }); } } // 应用图片资源路径替换 Future _applyImageAssetReplacements( MovieEntity videoItem, Map imageAssetPaths, ) async { // 如果需要从assets加载图片,可以实现这里 // 这通常需要将assets图片转换为Uint8List } // 开始播放动画 void _startAnimation() { _animationController?.reset(); // 根据循环次数设置播放方式 if (widget.loops == 0) { _animationController?.repeat(); } else { _animationController?.repeat(count: widget.loops); } } // 播放指定资源(外部调用) Future playResource({ required String resource, DynamicData? dynamicData, }) async { if (widget.useQueue) { // 使用队列,添加到队列 final queueItem = AnimationQueueItem( resource: resource, dynamicData: dynamicData != null ? { 'textReplacements': dynamicData.textReplacements, 'imageReplacements': dynamicData.imageReplacements, 'imageAssetPaths': dynamicData.imageAssetPaths, } : null, ); return _queueManager.addToQueue(queueItem); } else { // 不使用队列,直接播放 return _loadAnimation( resource: resource, dynamicData: dynamicData, isFromQueue: false, ); } } // 暂停动画 void pause() { _animationController?.stop(); } // 停止动画 void stop() { _animationController?.stop(); if (widget.clearsAfterStop) { _animationController?.videoItem = null; setState(() { _currentResource = null; }); } } // 重新加载当前动画 void reload() { if (_currentResource != null) { if (widget.useCache) { SCGiftVapSvgaManager().videoItemCache.remove(_currentResource); } _loadAnimation( resource: _currentResource!, dynamicData: _currentDynamicData, isFromQueue: false, ); } } @override void dispose() { stop(); _animationController?.dispose(); // 从队列管理器中移除当前播放器 _queueManager.clearCurrentPlayer(); super.dispose(); } @override Widget build(BuildContext context) { // 显示加载状态 if (_isLoading) { return Container( width: widget.width, height: widget.height, child: Center(child: CircularProgressIndicator()), ); } // 显示错误状态 if (_hasError) { return Container( width: widget.width, height: widget.height, child: Center(child: Icon(Icons.error, color: Colors.red)), ); } // 显示空状态(没有动画时) if (_animationController?.videoItem == null) { return Container( width: widget.width, height: widget.height, child: Center( child: Text('等待动画...', style: TextStyle(color: Colors.grey)), ), ); } // 显示SVGA动画 return Container( width: widget.width, height: widget.height, child: SVGAImage( key: _svgaKey, _animationController!, fit: BoxFit.contain, clearsAfterStop: widget.clearsAfterStop, ), ); } } // 便捷使用的全局方法 class RoomEntranceHelper { // 添加入场动画到队列 static Future addEntranceAnimation({ required String resource, Map? textReplacements, Map? imageReplacements, Map? imageAssetPaths, VoidCallback? onCompleted, Function(String)? onError, }) async { final dynamicData = DynamicData( textReplacements: textReplacements, imageReplacements: imageReplacements, imageAssetPaths: imageAssetPaths, ); final queueItem = AnimationQueueItem( resource: resource, dynamicData: { 'textReplacements': dynamicData.textReplacements, 'imageReplacements': dynamicData.imageReplacements, 'imageAssetPaths': dynamicData.imageAssetPaths, }, onCompleted: onCompleted, onError: onError, ); return RoomEntranceQueueManager().addToQueue(queueItem); } // 获取队列状态 static Map getQueueStatus() { return RoomEntranceQueueManager().queueStatus; } // 清空队列 static void clearQueue() { RoomEntranceQueueManager().clearQueue(); } // 设置队列更新回调 static void setQueueUpdateCallback(void Function() callback) { RoomEntranceQueueManager().onQueueUpdated = callback; } // 设置队列最大长度 static void setMaxQueueLength(int length) { RoomEntranceQueueManager().maxQueueLength = length; } // 设置自动播放延迟 static void setAutoPlayDelay(int delayMs) { RoomEntranceQueueManager().autoPlayDelay = delayMs; } } // 使用示例 class RoomEntranceExample extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // 创建播放器(不需要初始资源) RoomEntranceWidget( width: 200, height: 200, useQueue: true, autoStartQueue: true, ), SizedBox(height: 20), // 添加动画到队列的按钮 ElevatedButton( onPressed: () { RoomEntranceHelper.addEntranceAnimation( resource: 'assets/animations/entrance.svga', textReplacements: {'username': '张三', 'level': 'VIP 10'}, onCompleted: () { print('入场动画播放完成'); }, ); }, child: Text('播放入场动画'), ), // 添加另一个动画 ElevatedButton( onPressed: () { RoomEntranceHelper.addEntranceAnimation( resource: 'https://example.com/animations/vip_entrance.svga', textReplacements: {'username': '李四', 'gift_name': '超级火箭'}, ); }, child: Text('播放VIP入场动画'), ), // 查看队列状态 ElevatedButton( onPressed: () { final status = RoomEntranceHelper.getQueueStatus(); print('队列状态: $status'); }, child: Text('查看队列状态'), ), ], ); } }