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);
}
Related Documentation
- Banner Ads - Fixed-position ads
- Interstitial Ads - Full-screen display ads
- Rewarded Ads - Rewarded ad formats
Support
Need help with video ads? Contact: support@getjar.com