🎯 广告类型
版本 GroMore

简介

激励视频广告是一种全新的广告形式,用户可选择观看视频广告以换取有价物,例如虚拟货币、应用内物品和独家内容等。这类广告的特点是:

  • 全屏展示:视频广告全屏播放,沉浸式体验
  • 奖励激励:观看完成后发放奖励,提升用户参与度
  • 可控跳过:部分广告支持跳过(视不同ADN而定)
  • 奖励验证:支持客户端和服务端两种验证方式

典型使用场景

  1. 游戏应用:观看视频获得游戏内金币、道具、生命值等
  2. 内容应用:观看视频解锁章节、文章、视频内容等
  3. 工具应用:观看视频获得会员时长、去广告特权等
  4. 社交应用:观看视频获得虚拟礼物、特殊权限等

快速开始

激励视频广告采用加载+展示两步流程,先调用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,
})

参数说明

参数名类型必填说明平台支持
posIdString广告位ID全平台
orientationint?屏幕方向。1=竖屏,2=横屏全平台
customDataObject?服务端验证透传参数。可传String或Map全平台
userIdString?用户唯一标识,用于奖励验证全平台
rewardNameString?奖励名称(如"金币"),会在广告中显示全平台
rewardAmountint?奖励数量,会在广告中显示全平台
mutedIfCanbool?是否静音。SDK默认值为false全平台
volumedouble?音量大小,范围0.0-1.0💡 仅Android
bidNotifybool?竞价结果是否通知ADN全平台
scenarioIdString?广告场景ID,用于数据分析全平台
useSurfaceViewbool?是否使用SurfaceView渲染💡 仅Android
enablePlayAgainbool?是否启用"再看一个"功能全平台

返回值

返回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)

参数说明

参数名类型必填说明
posIdString广告位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:用户ID
  • trans_id:订单ID
  • reward_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未正确初始化

解决方案:

  1. 检查网络连接
  2. 检查广告位ID是否正确
  3. 查看错误码和错误信息
  4. 实施重试机制(最多重试1次)

Q2: 为什么奖励没有发放?

A: 检查以下情况:

  • 用户是否看完了完整视频
  • verified字段是否为true
  • 服务端回调是否正常
  • 是否监听了reward_video_rewarded事件

Q3: 如何防止用户刷奖励?

A: 最佳实践:

  1. 使用服务端验证,不要完全信任客户端
  2. 在customData中传入唯一订单ID
  3. 服务端记录已处理的订单,防止重复
  4. 添加用户行为分析,检测异常

Q4: useSurfaceView参数有什么用?

A: useSurfaceView是Android专属参数:

  • true:使用SurfaceView渲染,性能更好
  • false:使用TextureView渲染,兼容性更好
  • 如果遇到渲染问题,可以尝试切换

Q5: 再看一个功能如何配置?

A: 需要同时满足:

  1. 加载时设置enablePlayAgain: true
  2. 在GroMore平台开启"再看一个"开关
  3. 配置再得广告的底价

Q6: 如何测试服务端验证?

A: 开发阶段可以:

  1. 使用测试广告位ID
  2. 在本地搭建测试服务器
  3. 使用ngrok等工具将本地服务暴露到公网
  4. 在GroMore平台配置回调URL

Q7: 广告展示后App卡顿怎么办?

A: 优化建议:

  1. 检查是否在主线程执行了耗时操作
  2. 适当降低视频音量或静音
  3. Android端可以尝试useSurfaceView: false
  4. 减少广告展示时的其他动画效果

平台差异

功能AndroidiOS说明
useSurfaceView仅Android支持,控制视频渲染方式
其他参数所有参数都支持

相关文档

需要进一步协助?

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