Flutter 语聊房项目 ----- 礼物特效播放

发布于:2025-09-14 ⋅ 阅读:(25) ⋅ 点赞:(0)

在语聊房项目中,礼物特效播放是一个常见的需求,通常包括动画、声音等多种媒体形式。为了处理不同的礼物类型,我们可以采用抽象的设计方法,使得系统易于扩展和维护。

设计架构思路:

  1. 抽象礼物特效接口:定义一个通用的礼物特效接口,所有类型的礼物特效都实现这个接口。接口中包括播放、停止、释放资源等方法。

  2. 礼物特效工厂:使用工厂模式根据礼物类型创建对应的特效实例。

  3. 特效管理:有一个特效管理器来管理当前正在播放的特效,处理多个礼物同时播放时的排队、叠加等逻辑。

  4. 资源管理:礼物特效可能包含动画、声音等资源,需要统一管理资源的加载和释放。

  5. 与UI层解耦:特效播放器应该与UI层解耦,通过事件或回调与UI通信。

一、架构设计

二、核心抽象设计

1. 礼物特效抽象基类

abstract class GiftEffect {
  final String effectId;
  final int priority; // 特效优先级,用于处理多个特效同时播放的情况
  final Duration duration; // 特效持续时间
  
  GiftEffect({
    required this.effectId,
    required this.priority,
    required this.duration,
  });
  
  // 初始化特效资源
  Future<void> initialize();
  
  // 播放特效
  Future<void> play();
  
  // 停止特效
  Future<void> stop();
  
  // 释放资源
  Future<void> dispose();
  
  // 特效状态变化回调
  void Function(GiftEffectState state)? onStateChanged;
}

// 特效状态枚举
enum GiftEffectState {
  initializing,
  ready,
  playing,
  paused,
  completed,
  error,
}

2. 不同礼物类型的特效实现

基础礼物特效

class BasicGiftEffect extends GiftEffect {
  final String animationPath;
  final String? soundPath;
  AnimationController? _animationController;
  AudioPlayer? _audioPlayer;
  
  BasicGiftEffect({
    required super.effectId,
    required super.priority,
    required super.duration,
    required this.animationPath,
    this.soundPath,
  });
  
  @override
  Future<void> initialize() async {
    try {
      // 预加载动画资源
      await precacheAnimation(animationPath);
      
      // 如果有音效,预加载音效
      if (soundPath != null) {
        _audioPlayer = AudioPlayer();
        await _audioPlayer!.setAsset(soundPath!);
      }
      
      onStateChanged?.call(GiftEffectState.ready);
    } catch (e) {
      onStateChanged?.call(GiftEffectState.error);
      rethrow;
    }
  }
  
  @override
  Future<void> play() async {
    onStateChanged?.call(GiftEffectState.playing);
    
    // 播放动画
    _animationController = AnimationController(
      duration: duration,
      vsync: // 获取TickerProvider,
    )..forward();
    
    // 播放音效
    if (_audioPlayer != null) {
      await _audioPlayer!.play();
    }
    
    // 监听动画完成
    _animationController!.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        onStateChanged?.call(GiftEffectState.completed);
      }
    });
  }
  
  @override
  Future<void> stop() async {
    _animationController?.stop();
    await _audioPlayer?.stop();
    onStateChanged?.call(GiftEffectState.paused);
  }
  
  @override
  Future<void> dispose() async {
    _animationController?.dispose();
    await _audioPlayer?.dispose();
    onStateChanged?.call(GiftEffectState.completed);
  }
}

高级礼物特效(如全屏特效)

class FullScreenGiftEffect extends GiftEffect {
  final String lottieAnimationPath;
  final String particleEffectPath;
  final String backgroundMusicPath;
  final List<String> additionalEffects;
  
  FullScreenGiftEffect({
    required super.effectId,
    required super.priority,
    required super.duration,
    required this.lottieAnimationPath,
    required this.particleEffectPath,
    required this.backgroundMusicPath,
    this.additionalEffects = const [],
  });
  
  @override
  Future<void> initialize() async {
    // 使用Isolate预加载复杂资源,避免阻塞UI线程
    await compute(_preloadFullScreenResources, {
      'lottie': lottieAnimationPath,
      'particles': particleEffectPath,
      'music': backgroundMusicPath,
      'additional': additionalEffects,
    });
    
    onStateChanged?.call(GiftEffectState.ready);
  }
  
