videojs-ima
videojs-ima copied to clipboard
Why mid-roll ad not working when changing playback speed from 1 to 2?
Can you explain to me why videojs is not playing ads when set video playback from 1 to 2? Thank for your help.
Hello @t-duong ,
Would you be able to share a code snippet or sample project where this change is being done? I think that would help be in debugging any issue you are experiencing.
Thank you, Jackson IMA SDK team
@Kiro705 Thank you for answering my question. Here is my code.
<template lang="pug">
div(:class="$style.player_wrapper")
video.video-js.theme-custom(
ref="player"
:class="$style.player"
)
</template>
<script>
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
import * as contribAds from 'videojs-contrib-ads'
import 'videojs-ima'
import 'videojs-ima/dist/videojs.ima.css'
import * as contribQualityLevels from 'videojs-contrib-quality-levels'
import './MediaText'
import './LoadingSpinner'
import './PlaybackRateMenuButton'
import './BigPlayToggle'
import './EndedNextMedia'
import './HlsQualitySelector'
import './SeekPreview'
videojs.registerPlugin('ads', contribAds.default)
videojs.registerPlugin('qualityLevels', contribQualityLevels.default)
export default {
props: {
media: { type: Object, required: true },
nextMedia: { type: Object, default: null },
isAutoplay: { type: Boolean, default: true },
isMux: { type: Boolean, default: false },
isLog: { type: Boolean, default: true },
nextDelay: { type: Number, default: null },
nextAutoplay: { type: Boolean, default: false },
isAdminMode: { type: Boolean, default: false }
},
data: () => ({
player: null,
playlistId: null,
initialized: false,
options: {
html5: {
vhs: {
overrideNative: !videojs.browser.IS_IPHONE
}
},
language: 'ja',
aspectRatio: '16:9',
fill: true,
fluid: false,
controls: true,
liveui: true,
poster: null,
autoplay: false,
muted: false,
playsinline: true,
playbackRates: [1, 2],
techOrder: ['html5'],
enableSourceset: true,
textTrackSettings: false,
mediaText: {
title: '',
description: ''
},
bigPlayToggle: true,
endedNextMedia: {
autoplay: false,
delay: 10,
media: null
},
controlBar: {
volumePanel: { inline: false },
progressControl: {
seekBar: {
seekPreview: {
spriteUrlPrefix: null,
spriteUrlQuery: null
}
}
},
children: [
// 'playToggle',
'currentTimeDisplay',
'timeDivider',
'durationDisplay',
'progressControl',
'liveDisplay',
'seekToLive',
'remainingTimeDisplay',
'customControlSpacer',
'PlaybackRateMenuButton',
// 'chaptersButton',
// 'descriptionsButton',
// 'subsCapsButton',
// 'audioTrackButton',
// 'pictureInPictureToggle',
'volumePanel',
'HlsQualitySelector',
'fullscreenToggle'
]
}
},
needsPlayingAfterAdEnd: true // Postroll時のみfalseにする
}),
watch: {
media(newMedia, oldMedia) {
if (!newMedia && !oldMedia) return false
if (newMedia.id !== oldMedia.id) {
this.reset()
this.init()
}
}
},
mounted() {
// 異なるレイアウト(検索結果一覧のempty)から遷移してくると2重にマウントされてしまう問題の対応
// https://github.com/nuxt/nuxt.js/issues/5703
// 2重でマウントされるとプレイヤーが2つ動いてしまい、1つは制御不能で再生され続けてしまう
// Nuxt.jsコントロール配下でない場合(embed.jsなど)もあるので注意する
if (this.$nuxt) {
if (this.validLayoutName()) {
this.init()
}
} else {
this.init()
}
},
beforeDestroy() {
this.destroy()
},
methods: {
validLayoutName() {
return this.$nuxt.layoutName === 'default'
},
async init() {
const { player } = this.$refs
if (this.isAdminMode) {
if (!this.media.video.live_hls_for_admin) {
console.error('live_hls_for_admin not found')
return
}
} else if (!this.media.video.hls) {
console.error('hls not found')
return
}
let videoSrc
if (this.isAdminMode) {
videoSrc = this.media.video.live_hls_for_admin || ''
} else {
videoSrc = this.media.video.hls
}
videoSrc = videoSrc
.replace('%%device_type%%', 'web')
.replace('%%page%%', location.href)
.replace('%%uuid%%', '')
const hlsKeyQuery = this.media.video.hls_key_query
const appendDelimiter = videoSrc.includes('?') ? '&' : '?'
const videoSrcWithToken =
videojs.browser.IS_IPHONE && hlsKeyQuery
? videoSrc + appendDelimiter + hlsKeyQuery
: videoSrc
const video = {
type: 'application/x-mpegURL',
// src: 'https://d2zihajmogu5jn.cloudfront.net/elephantsdream/hls/ed_hd.m3u8',
// src: 'https://d2zihajmogu5jn.cloudfront.net/big-buck-bunny/master.m3u8',
// src: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8',
src: videoSrcWithToken,
withCredentials: false
}
// - 埋込プレイヤーでは $route が存在しない
// - 緊急ライブでは media.playlist が存在しない
if (this.$route && this.media.playlist) {
const { query } = this.$route
this.playlistId =
query.list !== undefined ? query.list : this.media.playlist.id
}
if (this.nextDelay) {
this.options.endedNextMedia.delay = this.nextDelay
}
this.options.endedNextMedia.autoplay = this.nextAutoplay
// can autoplay
const autoplaySupport = await this.$canAutoplay.checkUnmutedAutoplaySupport()
if (this.isAutoplay && autoplaySupport.autoplayAllowed) {
// autoplay=true では、回線Fast3G + 広告有り 状態の場合に
// 広告後に本編がautoplayされない場合がある為、autoplay='any'にする
this.options.autoplay = 'any'
this.options.muted = autoplaySupport.autoplayRequiresMute
}
if (!this.canTimeShiftedViewing()) {
delete this.options.controlBar.progressControl
const i = this.options.controlBar.children.indexOf('progressControl')
this.options.controlBar.children.splice(i, 1)
}
this.player = videojs(player, this.options, () => {
// this.player.volume(1)
})
this.player.on('nextplay', this._onNextPlay)
this.player.on('timeupdate', this._onTimeupdate)
this.player.one('loadedmetadata', this._onLoadedmetadata)
this.player.on('play', this._onPlay)
this.player.on('pause', this._onPause)
this.player.on('ended', this._onEnded)
this.player.on('error', this._onError)
const tech = this.player.tech({ IWillNotUseThisInPlugins: true })
tech.on('retryplaylist', this._onRetryplaylist)
// for encrypted hls
videojs.Vhs.xhr.beforeRequest = function(options) {
// ex: dev-license.locipo.jp/keys/*** / licence.locipo.jp/keys/***
if (options.uri.includes('licence.locipo.jp/keys/')) {
const appendDelimiter = options.uri.includes('?') ? '&' : '?'
options.uri += hlsKeyQuery ? appendDelimiter + hlsKeyQuery : ''
}
return options
}
// components setup
this.player.poster(this.media.thumb)
this.player.mediaText.options({
title: this.media.title,
description: this.media.description
})
if (this.canTimeShiftedViewing()) {
this.player.controlBar.progressControl.seekBar.seekPreview.options({
spriteUrlPrefix: this.media.video.seek_preview_url_prefix,
spriteUrlQuery: this.media.video.seek_preview_url_query
})
}
this.player.endedNextMedia.options({
media: this.nextMedia
})
// tracker setup
const ga = this.$ga
.set('mediaId', this.media.id)
.set('mediaTitle', this.media.title)
.set('stationId', this.media.station_id)
.set('stationCd', this.media.station_cd)
.set('firstPlay', true)
ga.playerWatcherStart()
if (this.media.playlist) {
this.$ga
.set('seriesUuid', this.media.playlist.id)
.set('seriesName', this.media.playlist.title)
}
// add ima setup
if (this.media.video.ad_uri) {
const adTagUrl = this.media.video.ad_uri
.replace('%7Bdevice%7D', this._isMobile() ? 'sp_web' : 'web')
.replace('%7Breferrer_url%7D', location.href)
.replace('%7Buuid%7D', '')
.replace('%7Bidtype%7D', '')
if (!this.player.ima.changeAdTag) {
const imaOptions = {
// debug: true,
locale: 'ja',
adLabel: '広告',
adLabelNofN: '/',
// adTagUrl: 'https://search.spotxchange.com/vmap/1.0/207470?adPlaylistId=4070&channelId=219765?VPI[]=MP4&player_width=640&player_height=360&content_page_url=https%3A%2F%2Fwww.cci.co.jp%2F&custom[genre]=drama&custom[program]=test&custom[episode]=001'
// adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator='
adTagUrl,
contribAdsSettings: {
// adtimeout発生後にadsreadyが処理されて一時停止状態が解除されなくなる問題があるため、timeoutと同じ時間を設定しておく
prerollTimeout: 5000
},
disableCustomPlaybackForIOS10Plus: true
}
this.player.on('adsready', this._onAdsready)
this.player.on('adend', this._onAdEnd)
this.player.ima(imaOptions)
} else {
this.player.ima.changeAdTag(adTagUrl)
this.player.ima.requestAds()
}
}
this.player.src(video)
if (this.initialized && this.player.paused()) {
this.player.play()
}
this.initialized = true
},
reset() {
if (this.player) {
this.player.pause()
this.player.off('nextplay', this._onNextPlay)
this.player.off('timeupdate', this._onTimeupdate)
this.player.off('play', this._onPlay)
this.player.off('pause', this._onPause)
this.player.off('ended', this._onEnded)
this.player.off('error', this._onError)
this.player.off('adsready', this._onAdsready)
this.player.off('adend', this._onAdEnd)
this.player.reset()
}
this.$ga.playerWatcherDestroy()
},
destroy() {
this.reset()
if (this.player) {
this.player.dispose()
this.player = null
}
},
canTimeShiftedViewing() {
if (this.media.creative_type === 'live_stream') {
return this.media.time_shifted_viewing
} else {
return true
}
},
seekToLiveEdge() {
this.player.currentTime(this.player.liveTracker.liveCurrentTime())
},
_onNextPlay(event) {
this.$emit('nextplay')
},
_onError(event) {
this.$emit('error', event)
},
_onRetryplaylist(event) {
this.$emit('retryplaylist', event)
},
_onTimeupdate(event) {
const ct = this.player.currentTime()
const duration = this.player.duration()
this.$ga.set('currentTime', ct)
if (this.isLog && this.$store && duration !== 'Infinity') {
this.$store.commit('log/addLog', {
media: this.media,
duration: duration * 1000, // milliseconds
currentPosition: ct * 1000, // milliseconds
playlistId: this.playlistId,
updateAt: Date.now()
})
}
},
_onLoadedmetadata(event) {
if (this.isLog && this.$store) {
const log = this.$store.getters['log/getLog'](this.media)
if (log) {
// 保存しているcurrentPositionが動画終了時間から6秒以内だったら最初から再生させる
const marginSecond = 6
const logPos = log.currentPosition / 1000
const duration = this.player.duration()
const startPos = duration > logPos + marginSecond ? logPos : 0
this.player.currentTime(startPos)
}
if (this._needsCurrentLive()) {
this.seekToLiveEdge()
}
}
this.$ga.set('currentTime', this.player.currentTime())
},
_onPlay(event) {
this.$ga.sendPlayEvent()
this.$ga.set('firstPlay', false)
if (this._needsCurrentLive()) {
this.seekToLiveEdge()
}
},
_onPause(event) {
this.$ga.sendPauseEvent()
},
_onEnded(event) {
this.$ga.sendEndedEvent()
},
_onAdsready() {
const { STARTED } = window.google.ima.AdEvent.Type
this.player.ima.addEventListener(STARTED, this._onAdsStarted)
this.player.ads.startLinearAdMode()
},
_onAdsStarted() {
// ライブ時の広告終了後に再生を再開させるかどうかのフラグを設定
// readyforpreroll,readyforpostrollイベントをフックして設定しても良いが、
// midrollにも対応させるためここで設定しておく
// postroll後のみ再開させない
if (this.player.ads.adType === 'postroll') {
this.needsPlayingAfterAdEnd = false
} else {
this.needsPlayingAfterAdEnd = true
}
// Preroll Ad時のみ一時停止イベントが発火しないので発火させる
this.$ga.sendPauseEvent()
},
_onAdEnd() {
if (
this.initialized &&
this.player.paused() &&
this.needsPlayingAfterAdEnd
) {
// ライブでの広告再生後に再開されないため再開しておく
this.player.play()
}
},
_needsCurrentLive() {
if (this.media.creative_type === 'live_stream' && this.isAdminMode) {
return true
}
if (!this.canTimeShiftedViewing()) {
return true
}
return false
},
_isMobile() {
const mobileKey = ['mobile', 'android', 'iphone', 'ipad', 'ipod']
return mobileKey.some((keyword) =>
navigator.userAgent.toLowerCase().includes(keyword)
)
}
}
}
</script>
@Kiro705 Sorry, I can't share all the sources of my project. Can you help me?
Hello @t-duong ,
Thank you for sharing the code snippet, I was able to see the issue by adding the following line to the plugin's sample app:
playbackRates: [1, 2],
I was able to see that ads are not played at x2 speed. Testing on a HTML5 basic
I can plan to look into a fix, but right a work-around would be to use a different player.
Thank you, Jackson IMA SDK team
@Kiro705 Thank for your support!
are there any workarounds to solve this case?
Hello @Kiro705 ,
Is there any update about this? Thank you!
My temporary solution is : https://www.youtube.com/watch?v=w67wSxbyQTw
I get all cue-points from
var cuePoints = player.ima.getAdsManager().getCuePoints();
and if currentTime in timeupdate event 2 secs near/before of each cues, then i change speed to 1x