RewardedVideoPlayer.js

import {singleAisle, errSafe} from './decorators';
import {ctxDependConsole as debugConsole} from './debugKit';

/**
 * 激励视频播放器,封装激励视频加载、播放时序,详见{@tutorial 2.b-rewardedVideoPlayer}
 */
class RewardedVideoPlayer {
  _adUnitId = ''; //广告位id
  
  _rewardedVideo = null; //原生视频实例
  _loadStateValue = 'notStart'; //视频加载状态:notStart-未开始 | notSupport-版本过低不支持 |  loading-加载中 | failed-加载失败 | loaded-加载成功
  _playStateValue = 'idle'; //视频播放状态:idle-空闲中 | playing-播放中 | aborted-中途退出 | ended-播放完毕
  
  _loadStateChangeListeners = []; //加载状态监听列表

  /**
   * 构造函数
   * @param {string} adUnitId 广告位id
   */
  constructor({adUnitId}){
    this._adUnitId = adUnitId;
    this._init();
  }

  _init(){
    if (!wx.createRewardedVideoAd) {
      this._rewardedVideo = null;
      this._loadState = 'notSupport';
      this._playState = 'idle';
      return;
    }

    this._rewardedVideo = wx.createRewardedVideoAd({
      adUnitId: this._adUnitId
    });
    
    this._rewardedVideo.onError((e)=>{
      console.error('[rewardedVideoPlayer] error:', e);
      this._loadState==='loading' && (this._loadState = 'failed');
      this._playState==='playing' && (this._playState = 'failed');
    });
    
    this._loadState = 'notStart';
    this._playState = 'idle';
  }

  /**
   * 切换页面时原生视频实例失效,故每次onShow需重新初始化
   * @param {boolean} [preload=true] 是否需要预加载视频:true-开始预加载 | false-不进行预加载(后续可手动调用load()决定加载时机)
   */
  handlePageChange({preload=true}={}){
    debugConsole.log('[rewardedVideoPlayer] enter handlePageChange');
    if (this._playState === 'playing') //点击广告链接跳转其它小程序返回造成的onShow
      return;

    debugConsole.log('[rewardedVideoPlayer] re init');
    this._init();
    preload && this.load();
  }
  
  /**
   * 开始加载视频
   * @param {boolean} [reset=false] 是否需要重置:true-强制重新加载 | false-可复用已有视频
   */
  @singleAisle
  @errSafe
  async load({reset=false}={}){
    debugConsole.log('[rewardedVideoPlayer] enter load');
    if (!this._rewardedVideo)
      return {succeeded: false};
    
    if (this._loadState==='loaded' && !reset)
      return {succeeded: true};
    
    this._loadState = 'loading';

    debugConsole.log('[rewardedVideoPlayer] begin load');
    let loadRes = await new Promise((resolve,reject)=>{
      this._rewardedVideo.load().then(()=>{
        resolve({succeeded: true});
      }).catch((e)=>{
        console.error('[rewardedVideoPlayer] load failed, exception:', e);
        resolve({succeeded: false});
      });
    });

    debugConsole.log('[rewardedVideoPlayer] finish load, res:', loadRes);
    this._loadState = loadRes.succeeded ? 'loaded' : 'failed';
    return {succeeded: loadRes.succeeded};
  }

  /**
   * 开始播放视频
   * @return {object} 播放结果 {
        code: 0, //是否正常:0-正常播放,其它-播放异常(微信版本过低/视频加载失败/其它异常情况)
        errMsg: '', //异常提示信息
        isEnded: true, //(正常时)是否观看完整
   * }
   */
  @errSafe
  async play(){
    debugConsole.log('[rewardedVideoPlayer] enter play');
    //低版本兼容
    if (!this._rewardedVideo)
      return {code: -1, errMsg: '您的微信版本较低,不支持此功能', isEnded: false};
    
    //加载视频
    if (this._loadState !== 'loaded'){
      wx.showLoading({
        title: '视频加载中'
      });
      
      await this.load();
      
      wx.hideLoading();
      
      if (this._loadState !== 'loaded')
        return {code: -2, errMsg: '没有更多视频了', isEnded: false};
    }

    //播放视频
    debugConsole.log('[rewardedVideoPlayer] begin play');
    this._playState = 'playing';
    let playRes = await new Promise((resolve, reject)=>{
      let closeHandler = (status)=>{
        let isEnded = (status && status.isEnded || status === undefined);
        this._rewardedVideo.offClose(closeHandler);
        resolve({code: 0, isEnded, errMsg: 'ok'});
      };
      this._rewardedVideo.onClose(closeHandler);
      this._rewardedVideo.show().catch((e)=>{
        console.error('[rewardedVideoPlayer] play failed, exception:', e);
        resolve({code: -3, errMsg: '播放异常', isEnded: false});
      });
    });

    debugConsole.log('[rewardedVideoPlayer] finish play, res:', playRes);
    //结果处理
    this._playState = playRes.code===0&&playRes.isEnded ? 'ended' : 'aborted';
    this.load({reset: true}); //开始后台加载下一个视频(不管播没播完,都需要重新加载)
    
    return playRes;
  }

  /**
   * 监听加载状态变化,用于展示/隐藏入口等
   * @param {Function} handler 监听函数
   * @example
   * rewardedVideoPlayer.onLoadStateChange(({state})=>{
   *  console.log('state', state); //加载状态,详见{@link RewardedVideoPlayer#loadState}
   * })
   */
  onLoadStateChange(handler){
    if (typeof handler !== 'function')
      return;
    
    handler({state: this._loadState});
    this._loadStateChangeListeners.push(handler);
  }

  /**
   * 获取当前加载状态:notStart-未开始 | notSupport-版本过低不支持 |  loading-加载中 | failed-加载失败 | loaded-加载成功
   */
  get loadState(){ //供外部调用,只读
    return this._loadStateValue;
  }
  get _loadState(){ //内部使用,可读可写
    return this._loadStateValue;
  }
  set _loadState(newState){ //内部使用,可读可写
    if (this._loadState === newState)
      return;
    
    this._loadStateValue = newState;
    this._loadStateChangeListeners.forEach(listener=>listener({state: newState}));
  }
  get _playState(){
    return this._playStateValue;
  }
  set _playState(newState){
    this._playStateValue = newState;
  }
}

export default RewardedVideoPlayer;