  static void _preloadFullScreenResources(Map<String, dynamic> resources) {
    // 在Isolate中执行资源预加载
    // 这里可以加载Lottie动画、粒子效果、音乐等
  }
  
  @override
  Future<void> play() async {
    // 实现全屏特效播放逻辑
    // 可能涉及多个动画同步、粒子系统、音乐播放等
  }
  
  // 其他方法实现...
}

3D礼物特效

class ThreeDGiftEffect extends GiftEffect {
  final String modelPath;
  final String animationName;
  final List<LightConfig> lights;
  final CameraConfig camera;
  
  ThreeDGiftEffect({
    required super.effectId,
    required super.priority,
    required super.duration,
    required this.modelPath,
    required this.animationName,
    required this.lights,
    required this.camera,
  });
  
  @override
  Future<void> initialize() async {
    // 使用Isolate加载3D模型和动画
    await compute(_load3DModel, modelPath);
    
    onStateChanged?.call(GiftEffectState.ready);
  }
  
  static void _load3DModel(String modelPath) {
    // 在Isolate中加载3D模型
  }
  
  @override
  Future<void> play() async {
    // 实现3D特效播放逻辑
    // 使用Flutter 3D渲染引擎(如Filament或自定义OpenGL渲染)
  }
  
  // 其他方法实现...
}

3. 礼物特效工厂

class GiftEffectFactory {
  static GiftEffect createEffect(GiftItem gift) {
    switch (gift.type) {
      case GiftType.basic:
        return BasicGiftEffect(
          effectId: gift.id,
          priority: gift.priority,
          duration: gift.duration,
          animationPath: gift.animationPath,
          soundPath: gift.soundPath,
        );
      
      case GiftType.fullScreen:
        return FullScreenGiftEffect(
          effectId: gift.id,
          priority: gift.priority,
          duration: gift.duration,
          lottieAnimationPath: gift.animationPath,
          particleEffectPath: gift.particleEffectPath,
          backgroundMusicPath: gift.backgroundMusicPath,
          additionalEffects: gift.additionalEffects,
        );
      
      case GiftType.threeD:
        return ThreeDGiftEffect(
          effectId: gift.id,
          priority: gift.priority,
          duration: gift.duration,
          modelPath: gift.modelPath,
          animationName: gift.animationName,
          lights: gift.lights,
          camera: gift.camera,
        );
      
      case GiftType.special:
        // 特殊礼物类型的处理
        return SpecialGiftEffect(
          effectId: gift.id,
          priority: gift.priority,
          duration: gift.duration,
          // 特殊参数...
        );
      
      default:
        throw Exception('Unsupported gift type: ${gift.type}');
    }
  }
}

4. 礼物特效管理器

class GiftEffectManager {
  final List<GiftEffect> _activeEffects = [];
  final Queue<GiftEffect> _effectQueue = Queue();
  final int _maxConcurrentEffects;
  
  GiftEffectManager({int maxConcurrentEffects = 3})
      : _maxConcurrentEffects = maxConcurrentEffects;
  
  // 添加礼物特效到播放队列
  Future<void> addGiftEffect(GiftItem gift) async {
    final effect = GiftEffectFactory.createEffect(gift);
    
    // 初始化特效
    await effect.initialize();
    
    // 监听状态变化
    effect.onStateChanged = (state) {
      if (state == GiftEffectState.completed || state == GiftEffectState.error) {
        _removeEffect(effect);
        _playNextEffect();
      }
    };
    
    // 根据优先级决定是立即播放还是加入队列
    if (_activeEffects.length < _maxConcurrentEffects) {
      _activeEffects.add(effect);
      await effect.play();
    } else {
      // 查找队列中是否有更低优先级的特效可以替换
      final lowestPriorityEffect = _findLowestPriorityEffect();
      if (lowestPriorityEffect != null && effect.priority > lowestPriorityEffect.priority) {
        // 暂停低优先级特效,播放高优先级特效
        await lowestPriorityEffect.stop();
        _activeEffects.remove(lowestPriorityEffect);
        _effectQueue.addFirst(lowestPriorityEffect);
        
        _activeEffects.add(effect);
        await effect.play();
      } else {
        // 加入队列等待
        _effectQueue.add(effect);
      }
    }
  }
  
