Browse Source
feat(discover): 实现SVGA动画播放管理器和播放组件
feat(discover): 实现SVGA动画播放管理器和播放组件
- 新增 SvgaPlayerManager 管理SVGA动画播放队列 - 创建 SvgaPlayerWidget 用于实际播放SVGA动画 - 支持本地assets和网络URL两种SVGA文件加载方式 - 实现动画播放完成和错误处理回调机制 - 提供队列控制方法如添加、清空、停止播放等功能 - 修复房间控制器中SVGA播放器导入路径错误问题ios
3 changed files with 191 additions and 1 deletions
Split View
Diff Options
-
2lib/controller/discover/room_controller.dart
-
86lib/controller/discover/svga_player_manager.dart
-
104lib/widget/live/svga_player_widget.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<SvgaAnimationItem> _animationQueue = Queue<SvgaAnimationItem>(); |
|||
final Rx<SvgaAnimationItem?> currentItem = Rx<SvgaAnimationItem?>(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; |
|||
} |
|||
|
|||
@ -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<SvgaPlayerWidget> createState() => _SvgaPlayerWidgetState(); |
|||
} |
|||
|
|||
class _SvgaPlayerWidgetState extends State<SvgaPlayerWidget> |
|||
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<void> _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!), |
|||
), |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save