AdError 1009: The VAST response document is empty on iOS

673 views Asked by At

This is my first post on Stack Overflow, please let me know if you don't understand or if I forgot something.

I am developing a site aimed at broadcasting video content. The application is coded in Next.js (SSR) + Typescript and is hosted on AWS. For the video player I use the google library: videojs-ima.

The goal is to distribute ads using a VAST tag.

Almost all OS and browsers are OK, only iOS is problematic. Indeed on iOS, when clicking on the video, the ad is not displayed, as if it had been ignored. Looking at the Safari browser logs, this error occurs:

AdError 1009: The VAST response document is empty.

As said before, the VAST tag seems correct to me because it works on other OS and browsers. In addition, I tested it with this tool and the ads are present:

https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/vastinspector

My other problem is that I cannot test locally because I always get the same error:

AdError 1010: the ad response was not understood and cannot be parsed.

I suspect Next.js with SSR is causing this. For now, I'm testing the changes on the demo environment. Which is really not practical ...

As you can see in the code below, I have already tested the autoplay and muted solution for iOS. But without success.

I thank you in advance for the time and the help you give me.

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);
0

There are 0 answers