  // 查找当前播放中优先级最低的特效
  GiftEffect? _findLowestPriorityEffect() {
    if (_activeEffects.isEmpty) return null;
    
    GiftEffect lowest = _activeEffects.first;
    for (final effect in _activeEffects) {
      if (effect.priority < lowest.priority) {
        lowest = effect;
      }
    }
    return lowest;
  }
  
  // 移除特效
  void _removeEffect(GiftEffect effect) {
    _activeEffects.remove(effect);
    effect.dispose();
  }
  
  // 播放下一个队列中的特效
  void _playNextEffect() {
    if (_effectQueue.isNotEmpty && _activeEffects.length < _maxConcurrentEffects) {
      final nextEffect = _effectQueue.removeFirst();
      _activeEffects.add(nextEffect);
      nextEffect.play();
    }
  }
  
  // 清空所有特效
  Future<void> clearAllEffects() async {
    for (final effect in _activeEffects) {
      await effect.stop();
      await effect.dispose();
    }
    _activeEffects.clear();
    
    for (final effect in _effectQueue) {
      await effect.dispose();
    }
    _effectQueue.clear();
  }
}

5. 资源加载与缓存

class EffectResourceLoader {
  static final Map<String, dynamic> _resourceCache = {};
  
  // 预加载常用礼物资源
  static Future<void> preloadCommonGiftResources(List<String> giftIds) async {
    await Future.wait(giftIds.map((id) => _preloadGiftResources(id)));
  }
  
  static Future<void> _preloadGiftResources(String giftId) async {
    // 获取礼物配置信息
    final giftConfig = await GiftConfigService.getConfig(giftId);
    
    // 使用Isolate加载资源,避免阻塞UI线程
    final resources = await compute(_loadResourcesInIsolate, giftConfig);
    
    // 缓存资源
    _resourceCache[giftId] = resources;
  }
  
  static Future<Map<String, dynamic>> _loadResourcesInIsolate(GiftConfig config) async {
    final resources = <String, dynamic>{};
    
    // 加载动画资源
    if (config.animationPath != null) {
      resources['animation'] = await loadAnimation(config.animationPath!);
    }
    
    // 加载音效资源
    if (config.soundPath != null) {
      resources['sound'] = await loadAudio(config.soundPath!);
    }
    
    // 加载其他资源...
    
    return resources;
  }
  
  // 获取已缓存的资源
  static dynamic getCachedResource(String giftId, String resourceType) {
    final giftResources = _resourceCache[giftId];
    return giftResources != null ? giftResources[resourceType] : null;
  }
  
  // 清理缓存
  static void clearCache() {
    _resourceCache.clear();
  }
}

三、性能优化策略

1. 资源预加载与缓存

​​​​​​​
  • 在应用启动时预加载常用礼物资源

  • 实现LRU缓存策略,自动清理不常用的资源

  • 根据用户行为预测下一步可能发送的礼物,提前加载

2. Isolate的使用

​​​​​​​​​​​​​​
  • 使用Isolate进行资源加载,避免阻塞UI线程

  • 复杂特效的渲染在单独的Isolate中进行

  • 实现Isolate池管理,复用Isolate实例

3. 内存管理

​​​​​​​​​​​​​​
  • 实现特效资源的引用计数,及时释放不再使用的资源

  • 监控内存使用情况,在内存紧张时自动降低特效质量或跳过次要特效

4. 特效优先级与队列管理

​​​​​​​​​​​​​​
  • 根据礼物价值、发送者身份等因素动态调整特效优先级

  • 实现智能队列管理,避免低优先级特效阻塞高优先级特效

总结

这个礼物特效播放架构通过抽象工厂模式创建不同类型的特效,使用管理器处理特效的优先级和并发播放,利用Isolate进行资源加载和复杂计算,实现了高性能、可扩展的礼物特效系统。该架构具有以下优点:

  1. 良好的扩展性:通过抽象接口,可以轻松添加新的礼物特效类型

  2. 高性能:使用Isolate进行资源加载和复杂计算,避免阻塞UI线程

  3. 智能调度:基于优先级的特效队列管理,确保重要特效优先播放

  4. 内存友好:实现了资源缓存和内存管理机制,避免内存泄漏

  5. 易于维护:分层架构和清晰的职责划分,使代码易于理解和维护

这种设计能够满足语聊房项目中各种复杂礼物特效的需求,同时保证应用的流畅性和稳定性。