From d651121d5784482b6278ffe92c902de2af69639c Mon Sep 17 00:00:00 2001 From: handongxun Date: Sun, 24 Apr 2022 15:39:06 +0800 Subject: [PATCH] add: ad-rewarded-video, ad-interstitial, ad-full-screen-video, ad-interactive --- .../components/ad-full-screen-video.vue | 30 + .../components/ad-interactive.vue | 142 +++++ .../components/ad-interstitial.vue | 30 + .../components/ad-rewarded-video.vue | 29 + .../uni-cli-shared/components/ad.mixin.js | 538 ++++++++++++++++++ .../uni-cli-shared/components/ad.mixin.mp.js | 87 +++ packages/uni-cli-shared/components/uniad.vue | 17 + packages/uni-cli-shared/lib/pages.js | 2 +- .../lib/platforms/mp-weixin/app.json.uniad.js | 24 + 9 files changed, 898 insertions(+), 1 deletion(-) create mode 100644 packages/uni-cli-shared/components/ad-full-screen-video.vue create mode 100644 packages/uni-cli-shared/components/ad-interactive.vue create mode 100644 packages/uni-cli-shared/components/ad-interstitial.vue create mode 100644 packages/uni-cli-shared/components/ad-rewarded-video.vue create mode 100644 packages/uni-cli-shared/components/ad.mixin.js create mode 100644 packages/uni-cli-shared/components/ad.mixin.mp.js create mode 100644 packages/uni-cli-shared/components/uniad.vue create mode 100644 packages/webpack-uni-pages-loader/lib/platforms/mp-weixin/app.json.uniad.js diff --git a/packages/uni-cli-shared/components/ad-full-screen-video.vue b/packages/uni-cli-shared/components/ad-full-screen-video.vue new file mode 100644 index 000000000..b650f0a73 --- /dev/null +++ b/packages/uni-cli-shared/components/ad-full-screen-video.vue @@ -0,0 +1,30 @@ + + + diff --git a/packages/uni-cli-shared/components/ad-interactive.vue b/packages/uni-cli-shared/components/ad-interactive.vue new file mode 100644 index 000000000..c33b5f115 --- /dev/null +++ b/packages/uni-cli-shared/components/ad-interactive.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/packages/uni-cli-shared/components/ad-interstitial.vue b/packages/uni-cli-shared/components/ad-interstitial.vue new file mode 100644 index 000000000..dcb58220d --- /dev/null +++ b/packages/uni-cli-shared/components/ad-interstitial.vue @@ -0,0 +1,30 @@ + + + diff --git a/packages/uni-cli-shared/components/ad-rewarded-video.vue b/packages/uni-cli-shared/components/ad-rewarded-video.vue new file mode 100644 index 000000000..a1f138e26 --- /dev/null +++ b/packages/uni-cli-shared/components/ad-rewarded-video.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/uni-cli-shared/components/ad.mixin.js b/packages/uni-cli-shared/components/ad.mixin.js new file mode 100644 index 000000000..125c3a594 --- /dev/null +++ b/packages/uni-cli-shared/components/ad.mixin.js @@ -0,0 +1,538 @@ +const ADType = { + RewardedVideo: "RewardedVideo", + FullScreenVideo: "FullScreenVideo", + Interstitial: "Interstitial" +} + +const EventType = { + Load: 'load', + Close: 'close', + Error: 'error' +} + +const EXPIRED_TIME = 1000 * 60 * 30 +const ProviderType = { + CSJ: 'csj', + GDT: 'gdt' +} + +const RETRY_COUNT = 1 + +class AdBase { + + constructor(adInstance, options = {}, interstitial) { + this._isLoad = false + this._isLoading = false + this._isPlaying = false + this._lastLoadTime = 0 + this._lastError = null + this._retryCount = 0 + this._isInterstitial = interstitial || false + if (options.retry !== undefined) { + this._retry = options.retry + } else { + this._retry = true + } + + this._loadCallback = null + this._closeCallback = null + this._errorCallback = null + + const ad = this._ad = adInstance + ad.onLoad((e) => { + this._isLoading = false + this._isLoad = true + this._lastLoadTime = Date.now() + + this.onLoad() + }) + ad.onClose((e) => { + this._isLoad = false + this._isPlaying = false + this.onClose(e) + }) + ad.onVerify && ad.onVerify((e) => { + // e.isValid + }) + ad.onError(({ + code, + message + }) => { + this._isLoading = false + const data = { + code: code, + errMsg: message + } + + if (this._retry && code === -5008) { + this._loadAd() + return + } + + if (this._retry && this._retryCount < RETRY_COUNT) { + this._retryCount += 1 + this._loadAd() + return + } + + this._lastError = data + this.onError(data) + }) + } + + get isExpired() { + return (this._lastLoadTime !== 0 && (Math.abs(Date.now() - this._lastLoadTime) > EXPIRED_TIME)) + } + + get isLoad() { + return this._isLoad + } + + get isLoading() { + return this._isLoading + } + + getProvider() { + return this._ad.getProvider() + } + + load(onload, onerror) { + this._loadCallback = onload + this._errorCallback = onerror + + if (this._isPlaying) { + onerror && onerror() + return + } + + if (this._isLoading) { + return + } + + if (this._isLoad) { + this.onLoad() + return + } + + this._retryCount = 0 + + this._loadAd() + } + + show(onclose, onshow) { + this._closeCallback = onclose + + if (this._isLoading || this._isPlaying || !this._isLoad) { + return + } + + if (this._lastError !== null) { + this.onError(this._lastError) + return + } + + const provider = this.getProvider() + if (provider === ProviderType.CSJ && this.isExpired) { + if (this._retry) { + this._loadAd() + } else { + this.onError(this._lastError) + } + return + } + + // TODO + if (this._isInterstitial && provider === ProviderType.GDT) { + setTimeout(() => { + this._isPlaying = true + this._ad.show() + onshow && onshow() + }, 1000) + } else { + this._isPlaying = true + this._ad.show() + onshow && onshow() + } + } + + onLoad(e) { + if (this._loadCallback != null) { + this._loadCallback() + } + } + + onClose(e) { + if (this._closeCallback != null) { + this._closeCallback({ + isEnded: e.isEnded + }) + } + } + + onError(e) { + if (this._errorCallback != null) { + this._errorCallback(e) + } + } + + destroy() { + this._ad.destroy() + } + + _loadAd() { + this._isLoad = false + this._isLoading = true + this._lastError = null + this._ad.load() + } +} + +class RewardedVideo extends AdBase { + constructor(options = {}) { + super(plus.ad.createRewardedVideoAd(options), options) + } +} + +class FullScreenVideo extends AdBase { + constructor(options = {}) { + super(plus.ad.createFullScreenVideoAd(options), options) + } +} + +class Interstitial extends AdBase { + constructor(options = {}, interstitial) { + super(plus.ad.createInterstitialAd(options), options, interstitial) + } +} + +class AdHelper { + + constructor(adType) { + this._ads = {} + this._adType = adType + } + + load(options, onload, onerror) { + if (!options.adpid || this.isBusy(options.adpid)) { + return + } + + this.get(options).load(onload, onerror) + } + + show(options, onload, onerror, onclose, onshow) { + let ad = this.get(options) + + if (ad.isLoad) { + ad.show((e) => { + onclose && onclose(e) + }, () => { + onshow && onshow() + }) + } else { + ad.load(() => { + onload && onload() + ad.show((e) => { + onclose && onclose(e) + }, () => { + onshow && onshow() + }) + }, (err) => { + onerror && onerror(err) + }) + } + } + + // 底价预载逻辑 + loadWaterfall(options, onload, onfail, index = 0) { + let { + adpid, + urlCallback + } = options + if (!Array.isArray(adpid)) { + return + } + + let options2 = { + adpid: adpid[index], + urlCallback, + retry: false + }; + + this.load(options2, (res) => { + onload(options2); + }, (err) => { + index++; + if (index >= adpid.length) { + onfail(err); + } else { + console.log('loadWaterfall::index=' + index); + this.loadWaterfall(options, onload, onfail, index); + } + }); + } + + // 底价逻辑,失败后下一个,无重试机制 + showWaterfall(options, onload, onfail, onclose, onshow, index = 0) { + let { + adpid, + urlCallback + } = options + if (!Array.isArray(adpid)) { + return + } + + let options2 = { + adpid: adpid[index], + urlCallback, + retry: false + }; + + this.show(options2, () => { + onload(); + }, (err) => { + index++; + if (index >= adpid.length) { + onfail(err); + } else { + this.showWaterfall(options, onload, onfail, onclose, onshow, index); + } + }, (res) => { + onclose(res); + }, () => { + onshow(); + }); + } + + // 预载底价瀑布流 + preloadWaterfall(options, index = 0, step = 1) { + if (step === 1) { + this.loadWaterfall(options, (res) => { + console.log("preloadWaterfall.success::", res); + }, (err) => { + console.log("loadWaterfall.fail", err); + }) + return; + } + + let { + adpid, + urlCallback + } = options + let ads = []; + for (let i = 0; i < step; i++) { + if (index < adpid.length) { + let options2 = { + adpid: adpid[index], + urlCallback + }; + this.loadWaterfall(options2, (res) => { + console.log("preloadWaterfall.success::", res); + }, (err) => { + console.log("loadWaterfall.fail", err); + this.preloadWaterfall(options, index, step); + }) + index++; + } else { + break; + } + } + } + + isBusy(adpid) { + return (this._ads[adpid] && this._ads[adpid].isLoading) + } + + get(options) { + const { + adpid, + } = options + + if (!this._ads[adpid]) { + this._ads[adpid] = this._createInstance(options) + } + + return this._ads[adpid] + } + + remove(adpid) { + if (this._ads[adpid]) { + this._ads[adpid].destroy() + delete this._ads[adpid] + } + } + + _createInstance(options) { + const adType = options.adType || this._adType + delete options.adType + + let ad = null; + if (adType === ADType.RewardedVideo) { + ad = new RewardedVideo(options) + } else if (adType === ADType.FullScreenVideo) { + ad = new FullScreenVideo(options) + } else if (adType === ADType.Interstitial) { + ad = new Interstitial(options, true) + } + + return ad + } +} + +export default { + props: { + options: { + type: [Object, Array], + default () { + return {} + } + }, + disabled: { + type: [Boolean, String], + default: false + }, + adpid: { + type: [Number, String, Array], + default: '' + }, + preload: { + type: [Boolean, String], + default: true + }, + loadnext: { + type: [Boolean, String], + default: false + }, + urlCallback: { + type: Object, + default () { + return {} + } + } + }, + data() { + return { + loading: false, + errorMessage: null + } + }, + watch: { + adpid(newValue, oldValue) { + this._removeInstance(oldValue) + if (this.preload) { + this._loadAd() + } + }, + // 服务器回调透传参数,仅在创建广告实例时可传递参数,如果发生变化需要重新创建广告实例 + urlCallback() { + this._removeInstance() + } + }, + created() { + this._adHelper = new AdHelper(this.adType) + + setTimeout(() => { + if (this.preload) { + this._loadAd() + } + }, 100) + }, + methods: { + load() { + this._startLoading() + const invoke = this._isWaterfall() ? "loadWaterfall" : "load" + this._adHelper[invoke](this._getAdOptions(), () => { + this._onLoad() + }, (err) => { + this._onLoadFail(err) + }) + }, + + show() { + this._startLoading() + const invoke = this._isWaterfall() ? "showWaterfall" : "show" + this._adHelper[invoke](this._getAdOptions(), () => { + this._onLoad() + }, (err) => { + this._onLoadFail(err) + }, (res) => { + this._dispatchEvent(EventType.Close, res) + + if (this.loadnext) { + this.load() + } + }, () => { + // show + this.loading = false + }) + }, + + _loadAd() { + if (this._canCreateAd()) { + this.load() + } + }, + + _onclick() { + if (!this.disabled) { + this.show() + } + }, + + _getAdOptions() { + return { + adpid: this.adpid, + urlCallback: this.urlCallback + } + }, + + _isWaterfall() { + return (Array.isArray(this.adpid) && this.adpid.length > 0) + }, + + _canCreateAd() { + let result = false + if (Array.isArray(this.adpid) && this.adpid.length > 0) { + result = true + } else if (typeof this.adpid === 'string' && this.adpid.length > 0) { + result = true + } else if (typeof this.adpid === 'number') { + result = true + } + return result + }, + + _removeInstance(adpid) { + let id = adpid || this.adpid + if (Array.isArray(id)) { + id.forEach((item) => { + this._adHelper.remove(item) + }) + } else if (id) { + this._adHelper.remove(id) + } + }, + + _startLoading() { + this.loading = true + this.errorMessage = null + }, + + _onLoad(err) { + this.loading = false + this._dispatchEvent(EventType.Load, {}) + }, + + _onLoadFail(err) { + this.loading = false + this.errorMessage = JSON.stringify(err) + this._dispatchEvent(EventType.Error, err) + }, + + _dispatchEvent(type, data) { + this.$emit(type, { + detail: data + }) + } + } +} diff --git a/packages/uni-cli-shared/components/ad.mixin.mp.js b/packages/uni-cli-shared/components/ad.mixin.mp.js new file mode 100644 index 000000000..75ba65c56 --- /dev/null +++ b/packages/uni-cli-shared/components/ad.mixin.mp.js @@ -0,0 +1,87 @@ +const EventType = { + Load: 'load', + Close: 'close', + Error: 'error' +} + +export default { + props: { + options: { + type: [Object, Array], + default () { + return {} + } + }, + adpid: { + type: [Number, String], + default: '' + }, + unitId: { + type: [Number, String], + default: '' + }, + preload: { + type: [Boolean, String], + default: true + }, + loadnext: { + type: [Boolean, String], + default: false + } + }, + data() { + return { + loading: false, + errorMessage: null + } + }, + created() { + this._ad = null + setTimeout(() => { + if (this.preload && this._canCreateAd()) { + this.load(); + } + }, 100) + }, + methods: { + load() { + this.errorMessage = null + }, + + show() { + this.errorMessage = null + this._ad = this.selectComponent('.uni-ad'); + this._ad.show(); + }, + + _onclick() { + this.show() + }, + + _startLoading() { + this.loading = true + this.errorMessage = null + }, + + _onmpload(e) { + this.loading = false + this._dispatchEvent(EventType.Load, {}) + }, + + _onmpclose(e) { + this._dispatchEvent(EventType.Close, e.detail) + }, + + _onmperror(e) { + this.loading = false + this.errorMessage = JSON.stringify(e.detail) + this._dispatchEvent(EventType.Error, e.detail) + }, + + _dispatchEvent(type, data) { + this.$emit(type, { + detail: data + }) + } + } +} diff --git a/packages/uni-cli-shared/components/uniad.vue b/packages/uni-cli-shared/components/uniad.vue new file mode 100644 index 000000000..d1272309c --- /dev/null +++ b/packages/uni-cli-shared/components/uniad.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/uni-cli-shared/lib/pages.js b/packages/uni-cli-shared/lib/pages.js index 29d15f3bf..0505a98e8 100644 --- a/packages/uni-cli-shared/lib/pages.js +++ b/packages/uni-cli-shared/lib/pages.js @@ -519,7 +519,7 @@ function parseUsingAutoImportComponents (usingAutoImportComponents) { const BUILT_IN_COMPONENTS = ['page-meta', 'navigation-bar', 'uni-match-media'] -const BUILT_IN_EASYCOMS = ['unicloud-db'] +const BUILT_IN_EASYCOMS = ['unicloud-db', 'ad-rewarded-video', 'ad-full-screen-video', 'ad-interstitial'] function isBuiltInComponent (name) { // uni-template-compiler/lib/util.js 识别微信内置组件 return BUILT_IN_COMPONENTS.includes(name) diff --git a/packages/webpack-uni-pages-loader/lib/platforms/mp-weixin/app.json.uniad.js b/packages/webpack-uni-pages-loader/lib/platforms/mp-weixin/app.json.uniad.js new file mode 100644 index 000000000..74a893fb3 --- /dev/null +++ b/packages/webpack-uni-pages-loader/lib/platforms/mp-weixin/app.json.uniad.js @@ -0,0 +1,24 @@ +module.exports = function(appJson) { + if (!appJson.plugins) { + appJson.plugins = {} + } + if (!appJson.plugins['uni-ad']) { + appJson.plugins['uni-ad'] = { + "version": "1.0.2", + "provider": "wx999bf02c8e05dfc9" + } + } + if (!appJson.plugins['coral-adv']) { + appJson.plugins['coral-adv'] = { + "version": "1.0.7", + "provider": "wx0e203209e27b1e66" + } + } + + if (!appJson.usingComponents) { + appJson.usingComponents = {} + } + if (!appJson.usingComponents['uni-ad-plugin']) { + appJson.usingComponents['uni-ad-plugin'] = 'plugin://uni-ad/ad' + } +} -- GitLab