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

# Publishing Patterns

> Embed players, share content, and deliver video to end users

Deliver video content to end users through embedded players, shareable links, and social media integrations.

## Quick Example

<CodeGroup>
  ```python Python theme={null}
  import videodb

  conn = videodb.connect()
  coll = conn.get_collection()
  video = coll.get_video("m-xxx")

  # Get streaming URL
  stream_url = video.generate_stream()

  # Get thumbnail
  thumbnail_url = video.generate_thumbnail()

  # Get download link
  download_url = video.download(name="sample video")
  ```

  ```javascript Node.js theme={null}
  import { connect } from 'videodb';

  const conn = connect();
  const coll = await conn.getCollection();
  const video = await coll.getVideo("m-xxx");

  // Get streaming URL
  const streamUrl = await video.generateStream();

  // Get thumbnail URL
  const thumbnailUrl = await video.generateThumbnail();

  // Get download link
  const downloadUrl = await video.download("sample video");
  ```
</CodeGroup>

***

## Embed Player

### Basic HTML Embed

```html theme={null}
<video
  id="player"
  controls
  width="100%"
  poster="{thumbnail_url}"
>
  <source
    src="{stream_url}"
    type="application/x-mpegURL"
  >
</video>

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
  const video = document.getElementById('player');
  const src = video.querySelector('source').src;

  if (Hls.isSupported()) {
    const hls = new Hls();
    hls.loadSource(src);
    hls.attachMedia(video);
  }
</script>
```

### React Component

```jsx theme={null}
import Hls from 'hls.js';
import { useRef, useEffect } from 'react';

function VideoPlayer({ streamUrl, thumbnailUrl, title }) {
  const videoRef = useRef(null);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource(streamUrl);
      hls.attachMedia(video);

      return () => hls.destroy();
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      // Safari native HLS
      video.src = streamUrl;
    }
  }, [streamUrl]);

  return (
    <video
      ref={videoRef}
      controls
      poster={thumbnailUrl}
      title={title}
      style={{ width: '100%' }}
    />
  );
}
```

### Vue Component

```vue theme={null}
<template>
  <video
    ref="videoPlayer"
    controls
    :poster="thumbnailUrl"
    style="width: 100%"
  />
</template>

<script setup>
import Hls from 'hls.js';
import { ref, onMounted, onUnmounted } from 'vue';

const props = defineProps(['streamUrl', 'thumbnailUrl']);
const videoPlayer = ref(null);
let hls = null;

onMounted(() => {
  if (Hls.isSupported()) {
    hls = new Hls();
    hls.loadSource(props.streamUrl);
    hls.attachMedia(videoPlayer.value);
  }
});

onUnmounted(() => {
  if (hls) hls.destroy();
});
</script>
```

***

## Embed Code Generation

### `get_embed_code()`

Generate an HTML iframe embed string directly from SDK objects. Available on `Video`, `Shot`, `SearchResult`, `Timeline`, `RTStream`, `RTStreamShot`, `RTStreamExportResult`, and `Editor` `Timeline`.

```python theme={null}
embed_html = obj.get_embed_code(
    width="100%",             # iframe width
    height=405,               # iframe height in pixels
    title="VideoDB Player",   # iframe title attribute
    allow_fullscreen=True,    # allow fullscreen
    auto_generate=True        # auto-call generate_stream() if player_url missing
)
```

Returns an HTML `<iframe>` string. Raises `ValueError` if `player_url` is not available and cannot be auto-generated.

### Video Embed

```python theme={null}
video = coll.get_video("m-xxx")

# Auto-generates stream URL if needed
embed_html = video.get_embed_code()
print(embed_html)
# <iframe src="https://console.videodb.io/player?url=..." width="100%" height="405" ...></iframe>

# Custom dimensions
embed_html = video.get_embed_code(width="640px", height=360, title="My Video")
```

### Search Result Embed

```python theme={null}
results = video.search("product demo")

# Embed the compiled search results
embed_html = results.get_embed_code(height=480)
```

### RTStream Note

`RTStream` does **not** support `auto_generate`. You must call `generate_stream(start, end)` explicitly before calling `get_embed_code()`:

```python theme={null}
rt_stream.generate_stream(start=0, end=120)
embed_html = rt_stream.get_embed_code(auto_generate=False)
```

### `build_iframe_embed_code()` Utility

A standalone helper when you already have a player URL:

```python theme={null}
from videodb import build_iframe_embed_code

embed_html = build_iframe_embed_code(player_url, width="100%", height=405)
```

***

## Thumbnail Generation

### Get Video Thumbnail

