🎯 广告类型
版本 GroMore
简介
激励视频广告是一种全新的广告形式,用户可选择观看视频广告以换取有价物,例如虚拟货币、应用内物品和独家内容等。这类广告的特点是:
- 全屏展示:视频广告全屏播放,沉浸式体验
- 奖励激励:观看完成后发放奖励,提升用户参与度
- 可控跳过:部分广告支持跳过(视不同ADN而定)
- 奖励验证:支持客户端和服务端两种验证方式
典型使用场景
- 游戏应用:观看视频获得游戏内金币、道具、生命值等
- 内容应用:观看视频解锁章节、文章、视频内容等
- 工具应用:观看视频获得会员时长、去广告特权等
- 社交应用:观看视频获得虚拟礼物、特殊权限等
快速开始
激励视频广告采用加载+展示两步流程,先调用loadRewardVideoAd
加载广告,成功后调用showRewardVideoAd
展示。
最小示例(30秒可运行)
import 'package:gromore_ads/gromore_ads.dart';
// 1. 加载激励视频广告
await GromoreAds.loadRewardVideoAd('your_reward_video_ad_id');
// 2. 展示激励视频广告
await GromoreAds.showRewardVideoAd('your_reward_video_ad_id');
完整示例(含奖励验证)
import 'package:gromore_ads/gromore_ads.dart';
class RewardVideoExample extends StatefulWidget {
@override
_RewardVideoExampleState createState() => _RewardVideoExampleState();
}
class _RewardVideoExampleState extends State<RewardVideoExample> {
bool _isLoaded = false;
int _userCoins = 100; // 用户当前金币数
AdEventSubscription? _adEventSubscription;
@override
void initState() {
super.initState();
// 监听广告事件
_adEventSubscription = GromoreAds.onRewardVideoEvents(
'your_reward_video_ad_id',
onLoaded: (event) {
setState(() => _isLoaded = true);
print('✅ 激励视频广告加载成功');
},
onRewarded: (event) {
// 奖励发放
if (event.verified) {
setState(() => _userCoins += event.rewardAmount);
print('🎁 获得奖励: ${event.rewardAmount} 金币');
_showRewardDialog(event.rewardAmount);
} else {
print('❌ 奖励验证失败');
}
},
onError: (event) {
setState(() => _isLoaded = false);
print('❌ 激励视频广告加载失败: ${event.message}');
},
);
// 自动加载广告
_loadAd();
}
@override
void dispose() {
_adEventSubscription?.cancel();
super.dispose();
}
Future<void> _loadAd() async {
try {
await GromoreAds.loadRewardVideoAd(
'your_reward_video_ad_id',
userId: 'user_123', // 用户唯一标识
rewardName: '金币', // 奖励名称
rewardAmount: 50, // 奖励数量
customData: { // 服务端验证透传参数
'order_id': DateTime.now().millisecondsSinceEpoch.toString(),
'user_level': '5',
},
);
} catch (e) {
print('加载失败: $e');
}
}
Future<void> _showAd() async {
if (!_isLoaded) {
_showMessage('广告尚未加载,请稍候');
return;
}
try {
await GromoreAds.showRewardVideoAd('your_reward_video_ad_id');
} catch (e) {
print('展示失败: $e');
}
}
void _showRewardDialog(int amount) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('🎉 恭喜获得奖励'),
content: Text('你获得了 $amount 金币!'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('太棒了!'),
),
],
),
);
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('激励视频广告示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('当前金币: $_userCoins', style: TextStyle(fontSize: 24)),
SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _isLoaded ? _showAd : null,
icon: Icon(Icons.play_circle_filled),
label: Text('观看视频获得50金币'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
),
),
if (!_isLoaded) ...[
SizedBox(height: 16),
CircularProgressIndicator(),
Text('广告加载中...'),
],
],
),
),
);
}
}
API参考
loadRewardVideoAd - 加载激励视频广告
Future<bool> loadRewardVideoAd(
String posId, {
int? orientation,
Object? customData,
String? userId,
String? rewardName,
int? rewardAmount,
bool? mutedIfCan,
double? volume,
bool? bidNotify,
String? scenarioId,
bool? useSurfaceView,
bool? enablePlayAgain,
})
参数说明
参数名 | 类型 | 必填 | 说明 | 平台支持 |
---|---|---|---|---|
posId | String | ✅ | 广告位ID | 全平台 |
orientation | int? | ❌ | 屏幕方向。1=竖屏,2=横屏 | 全平台 |
customData | Object? | ❌ | 服务端验证透传参数。可传String或Map | 全平台 |
userId | String? | ❌ | 用户唯一标识,用于奖励验证 | 全平台 |
rewardName | String? | ❌ | 奖励名称(如"金币"),会在广告中显示 | 全平台 |
rewardAmount | int? | ❌ | 奖励数量,会在广告中显示 | 全平台 |
mutedIfCan | bool? | ❌ | 是否静音。SDK默认值为false | 全平台 |
volume | double? | ❌ | 音量大小,范围0.0-1.0 | 💡 仅Android |
bidNotify | bool? | ❌ | 竞价结果是否通知ADN | 全平台 |
scenarioId | String? | ❌ | 广告场景ID,用于数据分析 | 全平台 |
useSurfaceView | bool? | ❌ | 是否使用SurfaceView渲染 | 💡 仅Android |
enablePlayAgain | bool? | ❌ | 是否启用"再看一个"功能 | 全平台 |
返回值
返回Future<bool>
,表示广告是否开始加载。加载结果通过事件回调返回。
示例
// 基础用法
await GromoreAds.loadRewardVideoAd('reward_ad_id');
// 完整配置
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
orientation: 1, // 竖屏
userId: 'user_123', // 用户ID
rewardName: '金币', // 奖励名称
rewardAmount: 100, // 奖励数量
customData: { // 服务端验证参数
'order_id': '12345',
'timestamp': '1640000000',
},
mutedIfCan: false, // 不静音
volume: 0.8, // 音量80%
scenarioId: 'game_reward', // 场景ID
useSurfaceView: true, // Android使用SurfaceView
enablePlayAgain: true, // 启用再看一个
);
showRewardVideoAd - 展示激励视频广告
Future<bool> showRewardVideoAd(String posId)
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
posId | String | ✅ | 广告位ID,必须与加载时的posId一致 |
返回值
返回Future<bool>
,表示广告是否开始展示。
示例
// 展示广告
await GromoreAds.showRewardVideoAd('reward_ad_id');
事件回调
使用订阅模式监听激励视频广告事件:
AdEventSubscription? _subscription;
_subscription = GromoreAds.onRewardVideoEvents(
'your_reward_video_ad_id',
onLoaded: (event) {
print('✅ 广告加载成功: ${event.posId}');
},
onShowed: (event) {
print('📺 广告开始展示');
},
onCompleted: (event) {
print('✅ 视频播放完成');
},
onSkipped: (event) {
print('⏭️ 用户跳过了视频');
},
onRewarded: (event) {
// 获得奖励(关键事件!)
if (event.verified) {
print('🎁 奖励验证通过: ${event.rewardName} x${event.rewardAmount}');
print('用户ID: ${event.userId}');
print('自定义数据: ${event.customData}');
// 发放奖励给用户
} else {
print('❌ 奖励验证失败');
}
},
onClicked: (event) {
print('👆 用户点击了广告');
},
onClosed: (event) {
print('📌 广告关闭');
},
onError: (event) {
print('❌ 广告加载失败: ${event.message} (${event.code})');
},
);
// 记得在页面销毁时取消订阅
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
核心事件说明
事件名 | 说明 | 关键数据 |
---|---|---|
reward_video_loaded | 广告加载成功 | - |
reward_video_load_fail | 广告加载失败 | errorCode, errorMessage |
reward_video_showed | 广告开始展示 | - |
reward_video_play | 视频开始播放 | - |
reward_video_completed | 视频播放完成 | - |
reward_video_skipped | 用户跳过视频 | - |
reward_video_rewarded | ⭐ 获得奖励 | verified, rewardType, rewardName, rewardAmount, userId, customData |
reward_video_clicked | 用户点击广告 | - |
reward_video_closed | 广告关闭 | - |
奖励验证
激励视频广告的核心是奖励验证,确保用户真实观看了广告后才发放奖励。
客户端验证(基础方式)
通过奖励事件的 verified
字段判断:
_subscription = GromoreAds.onRewardVideoEvents(
'your_reward_video_ad_id',
onRewarded: (event) {
if (event.verified) {
// 客户端验证通过,发放奖励
_giveUserReward(event.rewardAmount);
} else {
print('❌ 奖励验证失败');
}
},
);
服务端验证(推荐方式)
对于重要奖励(如虚拟货币),建议使用服务端验证:
1. 加载广告时传入验证参数
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
userId: 'user_123', // 用户唯一标识
customData: { // 透传到服务端的参数
'order_id': generateOrderId(),
'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
'signature': calculateSignature(), // 签名防篡改
},
);
2. 服务端接收回调
广告平台会将以下参数回调到您配置的服务端URL:
user_id
:用户IDtrans_id
:订单IDreward_name
:奖励名称reward_amount
:奖励数量extra
:customData内容sign
:签名
3. 服务端验证并发放奖励
// Node.js示例
app.post('/reward-callback', async (req, res) => {
const { user_id, trans_id, reward_amount, extra, sign } = req.body;
// 1. 验证签名
if (!verifySign(sign)) {
return res.json({ success: false, message: '签名验证失败' });
}
// 2. 检查订单是否已处理(防重复)
if (await isOrderProcessed(trans_id)) {
return res.json({ success: true, message: '订单已处理' });
}
// 3. 发放奖励
await giveUserReward(user_id, reward_amount);
await markOrderProcessed(trans_id);
res.json({ success: true });
});
4. 客户端收到奖励事件
_subscription = GromoreAds.onRewardVideoEvents(
'your_reward_video_ad_id',
onRewarded: (event) {
// 服务端验证成功后,客户端也会收到此事件
print('✅ 服务端验证成功,用户ID: ${event.userId}');
print('自定义数据: ${event.customData}');
// 可选:调用您的服务器API同步奖励状态
await syncRewardStatus(event.userId);
},
);
验证流程图
用户观看视频
↓
视频播放完成
↓
SDK向广告平台报告
↓
广告平台回调您的服务器
↓
您的服务器验证并发放奖励
↓
广告平台通知SDK
↓
SDK触发reward_video_rewarded事件
↓
客户端收到事件
高级功能
再看一个功能
"再看一个"功能允许用户在看完第一个激励视频后,选择继续观看第二个视频以获得更多奖励:
// 1. 加载时启用再看一个
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
enablePlayAgain: true, // 启用再看一个
rewardName: '金币',
rewardAmount: 50, // 第一次奖励50金币
);
// 2. 监听再看一个相关事件
_subscription = GromoreAds.onRewardVideoEvents(
'reward_ad_id',
onEvent: (event) {
if (event.action == 'reward_video_play_again_showed') {
print('📺 第二个视频开始播放');
}
},
onRewarded: (event) {
print('🎁 再看一个获得奖励: ${event.rewardAmount} 金币');
},
);
显示奖励内容
在广告中显示奖励信息,激发用户观看欲望:
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
rewardName: '金币', // 会在广告挽留弹窗中显示
rewardAmount: 100, // 会在广告挽留弹窗中显示
);
效果:当用户尝试跳过时,弹窗显示"继续观看可获得100金币"。
音频控制
控制视频广告的音频播放:
// 静音播放
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
mutedIfCan: true,
);
// 设置音量
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
mutedIfCan: false,
volume: 0.5, // 50%音量
);
场景化投放
通过场景ID区分不同使用场景,便于数据分析:
// 游戏关卡完成后的奖励
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
scenarioId: 'level_complete',
rewardName: '金币',
rewardAmount: 50,
);
// 每日签到奖励
await GromoreAds.loadRewardVideoAd(
'reward_ad_id',
scenarioId: 'daily_checkin',
rewardName: '签到积分',
rewardAmount: 10,
);
错误处理
常见错误
错误场景 | 原因 | 解决方案 |
---|---|---|
加载失败 | 网络问题、广告填充不足 | 重试1次,提供兜底方案 |
展示失败 | 广告未加载完成 | 监听loaded事件后再展示 |
奖励验证失败 | 用户未看完、服务端异常 | 检查verified字段,记录日志 |
广告过期 | 加载后长时间未展示 | 重新加载 |
错误处理示例
class RewardVideoManager {
bool _isLoaded = false;
int _retryCount = 0;
static const _maxRetry = 1;
Future<void> loadWithRetry(String posId) async {
try {
final success = await GromoreAds.loadRewardVideoAd(posId);
if (!success && _retryCount < _maxRetry) {
_retryCount++;
print('加载失败,重试第$_retryCount次');
await Future.delayed(Duration(seconds: 2));
await loadWithRetry(posId);
}
} catch (e) {
print('加载异常: $e');
_showFallbackReward(); // 兜底方案
}
}
Future<void> showWithCheck(String posId) async {
if (!_isLoaded) {
print('广告尚未加载');
return;
}
try {
await GromoreAds.showRewardVideoAd(posId);
} catch (e) {
print('展示失败: $e');
await loadWithRetry(posId); // 重新加载
}
}
void _showFallbackReward() {
// 兜底方案:直接发放部分奖励
print('广告加载失败,发放基础奖励');
}
}
最佳实践
1. 提前预加载
在用户可能使用前提前加载广告:
@override
void initState() {
super.initState();
// 页面初始化时预加载
_preloadAd();
}
Future<void> _preloadAd() async {
await GromoreAds.loadRewardVideoAd('reward_ad_id');
}
2. 明确奖励规则
在展示广告前,清楚告知用户可获得的奖励:
void _showRewardPrompt() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('观看视频获得奖励'),
content: Text('观看完整视频可获得50金币\n您确定要观看吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_showAd();
},
child: Text('观看视频'),
),
],
),
);
}
3. 优雅的加载状态
显示加载状态,提升用户体验:
ElevatedButton.icon(
onPressed: _isLoaded ? _showAd : _loadAd,
icon: _isLoading
? SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Icon(Icons.play_circle),
label: Text(_isLoaded ? '观看视频' : '加载中...'),
)
4. 防止重复发放
记录已发放的奖励,防止重复:
final _processedOrders = <String>{};
void _handleRewardEvent(AdEvent event) {
final orderId = event.extra?['customData']?['order_id'];
if (orderId == null) return;
if (_processedOrders.contains(orderId)) {
print('订单已处理,跳过');
return;
}
_processedOrders.add(orderId);
_giveUserReward();
}
5. 合理的展示频率
避免过度展示广告影响用户体验:
class AdFrequencyController {
DateTime? _lastShowTime;
static const _minInterval = Duration(minutes: 5);
bool canShowAd() {
if (_lastShowTime == null) return true;
return DateTime.now().difference(_lastShowTime!) >= _minInterval;
}
void recordShow() {
_lastShowTime = DateTime.now();
}
}
常见问题
Q1: 为什么广告加载失败?
A: 可能的原因:
- 网络连接问题
- 广告填充不足(竞价失败)
- 广告位ID配置错误
- SDK未正确初始化
解决方案:
- 检查网络连接
- 检查广告位ID是否正确
- 查看错误码和错误信息
- 实施重试机制(最多重试1次)
Q2: 为什么奖励没有发放?
A: 检查以下情况:
- 用户是否看完了完整视频
verified
字段是否为true- 服务端回调是否正常
- 是否监听了
reward_video_rewarded
事件
Q3: 如何防止用户刷奖励?
A: 最佳实践:
- 使用服务端验证,不要完全信任客户端
- 在customData中传入唯一订单ID
- 服务端记录已处理的订单,防止重复
- 添加用户行为分析,检测异常
Q4: useSurfaceView参数有什么用?
A: useSurfaceView
是Android专属参数:
- true:使用SurfaceView渲染,性能更好
- false:使用TextureView渲染,兼容性更好
- 如果遇到渲染问题,可以尝试切换
Q5: 再看一个功能如何配置?
A: 需要同时满足:
- 加载时设置
enablePlayAgain: true
- 在GroMore平台开启"再看一个"开关
- 配置再得广告的底价
Q6: 如何测试服务端验证?
A: 开发阶段可以:
- 使用测试广告位ID
- 在本地搭建测试服务器
- 使用ngrok等工具将本地服务暴露到公网
- 在GroMore平台配置回调URL
Q7: 广告展示后App卡顿怎么办?
A: 优化建议:
- 检查是否在主线程执行了耗时操作
- 适当降低视频音量或静音
- Android端可以尝试
useSurfaceView: false
- 减少广告展示时的其他动画效果
平台差异
功能 | Android | iOS | 说明 |
---|---|---|---|
useSurfaceView | ✅ | ❌ | 仅Android支持,控制视频渲染方式 |
其他参数 | ✅ | ✅ | 所有参数都支持 |