SwUploadVideo is a chunked video upload component. Upload logic is fully injected by the caller via the uploadFn and queryFn props — the component has no built-in endpoint, making it compatible with any backend protocol.

After upload completes, the component can optionally poll for transcoding results via queryFn (controlled by pollVideo). The default slot exposes current state, enabling fully custom trigger UI including drag-and-drop zones.

Basic usage (button)

Simplest form: button picks a video → chunk progress → transcode polling → playable preview when done.

Basic (button + progress + playable preview)

加载中...
<!-- accept restricts formats (default 'video/*'); multiple allows multi-select —
     each file goes through "upload + transcode polling" independently, emitting @success one by one -->
<SwUploadVideo
  v-slot="{ open, pending }"
  :upload-fn="chunkUpload"
  :query-fn="queryVideo"
  accept="video/mp4"
  multiple
  @success="(r) => list.push(r)"
>
  <SwButton :disabled="pending" @click="() => open()">Batch upload videos</SwButton>
</SwUploadVideo>

Custom trigger area (#default slot)

The "+ upload card": the default slot fully takes over the trigger UI, drawing upload / transcode progress inside the card.

+ upload card (slot customization)

加载中...

Drag-and-drop upload

The slot's isOver highlights the drop zone; the progress bar and transcode-wait hint are also slot-drawn.

Drag + progress + transcode polling

加载中...

Skip transcoding poll (pollVideo=false)

Use when you only need the raw chunk-upload result (ChunkSuccess) without waiting for transcoding.

No transcode wait (ChunkSuccess returned directly)

加载中...

Error handling

The error event fires when a chunk fails (retries exhausted).

Chunk failure feedback (@error)

加载中...

Controlled list (fileList + #file + maxCount + beforeUpload)

With v-model:file-list the component manages the uploaded list: successful results are appended automatically, and the #file slot renders each item with preview + removal; maxCount caps the count (an over-limit selection is rejected as a batch and emits exceed), and beforeUpload runs per-file validation (size / duplicate name / type — return false to drop).

<template>
  <SwUploadVideo
    v-model:file-list="videos"
    :upload-fn="chunkUpload"
    :query-fn="queryVideo"
    :max-count="3"
    :before-upload="(f) => f.size <= maxSize || (useToast().warning('视频过大'), false)"
    multiple
    @exceed="useToast().warning('最多上传 3 个视频')"
  >
    <template #default="{ open, pending, progress }">
      <SwButton :loading="pending" @click="open">
        {{ pending ? `上传中 ${progress}%` : '选择视频' }}
      </SwButton>
    </template>
    <template #file="{ file, remove }">
      <span>{{ file.id }} <SwButton aria-label="删除" @click="remove">✕</SwButton></span>
    </template>
  </SwUploadVideo>
</template>

Props

NameTypeRequiredDefaultDescription
uploadFnFunction✓—Chunked upload function; see VideoUploadOptions
queryFnFunction✓—Transcoding query; returns Promise<VideoQueryResult>
acceptstring'video/*'Accepted file types
chunkSizenumber2097152Chunk size in bytes (default 2 MB)
retriesnumber3Retry attempts on chunk failure
multiplebooleanfalseAllow multiple file selection
pollVideobooleantruePoll for transcoding result after upload
fileListChunkSuccess | VideoQueryResult[]undefinedControlled list of uploaded files (v-model:file-list). When provided, successful results are appended automatically; pair with the #file slot for preview + removal. Omit to keep the "single-shot shell" behavior
maxCountnumberundefinedMaximum file count (including the current fileList). A selection that would exceed it is rejected as a whole batch and emits exceed
beforeUpload(file: File, batch: File[]) => boolean | Promise<boolean>undefinedPer-file veto hook: return false to drop that file (others still upload; if all are dropped the batch is aborted). Size / duplicate-name / custom checks and user feedback belong in the hook

Type Definitions

VideoUploadOptions

interface VideoUploadOptions {
  chunkSize?: number
  retries?: number
  onProcess?: (result: ChunkProgress) => void
  onSuccess?: (result: { id: string; file_md5: string; file: File }) => void
  onError?: (result: { file: File; error: unknown }) => void
  onBefore?: (file: File) => void
}

interface ChunkProgress {
  file: File
  processed_num: number
  chunk_total: number
  chunk_index: number
  file_md5: string
}

VideoQueryResult

interface VideoQueryResult {
  id: string
  file_m3u8: string
  file_duration: string
  file_preview_m3u8: string
  file_status: string
  width: number
  height: number
}

Events

NamePayloadDescription
progress[pct: number]Chunk upload progress, 0–100
success[result: ChunkSuccess | VideoQueryResult]ChunkSuccess when pollVideo=false; VideoQueryResult otherwise
error[err: Error]Fired on upload or poll failure
update:fileListChunkSuccess | VideoQueryResult[]Controlled list changed (result appended / removed via remove()), carries the new list
remove(file: ChunkSuccess | VideoQueryResult, index: number)The #file slot's remove() was called
exceedFile[]Selection exceeded maxCount, carries the selected files (the whole batch was rejected)

Slots

default (scoped slot)

Scope variableTypeDescription
isOverbooleanFile is being dragged over the zone
pendingbooleanUpload or poll is in progress
progressnumberCurrent upload progress (0–100)
open() => voidProgrammatically open file picker
file{ file, index, remove }Rendered once per item when fileList is provided (preview / delete UI is yours to draw); remove() removes the item and emits remove

Notes

  • Both uploadFn and queryFn are required and must be implemented by the consumer.
  • chunkSize defaults to 2 MB (2097152 bytes); must match the backend's expected chunk size.
  • When pollVideo=true, the success event fires only after transcoding completes, with a VideoQueryResult payload.
  • When pollVideo=false, success fires immediately after chunk upload with { id, file_md5, file }.