📹 小视频模块
版本 PanGrowth

📱 沉浸式小视频

沉浸式小视频提供类似抖音的全屏垂直视频浏览体验,支持上下滑动切换、点赞评论、一起看视频等丰富功能。

🚪 接入方式概览

方式推荐场景实现特点
NativeView组件(推荐)Flutter页面内嵌视频内容,追求最快集成VideoNativeView 自动管理创建/销毁,Flutter布局友好
API调用需要在原生Activity/ViewController中展示,或精确控制生命周期createVideo() / showVideo() / destroyVideo() 主动控制

两种方式共用 VideoConfig 配置对象,事件回调使用 VideoListener


✅ 方式一:VideoNativeView(推荐)

VideoNativeView 通过 PlatformView 内嵌原生视频播放器,Flutter 端像普通 Widget 一样使用。

基础用法

import 'package:flutter/material.dart';
import 'package:pangrowth_content/pangrowth_content.dart';

class VideoPage extends StatelessWidget {
  const VideoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: VideoNativeView(
        channelType: 1,  // 1-推荐,2-关注,3-推荐+关注
        autoPlay: true,
        listener: VideoListener(
          onVideoReady: (videoId) {
            debugPrint('视频就绪: $videoId');
          },
          onVideoError: (error) {
            debugPrint('视频错误: $error');
          },
          onVideoDisposed: () {
            debugPrint('视频已销毁');
          },
        ),
      ),
    );
  }
}

VideoNativeView参数

参数类型默认值说明
channelTypeint1频道类型:1-推荐,2-关注,3-推荐+关注
contentTypeint1内容类型:1-小视频,2-直播,3-小视频+直播
styleint2样式类型:2-全屏沉浸式
hideChannelNameboolfalse是否隐藏频道名称(顶部切换栏)
autoPlaybooltrue是否自动播放
controllerVideoController?null视频控制器(可选)
listenerVideoListener?null事件监听器
videoIdString?自动生成自定义视频标识
extraMap<String, dynamic>?null传递给原生层的额外参数

结合Controller控制

使用 VideoController 可以在外部控制视频播放:

class VideoPlayerPage extends StatefulWidget {
  const VideoPlayerPage({super.key});

  @override
  State<VideoPlayerPage> createState() => _VideoPlayerPageState();
}