<CodeGroup>
  ```python Python theme={null}
  # Default thumbnail (first frame)
  thumbnail_url = video.generate_thumbnail()

  # Thumbnail at specific time
  thumbnail_url = video.generate_thumbnail(time=30.5)
  ```

  ```javascript Node.js theme={null}
  // Default thumbnail (first frame)
  const thumbnailUrl = await video.generateThumbnail();

  // Thumbnail at specific time
  const thumbnailUrl = await video.generateThumbnail(30.5);
  ```
</CodeGroup>

### Multiple Thumbnails for Preview

<CodeGroup>
  ```python Python theme={null}
  def generate_preview_thumbnails(video, count=5):
      """Generate evenly spaced thumbnails across video"""
      duration = video.duration
      interval = duration / (count + 1)

      thumbnails = []
      for i in range(1, count + 1):
          time = interval * i
          url = video.generate_thumbnail(time=time)
          thumbnails.append({
              "time": time,
              "url": url
          })

      return thumbnails

  previews = generate_preview_thumbnails(video, count=5)
  ```

  ```javascript Node.js theme={null}
  async function generatePreviewThumbnails(video, count = 5) {
      const duration = video.duration;
      const interval = duration / (count + 1);

      const thumbnails = [];
      for (let i = 1; i <= count; i++) {
          const time = interval * i;
          const url = await video.generateThumbnail(time);
          thumbnails.push({ time, url });
      }

      return thumbnails;
  }

  const previews = await generatePreviewThumbnails(video, 5);
  ```
</CodeGroup>

***

## Share Links

### VideoDB Console Player

Generate shareable links using the console player:

<CodeGroup>
  ```python Python theme={null}
  import urllib.parse

  stream_url = video.generate_stream()
  encoded_url = urllib.parse.quote(stream_url, safe='')

  share_link = f"https://console.videodb.io/player?url={encoded_url}"
  ```

  ```javascript Node.js theme={null}
  const streamUrl = await video.generateStream();
  const encodedUrl = encodeURIComponent(streamUrl);

  const shareLink = `https://console.videodb.io/player?url=${encodedUrl}`;
  ```
</CodeGroup>

### Share Specific Clips

<CodeGroup>
  ```python Python theme={null}
  # Share a specific segment
  clip_url = video.generate_stream([(120, 180)])
  share_clip = f"https://console.videodb.io/player?url={urllib.parse.quote(clip_url)}"
  ```

  ```javascript Node.js theme={null}
  // Share a specific segment
  const clipUrl = await video.generateStream([[120, 180]]);
  const shareClip = `https://console.videodb.io/player?url=${encodeURIComponent(clipUrl)}`;
  ```
</CodeGroup>

***

## Social Media Metadata

### Open Graph Tags

```html theme={null}
<!-- Video metadata for social sharing -->
<meta property="og:type" content="video.other" />
<meta property="og:title" content="{video_title}" />
<meta property="og:description" content="{video_description}" />
<meta property="og:image" content="{thumbnail_url}" />
<meta property="og:video" content="{stream_url}" />
<meta property="og:video:type" content="application/x-mpegURL" />
<meta property="og:video:width" content="1920" />
<meta property="og:video:height" content="1080" />
```

### Twitter Card

```html theme={null}
<meta name="twitter:card" content="player" />
<meta name="twitter:title" content="{video_title}" />
<meta name="twitter:description" content="{video_description}" />
<meta name="twitter:image" content="{thumbnail_url}" />
<meta name="twitter:player" content="{embed_url}" />
<meta name="twitter:player:width" content="1280" />
<meta name="twitter:player:height" content="720" />
```

### Generate Metadata Object

<CodeGroup>
  ```python Python theme={null}
  def generate_social_metadata(video, title, description):
      """Generate metadata for social sharing"""
      stream_url = video.generate_stream()
      thumbnail_url = video.generate_thumbnail()

      return {
          "title": title,
          "description": description,
          "thumbnail": thumbnail_url,
          "video_url": stream_url,
          "og": {
              "type": "video.other",
              "title": title,
              "description": description,
              "image": thumbnail_url,
              "video": stream_url
          },
          "twitter": {
              "card": "player",
              "title": title,
              "description": description,
              "image": thumbnail_url
          }
      }
  ```

  ```javascript Node.js theme={null}
  async function generateSocialMetadata(video, title, description) {
      const streamUrl = await video.generateStream();
      const thumbnailUrl = await video.generateThumbnail();

      return {
          title,
          description,
          thumbnail: thumbnailUrl,
          videoUrl: streamUrl,
          og: {
              type: "video.other",
              title,
              description,
              image: thumbnailUrl,
              video: streamUrl
          },
          twitter: {
              card: "player",
              title,
              description,
              image: thumbnailUrl
          }
      };
  }
  ```
</CodeGroup>

***

## CDN Delivery

### Stream URL Structure

VideoDB serves content through a global CDN:

```
https://stream.videodb.io/v3/published/manifests/{manifest-id}.m3u8
```

| Endpoint            | Purpose               |
| :------------------ | :-------------------- |
| `stream.videodb.io` | HLS streaming         |
| `cdn.videodb.io`    | Direct file downloads |

### URL Caching Strategy

<CodeGroup>
  ```python Python theme={null}
  import time
  from functools import lru_cache

  class VideoURLCache:
      def __init__(self, ttl=23 * 3600):  # 23 hours
          self.cache = {}
          self.ttl = ttl

      def get_stream_url(self, video, timestamps=None):
          key = f"{video.id}:{timestamps}"

          if key in self.cache:
              url, expires = self.cache[key]
              if time.time() < expires:
                  return url

          # Generate new URL
          if timestamps:
              url = video.generate_stream(timestamps)
          else:
              url = video.generate_stream()

          self.cache[key] = (url, time.time() + self.ttl)
          return url

  # Usage
  url_cache = VideoURLCache()
  stream_url = url_cache.get_stream_url(video)
  ```

  ```javascript Node.js theme={null}
  class VideoURLCache {
      constructor(ttl = 23 * 60 * 60 * 1000) {  // 23 hours
          this.cache = new Map();
          this.ttl = ttl;
      }

      async getStreamUrl(video, timestamps = null) {
          const key = `${video.id}:${JSON.stringify(timestamps)}`;

          if (this.cache.has(key)) {
              const { url, expires } = this.cache.get(key);
              if (Date.now() < expires) {
                  return url;
              }
          }

          // Generate new URL
          const url = timestamps
              ? await video.generateStream(timestamps)
              : await video.generateStream();

          this.cache.set(key, {
              url,
              expires: Date.now() + this.ttl
          });

          return url;
      }
  }

  // Usage
  const urlCache = new VideoURLCache();
  const streamUrl = await urlCache.getStreamUrl(video);
  ```
</CodeGroup>

***

## Player Features

### Autoplay with Mute

```html theme={null}
<video
  id="player"
  autoplay
  muted
  playsinline
  poster="{thumbnail_url}"
