ad.mixin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. const ADType = {
  2. RewardedVideo: 'RewardedVideo',
  3. FullScreenVideo: 'FullScreenVideo',
  4. Interstitial: 'Interstitial'
  5. }
  6. const EventType = {
  7. Load: 'load',
  8. Close: 'close',
  9. Error: 'error'
  10. }
  11. const EXPIRED_TIME = 1000 * 60 * 30
  12. const ProviderType = {
  13. CSJ: 'csj',
  14. GDT: 'gdt'
  15. }
  16. const RETRY_COUNT = 1
  17. class AdBase {
  18. constructor (adInstance, options = {}) {
  19. this._isLoad = false
  20. this._isLoading = false
  21. this._isPlaying = false
  22. this._lastLoadTime = 0
  23. this._lastError = null
  24. this._retryCount = 0
  25. if (options.retry !== undefined) {
  26. this._retry = options.retry
  27. } else {
  28. this._retry = true
  29. }
  30. this._loadCallback = null
  31. this._closeCallback = null
  32. this._errorCallback = null
  33. const ad = this._ad = adInstance
  34. ad.onLoad((e) => {
  35. this._isLoading = false
  36. this._isLoad = true
  37. this._lastLoadTime = Date.now()
  38. this.onLoad()
  39. })
  40. ad.onClose((e) => {
  41. this._isLoad = false
  42. this._isPlaying = false
  43. this.onClose(e)
  44. })
  45. ad.onVerify && ad.onVerify((e) => {
  46. // e.isValid
  47. })
  48. ad.onError(({
  49. code,
  50. message
  51. }) => {
  52. this._isLoading = false
  53. const data = {
  54. code: code,
  55. errMsg: message
  56. }
  57. if (this._retry && code === -5008) {
  58. this._loadAd()
  59. return
  60. }
  61. if (this._retry && this._retryCount < RETRY_COUNT) {
  62. this._retryCount += 1
  63. this._loadAd()
  64. return
  65. }
  66. this._lastError = data
  67. this.onError(data)
  68. })
  69. }
  70. get isExpired () {
  71. return (this._lastLoadTime !== 0 && (Math.abs(Date.now() - this._lastLoadTime) > EXPIRED_TIME))
  72. }
  73. get isLoad () {
  74. return this._isLoad
  75. }
  76. get isLoading () {
  77. return this._isLoading
  78. }
  79. getProvider () {
  80. return this._ad.getProvider()
  81. }
  82. load (onload, onerror) {
  83. this._loadCallback = onload
  84. this._errorCallback = onerror
  85. if (this._isPlaying) {
  86. onerror && onerror()
  87. return
  88. }
  89. if (this._isLoading) {
  90. return
  91. }
  92. if (this._isLoad) {
  93. this.onLoad()
  94. return
  95. }
  96. this._retryCount = 0
  97. this._loadAd()
  98. }
  99. show (onclose, onshow) {
  100. this._closeCallback = onclose
  101. if (this._isLoading || this._isPlaying || !this._isLoad) {
  102. return
  103. }
  104. if (this._lastError !== null) {
  105. this.onError(this._lastError)
  106. return
  107. }
  108. const provider = this.getProvider()
  109. if (provider === ProviderType.CSJ && this.isExpired) {
  110. if (this._retry) {
  111. this._loadAd()
  112. } else {
  113. this.onError(this._lastError)
  114. }
  115. return
  116. }
  117. this._isPlaying = true
  118. this._ad.show()
  119. onshow && onshow()
  120. }
  121. onLoad (e) {
  122. if (this._loadCallback != null) {
  123. this._loadCallback()
  124. }
  125. }
  126. onClose (e) {
  127. if (this._closeCallback != null) {
  128. this._closeCallback({
  129. isEnded: e.isEnded
  130. })
  131. }
  132. }
  133. onError (e) {
  134. if (this._errorCallback != null) {
  135. this._errorCallback(e)
  136. }
  137. }
  138. destroy () {
  139. this._ad.destroy()
  140. }
  141. _loadAd () {
  142. this._isLoad = false
  143. this._isLoading = true
  144. this._lastError = null
  145. this._ad.load()
  146. }
  147. }
  148. class RewardedVideo extends AdBase {
  149. constructor (options = {}) {
  150. super(plus.ad.createRewardedVideoAd(options), options)
  151. }
  152. }
  153. class FullScreenVideo extends AdBase {
  154. constructor (options = {}) {
  155. super(plus.ad.createFullScreenVideoAd(options), options)
  156. }
  157. }
  158. class Interstitial extends AdBase {
  159. constructor (options = {}) {
  160. super(plus.ad.createInterstitialAd(options), options)
  161. }
  162. }
  163. class AdHelper {
  164. constructor (adType) {
  165. this._ads = {}
  166. this._adType = adType
  167. this._lastWaterfallIndex = -1
  168. }
  169. load (options, onload, onerror) {
  170. if (!options.adpid || this.isBusy(options.adpid)) {
  171. return
  172. }
  173. this.get(options).load(onload, onerror)
  174. }
  175. show (options, onload, onerror, onclose, onshow) {
  176. const ad = this.get(options)
  177. if (ad.isLoad) {
  178. this._lastWaterfallIndex = -1
  179. ad.show((e) => {
  180. onclose && onclose(e)
  181. }, () => {
  182. onshow && onshow()
  183. })
  184. } else {
  185. ad.load(() => {
  186. this._lastWaterfallIndex = -1
  187. onload && onload()
  188. ad.show((e) => {
  189. onclose && onclose(e)
  190. }, () => {
  191. onshow && onshow()
  192. })
  193. }, (err) => {
  194. onerror && onerror(err)
  195. })
  196. }
  197. }
  198. // 底价预载逻辑
  199. loadWaterfall (options, onload, onfail, index = 0) {
  200. const {
  201. adpid,
  202. urlCallback
  203. } = options
  204. if (!Array.isArray(adpid)) {
  205. return
  206. }
  207. const options2 = {
  208. adpid: adpid[index],
  209. urlCallback,
  210. retry: false
  211. }
  212. console.log('ad.loadWaterfall::index=' + index)
  213. this.load(options2, (res) => {
  214. this._lastWaterfallIndex = index
  215. onload(options2)
  216. }, (err) => {
  217. index++
  218. if (index >= adpid.length) {
  219. onfail(err)
  220. } else {
  221. this.loadWaterfall(options, onload, onfail, index)
  222. }
  223. })
  224. }
  225. // 底价逻辑,失败后下一个,无重试机制
  226. showWaterfall (options, onload, onfail, onclose, onshow, index = 0) {
  227. const {
  228. adpid,
  229. urlCallback
  230. } = options
  231. if (!Array.isArray(adpid)) {
  232. return
  233. }
  234. if (this._lastWaterfallIndex > -1) {
  235. index = this._lastWaterfallIndex
  236. }
  237. const options2 = {
  238. adpid: adpid[index],
  239. urlCallback,
  240. retry: false
  241. }
  242. console.log('ad.showWaterfall::index=' + index)
  243. this.show(options2, () => {
  244. onload()
  245. }, (err) => {
  246. index++
  247. if (index >= adpid.length) {
  248. onfail(err)
  249. } else {
  250. this.showWaterfall(options, onload, onfail, onclose, onshow, index)
  251. }
  252. }, (res) => {
  253. onclose(res)
  254. }, () => {
  255. onshow()
  256. })
  257. }
  258. // 预载底价瀑布流
  259. preloadWaterfall (options, index = 0, step = 1) {
  260. if (step === 1) {
  261. this.loadWaterfall(options, (res) => {
  262. console.log('preloadWaterfall.success::', res)
  263. }, (err) => {
  264. console.log('loadWaterfall.fail', err)
  265. })
  266. return
  267. }
  268. const {
  269. adpid,
  270. urlCallback
  271. } = options
  272. for (let i = 0; i < step; i++) {
  273. if (index < adpid.length) {
  274. const options2 = {
  275. adpid: adpid[index],
  276. urlCallback
  277. }
  278. this.loadWaterfall(options2, (res) => {
  279. console.log('preloadWaterfall.success::', res)
  280. }, (err) => {
  281. console.log('loadWaterfall.fail', err)
  282. this.preloadWaterfall(options, index, step)
  283. })
  284. index++
  285. } else {
  286. break
  287. }
  288. }
  289. }
  290. isBusy (adpid) {
  291. return (this._ads[adpid] && this._ads[adpid].isLoading)
  292. }
  293. get (options) {
  294. const {
  295. adpid
  296. } = options
  297. if (!this._ads[adpid]) {
  298. this._ads[adpid] = this._createInstance(options)
  299. }
  300. return this._ads[adpid]
  301. }
  302. getProvider (adpid) {
  303. if (this._ads[adpid]) {
  304. return this._ads[adpid].getProvider()
  305. }
  306. return null
  307. }
  308. remove (adpid) {
  309. if (this._ads[adpid]) {
  310. this._ads[adpid].destroy()
  311. delete this._ads[adpid]
  312. }
  313. }
  314. _createInstance (options) {
  315. const adType = options.adType || this._adType
  316. delete options.adType
  317. let ad = null
  318. if (adType === ADType.RewardedVideo) {
  319. ad = new RewardedVideo(options)
  320. } else if (adType === ADType.FullScreenVideo) {
  321. ad = new FullScreenVideo(options)
  322. } else if (adType === ADType.Interstitial) {
  323. ad = new Interstitial(options, true)
  324. }
  325. return ad
  326. }
  327. }
  328. export default {
  329. props: {
  330. options: {
  331. type: [Object, Array],
  332. default () {
  333. return {}
  334. }
  335. },
  336. disabled: {
  337. type: [Boolean, String],
  338. default: false
  339. },
  340. adpid: {
  341. type: [Number, String, Array],
  342. default: ''
  343. },
  344. preload: {
  345. type: [Boolean, String],
  346. default: true
  347. },
  348. loadnext: {
  349. type: [Boolean, String],
  350. default: false
  351. },
  352. urlCallback: {
  353. type: Object,
  354. default () {
  355. return {}
  356. }
  357. }
  358. },
  359. data () {
  360. return {
  361. loading: false,
  362. errorMessage: null
  363. }
  364. },
  365. created() {
  366. this.$watch('adpid', (newValue, oldValue) => {
  367. this._removeInstance(oldValue)
  368. if (this.preload) {
  369. this._loadAd()
  370. }
  371. })
  372. // 服务器回调透传参数,仅在创建广告实例时可传递参数,如果发生变化需要重新创建广告实例
  373. this.$watch('urlCallback', () => {
  374. this._removeInstance()
  375. })
  376. this._adHelper = new AdHelper(this.adType)
  377. setTimeout(() => {
  378. if (this.preload) {
  379. this._loadAd()
  380. }
  381. }, 100)
  382. },
  383. methods: {
  384. load () {
  385. if (this.isLoading) {
  386. return
  387. }
  388. this._startLoading()
  389. const invoke = this._isWaterfall() ? 'loadWaterfall' : 'load'
  390. this._adHelper[invoke](this._getAdOptions(), () => {
  391. this._onLoad()
  392. }, (err) => {
  393. this._onLoadFail(err)
  394. })
  395. },
  396. show () {
  397. if (this.isLoading) {
  398. return
  399. }
  400. this._startLoading()
  401. const invoke = this._isWaterfall() ? 'showWaterfall' : 'show'
  402. this._adHelper[invoke](this._getAdOptions(), () => {
  403. this._onLoad()
  404. }, (err) => {
  405. this._onLoadFail(err)
  406. }, (res) => {
  407. this._dispatchEvent(EventType.Close, res)
  408. if (this.loadnext) {
  409. this.load()
  410. }
  411. }, () => {
  412. // show
  413. this.loading = false
  414. })
  415. },
  416. getProvider () {
  417. if (Array.isArray(this.adpid)) {
  418. return null
  419. }
  420. return this._adHelper.getProvider(this.adpid)
  421. },
  422. _loadAd () {
  423. if (this._canCreateAd()) {
  424. this.load()
  425. }
  426. },
  427. _onclick () {
  428. if (!this.disabled) {
  429. this.show()
  430. }
  431. },
  432. _getAdOptions () {
  433. return {
  434. adpid: this.adpid,
  435. urlCallback: this.urlCallback
  436. }
  437. },
  438. _isWaterfall () {
  439. return (Array.isArray(this.adpid) && this.adpid.length > 0)
  440. },
  441. _canCreateAd () {
  442. let result = false
  443. if (Array.isArray(this.adpid) && this.adpid.length > 0) {
  444. result = true
  445. } else if (typeof this.adpid === 'string' && this.adpid.length > 0) {
  446. result = true
  447. } else if (typeof this.adpid === 'number') {
  448. result = true
  449. }
  450. return result
  451. },
  452. _removeInstance (adpid) {
  453. const id = adpid || this.adpid
  454. if (Array.isArray(id)) {
  455. id.forEach((item) => {
  456. this._adHelper.remove(item)
  457. })
  458. } else if (id) {
  459. this._adHelper.remove(id)
  460. }
  461. },
  462. _startLoading () {
  463. this.loading = true
  464. this.errorMessage = null
  465. },
  466. _onLoad () {
  467. this.loading = false
  468. this._dispatchEvent(EventType.Load, {})
  469. },
  470. _onLoadFail (err) {
  471. this.loading = false
  472. this.errorMessage = JSON.stringify(err)
  473. this._dispatchEvent(EventType.Error, err)
  474. },
  475. _dispatchEvent (type, data) {
  476. this.$emit(type, {
  477. detail: data
  478. })
  479. }
  480. }
  481. }