📱 沉浸式小视频
沉浸式小视频提供类似抖音的全屏垂直视频浏览体验,支持上下滑动切换、点赞评论、一起看视频等丰富功能。
🚪 接入方式概览
方式 | 推荐场景 | 实现特点 |
---|---|---|
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参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
channelType | int | 1 | 频道类型:1-推荐,2-关注,3-推荐+关注 |
contentType | int | 1 | 内容类型:1-小视频,2-直播,3-小视频+直播 |
style | int | 2 | 样式类型:2-全屏沉浸式 |
hideChannelName | bool | false | 是否隐藏频道名称(顶部切换栏) |
autoPlay | bool | true | 是否自动播放 |
controller | VideoController? | null | 视频控制器(可选) |
listener | VideoListener? | null | 事件监听器 |
videoId | String? | 自动生成 | 自定义视频标识 |
extra | Map<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个配置参数。
通用参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
showBackBtn | bool? | - | 是否显示返回按钮 |
progressBarStyle | int? | - | 进度条样式:1-浅色(白色),2-深色(蓝色) |
customCategory | String? | - | 自定义频道名称 |
scene | String? | - | 场景标识 |
drawChannelType | int? | - | 频道类型:1-推荐,2-关注,3-推荐+关注 |
hideFollow | bool? | - | 是否隐藏关注功能 |
hideChannelName | bool? | - | 是否隐藏频道名称 |
enableRefresh | bool? | - | 是否支持下拉刷新 |
hideClose | bool? | - | 是否隐藏关闭按钮 |
adOffset | int? | - | 广告偏移量(距离底部),单位dp |
bottomOffset | int? | - | 底部偏移量,单位dp |
titleTopMargin | int? | - | 标题栏上边距,单位dp |
titleLeftMargin | int? | - | 标题栏左边距,单位dp |
titleRightMargin | int? | - | 标题栏右边距,单位dp |
Android专用参数
参数 | 类型 | 说明 |
---|---|---|
isShowActionBar | bool? | 是否展示ActionBar |
fontSize | int? | 字体大小 |
isBold | bool? | 字体是否加粗 |
actionBarBackgroundColor | String? | ActionBar背景颜色(十六进制,如'#FF6200EE') |
supportOrientationChange | bool? | 是否支持横竖屏切换 |
isShowNeedTopMargin | bool? | 是否显示距离顶部的距离参数 |
topMargin | int? | 距离顶部的距离,单位dp |
iOS专用参数
参数 | 类型 | 说明 |
---|---|---|
allowPanBack | bool? | 是否允许左滑返回 |
isShowNavigationBar | bool? | 是否显示导航栏 |
navigationBarBackgroundColor | String? | 导航栏背景颜色 |
navigationBarTitleColor | String? | 导航栏标题颜色 |
navigationBarButtonColor | String? | 导航栏按钮颜色 |
statusBarStyle | String? | 状态栏样式:'default','lightContent','darkContent' |
showCloseButton | bool? | 是否显示关闭按钮 |
hiddenGuideGeste | bool? | 是否隐藏首次安装引导手势 |
outBottomOffset | double? | 外流进度条距离底部距离 |
featureValuesArr | List<String>? | 支持的返回内容类型 |
recommandTabName | String? | 推荐频道名称(个性化推荐关闭后) |
drawVCTabOptions | int? | 需要展示的tab |
shouldDisableFollowingFunc | bool? | 是否屏蔽关注功能 |
shouldHideTabBarView | bool? | 是否隐藏频道tab |
customRefresh | bool? | 自定义刷新行为和手势 |
navBarInset | Map<String, double>? | 导航栏内边距配置 |
hiddenPlayletTitleView | bool? | 是否隐藏短剧title |
hiddenPlayletEnterView | bool? | 是否隐藏底部跳转区域 |
一起看视频参数
参数 | 类型 | 说明 |
---|---|---|
watchTogetherRole | String? | 角色:'NONE'-普通模式,'HOST'-主控端,'USER'-被控端 |
hostGroupId | int? | 主控端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: 确认 watchTogetherRole
和 hostGroupId
参数设置正确,主控端和被控端的 hostGroupId
必须一致
Q: 事件回调收不到
A: 确保已正确设置事件监听器:
- NativeView方式: 传入
VideoListener
- API方式: 调用
PangrowthContent.onEventListener()
Q: Android端容器显示异常
A: 检查 containerId
参数,或使用默认值(传入null)
Q: 视频自动暂停
A: NativeView会自动监听应用生命周期,切换到后台时会自动暂停,这是正常行为
调试建议
- 开启日志输出: 在代码中使用
debugPrint
查看详细错误信息 - 确认SDK版本: 确保SDK版本和文档版本匹配
- 检查网络连接: 验证网络状态
- 验证配置参数: 检查AppID和AppSecret配置
- 测试基础功能: 先使用最简配置进行测试
🌍 平台差异说明
展示方式差异
平台 | 实现方式 | 参数 |
---|---|---|
Android | 使用Activity展示 | containerId :指定展示容器 |
iOS | 使用ViewController展示 | presentationStyle :控制展示样式 |
平台专属方法
Android专属
scrollToTop()
- 滚动到顶部backRefresh()
- 返回刷新(挽留用户)
iOS专属
getCurrentIndex()
- 获取当前位置scrollToIndex()
- 滚动到指定位置pauseCurrentVideo()
/playCurrentVideo()
- 播放控制seekVideoToMSeconds()
- 精确进度控制(毫秒)createReportContent()
- 创建举报页面refreshVideoToStayUser()
/showToastToStayUser()
- 用户留存
配置参数平台支持
参见上文"VideoConfig配置参数"章节,已清晰标注每个参数的平台支持情况。
🔗 相关文档
- 宫格与双Feed小视频 - 宫格/双Feed布局的小视频
- 个人主页 - 用户个人主页组件
- 事件处理 - 完整的事件体系说明
💡 提示: 沉浸式小视频功能需要有效的内容源和网络连接。建议在正式使用前先进行测试,确保内容加载正常。一起看视频功能需要额外的服务端支持。