> ## Documentation Index
> Fetch the complete documentation index at: https://docs.topsort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Video Ads

export const LastUpdated = ({date, lang = "en"}) => {
  const translations = {
    en: "Last updated:",
    es: "Última actualización:",
    pt: "Última atualização:",
    fr: "Dernière mise à jour:",
    de: "Zuletzt aktualisiert:"
  };
  const label = translations[lang] || translations.en;
  return <>
<style>{`
.last-updated-component {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-radius: 8px;
margin-top: 12px;
margin-bottom: 16px;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.12);
color: rgba(0, 0, 0, 0.75);
line-height: 1;
}

        .last-updated-component svg {
          flex-shrink: 0;
          vertical-align: middle;
        }

        .last-updated-component span {
          display: inline-flex !important;
          align-items: center !important;
          line-height: 1 !important;
        }

        [data-theme="dark"] .last-updated-component {
          background-color: #3a3a3a;
          border: 2px solid #888888;
          color: #ffffff;
        }

        [data-theme="dark"] .last-updated-component svg {
          stroke: #ffffff;
        }
      `}</style>
      <div className="last-updated-component">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <circle cx="12" cy="12" r="10" />
          <polyline points="12 6 12 12 16 14" />
        </svg>
        <span>
          <strong style={{
    fontWeight: 600
  }}>{label}</strong> 
          <time dateTime={date}>{date}</time>
        </span>
      </div>
    </>;
};

<div style={{textAlign: 'justify', marginBottom: '1.5rem'}}>
  Video ads are a new format designed to enhance the advertiser’s brand and product experience.
</div>

## How It Works

<div style={{textAlign: 'justify', marginBottom: '1.5rem'}}>
  The campaign creation process is similar to that of [banner ads](/en/knowledge-base/ad-platform/banners/banner-ads-campaigns/):
</div>

<Steps>
  <Step title="Upload a video" />

  <Step title="Select a slot" />

  <Step title="Define placements/context (automatic, category, search)" />

  <Step title="Set campaign details: name, budget, and duration" />
</Steps>

<Note>Videos uploaded by vendors require approval from the marketplace admin.</Note>

## Video Requirements

* **Duration**: 6 to 20 seconds (shorter than 20s recommended)
* **Formats**: MP4 or MOV
* **Max size**: 200MB

## Auction Flow

During an auction request, marketplaces must send the slot ID and may include contextual information (e.g., category, keywords). The auction response will return the video URL for rendering.

### Sample Auction Response

```json theme={null}
{
  "resultType": "banners",
  "winners": [
    {
      "rank": 1,
      "asset": [
        {
          "url": "https://customer-axfyyvfgsfxowp1c.cloudflarestream.com/6541f123389a4796889166a2b9491f09/manifest/video.m3u8"
        }
      ],
      "type": "vendor",
      "id": "972776",
      "resolvedBidId": "ChAGjBuGm-lyS6oE7YQebGh-EhABmTSoITB38Z2Nmqd_rVLpGhABmTLlo1V1wJNc17VoVGUKIgoKBjk3Mjc3NhADMJTpbDoBAUAFSAJQx7rhpZMz",
      "vendorId": "972776",
      "campaignId": "019934a8-2130-77f1-9d8d-9aa77fad52e9"
    }
  ],
  "error": false
}
```

The response includes an HLS manifest URL (`.m3u8`) that can be rendered using any HLS-compatible video player.

## Video Integration Options

### Use Your Own Video Player

Topsort's video ads are compatible with all players that support **HLS** and **DASH** formats. Below are implementation examples for each platform.

#### Web Implementation

```html theme={null}
<video id="video-ad" muted autoplay loop playsinline style="width: 100%; max-width: 640px;">
  <source src="YOUR_VIDEO_URL_HERE" type="application/x-mpegURL">
</video>

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
// Initialize HLS.js for better browser support
const video = document.getElementById('video-ad');
if (Hls.isSupported()) {
  const hls = new Hls();
  hls.loadSource('YOUR_VIDEO_URL_HERE');
  hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // Native HLS support (Safari)
  video.src = 'YOUR_VIDEO_URL_HERE';
}

// Track impressions (50% visible for 2+ seconds)
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.intersectionRatio >= 0.5) {
      setTimeout(() => {
        if (entry.intersectionRatio >= 0.5) {
          // Report impression
          window.topsortEvents.reportEvent({
            type: 'impression',
            resolvedBidId: 'YOUR_RESOLVED_BID_ID'
          });
        }
      }, 2000);
    }
  });
}, { threshold: 0.5 });

observer.observe(video);

// Handle clicks
video.addEventListener('click', () => {
  window.topsortEvents.reportEvent({
    type: 'click',
    resolvedBidId: 'YOUR_RESOLVED_BID_ID'
  });
  // Navigate to vendor/product page
});
</script>
```

#### iOS Implementation (Swift)