class _VideoPlayerPageState extends State<VideoPlayerPage> {
  final _controller = VideoController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          VideoNativeView(
            controller: _controller,
            channelType: 3, // 推荐+关注双频道
            listener: VideoListener(
              onVideoReady: (videoId) {
                debugPrint('视频就绪: $videoId');
              },
            ),
          ),
          Positioned(
            bottom: 50,
            right: 20,
            child: Row(
              children: [
                FloatingActionButton(
                  mini: true,
                  onPressed: () => _controller.pause(),
                  child: const Icon(Icons.pause),
                ),
                const SizedBox(width: 10),
                FloatingActionButton(
                  mini: true,
                  onPressed: () => _controller.resume(),
                  child: const Icon(Icons.play_arrow),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

生命周期管理

VideoNativeView 自动管理视频生命周期:

  • 创建: Widget initState 时自动创建原生视图
  • 显示: Widget build 时自动显示
  • 暂停/恢复: 自动监听应用生命周期(前后台切换)
  • 销毁: Widget dispose 时自动销毁原生视图

🧭 方式二:API控制方式

适合需要精确控制视频生命周期的场景,如原生Activity/ViewController中展示。

完整生命周期演示

import 'package:flutter/material.dart';
import 'package:pangrowth_content/pangrowth_content.dart';

class VideoAPIDemo extends StatefulWidget {
  const VideoAPIDemo({super.key});

  @override
  State<VideoAPIDemo> createState() => _VideoAPIDemoState();
}

class _VideoAPIDemoState extends State<VideoAPIDemo> {
  String? _videoId;
  bool _isLoading = false;

  Future<void> _createAndShowVideo() async {
    setState(() {
      _isLoading = true;
    });

    try {
      // 1. 创建视频配置
      final config = VideoConfig(
        drawChannelType: 1,        // 推荐频道
        progressBarStyle: 1,        // 浅色进度条
        hideChannelName: false,     // 显示频道名称
        enableRefresh: true,        // 支持下拉刷新
      );

      // 2. 创建视频
      final result = await PangrowthContent.createVideo(config);

      if (result['success'] == true) {
        _videoId = result['videoId'] as String;
        debugPrint('创建成功: $_videoId');

        // 3. 展示视频
        await PangrowthContent.showVideo(
          _videoId!,
          // iOS专用:控制展示样式
          presentationStyle: 'fullScreen',  // fullScreen/pageSheet/formSheet
          // Android专用:指定容器ID
          // containerId: android.R.id.content,
        );

        debugPrint('展示成功');
      }
    } catch (e) {
      debugPrint('创建或展示失败: $e');
      _showSnackBar('操作失败: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _pauseVideo() async {
    if (_videoId != null) {
      await PangrowthContent.pauseVideo(_videoId!);
      debugPrint('暂停成功');
    }
  }

  Future<void> _resumeVideo() async {
    if (_videoId != null) {
      await PangrowthContent.resumeVideo(_videoId!);
      debugPrint('恢复成功');
    }
  }

  Future<void> _refreshVideo() async {
    if (_videoId != null) {
      await PangrowthContent.refreshVideo(_videoId!);
      debugPrint('刷新成功');
    }
  }

  Future<void> _destroyVideo() async {
    if (_videoId != null) {
      await PangrowthContent.destroyVideo(_videoId!);
      debugPrint('销毁成功');
      setState(() {
        _videoId = null;
      });
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  void dispose() {
    // 清理资源
    if (_videoId != null) {
      PangrowthContent.destroyVideo(_videoId!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('API方式演示')),
      body: Center(
        child: _isLoading
            ? const CircularProgressIndicator()
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: _videoId == null ? _createAndShowVideo : null,
                    child: const Text('创建并显示'),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _videoId != null ? _pauseVideo : null,
                    child: const Text('暂停'),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _videoId != null ? _resumeVideo : null,
                    child: const Text('恢复'),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _videoId != null ? _refreshVideo : null,
                    child: const Text('刷新'),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _videoId != null ? _destroyVideo : null,
                    child: const Text('销毁'),
                  ),
                ],
              ),
      ),
    );
  }
}

API方法说明

核心方法

方法说明返回值
createVideo(VideoConfig)创建视频实例Map{'videoId': String, 'success': bool}
showVideo(videoId, ...)展示视频页面Map{'videoId': String, 'shown': bool}
destroyVideo(videoId)销毁视频实例bool

播放控制方法

方法说明平台支持
pauseVideo(videoId)暂停播放双端
resumeVideo(videoId)恢复播放双端
refreshVideo(videoId)刷新视频内容双端

高级控制方法

方法说明平台支持
setAwakeData(videoId, data)设置唤醒数据双端
setCurrentPage(videoId, position)设置播放特定位置的视频双端
seekToPosition(videoId, position, time)播放指定位置视频到指定时间点双端
setSyncData(videoId, data, type)设置播放数据(一起看视频功能)双端
pauseForWatchTogether(videoId)一起看视频-暂停播放双端
resumeForWatchTogether(videoId)一起看视频-恢复播放双端

Android专用方法

方法说明
scrollToTop(videoId)滚动到顶部
backRefresh(videoId)返回刷新(挽留用户)

iOS专用方法

方法说明
getCurrentIndex(videoId)获取当前视频在列表中的位置
scrollToIndex(videoId, index)滚动到指定位置
pauseCurrentVideo(videoId)暂停当前视频
playCurrentVideo(videoId)播放当前视频
seekVideoToMSeconds(videoId, mSeconds)控制播放进度(毫秒)
createReportContent(videoId)创建举报内容页面
refreshVideoToStayUser(videoId)刷新视频来留住用户
showToastToStayUser(videoId)显示Toast留住用户

🧩 VideoConfig配置参数

VideoConfig 基于官方SDK的 DPWidgetDrawParams(Android) 和 LCDDrawVideoVCConfig(iOS) 设计,提供43个配置参数。

通用参数

参数类型默认值说明
showBackBtnbool?-是否显示返回按钮
progressBarStyleint?-进度条样式:1-浅色(白色),2-深色(蓝色)
customCategoryString?-自定义频道名称
sceneString?-场景标识
drawChannelTypeint?-频道类型:1-推荐,2-关注,3-推荐+关注
hideFollowbool?-是否隐藏关注功能
hideChannelNamebool?-是否隐藏频道名称
enableRefreshbool?-是否支持下拉刷新
hideClosebool?-是否隐藏关闭按钮
adOffsetint?-广告偏移量(距离底部),单位dp
bottomOffsetint?-底部偏移量,单位dp
titleTopMarginint?-标题栏上边距,单位dp
titleLeftMarginint?-标题栏左边距,单位dp
titleRightMarginint?-标题栏右边距,单位dp

Android专用参数

参数类型说明
isShowActionBarbool?是否展示ActionBar
fontSizeint?字体大小
isBoldbool?字体是否加粗
actionBarBackgroundColorString?ActionBar背景颜色(十六进制,如'#FF6200EE')
supportOrientationChangebool?是否支持横竖屏切换
isShowNeedTopMarginbool?是否显示距离顶部的距离参数
topMarginint?距离顶部的距离,单位dp

iOS专用参数

参数类型说明
allowPanBackbool?是否允许左滑返回
isShowNavigationBarbool?是否显示导航栏
navigationBarBackgroundColorString?导航栏背景颜色
navigationBarTitleColorString?导航栏标题颜色
navigationBarButtonColorString?导航栏按钮颜色
statusBarStyleString?状态栏样式:'default','lightContent','darkContent'
showCloseButtonbool?是否显示关闭按钮
hiddenGuideGestebool?是否隐藏首次安装引导手势
outBottomOffsetdouble?外流进度条距离底部距离
featureValuesArrList<String>?支持的返回内容类型
recommandTabNameString?推荐频道名称(个性化推荐关闭后)
drawVCTabOptionsint?需要展示的tab
shouldDisableFollowingFuncbool?是否屏蔽关注功能
shouldHideTabBarViewbool?是否隐藏频道tab
customRefreshbool?自定义刷新行为和手势
navBarInsetMap<String, double>?导航栏内边距配置
hiddenPlayletTitleViewbool?是否隐藏短剧title
hiddenPlayletEnterViewbool?是否隐藏底部跳转区域

一起看视频参数

参数类型说明
watchTogetherRoleString?角色:'NONE'-普通模式,'HOST'-主控端,'USER'-被控端
hostGroupIdint?主控端GroupID(仅在主控端生效)

配置示例

Android自定义样式

final config = VideoConfig(
  drawChannelType: 1,
  // Android专用配置
  isShowActionBar: true,
  actionBarBackgroundColor: '#FF6200EE',
  fontSize: 16,
  isBold: true,
  topMargin: 20,
  // 通用配置
  progressBarStyle: 1,
  enableRefresh: true,
);

iOS自定义样式

final config = VideoConfig(
  drawChannelType: 1,
  // iOS专用配置
  isShowNavigationBar: true,
  navigationBarBackgroundColor: '#FFFFFF',
  navigationBarTitleColor: '#000000',
  statusBarStyle: 'darkContent',
  allowPanBack: true,
  // 通用配置
  progressBarStyle: 2,
  enableRefresh: true,
);

一起看视频配置

// 主控端配置
final hostConfig = VideoConfig(
  drawChannelType: 1,
  watchTogetherRole: 'HOST',
  hostGroupId: 12345,
  customCategory: '一起看',
);

// 被控端配置
final userConfig = VideoConfig(
  drawChannelType: 1,
  watchTogetherRole: 'USER',
  hostGroupId: 12345,
  customCategory: '一起看',
);

📡 事件监听

使用VideoListener(NativeView方式)

VideoNativeView(
  listener: VideoListener(
    onVideoReady: (videoId) {
      debugPrint('✅ 视频就绪: $videoId');
    },
    onVideoDisposed: () {
      debugPrint('🗑️ 视频已销毁');
    },
    onVideoError: (error) {
      debugPrint('❌ 视频错误: $error');
    },
  ),
)

常见事件类型

事件类型说明数据字段
播放控制
play视频开始播放videoId, groupId
pause视频暂停videoId, groupId, duration
continue视频继续播放videoId, groupId
completion视频播放完成videoId, groupId
over视频播放结束videoId, groupId, percent, duration
用户交互
pageChange视频切换videoId, position
clickLike点赞/取消点赞videoId, isLike
clickComment点击评论videoId, groupId
clickShare点击分享videoId, groupId, title
clickAvatar点击用户头像videoId, groupId
clickAuthorName点击作者昵称videoId, groupId
系统事件
close视频关闭videoId
refreshFinish刷新完成videoId
seekTo进度跳转videoId, position, time
一起看视频
dataSourceChange数据源变更dataSource, action
seekToTime拖动进度条endTime, position
广告事件
adRequest广告请求videoId, adSlotID
adShow广告展示videoId, adSlotID
adClicked广告点击videoId, adSlotID
adLoadSuccess广告加载成功videoId, adSlotID
adLoadFail广告加载失败videoId, error

💡 完整使用示例

1. 一起看视频实现

class WatchTogetherService {
  /// 主控端:启动一起看视频
  static Future<void> startAsHost(String roomId) async {
    final config = VideoConfig(
      watchTogetherRole: 'HOST',
      hostGroupId: int.parse(roomId),
      drawChannelType: 1,
      customCategory: '一起看',
      scene: 'watch_together',
    );

    final result = await PangrowthContent.createVideo(config);
    final videoId = result['videoId'];
    await PangrowthContent.showVideo(videoId);
  }

  /// 被控端:加入一起看视频
  static Future<void> joinAsUser(String roomId, String syncData) async {
    final config = VideoConfig(
      watchTogetherRole: 'USER',
      hostGroupId: int.parse(roomId),
      drawChannelType: 1,
      customCategory: '一起看',
      scene: 'watch_together',
    );

    final result = await PangrowthContent.createVideo(config);
    final videoId = result['videoId'];

    // 设置同步数据
    await PangrowthContent.setSyncData(videoId, syncData, 1);
    await PangrowthContent.showVideo(videoId);
  }

  /// 同步播放数据
  static Future<void> syncPlayback(String videoId, String syncData) async {
    await PangrowthContent.setSyncData(videoId, syncData, 2);
  }
}

2. 自定义主题配置

class VideoThemeConfig {
  /// 深色主题
  static VideoConfig get darkTheme => VideoConfig(
    progressBarStyle: 2,
    drawChannelType: 3,
    enableRefresh: true,
    hideFollow: false,
    // iOS
    navigationBarBackgroundColor: '#000000',
    navigationBarTitleColor: '#FFFFFF',
    statusBarStyle: 'lightContent',
    // Android
    actionBarBackgroundColor: '#1a1a1a',
    fontSize: 16,
  );

  /// 浅色主题
  static VideoConfig get lightTheme => VideoConfig(
    progressBarStyle: 1,
    drawChannelType: 3,
    enableRefresh: true,
    hideFollow: false,
    // iOS
    navigationBarBackgroundColor: '#FFFFFF',
    navigationBarTitleColor: '#000000',
    statusBarStyle: 'darkContent',
    // Android
    actionBarBackgroundColor: '#f5f5f5',
    fontSize: 16,
  );

  /// 简洁模式
  static VideoConfig get minimalistTheme => VideoConfig(
    hideClose: false,
    hideFollow: true,
    hideChannelName: true,
    progressBarStyle: 1,
    enableRefresh: false,
    drawChannelType: 1,
  );
}

3. TabBar场景暂停恢复

class VideoTabPage extends StatefulWidget {
  const VideoTabPage({super.key});

  @override
  State<VideoTabPage> createState() => _VideoTabPageState();
}

class _VideoTabPageState extends State<VideoTabPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  final _videoController = VideoController();

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);

    // 监听Tab切换,自动暂停/恢复
    _tabController.addListener(() {
      if (_tabController.index == 1) {
        _videoController.resume(); // 切换到视频Tab,恢复播放
      } else {
        _videoController.pause();  // 切换走,暂停播放
      }
    });
  }

  @override
  void dispose() {
    _videoController.dispose();
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: '首页'),
            Tab(text: '视频'), // 视频Tab
            Tab(text: '我的'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          const Center(child: Text('首页')),
          VideoNativeView(
            controller: _videoController,
            channelType: 3, // 推荐+关注双频道
            listener: VideoListener(
              onVideoReady: (videoId) {
                debugPrint('视频就绪: $videoId');
              },
            ),
          ),
          const Center(child: Text('我的')),
        ],
      ),
    );
  }
}

