From d6e85c12207aae332bd268acefc5cdb07577991e Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Wed, 26 Nov 2025 21:44:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(discover):=20=E5=AE=9E=E7=8E=B0SVGA?= =?UTF-8?q?=E5=8A=A8=E7=94=BB=E6=92=AD=E6=94=BE=E7=AE=A1=E7=90=86=E5=99=A8?= =?UTF-8?q?=E5=92=8C=E6=92=AD=E6=94=BE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SvgaPlayerManager 管理SVGA动画播放队列 - 创建 SvgaPlayerWidget 用于实际播放SVGA动画 - 支持本地assets和网络URL两种SVGA文件加载方式 - 实现动画播放完成和错误处理回调机制 - 提供队列控制方法如添加、清空、停止播放等功能 - 修复房间控制器中SVGA播放器导入路径错误问题 --- lib/controller/discover/room_controller.dart | 2 +- .../discover/svga_player_manager.dart | 86 +++++++++++++++ lib/widget/live/svga_player_widget.dart | 104 ++++++++++++++++++ 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 lib/controller/discover/svga_player_manager.dart create mode 100644 lib/widget/live/svga_player_widget.dart diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 3819ea6..984dc77 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/global.dart'; -import 'package:dating_touchme_app/controller/live/svga_player_manager.dart'; +import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; diff --git a/lib/controller/discover/svga_player_manager.dart b/lib/controller/discover/svga_player_manager.dart new file mode 100644 index 0000000..ffb9ede --- /dev/null +++ b/lib/controller/discover/svga_player_manager.dart @@ -0,0 +1,86 @@ +import 'dart:collection'; +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:get/get.dart'; + +/// SVGA 动画项 +class SvgaAnimationItem { + final String svgaFile; + final String? targetUserId; // 接收礼物的用户ID + final String? senderUserId; // 发送礼物的用户ID + final String? giftProductId; // 礼物产品ID + + SvgaAnimationItem({ + required this.svgaFile, + this.targetUserId, + this.senderUserId, + this.giftProductId, + }); +} + +/// SVGA 动画播放队列管理器 +/// 注意:由于 SVGAAnimationController 需要 vsync,实际播放需要在 Widget 中完成 +/// 这个管理器只负责管理队列,实际的播放需要通过回调通知外部 Widget +class SvgaPlayerManager extends GetxController { + static SvgaPlayerManager? _instance; + static SvgaPlayerManager get instance { + _instance ??= Get.put(SvgaPlayerManager()); + return _instance!; + } + + final Queue _animationQueue = Queue(); + final Rx currentItem = Rx(null); + final RxBool isPlaying = false.obs; + + /// 添加动画到队列 + void addToQueue(SvgaAnimationItem item) { + _animationQueue.add(item); + _playNext(); + } + + /// 播放下一个动画 + void _playNext() { + if (isPlaying.value || _animationQueue.isEmpty) { + return; + } + + final item = _animationQueue.removeFirst(); + currentItem.value = item; + isPlaying.value = true; + print('✅ SVGA 动画已添加到播放队列: ${item.svgaFile}'); + } + + /// 标记当前动画播放完成 + void onAnimationFinished() { + print('✅ SVGA 动画播放完成'); + isPlaying.value = false; + currentItem.value = null; + // 播放下一个 + _playNext(); + } + + /// 标记当前动画播放失败 + void onAnimationError(String error) { + print('❌ SVGA 动画播放失败: $error'); + isPlaying.value = false; + currentItem.value = null; + // 继续播放下一个 + _playNext(); + } + + /// 停止当前播放 + void stop() { + isPlaying.value = false; + currentItem.value = null; + } + + /// 清空队列 + void clearQueue() { + stop(); + _animationQueue.clear(); + } + + /// 获取队列长度 + int get queueLength => _animationQueue.length; +} + diff --git a/lib/widget/live/svga_player_widget.dart b/lib/widget/live/svga_player_widget.dart new file mode 100644 index 0000000..052f038 --- /dev/null +++ b/lib/widget/live/svga_player_widget.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:get/get.dart'; +import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart'; + +/// SVGA 动画播放 Widget +/// 监听 SvgaPlayerManager 的队列,自动播放 SVGA 动画 +class SvgaPlayerWidget extends StatefulWidget { + const SvgaPlayerWidget({super.key}); + + @override + State createState() => _SvgaPlayerWidgetState(); +} + +class _SvgaPlayerWidgetState extends State + with SingleTickerProviderStateMixin { + final SvgaPlayerManager _manager = SvgaPlayerManager.instance; + SVGAAnimationController? _controller; + SvgaAnimationItem? _currentItem; + + @override + void initState() { + super.initState(); + // 监听队列变化 + ever(_manager.currentItem, (item) { + if (item != null && item != _currentItem) { + _playAnimation(item); + } + }); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + /// 播放动画 + Future _playAnimation(SvgaAnimationItem item) async { + // 如果正在播放,先停止 + if (_controller != null) { + _controller!.dispose(); + _controller = null; + } + + _currentItem = item; + _controller = SVGAAnimationController(vsync: this); + + try { + // 判断是网络 URL 还是本地资源 + if (item.svgaFile.startsWith('http://') || + item.svgaFile.startsWith('https://')) { + // 网络 URL + SVGAParser.shared.decodeFromURL(item.svgaFile).then((video) { + if (mounted && _currentItem == item) { + _controller!.videoItem = video; + _controller!.repeat(); + print('✅ SVGA 动画加载成功(网络): ${item.svgaFile}'); + } + }).catchError((error) { + print('❌ SVGA 动画加载失败(网络): $error'); + _manager.onAnimationError(error.toString()); + }); + } else { + // 本地资源(assets) + SVGAParser.shared.decodeFromAssets(item.svgaFile).then((video) { + if (mounted && _currentItem == item) { + _controller!.videoItem = video; + _controller!.repeat(); + print('✅ SVGA 动画加载成功(本地): ${item.svgaFile}'); + } + }).catchError((error) { + print('❌ SVGA 动画加载失败(本地): $error'); + _manager.onAnimationError(error.toString()); + }); + } + + // 监听动画完成(repeat 模式不会自动完成,需要手动停止) + // 这里可以根据需要调整播放逻辑 + } catch (e) { + print('❌ SVGA 播放异常: $e'); + _manager.onAnimationError(e.toString()); + } + } + + @override + Widget build(BuildContext context) { + return Obx(() { + final currentItem = _manager.currentItem.value; + final isPlaying = _manager.isPlaying.value; + + if (!isPlaying || currentItem == null || _controller == null) { + return const SizedBox.shrink(); + } + + return Positioned.fill( + child: IgnorePointer( + child: SVGAImage(_controller!), + ), + ); + }); + } +} +