Paste Youtube, Vimeo or Wistia video url to get the thumbnail image and oembed data #


Video thumbnail:

Makes request to:

Result:

How it works #

I often google “get youtube video thumbnail”.

Oembed let’s you get metadata and embed code for a media asset.

If a web page has <link rel="alternate" type="application/json+oembed" href="...">, it means that the page supports oembed. The href is the oembed url. The first part of the url is the oembed API endpoint.

For example

<link rel="alternate" type="application/json+oembed" href="https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=vS-F1PlLyTk" title="Typesense search with Ruby on Rails #225">

means that the Youtube API endpoint is

https://www.youtube.com/oembed?format=json&url=

Vimeo (example url: https://vimeo.com/158115405)

https://vimeo.com/api/oembed.json?url=

Wistia (example url: https://clickfunnels-28.wistia.com/medias/661x8p4j6u)

https://fast.wistia.com/oembed?url=

At the end of the API endpoint append the url to the video to get the oembed data.

StimulusJS implementation #

// video_thumbnail_preview_controller.js
import { Controller } from '@hotwired/stimulus'

// <div data-controller="video-thumbnail-preview">
//   <input
//     data-video-thumbnail-preview-target="input"
//     type="text"
//     data-action="input->video-thumbnail-preview#fetchThumbnail"
//     value="https://www.youtube.com/watch?v=RNaaODDtTLw"
//   >
//   <div data-video-thumbnail-preview-target="output"></div>
// </div>

// public vimeo video:
// https://vimeo.com/158115405
// https://vimeo.com/api/oembed.json?url=https://vimeo.com/158115405

// public youtube video:
// www.youtube.com/watch?v=944lk4JAdyg
// https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=944lk4JAdyg

// https://docs.wistia.com/docs/wistia-and-oembed
// public wistia video:
// https://clickfunnels-28.wistia.com/medias/661x8p4j6u
// https://fast.wistia.com/oembed?url=https://clickfunnels-28.wistia.com/medias/661x8p4j6u

export default class extends Controller {
  static targets = ['input', 'output']

  connect() {
    this.fetchThumbnail()
  }

  outputTargetConnected() {
    this.fetchThumbnail()
  }

  inputTargetConnected() {
    this.fetchThumbnail()
  }

  inputTargetChanged() {
    this.fetchThumbnail()
  }

  async fetchThumbnail() {
    const url = this.inputTarget.value
    if (!url) {
      this.clearOutputs()
      return
    }

    const videoProvider = this.detectVideoProvider(url)
    if (!videoProvider) {
      this.clearOutputs()
      return
    }

    try {
      const thumbnailUrl = await this.fetchOembedThumbnail(url, videoProvider)
      if (thumbnailUrl) {
        this.outputTargets.forEach((target) => {
          target.innerHTML = `<img src="${thumbnailUrl}" alt="Video thumbnail">`
        })
      } else {
        this.clearOutputs()
      }
    } catch (error) {
      this.clearOutputs()
    }
  }

  clearOutputs() {
    this.outputTargets.forEach((target) => {
      target.innerHTML = ''
    })
  }

  detectVideoProvider(url) {
    if (url.match(/youtu/)) return 'youtube'
    if (url.match(/vimeo/)) return 'vimeo'
    if (url.match(/wistia/)) return 'wistia'
    return null
  }

  async fetchOembedThumbnail(url, provider) {
    const endpoints = {
      youtube: `https://www.youtube.com/oembed?url=${encodeURIComponent(
        url
      )}&format=json`,
      vimeo: `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(url)}`,
      wistia: `https://fast.wistia.com/oembed?url=${encodeURIComponent(
        url
      )}&format=json`
    }

    const endpoint = endpoints[provider]
    if (!endpoint) return null

    const response = await fetch(endpoint)
    if (!response.ok) return null

    const data = await response.json()
    return data.thumbnail_url
  }
}

That’s it!