4. 完整的视频播放管理器

class VideoPlayerManager {
  String? _currentVideoId;

  Future<void> startVideo({VideoConfig? config}) async {
    try {
      // 如果已有视频在播放,先销毁
      if (_currentVideoId != null) {
        await stopVideo();
      }

      // 创建新视频
      final result = await PangrowthContent.createVideo(config ?? VideoConfig());
      if (result['success'] == true) {
        _currentVideoId = result['videoId'];
        await PangrowthContent.showVideo(_currentVideoId!);
      }
    } catch (e) {
      debugPrint('启动视频失败: $e');
      rethrow;
    }
  }

  Future<void> stopVideo() async {
    if (_currentVideoId != null) {
      try {
        await PangrowthContent.destroyVideo(_currentVideoId!);
      } catch (e) {
        debugPrint('销毁视频失败: $e');
      } finally {
        _currentVideoId = null;
      }
    }
  }

  Future<void> pauseVideo() async {
    if (_currentVideoId != null) {
      await PangrowthContent.pauseVideo(_currentVideoId!);
    }
  }

  Future<void> resumeVideo() async {
    if (_currentVideoId != null) {
      await PangrowthContent.resumeVideo(_currentVideoId!);
    }
  }

  Future<void> refreshVideo() async {
    if (_currentVideoId != null) {
      await PangrowthContent.refreshVideo(_currentVideoId!);
    }
  }

