HextaUI

Video player

ドラッグ可能なシークバー、自動で隠れるコントロール、キーボードショートカット、再生速度、ピクチャーインピクチャー、全画面表示を備えた動画プレーヤー。

0:00 / --:--
import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPictureInPictureButton,
  VideoPlayerPlaybackRate,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSeekButton,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"

export function VideoPlayerDemo() {
  return (
    <VideoPlayer className="max-w-2xl">
      <VideoPlayerContent
        poster="https://media.w3.org/2010/05/sintel/poster.png"
        aria-label="Sintel trailer"
      >
        <source
          src="https://media.w3.org/2010/05/sintel/trailer.webm"
          type="video/webm"
        />
        <source
          src="https://media.w3.org/2010/05/sintel/trailer.mp4"
          type="video/mp4"
        />
      </VideoPlayerContent>
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerSeekButton offset={-10} />
        <VideoPlayerSeekButton offset={10} />
        <VideoPlayerVolume />
        <VideoPlayerTime />
        <VideoPlayerSpacer />
        <VideoPlayerPlaybackRate />
        <VideoPlayerPictureInPictureButton />
        <VideoPlayerFullscreenButton />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/video-player.json

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"
<VideoPlayer>
  <VideoPlayerContent src="/intro.mp4" poster="/intro.jpg" />
  <VideoPlayerControls>
    <VideoPlayerSeekBar />
    <VideoPlayerPlayButton />
    <VideoPlayerVolume />
    <VideoPlayerTime />
    <VideoPlayerSpacer />
    <VideoPlayerFullscreenButton />
  </VideoPlayerControls>
</VideoPlayer>
VideoPlayer
├── VideoPlayerContent
└── VideoPlayerControls
    ├── VideoPlayerPlayButton
    ├── VideoPlayerSeekButton
    ├── VideoPlayerSeekBar
    ├── VideoPlayerTime
    ├── VideoPlayerSpacer
    ├── VideoPlayerVolume
    ├── VideoPlayerPlaybackRate
    ├── VideoPlayerCaptionsButton
    ├── VideoPlayerPictureInPictureButton
    └── VideoPlayerFullscreenButton

バー

variant="bar" は、コントロールをページの面の上、映像の下に置きます。動画を隠したり覆ったりすることはありません。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPictureInPictureButton,
  VideoPlayerPlaybackRate,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSeekButton,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"

export function VideoPlayerBar() {
  return (
    <VideoPlayer variant="bar" className="max-w-2xl">
      <VideoPlayerContent
        poster="https://media.w3.org/2010/05/sintel/poster.png"
        aria-label="Sintel trailer"
      >
        <source
          src="https://media.w3.org/2010/05/sintel/trailer.webm"
          type="video/webm"
        />
        <source
          src="https://media.w3.org/2010/05/sintel/trailer.mp4"
          type="video/mp4"
        />
      </VideoPlayerContent>
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerSeekButton offset={-10} />
        <VideoPlayerSeekButton offset={10} />
        <VideoPlayerVolume />
        <VideoPlayerTime />
        <VideoPlayerSpacer />
        <VideoPlayerPlaybackRate />
        <VideoPlayerPictureInPictureButton />
        <VideoPlayerFullscreenButton />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

ミニマル

必要なパーツだけを使います。tooltips={false} はホバーのヒントをオフにし、type="remaining" は加算ではなくカウントダウンにします。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSpacer,
  VideoPlayerTime,
} from "@/components/ui/video-player"

export function VideoPlayerMinimal() {
  return (
    <VideoPlayer className="max-w-md">
      <VideoPlayerContent
        src="https://media.w3.org/2010/05/sintel/trailer.mp4"
        poster="https://media.w3.org/2010/05/sintel/poster.png"
        aria-label="Sintel trailer"
      />
      <VideoPlayerControls tooltips={false}>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerTime type="remaining" />
        <VideoPlayerSpacer />
        <VideoPlayerFullscreenButton />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

シークのオフセットと速度

offset は各シークボタンがジャンプする量を、rates はメニューの速度を設定します。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerPlaybackRate,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSeekButton,
  VideoPlayerSpacer,
  VideoPlayerTime,
} from "@/components/ui/video-player"

export function VideoPlayerSeekOffsets() {
  return (
    <VideoPlayer className="max-w-xl">
      <VideoPlayerContent
        src="https://media.w3.org/2010/05/sintel/trailer.mp4"
        poster="https://media.w3.org/2010/05/sintel/poster.png"
        aria-label="Sintel trailer"
      />
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerSeekButton offset={-15} />
        <VideoPlayerPlayButton />
        <VideoPlayerSeekButton offset={30} />
        <VideoPlayerTime />
        <VideoPlayerSpacer />
        <VideoPlayerPlaybackRate rates={[1, 1.5, 2, 3]} />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

ページ全体のショートカット

ショートカットは、フォーカスがプレーヤー内にある間機能します。globalShortcuts はページ上でもリッスンしますが、フィールドに入力中、ボタンを使用中、メニューやダイアログが開いている間は機能しません。ページに1つのプレーヤーがある場合に使います。

import { Kbd } from "@/components/ui/kbd"
import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"

export function VideoPlayerGlobalShortcuts() {
  return (
    <div className="flex w-full max-w-xl flex-col items-center gap-3">
      <VideoPlayer globalShortcuts>
        <VideoPlayerContent
          src="https://media.w3.org/2010/05/sintel/trailer.mp4"
          poster="https://media.w3.org/2010/05/sintel/poster.png"
          aria-label="Sintel trailer"
        />
        <VideoPlayerControls>
          <VideoPlayerSeekBar />
          <VideoPlayerPlayButton />
          <VideoPlayerVolume />
          <VideoPlayerTime />
          <VideoPlayerSpacer />
          <VideoPlayerFullscreenButton />
        </VideoPlayerControls>
      </VideoPlayer>
      <p className="text-sm text-muted-foreground">
        Press <Kbd keys="k" /> anywhere on the page to play or pause.
      </p>
    </div>
  )
}

字幕

<track> と VideoPlayerCaptionsButton を追加します。字幕はプレーヤーが描画するため、コントロールの表示中はその裏に隠れず、上に移動します。クロスオリジンのトラックでは、video に crossOrigin が必要です。

import {
  VideoPlayer,
  VideoPlayerCaptionsButton,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"

export function VideoPlayerCaptions() {
  return (
    <VideoPlayer className="max-w-xl">
      <VideoPlayerContent
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/friday.mp4"
        crossOrigin="anonymous"
        aria-label="Friday"
      >
        <track
          default
          kind="captions"
          srcLang="en"
          label="English"
          src="https://interactive-examples.mdn.mozilla.net/media/examples/friday.vtt"
        />
      </VideoPlayerContent>
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerVolume />
        <VideoPlayerTime />
        <VideoPlayerSpacer />
        <VideoPlayerCaptionsButton />
        <VideoPlayerFullscreenButton />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

カスタムコントロール

useVideoPlayer は、プレーヤー内の任意のコンポーネントから状態とアクションを読み取ります。使うものだけを選択すると、その値が変わったときだけコンポーネントが再レンダリングされます。

"use client"

import { Button } from "@/components/ui/button"
import {
  formatTime,
  useVideoPlayer,
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerTime,
} from "@/components/ui/video-player"

const chapters = [
  { title: "The cave", time: 0 },
  { title: "Searching", time: 13 },
  { title: "The dragon", time: 31 },
]

function Chapters() {
  const seek = useVideoPlayer((player) => player.seek)
  const play = useVideoPlayer((player) => player.play)
  const currentTime = useVideoPlayer((player) => Math.floor(player.currentTime))
  const active = chapters.findLast((chapter) => chapter.time <= currentTime)

  return (
    <div className="flex flex-wrap gap-2">
      {chapters.map((chapter) => (
        <Button
          key={chapter.title}
          variant={chapter === active ? "secondary" : "outline"}
          size="sm"
          aria-pressed={chapter === active}
          onClick={() => {
            seek(chapter.time)
            play()
          }}
        >
          <span className="tabular-nums">{formatTime(chapter.time)}</span>
          {chapter.title}
        </Button>
      ))}
    </div>
  )
}

export function VideoPlayerCustomControls() {
  return (
    <VideoPlayer variant="bar" className="max-w-xl">
      <VideoPlayerContent
        src="https://media.w3.org/2010/05/sintel/trailer.mp4"
        poster="https://media.w3.org/2010/05/sintel/poster.png"
        aria-label="Sintel trailer"
      />
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerTime />
        <div className="basis-full px-1 pt-1">
          <Chapters />
        </div>
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

エラー

ソースの読み込みに失敗すると、プレーヤーは errorMessage を表示して読み上げ、動作しないコントロールを無効にします。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerTime,
} from "@/components/ui/video-player"

export function VideoPlayerError() {
  return (
    <VideoPlayer
      className="max-w-md"
      errorMessage="We couldn’t load this video. Check your connection and try again."
    >
      <VideoPlayerContent
        src="https://media.w3.org/2010/05/sintel/missing.mp4"
        aria-label="Missing video"
      />
      <VideoPlayerControls>
        <VideoPlayerSeekBar />
        <VideoPlayerPlayButton />
        <VideoPlayerTime />
      </VideoPlayerControls>
    </VideoPlayer>
  )
}

右から左

ラベルはページの言語に従います。タイムラインと再生コントロールは、プラットフォームのメディアプレーヤーと同様に、左から右のままです。

import {
  VideoPlayer,
  VideoPlayerContent,
  VideoPlayerControls,
  VideoPlayerFullscreenButton,
  VideoPlayerPlaybackRate,
  VideoPlayerPlayButton,
  VideoPlayerSeekBar,
  VideoPlayerSpacer,
  VideoPlayerTime,
  VideoPlayerVolume,
} from "@/components/ui/video-player"

export function VideoPlayerRtl() {
  return (
    <div dir="rtl" className="w-full max-w-xl">
      <VideoPlayer aria-label="مشغل الفيديو">
        <VideoPlayerContent
          src="https://media.w3.org/2010/05/sintel/trailer.mp4"
          poster="https://media.w3.org/2010/05/sintel/poster.png"
          aria-label="إعلان سينتل"
        />
        <VideoPlayerControls>
          <VideoPlayerSeekBar label="تقديم" />
          <VideoPlayerPlayButton
            playLabel="تشغيل"
            pauseLabel="إيقاف مؤقت"
            replayLabel="إعادة التشغيل"
          />
          <VideoPlayerVolume
            label="مستوى الصوت"
            muteLabel="كتم الصوت"
            unmuteLabel="إلغاء كتم الصوت"
          />
          <VideoPlayerTime />
          <VideoPlayerSpacer />
          <VideoPlayerPlaybackRate label="سرعة التشغيل" normalLabel="عادي" />
          <VideoPlayerFullscreenButton
            enterLabel="ملء الشاشة"
            exitLabel="الخروج من ملء الشاشة"
          />
        </VideoPlayerControls>
      </VideoPlayer>
    </div>
  )
}

これらは、フォーカスがプレーヤー内のどこかにある間、または globalShortcuts ならページ上で機能します。修飾キーが押されている間や、フォーカスがテキストフィールドにある間はスキップされます。

キーアクション
SpaceK再生または一時停止します。
J10秒戻ります。
L10秒進みます。
←→5秒戻る、または進みます。シークバー上では Shift で10秒ジャンプします。
↑↓音量を5%ずつ上げ下げします。
Mミュートまたはミュート解除します。
C動画に字幕がある場合、字幕のオンとオフを切り替えます。
F全画面表示に入る、または終了します。
I対応している環境で、ピクチャーインピクチャーを開閉します。
Shift+.Shift+,再生速度を上げ下げします。
0–9動画の0%から90%にジャンプします。
HomeEnd先頭または末尾にジャンプします。
  • プレーヤーはラベル付きの領域です。すべてのボタンには状態に従う名前(Play、Pause、Replay)と、ショートカット付きの tooltip があります。
  • シークバーと音量は slider です。シークバーは値を「3 minutes 中 1 minute 5 seconds」のように読み上げます。
  • ショートカットや動画のクリックによるアクションは、「Paused」や「Volume 40%」のように控えめに読み上げられます。読み込みエラーは alert として読み上げられます。
  • overlay バリアントでは、ポインターを動かさずに2.5秒再生するとコントロールがフェードアウトします。一時停止中、ホバー中、キーボードで操作中、メニューが開いている間は表示されたままです。
  • タッチスクリーンでは、タップでコントロールの表示と非表示を切り替え、左または右の3分の1をダブルタップすると10秒戻る、または進みます。タップを続けるたびに10秒ずつ加算されます。
  • 字幕ボタンは aria-pressed を持つ toggle です。最後に使ったトラック、次にブラウザーの言語のトラック、それもなければ最初のトラックを選びます。
  • 視差効果の軽減が有効な場合、コントロールとフィードバックは移動や拡大縮小をせずにフェードします。

シークバーと音量は Base UI の slider 上に、ボタンは HextaUI の Button、Tooltip、Dropdown menu 上に構築されています。

プロパティ型デフォルト
variantoverlay は、自動で隠れるコントロールを動画の上に浮かせます。bar は動画の下に置きます。
"overlay" | "bar""overlay"
shortcutsフォーカスがプレーヤー内にある間のキーボードショートカット。
booleantrue
globalShortcutsページ全体でもショートカットをリッスンします。
booleanfalse
errorMessage
ReactNode"This video can’t be played."
属性説明
data-slot="video-player"CSSでルートを指定します。
data-variant現在のバリアント。
data-controls"visible" または "hidden"。カーソルはコントロールとともに隠れます。
data-fullscreenプレーヤーが全画面表示の間付きます。
aria-busy再生がデータを待っている間設定されます。

<video> 要素。すべての video 属性と、<source> または <track> の子要素を受け付けます。クリックで再生と一時停止、ダブルクリックで全画面の切り替え、タップでコントロールの表示と非表示、どちらかをダブルタップするとシークします。

プロパティ型デフォルト
autoPlayマウント時に再生を開始します。視差効果の軽減が有効な場合を除きます。
booleanfalse
playsInline
booleantrue
preload
"none" | "metadata" | "auto""metadata"
doubleTapSeekタッチスクリーンで、どちら側かをダブルタップしたときにジャンプする秒数。false でオフになります。
number | false10
renderHLS の video 要素など、別のメディア要素に差し替えます。
ReactElement | (props, state) => ReactElement<video>
属性説明
data-slot="video-player-content"CSS で動画を指定します。
プロパティ型デフォルト
tooltipsホバー時に、各コントロールのラベルとショートカットを表示します。
booleantrue
属性説明
data-slot="video-player-controls"CSS でコントロールバーを指定します。
data-hiddenoverlay のコントロールが非表示の間付きます。

常にボタンの上の専用の行を占めます。ホバーするとポインター位置の時刻が表示され、明るいトラックは読み込み済みの範囲を示します。

プロパティ型デフォルト
label
string"Seek"
onValueChange
(value: number, details) => void–
onValueCommitted
(value: number, details) => void–
disabled
booleanfalse
属性説明
data-slot="video-player-seek-bar"CSS でシークバーを指定します。
data-draggingスクラブしている間付きます。
data-previewingホバー時刻が表示されている間、コントロールに付きます。
--video-player-buffered動画の読み込み済みの部分。0から1。
--video-player-hoverバーに沿ったポインターの位置。0から1。
プロパティ型デフォルト
playLabel
string"Play"
pauseLabel
string"Pause"
replayLabel
string"Replay"
...propsvariant と size を含む、Button のすべての prop。
ButtonProps–
属性説明
data-slot="video-player-play-button"CSS でボタンを指定します。
data-state"paused"、"playing"、"ended"。
プロパティ型デフォルト
offsetジャンプする秒数。負の値は戻ります。
number10
label
string"Forward 10 seconds"
...propsvariant と size を含む、Button のすべての prop。
ButtonProps–
属性説明
data-slot="video-player-seek-button"CSS でボタンを指定します。
data-direction"backward" または "forward"。

ホバーまたはフォーカスで開くスライダー付きのミュートボタン。スマートフォンは本体のボタンで音量を調整するため、タッチスクリーンではミュートボタンだけが表示されます。

プロパティ型デフォルト
label
string"Volume"
muteLabel
string"Mute"
unmuteLabel
string"Unmute"
属性説明
data-slot="video-player-volume"CSSでグループを指定します。
data-slot="video-player-mute-button"ミュートボタン。VideoPlayerMuteButton としてもエクスポートされています。
data-stateミュートボタン上: "muted"、"low"、"high"。
プロパティ型デフォルト
type
"both" | "elapsed" | "remaining" | "duration""both"
属性説明
data-slot="video-player-time"CSS で時刻を指定します。
data-type現在のタイプ。
プロパティ型デフォルト
rates
number[][0.5, 0.75, 1, 1.25, 1.5, 2]
label
string"Playback speed"
normalLabel
string"Normal"
属性説明
data-slot="video-player-playback-rate"CSS でメニューのトリガーを指定します。

プレーヤー全体を全画面にします。iPhone では動画自体を全画面にします。全画面表示が使えない環境では何も描画しません。

プロパティ型デフォルト
enterLabel
string"Full screen"
exitLabel
string"Exit full screen"
属性説明
data-slot="video-player-fullscreen-button"CSS でボタンを指定します。
data-state"on" または "off"。

動画に字幕トラックができるまで、何も描画しません。

プロパティ型デフォルト
label
string"Captions"
属性説明
data-slot="video-player-captions-button"CSS でボタンを指定します。
data-state"on" または "off"。
data-slot="video-player-captions"動画上の字幕テキスト。コントロールの上に持ち上げられている間は data-lifted が付きます。

ピクチャーインピクチャーのないブラウザーでは何も描画しません。

プロパティ型デフォルト
enterLabel
string"Picture in picture"
exitLabel
string"Exit picture in picture"
属性説明
data-slot="video-player-pip-button"CSS でボタンを指定します。
data-state"on" または "off"。

コントロール行の空き領域を埋め、後ろのコントロールを末尾に押しやります。

プレーヤーの状態とアクションを返します。単一の値を返すセレクターを渡してください。

const paused = useVideoPlayer((player) => player.paused)
const seek = useVideoPlayer((player) => player.seek)
プロパティ型デフォルト
state
paused, ended, started, waiting, scrubbing, currentTime, duration, buffered, volume, muted, playbackRate, fullscreen, pictureInPicture, error, hasCaptions, captions, caption–
actions
play, pause, togglePaused, seek, seekBy, setVolume, toggleMuted, setPlaybackRate, toggleFullscreen, togglePictureInPicture, toggleCaptions–