Home Reference Source

src/formats/outstream.js

import { cpexLog, cpexError, addIframe, loadScript, addElement, enrichVast } from '../utils.js'

/**
 * Integrated outstream video ad format, using video.js library
 */
export default class Outstream {
  constructor (elementId, adUnit, main, width, height) {
    this.main = main
    this.type = 'outstream'
    this.elementId = elementId
    this.adUnit = adUnit
    this.settings = main.settings.formats.outstream
    this.loaded = false
    this.width = width || 640
    this.height = height || 360
    cpexLog('Outstream: Caught Outstream custom format, in elementId ' + elementId)
  }

  async render (winningBid, vastUrl, vastXml) {
    try {
      if (winningBid) {
        vastUrl = winningBid.vastUrl
        vastXml = winningBid.vastXml
      } else {
        vastUrl = enrichVast(vastUrl) // add gdpr, gdpr_consent, correlator and npa to vastUrl if needed
      }
      if (!vastUrl && !vastXml) { throw new Error('No VAST URL or XML provided') }

      // Load video.js library and css into an iframe
      this.containerEl = document.getElementById(this.elementId)
      this.iframe = addIframe(this.containerEl, { id: this.elementId + '-iframe', width: this.width, height: this.height })
      const styleLoad = loadScript(this.iframe.contentDocument, 'https://cdn.cpex.cz/video/video-js.min.css', 'video.js style', 'link')
      const playerLoad = loadScript(this.iframe.contentDocument, 'https://cdn.cpex.cz/video/video-js.min.js', 'video.js script')
      const pluginLoad = loadScript(this.iframe.contentDocument, 'https://cdn.cpex.cz/video/vast-plugin.js', 'VAST plugin')

      await Promise.all([styleLoad, playerLoad, pluginLoad])
      cpexLog('Outstream: video.js library loaded')
      this.loaded = true

      this.videoContainerEl = addElement('div', this.iframe.contentDocument.body, { id: 'cpex-outstream' })
      this.videoEl = addElement('video', this.videoContainerEl, { class: 'video-js', width: this.width, height: this.height, preload: 'auto', muted: true })

      const context = this.iframe.contentWindow
      const options = { autoplay: 'muted', controls: false, width: this.width, height: this.height, loop: false, autoSetup: false, fluid: true, language: 'cs', playsinline: true }

      this.player = context.videojs(this.videoEl, options)
      this.playerReady = new Promise(resolve => this.player.ready(resolve))
      this.vastPlugin = new context.VASTplugin({ iframeWindow: context, player: this.player })
      this.vast = await this.vastPlugin.loadVast(vastXml || vastUrl)

      await Promise.all([this.playerReady, this.vast])
      this.play()
      cpexLog('Outstream: Playing')
    } catch (e) {
      cpexError('Outstream: Error loading video.js library', e)
    }
  }

  play () {
    if (!this.vast || !this.vast.mediaFile || !this.vast.mediaFile.url) {
      return cpexError('Outstream: No playable media in VAST')
    }

    this.vastPlugin.pingUrls(this.vast.errorUrls)
    this.player.src({ src: this.vast.mediaFile.url, type: this.vast.mediaFile.type || 'video/mp4' })

    // clickthrough overlay
    if (this.vast.clickThroughUrl) {
      this.overlay = this.vastPlugin.addClickOverlay(this.videoContainerEl, () => {
        try { this.vastPlugin.pingUrls(this.vast.clickTrackingUrls) } finally {
          this.iframe.contentWindow.open(this.vast.clickThroughUrl, '_blank', 'noopener')
        }
      })
    }

    this.player.play()
    if (this.main.headerbidding) {
      this.main.headerbidding.triggerBidWon(this.adUnit)
    }
    // this.player.on('ended', () => this.onVideoEnd())
  }

  // onVideoEnd () { console.log('Outstream video ended') }

  /**
   * Returns the page to its original state
   */
  reset () {
    if (this.iframe) { this.iframe.remove() }
    if (this.vastPlugin) { this.vastPlugin.destroy() }
    if (this.overlay && this.overlay.parentNode) {
      this.overlay.parentNode.removeChild(this.overlay)
    }
    // if (this.overlay?.parentNode) { this.overlay.parentNode.removeChild(this.overlay) }
    const ads = window.cpexPackage.customAds
    if (ads) { delete ads[this.elementId] }
  }
}