  bool get isPlaying => _currentVideoId != null;

  void dispose() {
    stopVideo();
  }
}

⚠️ 注意事项

1. 资源管理

  • 必须在合适的时机调用 destroyVideo() 释放资源
  • NativeView方式会自动销毁,API方式需要手动销毁
  • 页面退出时务必销毁视频实例,避免内存泄漏

2. 网络要求

  • 功能依赖网络连接,建议在网络环境良好时使用
  • 考虑网络异常情况的处理

3. 权限配置

  • 确保应用已获得网络访问权限
  • Android需要网络状态权限
  • iOS需要网络访问权限

4. 版本兼容

  • 部分功能需要特定版本的原生SDK支持
  • 一起看视频功能需要服务端配合

5. 平台差异

  • Android和iOS的部分配置参数不同
  • 某些方法仅在特定平台可用(已在文档中标注)
  • iOS的展示样式通过presentationStyle控制,Android通过containerId控制容器

🔧 故障排除

常见问题

Q: 视频创建失败,提示"SDK_NOT_STARTED"

A: 确保在使用前已调用 PangrowthContent.initialize()PangrowthContent.start(startVideo: true)

Q: iOS端展示异常

A: 检查 presentationStyle 参数设置,建议使用 fullScreen

Q: 一起看视频功能不生效

