Skip to main content

Optimization

Smart TV platforms have unique constraints compared to desktop browsers. This guide covers optimization strategies for bundle size, performance, and platform-specific limitations.

Bundle Size Optimization

The SDK is designed to be lightweight with minimal dependencies.

Current Bundle Size

From README.md:405:
MetricValue
Gzipped Size< 20 KB
Minified Size~50 KB
Dependencies1 (fast-xml-parser)

Tree Shaking

Use ES modules for optimal tree shaking:
// Good: Import only what you need
import { AdPlayer, getPlatformAdapter } from 'adgent-sdk';

// Avoid: Importing entire module
import * as Adgent from 'adgent-sdk';
Build configuration (Webpack/Vite):
// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true,
    sideEffects: false
  },
  resolve: {
    mainFields: ['module', 'main']
  }
};
// vite.config.js
export default {
  build: {
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true // Remove console.logs in production
      }
    }
  }
};

Lazy Loading

Load the SDK only when needed:
class VideoPlayer {
  private adPlayer: any = null;
  
  async playPrerollAd(vastUrl: string) {
    // Lazy load SDK only when ad is needed
    if (!this.adPlayer) {
      const { AdPlayer } = await import('adgent-sdk');
      
      this.adPlayer = new AdPlayer({
        container: document.getElementById('ad-container'),
        vastUrl
      });
      
      await this.adPlayer.init();
    }
  }
  
  async playMainContent() {
    // Main content doesn't load ad SDK
    this.mainVideo.play();
  }
}
Benefits:
  • Reduces initial bundle size
  • Ad SDK only loaded when ads are served
  • Faster app startup

Code Splitting

Split ad-related code into separate chunk:
// routes/video.tsx
import { lazy, Suspense } from 'react';

// Lazy load ad player component
const AdPlayerComponent = lazy(() => import('./components/AdPlayer'));

function VideoPage() {
  return (
    <div>
      <Suspense fallback={<div>Loading ad...</div>}>
        <AdPlayerComponent vastUrl="https://example.com/vast.xml" />
      </Suspense>
      
      <MainVideoPlayer />
    </div>
  );
}

Bitrate Selection Strategy

Choosing the right bitrate is critical for TV performance.

Default Bitrate

From src/core/AdPlayer.ts:23:
const DEFAULT_CONFIG = {
  targetBitrate: 2500, // ~1080p quality
  // ...
};

Platform-Specific Recommendations

From src/core/PlatformAdapter.ts:357:
PlatformRecommended BitrateReason
Tizen15000 kbpsHigh-end Samsung TVs handle it well
WebOS15000 kbpsModern LG TVs
FireTV10000 kbpsGood performance
Roku8000 kbpsConservative (variable HEVC support)
Xbox/PS20000 kbpsGame consoles have powerful hardware
Generic5000 kbpsSafe default for web

Adaptive Bitrate Selection

import { AdPlayer, getPlatformAdapter } from 'adgent-sdk';

function getOptimalBitrate(): number {
  const adapter = getPlatformAdapter();
  const settings = adapter.getRecommendedVideoSettings();
  
  let bitrate = settings.maxBitrate;
  
  // Adjust based on screen resolution
  switch (adapter.capabilities.maxResolution) {
    case '4k':
      bitrate = Math.min(bitrate, 8000);
      break;
    case '1080p':
      bitrate = Math.min(bitrate, 3500);
      break;
    case '720p':
      bitrate = Math.min(bitrate, 2000);
      break;
  }
  
  // Further reduce for specific platforms
  if (adapter.platform === 'roku') {
    bitrate = Math.min(bitrate, 6000); // Roku struggles with high bitrates
  }
  
  return bitrate;
}

const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  targetBitrate: getOptimalBitrate()
});

Network-Aware Bitrate

Adjust based on network conditions:
async function getNetworkAwareBitrate(): Promise<number> {
  const adapter = getPlatformAdapter();
  let baseBitrate = adapter.getRecommendedVideoSettings().maxBitrate;
  
  // Check connection type (if available)
  if ('connection' in navigator) {
    const connection = (navigator as any).connection;
    
    if (connection.effectiveType === '4g') {
      baseBitrate = Math.min(baseBitrate, 5000);
    } else if (connection.effectiveType === '3g') {
      baseBitrate = Math.min(baseBitrate, 2000);
    } else if (connection.effectiveType === '2g') {
      baseBitrate = Math.min(baseBitrate, 800);
    }
    
    // Reduce for slow connections
    if (connection.downlink && connection.downlink < 5) {
      baseBitrate = Math.min(baseBitrate, 2000);
    }
  }
  
  return baseBitrate;
}

const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  targetBitrate: await getNetworkAwareBitrate()
});

VAST Caching

Cache VAST responses to reduce network requests and improve performance.

In-Memory Cache

class VASTCache {
  private cache = new Map<string, { response: string; timestamp: number }>();
  private ttl = 300000; // 5 minutes
  
  async getVAST(vastUrl: string): Promise<string> {
    const cached = this.cache.get(vastUrl);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      console.log('Using cached VAST');
      return cached.response;
    }
    
