这是我在 Stack Overflow 上的第一篇文章,如果您不明白或者我忘记了什么,请告诉我。
我正在开发一个旨在广播视频内容的网站。该应用程序使用 Next.js (SSR) + Typescript 编码,并托管在 AWS 上。对于视频播放器,我使用谷歌库:videojs-ima。
目标是使用 VAST 代码分发广告。
几乎所有的操作系统和浏览器都可以,只有 iOS 有问题。事实上,在 iOS 上,当点击视频时,广告不会显示,就好像它被忽略了一样。查看 Safari 浏览器日志,出现此错误:
AdError 1009:VAST 响应文档为空。
如前所述,VAST 标记对我来说似乎是正确的,因为它适用于其他操作系统和浏览器。此外,我使用此工具对其进行了测试,并且出现了广告:
https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/vastinspector
我的另一个问题是我无法在本地测试,因为我总是遇到同样的错误:
AdError 1010:广告响应不被理解,无法解析。
我怀疑带有 SSR 的 Next.js 会导致这种情况。目前,我正在测试演示环境的更改。这真的不实用......
正如您在下面的代码中看到的,我已经测试了iOS 的自动播放和静音解决方案。但没有成功。
我提前感谢你的时间和你给我的帮助。
import { makeStyles } from '@material-ui/core';
import { Video } from 'api/entities';
import { useFetch } from 'api/hooks';
import classNames from 'classnames';
import { ASSET_URL } from 'common/constants';
import { useUtils } from 'hooks';
import useCanPlay from 'hooks/useCanPlay';
import { uniqueId } from 'lodash';
import { useRouter } from 'next/router';
import { FC, memo, useEffect, useRef, useState } from 'react';
import videojs from 'video.js';
import 'video.js/dist/video-js.min.css';
import 'videojs-contrib-ads';
import 'videojs-ima';
import { NewAdsRequest } from '.';
export type PlayerProps = {
className?: string;
ima?: string;
play?: boolean;
controls?: boolean;
autoplay?: boolean;
width: number | string;
height: number | string;
video: Partial<Video>;
nextVideo?: Partial<Video>;
onDuration?: (duration: number) => void;
showTitle?: boolean;
showPlayButton?: boolean;
handlePlayClick?: () => void;
pictoSize?: number;
isLoading?: boolean;
isPreview?: boolean;
muted?: boolean;
};
export type State = {
player?: any;
iOS?: boolean;
isAdContainerInitialized?: boolean;
isImaInitialized?: boolean;
isImaSdkLoaded?: boolean;
autoplayAllowed?: boolean;
autoplayRequiresMute?: boolean;
};
const useStyles = makeStyles({
video: {
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
objectFit: 'cover',
},
});
const Player: FC<PlayerProps> = ({
play,
width,
video: {
id,
duration,
video: { key: src },
},
onDuration,
nextVideo,
className,
}) => {
const router = useRouter();
const { asPath } = router;
const classes = useStyles();
const { addScript, getAdTagUrl, isiOS } = useUtils();
const { checkUnmutedAutoplaySupport } = useCanPlay();
const videoRef = useRef<HTMLVideoElement>();
const [state, setState] = useState<State>({
autoplayAllowed: false,
autoplayRequiresMute: false,
isAdContainerInitialized: false,
isImaInitialized: false,
isImaSdkLoaded: false,
iOS: false,
});
const {
player,
isImaInitialized,
isImaSdkLoaded,
iOS,
isAdContainerInitialized,
autoplayAllowed,
autoplayRequiresMute,
} = state;
const playerId = uniqueId('player_');
const dispatch = ({ ...payload }: State) => setState({ ...state, ...payload });
useEffect(() => {
addScript({
src: '//imasdk.googleapis.com/js/sdkloader/ima3.js',
onLoad: async () => {
dispatch({ isImaSdkLoaded: true, iOS: isiOS(), ...(await checkUnmutedAutoplaySupport()) });
},
});
}, []);
useEffect(() => {
if (!isImaSdkLoaded) return;
const player = videojs(videoRef.current, {
autoplay: autoplayAllowed,
muted: autoplayRequiresMute,
}) as any;
player.on('ready', () => {
player.src(ASSET_URL ? src : `/${src}`);
const adTagUrl = getAdTagUrl({ asPath, playerId });
if (adTagUrl) {
player.ima({
id: playerId,
adTagUrl,
disableCustomPlaybackForIOS10Plus: iOS,
});
}
dispatch({
player,
isImaInitialized: true,
});
});
}, [isImaSdkLoaded]);
useEffect(() => {
if (!isImaInitialized) return;
player.src(ASSET_URL ? src : `/${src}`);
const adTagUrl = getAdTagUrl({ asPath, playerId });
if (!adTagUrl) return;
var adsRequest = NewAdsRequest();
adsRequest.adTagUrl = adTagUrl;
const adSize = (window.innerWidth * parseInt(width.toString().replace('vw', ''))) / 100;
adsRequest.linearAdSlotWidth = adSize;
adsRequest.linearAdSlotHeight = adSize;
adsRequest.nonLinearAdSlotWidth = adSize;
adsRequest.nonLinearAdSlotHeight = adSize;
player.ima.requestAds(adsRequest);
}, [id]);
useEffect(() => {
if (play) {
if (!isAdContainerInitialized) {
player?.ima?.initializeAdDisplayContainer();
dispatch({ isAdContainerInitialized: true });
}
player?.play();
} else {
player?.pause();
}
}, [play]);
return (
<div className={className}>
<div data-vjs-player>
<video
id={playerId}
ref={videoRef}
controls
playsInline
preload={duration ? 'none' : 'metadata'}
className={classNames('video-js', classes.video)}
onDurationChange={(e) => {
if (!duration) {
useFetch({
name: `video/${id}/duration`,
body: { duration: e.currentTarget.duration },
method: 'PUT',
});
onDuration(e.currentTarget.duration);
}
}}
onEnded={() => {
if (nextVideo) {
const { id, title } = nextVideo;
router.push(`/video/${id}/${title}`);
}
}}
/>
</div>
</div>
);
};
export default memo(Player);