Skip to main content

Video Ads (IMA SDK)

Video ads use Google's IMA (Interactive Media Ads) SDK to display VAST-compliant video advertisements. They support both interstitial and rewarded formats.

Overview

Formats:

  • Interstitial video (full-screen, skippable after 5s)
  • Rewarded video (must watch to completion)

Features:

  • VAST 2.0/3.0/4.0 compatible
  • Skip button support
  • Progress tracking
  • Error handling
  • Fullscreen playback

Video Interstitials

Full-screen video ads shown between game states.

Basic Usage

sdk.showInterstitialVideo({
placement: 'level_complete',
onComplete: () => {
console.log('Video completed');
loadNextLevel();
},
onError: (error) => {
console.warn('Video error:', error);
loadNextLevel();
}
}, 'https://pubads.g.doubleclick.net/gampad/ads?iu=/YOUR_AD_UNIT&...');

Custom VAST Tag

const vastTag = `https://pubads.g.doubleclick.net/gampad/ads?` +
`iu=/21775744923/getjar_interstitial_video` +
`&description_url=${encodeURIComponent(window.location.href)}` +
`&tfcd=0` +
`&npa=0` +
`&sz=640x480|854x480` +
`&gdfp_req=1` +
`&output=vast` +
`&unviewed_position_start=1` +
`&env=vp` +
`&impl=s` +
`&correlator=${Date.now()}`;

sdk.showInterstitialVideo({
placement: 'custom_interstitial',
onComplete: () => resumeGame()
}, vastTag);

Rewarded Videos

Video ads that grant in-game rewards.

Basic Usage

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onRewarded: (reward) => {
if (reward.granted) {
player.coins += 100;
showMessage('You earned 100 coins!');
} else {
showMessage(reward.message || 'Reward not available.');
}
},
onError: (error) => {
console.warn('Rewarded video error:', error);
}
}, 'https://pubads.g.doubleclick.net/gampad/ads?...');

With Progress Tracking

let videoProgress = 0;

sdk.showRewardedVideo({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onLoad: () => {
showLoadingIndicator('Loading video...');
},
onImpression: () => {
hideLoadingIndicator();
showProgressBar(0);
},
onProgress: (percent) => {
videoProgress = percent;
updateProgressBar(percent);
console.log(`Video ${percent}% complete`);
},
onRewarded: (reward) => {
hideProgressBar();
if (reward.granted) {
grantLives(3);
showSuccessAnimation();
}
},
onError: (error) => {
hideProgressBar();
hideLoadingIndicator();
console.error('Video error:', error);
}
}, rewardedVideoUrl);

VAST Tag Configuration

Basic VAST URL

const vastUrl = [
'https://pubads.g.doubleclick.net/gampad/ads',
'?iu=/21775744923/getjar_video', // Ad unit
'&description_url=' + encodeURIComponent(location.href),
'&tfcd=0', // Not child-directed
'&npa=0', // Personalized ads
'&sz=640x480', // Video size
'&gdfp_req=1',
'&output=vast',
'&unviewed_position_start=1',
'&env=vp',
'&impl=s',
'&correlator=' + Date.now()
].join('');

With Custom Targeting

function buildVastUrl(params) {
const baseUrl = 'https://pubads.g.doubleclick.net/gampad/ads';

const queryParams = {
iu: '/21775744923/getjar_video',
description_url: location.href,
tfcd: '0',
npa: '0',
sz: '640x480',
gdfp_req: '1',
output: 'vast',
unviewed_position_start: '1',
env: 'vp',
impl: 's',
correlator: Date.now(),
// Custom targeting
cust_params: encodeURIComponent([
`game=${params.gameId}`,
`level=${params.level}`,
`category=${params.category}`
].join('&'))
};

const queryString = Object.entries(queryParams)
.map(([key, val]) => `${key}=${val}`)
.join('&');

return `${baseUrl}?${queryString}`;
}

// Usage
const vastUrl = buildVastUrl({
gameId: 'bubble-shooter',
level: 5,
category: 'puzzle'
});

sdk.showRewardedVideo({ ... }, vastUrl);

Configuration Options

VideoConfig Interface

interface VideoConfig {
/** Placement identifier */
placement: string;

/** Reward type (for rewarded videos) */
rewardType?: string;

/** Reward amount (for rewarded videos) */
rewardAmount?: number;

/** Callbacks */
onLoad?: () => void;
onImpression?: () => void;
onProgress?: (percent: number) => void;
onComplete?: () => void;
onRewarded?: (reward: Reward) => void; // Rewarded only
onError?: (error: Error) => void;
onClose?: () => void;
}

Skip Button

Interstitial Videos

Skip button appears after 5 seconds:

sdk.showInterstitialVideo({
placement: 'level_complete',
onImpression: () => {
// Show "Skip in 5..." countdown
showSkipCountdown(5);
},
onProgress: (percent) => {
// Skip available after ~16% (5 seconds of 30 second ad)
if (percent >= 16) {
enableSkipButton();
}
},
onComplete: () => {
// Video completed or skipped
loadNextLevel();
}
}, vastUrl);

Rewarded Videos