    // Fetch fresh VAST
    const response = await fetch(vastUrl);
    const vastXml = await response.text();
    
    // Cache it
    this.cache.set(vastUrl, {
      response: vastXml,
      timestamp: Date.now()
    });
    
    return vastXml;
  }
  
  clear() {
    this.cache.clear();
  }
}

// Usage
const vastCache = new VASTCache();

async function playAd(vastUrl: string) {
  // Get cached or fresh VAST
  const vastXml = await vastCache.getVAST(vastUrl);
  
  // Convert to data URL to avoid re-fetch
  const vastDataUrl = `data:text/xml;base64,${btoa(vastXml)}`;
  
  const player = new AdPlayer({
    container: document.getElementById('ad-container'),
    vastUrl: vastDataUrl
  });
  
  await player.init();
}

LocalStorage Cache

For persistent caching across sessions:
class PersistentVASTCache {
  private storageKey = 'adgent_vast_cache';
  private ttl = 3600000; // 1 hour
  
  async getVAST(vastUrl: string): Promise<string> {
    try {
      const cacheData = localStorage.getItem(this.storageKey);
      
      if (cacheData) {
        const cache = JSON.parse(cacheData);
        const entry = cache[vastUrl];
        
        if (entry && Date.now() - entry.timestamp < this.ttl) {
          console.log('Using cached VAST from localStorage');
          return entry.response;
        }
      }
    } catch (e) {
      console.warn('Cache read failed:', e);
    }
    
    // Fetch fresh
    const response = await fetch(vastUrl);
    const vastXml = await response.text();
    
    // Save to cache
    this.saveToCache(vastUrl, vastXml);
    
    return vastXml;
  }
  
  private saveToCache(vastUrl: string, vastXml: string) {
    try {
      const cacheData = localStorage.getItem(this.storageKey);
      const cache = cacheData ? JSON.parse(cacheData) : {};
      
      cache[vastUrl] = {
        response: vastXml,
        timestamp: Date.now()
      };
      
      localStorage.setItem(this.storageKey, JSON.stringify(cache));
    } catch (e) {
      console.warn('Cache write failed:', e);
    }
  }
  
  clear() {
    localStorage.removeItem(this.storageKey);
  }
}
Be mindful of localStorage size limits (typically 5-10MB). Clear old entries periodically.

Performance Tips

1. Minimize DOM Operations

// Bad: Multiple DOM operations
const container = document.getElementById('ad-container');
container.style.position = 'relative';
container.style.width = '100%';
container.style.height = '100%';
container.style.background = '#000';

// Good: Single operation with cssText
const container = document.getElementById('ad-container');
container.style.cssText = '
  position: relative;
  width: 100%;
  height: 100%;
  background: #000;
';

2. Cleanup Resources

Always destroy the player when done:
const player = new AdPlayer({ /* config */ });

await player.init();

// ... ad plays ...

// IMPORTANT: Cleanup
player.destroy();
The destroy() method (src/core/AdPlayer.ts:770):
  • Removes event listeners
  • Cleans up video element
  • Removes UI elements
  • Clears tracking data

3. Debounce Progress Updates

function debounce(func: Function, wait: number) {
  let timeout: NodeJS.Timeout;
  return (...args: any[]) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  
  // Debounce UI updates
  onProgress: debounce((progress) => {
    updateProgressBar(progress.percentage);
  }, 100) // Update every 100ms instead of every frame
});

4. Reduce Timeout Values

From README.md:417:
const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  timeout: 8000, // Faster timeout = faster failure recovery
  maxWrapperDepth: 3 // Reduce wrapper depth for faster loads
});

5. Disable Debug in Production

const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  debug: process.env.NODE_ENV === 'development' // Only debug in dev
});
Debug mode logs extensively which impacts performance.

6. Use sendBeacon for Tracking

import { getPlatformAdapter } from 'adgent-sdk';

const adapter = getPlatformAdapter();

if (adapter.capabilities.sendBeacon) {
  // Use sendBeacon - more efficient
  navigator.sendBeacon(trackingUrl, data);
} else {
  // Fallback
  fetch(trackingUrl, { method: 'POST', body: data, keepalive: true });
}

Smart TV Constraints

Memory Limitations

Smart TVs typically have 512MB - 2GB RAM, much less than desktops.

Problem: Memory Leaks

// Bad: Creates memory leak
const players: AdPlayer[] = [];

function playAd(vastUrl: string) {
  const player = new AdPlayer({ /* config */ });
  players.push(player); // Never cleaned up!
  await player.init();
}

Solution: Proper Cleanup

// Good: Single player instance, properly cleaned up
class AdManager {
  private currentPlayer: AdPlayer | null = null;
  
  async playAd(vastUrl: string) {
    // Clean up previous player
    if (this.currentPlayer) {
      this.currentPlayer.destroy();
      this.currentPlayer = null;
    }
    
    // Create new player
    this.currentPlayer = new AdPlayer({
      container: document.getElementById('ad-container'),
      vastUrl,
      
      onComplete: () => {
        this.cleanup();
      },
      
      onError: () => {
        this.cleanup();
      }
    });
    
    await this.currentPlayer.init();
  }
  