>
  <source src="{stream_url}" type="application/x-mpegURL">
</video>
```

### Loop Playback

```html theme={null}
<video id="player" loop controls>
  <source src="{stream_url}" type="application/x-mpegURL">
</video>
```

### Custom Controls

```jsx theme={null}
function CustomPlayer({ streamUrl }) {
  const videoRef = useRef(null);
  const [playing, setPlaying] = useState(false);
  const [progress, setProgress] = useState(0);

  const togglePlay = () => {
    if (playing) {
      videoRef.current.pause();
    } else {
      videoRef.current.play();
    }
    setPlaying(!playing);
  };

  return (
    <div className="player-container">
      <video
        ref={videoRef}
        onTimeUpdate={(e) => {
          const pct = (e.target.currentTime / e.target.duration) * 100;
          setProgress(pct);
        }}
      />
      <div className="controls">
        <button onClick={togglePlay}>
          {playing ? 'Pause' : 'Play'}
        </button>
        <div className="progress-bar" style={{ width: `${progress}%` }} />
      </div>
    </div>
  );
}
```

***

## Responsive Embed

### Aspect Ratio Container

```css theme={null}
.video-container {
  position: relative;
  width: 100%;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
}

.video-container video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
```

```html theme={null}
<div class="video-container">
  <video controls>
    <source src="{stream_url}" type="application/x-mpegURL">
  </video>
</div>
```

### Multiple Aspect Ratios

```css theme={null}
.video-16-9 { padding-bottom: 56.25%; }
.video-4-3 { padding-bottom: 75%; }
.video-1-1 { padding-bottom: 100%; }
.video-9-16 { padding-bottom: 177.78%; }
```

***

## Best Practices

| Practice             | Reason                                 |
| :------------------- | :------------------------------------- |
| Cache stream URLs    | Avoid regenerating on every request    |
| Use thumbnails       | Improve perceived load time            |
| Preload metadata     | `preload="metadata"` for faster starts |
| Lazy load off-screen | Defer loading until visible            |
| Handle errors        | Show fallback on stream failure        |

### Error Handling

```jsx theme={null}
function VideoPlayer({ streamUrl, fallbackUrl }) {
  const [error, setError] = useState(false);

  if (error && fallbackUrl) {
    return <img src={fallbackUrl} alt="Video unavailable" />;
  }

  return (
    <video
      controls
      onError={() => setError(true)}
    >
      <source src={streamUrl} type="application/x-mpegURL" />
    </video>
  );
}
```

***

## Next Steps

<CardGroup cols={2}>
  <Card icon="radio" title="Streams and Exports" href="/pages/act/output-and-delivery/streams-and-exports">
    Generate clips and export video
  </Card>

  <Card icon="film" title="Timeline Architecture" href="/pages/act/programmable-editing/timeline-architecture">
    Compose video programmatically
  </Card>
</CardGroup>
