Admin 元件

Video Player

參考
影片播放器(vidstack)。來源:MP4、HLS、YouTube;控制:快進快退、解析度、播放速度、音量、全螢幕。

安裝

Terminal
pnpm dlx shadcn-vue add @admin/video-player
import {  AdminVideoPlayer,  AdminVideoPlayerControls,  AdminVideoPlayerFullscreen,  AdminVideoPlayerPlay,  AdminVideoPlayerProgress,  AdminVideoPlayerSettings,  AdminVideoPlayerTime,  AdminVideoPlayerToolbar,  AdminVideoPlayerVolume} from '@/components/admin/video-player'

vidstack 是一組 web components,需要在專案 build 設定告訴 Vue compiler 把 media-* 開頭的 tag 當成 native custom element:

Nuxt 3 / 4

nuxt.config.ts
export default defineNuxtConfig({  vue: {    compilerOptions: {      isCustomElement: (tag) => tag.startsWith('media-')    }  }})

Vue 3 + Vite

vite.config.ts
import vue from '@vitejs/plugin-vue'import { defineConfig } from 'vite'​export default defineConfig({  plugins: [    vue({      template: {        compilerOptions: {          isCustomElement: (tag) => tag.startsWith('media-')        }      }    })  ]})

用法

傳入 src 即可播放,元件根據 src 字串自動辨識 provider:YouTube URL → YouTube provider、.m3u8 → HLS、其他 → native MP4 / WebM

<script setup lang="ts">import { AdminVideoPlayer } from '@/components/admin/video-player'</script>​<template>  <admin-video-player    poster="https://files.vidstack.io/sprite-fight/poster.webp"    src="https://files.vidstack.io/sprite-fight/720p.mp4"  /></template>

場景

HLS 串流(含解析度切換)

來源為 .m3u8 時,齒輪設定菜單會出現「解析度」區塊,列出 vidstack qualities 過濾後的 480 / 720 / 1080 選項;切換後寫入 localStorage key admin:player:resolution,下次播放新 HLS 影片自動套用同設定。

<script setup lang="ts">import { AdminVideoPlayer } from '@/components/admin/video-player'</script>​<template>  <admin-video-player    poster="https://files.vidstack.io/sprite-fight/poster.webp"    src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"  /></template>

YouTube

src 為 YouTube URL(youtube.com/... / youtu.be/...)或 youtube/<id> 時自動切換到 YouTube provider;齒輪菜單不顯示解析度區塊(YouTube provider 不支援切換)。

<script setup lang="ts">import { ref } from 'vue'import { cn } from '@/lib/utils'import { AdminVideoPlayer } from '@/components/admin/video-player'​const presets = [  'https://www.youtube.com/watch?v=_cMxraX_5RE',  'https://www.youtube-nocookie.com/watch?v=_cMxraX_5RE',  'https://www.youtube.com/embed/_cMxraX_5RE',  'https://youtu.be/_cMxraX_5RE',  'youtube/_cMxraX_5RE'] as const​const src = ref<string>(presets[0])const draft = ref<string>(presets[0])​function applyDraft() {  const trimmed = draft.value.trim()  if (trimmed) {    src.value = trimmed  }}</script>​<template>  <div class="flex w-full flex-col gap-3">    <form class="flex gap-2" @submit.prevent="applyDraft">      <input        v-model="draft"        placeholder="貼上 YouTube URL…"        type="text"        :class="          cn(`            flex-1 rounded-md border border-bg-60 bg-bg-90 px-3 py-1.5 text-content-medium            text-text-90 outline-none            focus:border-primary-50          `)        "      />      <button        type="submit"        :class="          cn(`            rounded-md bg-primary-50 px-3 py-1.5 text-content-medium text-white            hover:bg-primary-60          `)        "      >        套用      </button>    </form>    <div class="flex flex-wrap gap-2">      <button        v-for="preset in presets"        :key="preset"        type="button"        :class="          cn(`            rounded-md border border-bg-60 bg-bg-90 px-2 py-1 text-caption-medium text-text-70            transition-colors            hover:bg-bg-70          `)        "        @click="          () => {            draft = preset            applyDraft()          }        "      >        {{ preset }}      </button>    </div>    <admin-video-player :key="src" :src="src" />  </div></template>

API 參考

組成

<template>  <admin-video-player :poster :src>    <admin-video-player-toolbar>      <admin-video-player-play />      <admin-video-player-time />      <admin-video-player-controls />      <admin-video-player-progress />      <admin-video-player-settings />      <admin-video-player-volume />      <admin-video-player-fullscreen />    </admin-video-player-toolbar>  </admin-video-player></template>

admin-video-player

Props

名稱型別預設值說明
src*MediaSrc—影片來源(vidstack `MediaSrc` 格式)
posterstring | undefined—載入前顯示之封面圖 URL
classClassValue—根元素之自訂 class

* 代表必填

Events

名稱Payload說明
ended—播放結束時觸發
errorError載入或播放發生錯誤時觸發
pause—暫停播放時觸發
play—開始播放時觸發
timeUpdatenumber播放進度更新時觸發,payload 為當前秒數
fullscreenChangeboolean全螢幕狀態變更時觸發
qualityChangenumber解析度變更時觸發,payload 為當前 height

Slots

名稱Bindings說明
default{}—

admin-video-player-controls

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-fullscreen

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-play

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-progress

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-settings

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-time

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

admin-video-player-toolbar

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

Slots

名稱Bindings說明
default{}—

admin-video-player-volume

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

注意事項

  • 必須加 isCustomElement 設定:未加上時 dev console 會對 <media-*> 元素噴 warning,詳細寫法見「安裝」章節。
  • 不支援 SSR:vidstack 為 browser-only;元件殼不含 <ClientOnly>。Nuxt 請自行以 <ClientOnly> 包覆;純 CSR 不需處理。見 安裝 · SSR。
  • YouTube 解析度不可切:vidstack YouTube provider 不暴露 quality API,齒輪菜單會自動隱藏解析度區塊。
  • MP4 解析度不可切:單一 MP4 來源沒有 quality 變體,齒輪菜單也不會顯示解析度區塊;如需多解析度切換,請改用 HLS 串流。
  • HLS qualities 載入時序:.m3u8 manifest 下載前 qualities 為空陣列,UI 顯示「載入中…」;manifest 解析後自動填入並依 localStorage key admin:player:resolution 套用最接近且 ≤ 目標的 quality。
  • 音量、解析度跨實例共享:所有 player 實例(含 audio-player)共用 admin:player:* namespace;多 tab 不互相同步避免操作被覆蓋。
  • 全螢幕請求失敗:若 iframe 沒給 allow="fullscreen" 或瀏覽器拒絕,toggleFullscreen 不會 throw,會 emit error 事件供消費端處理。

更新紀錄

  • chore: 導入 vite-doctor 雙 config 並收斂 portable 型別斷言000d326
  • fix: 以 preview client 島達標 heap 4096 並固定 portable 殼a0e6e17
  • fix: 修正 provider 偵測、seek clamp、音量持久化等 8 項問題b019397
  • chore: 啟用 vue/no-useless-v-bind 與 vue/prefer-separate-static-class16c34a8