  cleanup() {
    if (this.currentPlayer) {
      this.currentPlayer.destroy();
      this.currentPlayer = null;
    }
  }
}

Video Element Limits

// Limit number of video elements
class VideoManager {
  private static MAX_VIDEOS = 2; // Main + Ad
  private videos: HTMLVideoElement[] = [];
  
  createVideo(): HTMLVideoElement {
    // Remove excess videos
    while (this.videos.length >= VideoManager.MAX_VIDEOS) {
      const old = this.videos.shift();
      if (old && old.parentNode) {
        old.pause();
        old.src = '';
        old.load();
        old.remove();
      }
    }
    
    const video = document.createElement('video');
    this.videos.push(video);
    return video;
  }
}

Network Limitations

TV WiFi chips are often slower than mobile/desktop.

Optimize VAST Requests

// Reduce wrapper depth to minimize network requests
const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  maxWrapperDepth: 3, // Limit to 3 redirects
  timeout: 12000 // Longer timeout for TV WiFi
});

Prefetch VAST

class VideoPlayer {
  private vastCache: string | null = null;
  
  // Prefetch VAST before showing ad
  async prefetchAd(vastUrl: string) {
    try {
      const response = await fetch(vastUrl);
      this.vastCache = await response.text();
      console.log('VAST prefetched');
    } catch (e) {
      console.error('Prefetch failed:', e);
    }
  }
  
  async playAd() {
    const vastDataUrl = this.vastCache 
      ? `data:text/xml;base64,${btoa(this.vastCache)}`
      : 'https://example.com/vast.xml';
    
    const player = new AdPlayer({
      container: document.getElementById('ad-container'),
      vastUrl: vastDataUrl
    });
    
    await player.init();
  }
}

Compression

Ensure VAST responses are gzip compressed:
// Server-side (Express example)
app.get('/vast.xml', (req, res) => {
  res.set('Content-Encoding', 'gzip');
  res.set('Content-Type', 'text/xml');
  res.send(gzippedVast);
});

CPU Constraints

TV processors are slower than desktop CPUs.

Avoid Heavy Computation

// Bad: Heavy computation on every progress update
player.on((event) => {
  if (event.type === 'progress') {
    // This runs every ~16ms!
    const analytics = computeComplexAnalytics(event.data);
    sendToServer(analytics);
  }
});

// Good: Throttle heavy operations
let lastAnalyticsTime = 0;

player.on((event) => {
  if (event.type === 'progress') {
    const now = Date.now();
    
    // Send analytics every 5 seconds max
    if (now - lastAnalyticsTime > 5000) {
      lastAnalyticsTime = now;
      const analytics = computeComplexAnalytics(event.data);
      sendToServer(analytics);
    }
  }
});

Use requestIdleCallback

function scheduleBackgroundTask(task: () => void) {
  if ('requestIdleCallback' in window) {
    requestIdleCallback(task);
  } else {
    // Fallback for platforms without requestIdleCallback
    setTimeout(task, 1);
  }
}

player.on((event) => {
  if (event.type === 'complete') {
    // Schedule non-critical work for idle time
    scheduleBackgroundTask(() => {
      cleanupCache();
      sendBatchedAnalytics();
    });
  }
});
Optimized configuration for Smart TV platforms:
import { AdPlayer, getPlatformAdapter } from 'adgent-sdk';

const adapter = getPlatformAdapter();
const settings = adapter.getRecommendedVideoSettings();

// Conservative bitrate for TVs
const targetBitrate = Math.min(settings.maxBitrate, 3500);

const player = new AdPlayer({
  container: document.getElementById('ad-container'),
  vastUrl: 'https://example.com/vast.xml',
  
  // Performance optimizations
  targetBitrate,           // Platform-appropriate bitrate
  maxWrapperDepth: 3,      // Limit redirects
  timeout: 12000,          // Generous timeout for TV WiFi
  debug: false,            // Disable debug in production
  
  // Lifecycle management
  onComplete: () => {
    player.destroy();      // Clean up immediately
    resumeMainContent();
  },
  
  onError: (error) => {
    console.error(error);
    player.destroy();      // Always clean up on error
    resumeMainContent();
  },
  
  onSkip: () => {
    player.destroy();
    resumeMainContent();
  }
});

await player.init();

Performance Checklist

  • Use tree shaking and ES modules
  • Lazy load SDK when ads are needed
  • Set appropriate targetBitrate for platform
  • Cache VAST responses (5-60 min TTL)
  • Always call destroy() when done
  • Disable debug mode in production
  • Limit wrapper depth to 3-5
  • Use generous timeouts (10-15 sec) for TVs
  • Throttle progress updates and analytics
  • Clean up video elements immediately
  • Test on actual TV hardware
  • Monitor memory usage over time

Next Steps

Configuration

Learn about all configuration options

Platform Detection

Optimize for specific TV platforms

Error Handling

Handle errors efficiently

Event Handling

Optimize event handling

Build docs developers (and LLMs) love