```swift theme={null}
import AVKit
import UIKit

class VideoAdView: UIView {
    private var player: AVPlayer?
    private var playerLayer: AVPlayerLayer?

    func loadVideo(url: String, resolvedBidId: String) {
        guard let videoURL = URL(string: url) else { return }

        // Setup player
        player = AVPlayer(url: videoURL)
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.frame = bounds
        playerLayer?.videoGravity = .resizeAspectFill
        layer.addSublayer(playerLayer!)

        // Configure for autoplay
        player?.isMuted = true
        player?.play()

        // Loop video
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(playerDidFinishPlaying),
            name: .AVPlayerItemDidPlayToEndTime,
            object: player?.currentItem
        )

        // Add tap gesture for clicks
        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        addGestureRecognizer(tap)
    }

    @objc private func playerDidFinishPlaying() {
        player?.seek(to: .zero)
        player?.play()
    }

    @objc private func handleTap() {
        // Report click event via Topsort SDK
    }
}
```

#### Android Implementation (Kotlin)

```kotlin theme={null}
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player

class VideoAdView(context: Context) : PlayerView(context) {
    private var exoPlayer: ExoPlayer? = null

    fun loadVideo(url: String, resolvedBidId: String) {
        exoPlayer = ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(url))
            repeatMode = Player.REPEAT_MODE_ONE  // Loop
            volume = 0f  // Muted for autoplay
            prepare()
            play()
        }

        player = exoPlayer
        useController = false  // Hide controls

        // Handle clicks
        setOnClickListener {
            // Report click via Topsort SDK
        }
    }
}
```

For detailed player documentation, see:

* [Web Player Guide](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/web/)
* [iOS AVPlayer Guide](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/ios/)
* [Android ExoPlayer Guide](https://developers.cloudflare.com/stream/viewing-videos/using-own-player/android/)

## Reporting and Metrics

Topsort tracks video impressions and clicks using [banners.js](http://localhost:4321/ad-platform/banners/bannersjs/).

### Impression Tracking

* **Viewable Impression (IAB/MRC Standard)**: 50% of pixels in view for 2+ consecutive seconds
* **Engagement Impression**: Video watched for at least 5 seconds
* Use the `resolvedBidId` from the auction response when reporting events

### Attribution and Billing Standards

Per IAB/MRC Retail Media Measurement Guidelines, Topsort uses **Viewable Impressions** (IAB/MRC Standard) for:

* **Attribution of outcomes**: Only impressions meeting MRC viewability standards (50% pixels visible for 2+ seconds) are eligible for attribution
* **Campaign billing**: Advertisers are charged based on viewable impressions
* **Performance reporting**: Primary metrics and ROAS calculations based on viewable impressions

**Note**: Engagement impressions (5+ seconds watched) are available as an additional engagement metric but are NOT used for attribution or billing purposes.

### Click Tracking

* Report click events when users interact with the video
* Include the `resolvedBidId` to properly attribute the click
* Navigate to the appropriate vendor/product page after reporting

### Important Considerations

* **Autoplay Requirements**: Videos must be muted to autoplay on most browsers
* **Loop Playback**: Videos should loop continuously while in view
* **Mobile Optimization**: Use `playsinline` attribute to prevent fullscreen on iOS
* **Performance**: Consider lazy loading for videos below the fold

Marketplaces can also configure custom impressions and clicks reporting logic as needed.

## Frequently Asked Questions

1. **What are the recommended upload settings for video uploads?**

   * MP4 containers, AAC audio codec, H264 video codec, 30 or below frames per second
   * moov atom should be at the front of the file (Fast Start)
   * H264 progressive scan (no interlacing)
   * H264 high profile
   * Closed GOP
   * Content should be encoded and uploaded in the same frame rate it was recorded
   * Mono or Stereo audio (Stream will mix audio tracks with more than 2 channels down to stereo)

2. **What browsers does Stream work on?**

   * You can embed the Stream player on the following platforms:

     <table><tbody><tr><td><p><strong>Browser</strong></p></td><td><p><strong>Version</strong></p></td></tr><tr><td><p>Chrome</p></td><td><p>Supported since Chrome version 88+</p></td></tr><tr><td><p>Firefox</p></td><td><p>Supported since Firefox version 87+</p></td></tr><tr><td><p>Edge</p></td><td><p>Supported since Edge 89+</p></td></tr><tr><td><p>Safari</p></td><td><p>Supported since Safari version 14+</p></td></tr><tr><td><p>Opera</p></td><td><p>Supported since Opera version 75+</p></td></tr></tbody></table>

     <table><tbody><tr><td><p><strong>Mobile Platform</strong></p></td><td><p><strong>Version</strong></p></td></tr><tr><td><p>Chrome on Android</p></td><td><p>Supported on Chrome 90</p></td></tr><tr><td><p>UC Browser on Android</p></td><td><p>Supported on version 12.12+</p></td></tr><tr><td><p>Samsung Internet</p></td><td><p>Supported on 13+</p></td></tr><tr><td><p>Safari on iOS</p></td><td><p>Supported on iOS 13.4+. Speed selector supported when not in fullscreen.</p></td></tr></tbody></table>

***

<LastUpdated date="2025-11-18" />