No skip button - must watch to completion:

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onProgress: (percent) => {
// Show progress bar
updateProgressBar(percent);

// Optional: warn if user tries to close early
if (percent < 90) {
showWarning('Watch the full video to earn coins!');
}
},
onRewarded: (reward) => {
if (reward.granted) {
// User watched to completion
grantCoins(100);
} else {
// User closed early or other issue
showMessage('Watch the full video to earn rewards.');
}
}
}, vastUrl);

Error Handling

Common Errors

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onError: (error) => {
switch (error.code) {
case 'NO_AD_AVAILABLE':
showMessage('No video available right now.');
break;

case 'VIDEO_LOAD_TIMEOUT':
showMessage('Video took too long to load.');
break;

case 'VAST_ERROR':
console.error('VAST parsing error:', error);
showMessage('Video ad error.');
break;

case 'NETWORK_ERROR':
showMessage('Network error. Check your connection.');
break;

default:
console.error('Unknown video error:', error);
showMessage('Video unavailable.');
}

// Always provide fallback
continueGameplay();
}
}, vastUrl);

Timeout Fallback

async function showVideoWithTimeout() {
const timeout = setTimeout(() => {
console.warn('Video timeout - continuing anyway');
continueGame();
}, 30000); // 30 second fallback

try {
await sdk.showRewardedVideo({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onRewarded: (reward) => {
clearTimeout(timeout);
if (reward.granted) {
grantLives(3);
}
continueGame();
},
onError: (error) => {
clearTimeout(timeout);
console.error('Video error:', error);
continueGame();
}
}, vastUrl);
} catch (error) {
clearTimeout(timeout);
continueGame();
}
}

Best Practices

1. Preload Videos

Load video before showing:

class VideoAdManager {
constructor() {
this.preloadedVideo = null;
}

async preloadVideo() {
// Preload video in background
this.preloadedVideo = await sdk.prepareVideo(vastUrl);
}

async showVideo() {
if (!this.preloadedVideo) {
await this.preloadVideo();
}

await sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onRewarded: (reward) => {
if (reward.granted) {
grantCoins(100);
}
// Preload next video
this.preloadVideo();
}
}, vastUrl);
}
}

2. Loading Indicators

Show loading state:

async function showVideoWithLoader() {
// Show loading UI
showLoadingOverlay('Loading video...');

await sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onLoad: () => {
// Video loaded, ready to play
updateLoadingText('Starting video...');
},
onImpression: () => {
// Video started
hideLoadingOverlay();
},
onError: (error) => {
hideLoadingOverlay();
showError('Video unavailable');
}
}, vastUrl);
}

3. Mute Control

Respect user preferences:

// Check user's audio preference
const userMuted = localStorage.getItem('audio_muted') === 'true';

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
muted: userMuted, // Start muted if user preference
onImpression: () => {
// Show unmute button if muted
if (userMuted) {
showUnmuteButton();
}
}
}, vastUrl);

4. Progress Feedback

Show progress during rewarded videos:

sdk.showRewardedVideo({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onProgress: (percent) => {
// Update circular progress indicator
updateProgressCircle(percent);

// Show milestone messages
if (percent === 25) {
showMessage('25% complete...');
} else if (percent === 50) {
showMessage('Halfway there!');
} else if (percent === 75) {
showMessage('Almost done!');
}
},
onRewarded: (reward) => {
hideProgressCircle();
if (reward.granted) {
showSuccessMessage('Video complete! 3 lives earned!');
}
}
}, vastUrl);

Testing

Development Mode

// Test with sample VAST URL
const testVastUrl = 'https://pubads.g.doubleclick.net/gampad/ads?' +
'iu=/21775744923/external/single_ad_samples&' +
'sz=640x480&' +
'cust_params=sample_ct%3Dlinear&' +
'ciu_szs=300x250&' +
'gdfp_req=1&' +
'output=vast&' +
'unviewed_position_start=1&' +
'env=vp&' +
'impl=s&' +
'correlator=' + Date.now();

sdk.showRewardedVideo({
placement: 'test_video',
rewardType: 'test',
rewardAmount: 1,
onRewarded: (reward) => console.log('Test reward:', reward)
}, testVastUrl);

Debug Logging

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onLoad: () => console.log('[VIDEO] Loaded'),
onImpression: () => console.log('[VIDEO] Started'),
onProgress: (p) => console.log(`[VIDEO] ${p}% complete`),
onComplete: () => console.log('[VIDEO] Completed'),
onRewarded: (r) => console.log('[VIDEO] Reward:', r),
onError: (e) => console.error('[VIDEO] Error:', e),
onClose: () => console.log('[VIDEO] Closed')
}, vastUrl);

Performance

Video Size Optimization

// Mobile devices - smaller video
const isMobile = /Android|iPhone|iPad/i.test(navigator.userAgent);
const videoSize = isMobile ? '320x240' : '640x480';

const vastUrl = `https://pubads.g.doubleclick.net/gampad/ads?` +
`iu=/21775744923/getjar_video` +
`&sz=${videoSize}` +
`&...`;

Bandwidth Detection

// Check connection speed
const connection = navigator.connection;
const isSlowConnection = connection &&
(connection.effectiveType === 'slow-2g' ||
connection.effectiveType === '2g');

if (isSlowConnection) {
// Skip video ads on slow connections
console.log('Slow connection detected - skipping video');
showAlternativeReward();
} else {
sdk.showRewardedVideo({ ... }, vastUrl);
}


Support

Need help with video ads? Contact: support@getjar.com