A: 确认 watchTogetherRolehostGroupId 参数设置正确,主控端和被控端的 hostGroupId 必须一致

Q: 事件回调收不到

A: 确保已正确设置事件监听器:

  • NativeView方式: 传入 VideoListener
  • API方式: 调用 PangrowthContent.onEventListener()

Q: Android端容器显示异常

A: 检查 containerId 参数,或使用默认值(传入null)

Q: 视频自动暂停

A: NativeView会自动监听应用生命周期,切换到后台时会自动暂停,这是正常行为

调试建议

  1. 开启日志输出: 在代码中使用 debugPrint 查看详细错误信息
  2. 确认SDK版本: 确保SDK版本和文档版本匹配
  3. 检查网络连接: 验证网络状态
  4. 验证配置参数: 检查AppID和AppSecret配置
  5. 测试基础功能: 先使用最简配置进行测试

🌍 平台差异说明

展示方式差异

平台实现方式参数
Android使用Activity展示containerId:指定展示容器
iOS使用ViewController展示presentationStyle:控制展示样式

平台专属方法

Android专属

  • scrollToTop() - 滚动到顶部
  • backRefresh() - 返回刷新(挽留用户)

iOS专属

  • getCurrentIndex() - 获取当前位置
  • scrollToIndex() - 滚动到指定位置
  • pauseCurrentVideo() / playCurrentVideo() - 播放控制
  • seekVideoToMSeconds() - 精确进度控制(毫秒)
  • createReportContent() - 创建举报页面
  • refreshVideoToStayUser() / showToastToStayUser() - 用户留存

配置参数平台支持

参见上文"VideoConfig配置参数"章节,已清晰标注每个参数的平台支持情况。


🔗 相关文档


💡 提示: 沉浸式小视频功能需要有效的内容源和网络连接。建议在正式使用前先进行测试,确保内容加载正常。一起看视频功能需要额外的服务端支持。

需要进一步协助?

与 LightCore 技术顾问沟通,获取商业化策略与集成支持。