You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
9.6 KiB
285 lines
9.6 KiB
|
3 weeks ago
|
import { MiniGamePf } from '../../../mx/tools/MiniGameHostInfo';
|
||
|
|
import { sdkConfig } from '../sdkConfig';
|
||
|
|
|
||
|
|
/** vivo 广告组件(Banner / 原生模板 / 退出浮层) */
|
||
|
|
interface VivoAdInst {
|
||
|
|
/** 展示 */
|
||
|
|
show: () => Promise<unknown>;
|
||
|
|
/** 销毁。实例不能复用 */
|
||
|
|
destroy?: () => void;
|
||
|
|
/** 加载或展示失败 */
|
||
|
|
onError?: (cb: (err: unknown) => void) => void;
|
||
|
|
/** 用户关闭 */
|
||
|
|
onClose?: (cb: () => void) => void;
|
||
|
|
/** 取消错误监听 */
|
||
|
|
offError?: (cb: (err: unknown) => void) => void;
|
||
|
|
/** 取消关闭监听 */
|
||
|
|
offClose?: (cb: () => void) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* vivo 小游戏广告。仅 chanId 为 vivo 时生效。
|
||
|
|
* Banner:qg.createBannerAd。原生模板与退出浮层:qg.createCustomAd。参数用 posId。
|
||
|
|
*/
|
||
|
|
export class VivoAd {
|
||
|
|
/** 大厅 Banner */
|
||
|
|
private static _banner: VivoAdInst | null = null;
|
||
|
|
/** 结算原生模板 */
|
||
|
|
private static _native: VivoAdInst | null = null;
|
||
|
|
/** 退出浮层 */
|
||
|
|
private static _exit: VivoAdInst | null = null;
|
||
|
|
/** 退出浮层关闭或失败后的继续回调 */
|
||
|
|
private static _exitDone: (() => void) | null = null;
|
||
|
|
/** 当前尝试的退出浮层下标 */
|
||
|
|
private static _exitIndex = 0;
|
||
|
|
/** 退出浮层已结束,避免 onError 与 show 失败重复回调 */
|
||
|
|
private static _exitSettled = false;
|
||
|
|
/** 刚展示过退出浮层,下一次原生模板跳过,避免退出后立刻再叠一条 */
|
||
|
|
private static _blockNative = false;
|
||
|
|
|
||
|
|
/** 当前配置是否 vivo 渠道 */
|
||
|
|
static isVivo(): boolean {
|
||
|
|
return sdkConfig.config.chanId === 'vivo';
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 进入大厅时展示底部 Banner */
|
||
|
|
static showBanner(): void {
|
||
|
|
if (!this.isVivo()) return;
|
||
|
|
const posId = this._firstId(sdkConfig.config.platformConf?.banner);
|
||
|
|
if (!posId) {
|
||
|
|
console.warn('[VivoAd] 未配置 banner posId');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const qg = this._qg();
|
||
|
|
if (!qg) return;
|
||
|
|
if (typeof qg.createBannerAd !== 'function') {
|
||
|
|
console.warn('[VivoAd] 宿主无 createBannerAd');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._drop(this._banner, this._onBannerError, undefined);
|
||
|
|
this._banner = null;
|
||
|
|
let ad: VivoAdInst | null = null;
|
||
|
|
try {
|
||
|
|
ad = qg.createBannerAd({ posId, style: {} }) ?? null;
|
||
|
|
} catch (e) {
|
||
|
|
console.warn('[VivoAd] createBannerAd 失败', String(e));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!ad?.show) {
|
||
|
|
console.warn('[VivoAd] createBannerAd 返回空');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._banner = ad;
|
||
|
|
ad.onError?.(this._onBannerError);
|
||
|
|
this._callShow(ad, this._onBannerError);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 离开大厅时销毁 Banner */
|
||
|
|
static hideBanner(): void {
|
||
|
|
this._drop(this._banner, this._onBannerError, undefined);
|
||
|
|
this._banner = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 结算页展示原生模板(顶部,避免挡住领取按钮)。
|
||
|
|
* @returns 是否已发起展示
|
||
|
|
*/
|
||
|
|
static showNative(): boolean {
|
||
|
|
if (!this.isVivo()) return false;
|
||
|
|
if (this._blockNative) {
|
||
|
|
this._blockNative = false;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const posId = this._firstId(sdkConfig.config.platformConf?.nativeAd);
|
||
|
|
if (!posId) {
|
||
|
|
console.warn('[VivoAd] 未配置原生模板 posId');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const ad = this._createCustom(posId, { gravity: 'top' }, this._onNativeError, undefined);
|
||
|
|
if (!ad) return false;
|
||
|
|
this._drop(this._native, this._onNativeError, undefined);
|
||
|
|
this._native = ad;
|
||
|
|
this._callShow(ad, this._onNativeError);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 离开结算页时销毁原生模板 */
|
||
|
|
static hideNative(): void {
|
||
|
|
this._drop(this._native, this._onNativeError, undefined);
|
||
|
|
this._native = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 展示退出浮层。非 vivo、无广告位或宿主不支持时立即回调。
|
||
|
|
* @param onDone 关闭、失败或未展示
|
||
|
|
*/
|
||
|
|
static showExit(onDone: () => void): void {
|
||
|
|
const ids = this._ids(sdkConfig.config.platformConf?.exitAd);
|
||
|
|
if (!this.isVivo() || ids.length === 0) {
|
||
|
|
if (this.isVivo() && ids.length === 0) console.warn('[VivoAd] 未配置退出浮层 posId');
|
||
|
|
onDone();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this._exitDone) {
|
||
|
|
console.warn('[VivoAd] 退出浮层展示中');
|
||
|
|
onDone();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._exitDone = onDone;
|
||
|
|
this._exitIndex = 0;
|
||
|
|
this._exitSettled = false;
|
||
|
|
this._blockNative = true;
|
||
|
|
this._openExit();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Banner 失败 */
|
||
|
|
private static _onBannerError = (err: unknown): void => {
|
||
|
|
console.warn('[VivoAd] banner', VivoAd._errText(err));
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 原生模板失败 */
|
||
|
|
private static _onNativeError = (err: unknown): void => {
|
||
|
|
console.warn('[VivoAd] native', VivoAd._errText(err));
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 退出浮层失败,换下一条 */
|
||
|
|
private static _onExitFail = (err: unknown): void => {
|
||
|
|
if (VivoAd._exitSettled || !VivoAd._exit) return;
|
||
|
|
console.warn('[VivoAd] 退出浮层', VivoAd._errText(err));
|
||
|
|
VivoAd._nextExit();
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 用户关闭退出浮层 */
|
||
|
|
private static _onExitClose = (): void => {
|
||
|
|
VivoAd._finishExit();
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 创建并展示当前下标的退出浮层 */
|
||
|
|
private static _openExit(): void {
|
||
|
|
const ids = this._ids(sdkConfig.config.platformConf?.exitAd);
|
||
|
|
const posId = ids[this._exitIndex];
|
||
|
|
if (!posId) {
|
||
|
|
this._finishExit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const ad = this._createCustom(posId, undefined, this._onExitFail, this._onExitClose);
|
||
|
|
if (!ad) {
|
||
|
|
this._nextExit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._drop(this._exit, this._onExitFail, this._onExitClose);
|
||
|
|
this._exit = ad;
|
||
|
|
this._callShow(ad, this._onExitFail);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 当前退出浮层失败,尝试下一条 */
|
||
|
|
private static _nextExit(): void {
|
||
|
|
if (this._exitSettled) return;
|
||
|
|
this._drop(this._exit, this._onExitFail, this._onExitClose);
|
||
|
|
this._exit = null;
|
||
|
|
this._exitIndex++;
|
||
|
|
const ids = this._ids(sdkConfig.config.platformConf?.exitAd);
|
||
|
|
if (this._exitIndex < ids.length) {
|
||
|
|
this._openExit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._finishExit();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 退出浮层流程结束 */
|
||
|
|
private static _finishExit(): void {
|
||
|
|
if (this._exitSettled) return;
|
||
|
|
this._exitSettled = true;
|
||
|
|
const done = this._exitDone;
|
||
|
|
this._exitDone = null;
|
||
|
|
this._drop(this._exit, this._onExitFail, this._onExitClose);
|
||
|
|
this._exit = null;
|
||
|
|
done?.();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 创建原生模板。style 不传则用广告位后台样式。
|
||
|
|
* @param posId 广告位
|
||
|
|
* @param style 位置,结算页用 top
|
||
|
|
* @param onError 错误回调
|
||
|
|
* @param onClose 关闭回调
|
||
|
|
*/
|
||
|
|
private static _createCustom(posId: string, style: { gravity?: string } | undefined, onError: (err: unknown) => void, onClose: (() => void) | undefined): VivoAdInst | null {
|
||
|
|
const qg = this._qg();
|
||
|
|
if (!qg) return null;
|
||
|
|
if (typeof qg.createCustomAd !== 'function') {
|
||
|
|
console.warn('[VivoAd] 宿主无 createCustomAd');
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
let ad: VivoAdInst | null = null;
|
||
|
|
try {
|
||
|
|
ad = qg.createCustomAd(style ? { posId, style } : { posId }) ?? null;
|
||
|
|
} catch (e) {
|
||
|
|
console.warn('[VivoAd] createCustomAd 失败', posId, String(e));
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if (!ad?.show) {
|
||
|
|
console.warn('[VivoAd] createCustomAd 返回空', posId);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
ad.onError?.(onError);
|
||
|
|
if (onClose) ad.onClose?.(onClose);
|
||
|
|
return ad;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 调用 show,Promise 拒绝走 onFail */
|
||
|
|
private static _callShow(ad: VivoAdInst, onFail: (err: unknown) => void): void {
|
||
|
|
try {
|
||
|
|
const ret = ad.show();
|
||
|
|
if (ret && typeof ret.catch === 'function') ret.catch(onFail);
|
||
|
|
} catch (e) {
|
||
|
|
onFail(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 解绑并销毁 */
|
||
|
|
private static _drop(ad: VivoAdInst | null, onError: ((err: unknown) => void) | undefined, onClose: (() => void) | undefined): void {
|
||
|
|
if (!ad) return;
|
||
|
|
if (onError) ad.offError?.(onError);
|
||
|
|
if (onClose) ad.offClose?.(onClose);
|
||
|
|
try {
|
||
|
|
ad.destroy?.();
|
||
|
|
} catch (e) {
|
||
|
|
console.warn('[VivoAd] destroy 失败', String(e));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** vivo 小游戏宿主。非 qg 时警告并返回 null */
|
||
|
|
private static _qg(): Record<string, any> | null {
|
||
|
|
if (MiniGamePf.id() !== 'qg') {
|
||
|
|
console.warn('[VivoAd] 当前不是 qg 宿主');
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return MiniGamePf.api();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 取第一条非空广告位 */
|
||
|
|
private static _firstId(ids: string[] | undefined): string {
|
||
|
|
return this._ids(ids)[0] ?? '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 去掉空广告位 */
|
||
|
|
private static _ids(ids: string[] | undefined): string[] {
|
||
|
|
if (!ids) return [];
|
||
|
|
const out: string[] = [];
|
||
|
|
for (let i = 0; i < ids.length; i++) {
|
||
|
|
if (ids[i]) out.push(ids[i]);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 错误对象转日志文本 */
|
||
|
|
private static _errText(err: unknown): string {
|
||
|
|
if (err && typeof err === 'object') {
|
||
|
|
const o = err as { errCode?: number; errMsg?: string };
|
||
|
|
if (o.errCode != null || o.errMsg) return String(o.errCode ?? '') + ' ' + String(o.errMsg ?? '');
|
||
|
|
}
|
||
|
|
return String(err ?? '');
|
||
|
|
}
|
||
|
|
}
|