Vue Video Upload Component Guide: MediaRecorder Compression, Preview Playback, and Custom Upload Workflow

This is a production-oriented video upload solution for Vue admin dashboards. It uses el-upload to take control of the upload lifecycle, applies front-end compression with MediaRecorder + canvas.captureStream() when files exceed size limits, and adds video preview, upload cancellation, and cover echo capabilities. It addresses three common pain points: slow large-file uploads, inflexible default upload behavior, and complex video echo handling. Keywords: Vue video upload, MediaRecorder compression, custom upload.

Technical specification snapshot

Parameter Description
Core language JavaScript, Vue
UI component Element UI / el-upload
Browser capabilities MediaRecorder, Canvas, AudioContext
Network protocol HTTP / multipart/form-data
Upload implementation Custom axios request
Compression strategy Resolution downscaling + target bitrate re-encoding
Star count Not provided in the original article
Core dependencies Vue, Element UI, axios, MediaRecorder API

This solution breaks video uploads into a controllable pipeline

Traditional el-upload works well for direct file uploads, but it is not ideal for workflows that require processing before upload. This solution splits the flow into four stages: pre-upload validation, compression when needed, manually triggered requests, and post-upload preview plus echo.

The value is straightforward: you can replace the file object, customize progress and cancellation logic, and map the server response into a unified component state.

The upload entry point is taken over with http-request

<el-upload
  ref="uploadRef"
  :action="uploadVideoUrl"
  :http-request="submitUploadRequest"
  :before-upload="handleBeforeUpload"
  :file-list="fileList"
  list-type="picture-card"
  :accept="fileType.join(',')"
>
</el-upload>

The key here is that before-upload intercepts the file, while http-request takes over the actual upload.

Pre-upload validation must check both format and file size

Relying only on the MIME type is not reliable enough. Some browsers or operating systems may return unexpected values, so you should validate both file.type and the file extension. This reduces false positives and works especially well in admin scenarios.

The file size check determines whether the file can be uploaded directly. If the file is under the threshold, allow it immediately. If it exceeds the threshold, either send it to compression or block it directly based on configuration.

handleBeforeUpload(file) {
  const isVideo = this.fileType.some((ext) => {
    return file.type.includes(ext) || file.name.toLowerCase().endsWith(`.${ext}`); // Double-check MIME type and file extension
  });

  if (!isVideo) {
    return this.blockUpload(`Invalid file format. Please upload ${this.fileType.join('/')} files`);
  }

  const isLt = file.size / 1024 / 1024 < this.fileSize; // Check whether the file exceeds the size limit
  if (!isLt && !this.configNeedCompress) {
    return this.blockUpload(`Uploaded video size cannot exceed ${this.fileSize} MB`);
  }

  return true;
}

This logic completes format validation and compression branching before the network request starts.

Only oversized files should enter compression, which saves significant front-end resources

This is a highly practical strategy. Small files upload directly to reduce waiting time. Large files alone trigger transcoding, which avoids unnecessary CPU and memory usage in the page.

MediaRecorder compression is fundamentally in-browser re-encoding

This approach does not use ffmpeg.wasm. Instead, it loads the source video into a hidden video element, continuously redraws frames onto a canvas, generates a new video stream through canvas.captureStream(), and then re-encodes it with MediaRecorder.

Its advantages are low weight, fast integration, and no need to bundle a large WASM package. The tradeoff is that encoding capabilities and compatibility depend heavily on browser implementation.

Read video metadata before compression and make a decision

async getVideoMeta(file) {
  return new Promise((resolve, reject) => {
    const video = document.createElement('video');
    const objectUrl = URL.createObjectURL(file);
    video.preload = 'metadata';
    video.onloadedmetadata = () => {
      const duration = Number(video.duration) || 0;
      const width = Number(video.videoWidth) || 0;
      const height = Number(video.videoHeight) || 0;
      URL.revokeObjectURL(objectUrl); // Release the temporary URL to avoid memory leaks
      resolve({
        duration,
        width,
        height,
        bitrateMbps: duration > 0 ? (file.size * 8) / duration / 1024 / 1024 : 0 // Estimate the source video bitrate
      });
    };
    video.onerror = reject;
    video.src = objectUrl;
  });
}

This code extracts duration, resolution, and estimated bitrate to decide whether compression is necessary.

The compression strategy should be driven by both resolution and bitrate

A reasonable rule is this: if the resolution exceeds 1080p, or the bitrate exceeds 4 Mbps, compress the file. The first condition removes redundant visual detail, while the second controls file size inflation.

A compression plan usually includes the target file name, target video bitrate, and whether the video should be scaled into the 1080p range. This strategy fits business systems well because it balances transfer efficiency with acceptable visual quality.

buildCompressionPlan(file, meta) {
  const over1080P = meta.width > 1920 || meta.height > 1080;
  const overBitrate = meta.bitrateMbps > 4;

  if (!over1080P && !overBitrate) return null; // Do not compress if the thresholds are not exceeded

  return {
    outputBaseName: `${Date.now()}_${file.name.replace(/\.[^.]+$/, '')}_compressed`,
    targetVideoBitrate: over1080P ? '2500k' : '1800k', // Assign a target bitrate based on quality tier
    shouldScaleTo1080: over1080P
  };
}

This code turns the question of whether to compress into an executable compression plan.

Redrawing frames and mixing the audio track are critical for successful compression

If you draw only video frames, the result is a silent video. In addition to the video track generated from canvas, you must also reattach the original audio track to the destination stream through AudioContext.

Then, by setting videoBitsPerSecond and audioBitsPerSecond on MediaRecorder, you effectively record the video again at a lower bitrate.

const stream = canvas.captureStream(30);
const audioContext = new AudioContext();
const source = audioContext.createMediaElementSource(video);
const dest = audioContext.createMediaStreamDestination();
source.connect(dest);

dest.stream.getAudioTracks().forEach((track) => {
  stream.addTrack(track); // Mix the original audio track into the new stream to avoid silent output after compression
});

const recorder = new MediaRecorder(stream, {
  mimeType,
  videoBitsPerSecond: parseBitrate(plan.targetVideoBitrate), // Control the output video bitrate after compression
  audioBitsPerSecond: 128000
});

This code merges the visual stream and audio stream, then hands them to MediaRecorder to produce the compressed file.

The progress bar and cancellation support determine whether the component is usable

MediaRecorder does not provide a built-in percentage progress value, so the usual approach is to estimate compression progress with video.currentTime / video.duration, then infer ETA from elapsed time. It is not an exact encoding progress metric, but it is stable enough for users.

Cancellation support is also essential. The usual implementation is to maintain a manual interrupter. When the user clicks cancel, stop the recording, clear temporary state, and remove the placeholder file from the list in sync.

Custom upload is required to carry the new compressed file object

After compression, the file is no longer the original option.file, so you must temporarily store the new file and retrieve it in http-request for upload. At this point, axios + FormData is the most direct implementation.

async submitUploadRequest(option) {
  const uploadFile = this.pendingUploadFiles[option.file.uid] || option.file;
  const formData = new FormData();
  formData.append('file', uploadFile, uploadFile.name); // Upload the actual compressed file that should take effect

  return axios.post(this.uploadVideoUrl, formData, {
    headers: {
      ...this.headers,
      'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: (event) => {
      const percent = event.total ? (event.loaded / event.total) * 100 : 0;
      option.onProgress({ percent }); // Send upload progress back to el-upload
    }
  });
}

This code reconnects the compression result to the default lifecycle of the upload component.

Video preview and server echo complete the business workflow

Preview is straightforward to implement: when the user clicks a file item, write the URL into previewVideoUrl, then open a dialog with a video element. Before closing the dialog, remember to call pause() so playback does not continue in the background.

Server echo requires you to normalize the API response, whether it returns a string or an array, into fileList. If the server returns only the video URL, you can also infer the cover image URL from naming conventions to improve readability on edit pages.

![Video upload component workflow diagram](https://kunyu.csdn.net/1.png?p=56&adId=1071043&adBlockFlag=0&a=1071043&c=0&k=Vue 视频上传实战:视频预览、MediaRecorder 压缩与自定义上传&spm=1001.2101.3001.5000&articleId=160330424&d=1&t=3&u=2c9b21b99cd1426e92e98eb23e81d62b) AI Visual Insight: This image is closer to a page placement screenshot than a compression algorithm diagram. It reflects the front-end content context in which the article operates: components are usually embedded in admin dashboards or content platform pages, so upload, preview, and echo behavior must remain compatible with card-style lists and detail-page interactions.

This component fits admin and business systems better than professional video processing platforms

Its strengths are low weight, few dependencies, and fast integration. It is especially suitable for back-office forms, course management, and content review workflows. If you need to handle ultra-long videos, advanced encoding parameter control, or multi-format transmuxing, server-side transcoding or an FFmpeg-based approach is a better fit.

FAQ

Q1: Why not use ffmpeg.wasm directly for front-end compression?

A: ffmpeg.wasm is more powerful, but the bundle is large, initialization is slow, and memory usage is high. For an admin upload component, MediaRecorder + canvas is lighter and fully covers the primary requirement of compressing oversized files before upload.

Q2: Why is format compatibility unstable in some browsers after compression?

A: Because the codecs and containers supported by MediaRecorder depend on the browser implementation. You should dynamically select mp4, webm, or ogg with MediaRecorder.isTypeSupported() and run compatibility tests in your business environment.

Q3: When is front-end compression not a good fit?

A: Front-end compression is not recommended when the video is extremely large, very long, the client device is underpowered, or you need a standardized transcoding pipeline. In those cases, upload the original file directly and let the server handle transcoding, thumbnails, and distribution.

AI Readability Summary: This article reconstructs a Vue video upload component solution that covers format validation, oversized-file compression, progress estimation, task cancellation, custom upload, preview dialogs, and server-side echo. Built on el-upload, MediaRecorder, canvas.captureStream(), and axios, it is well suited for fast implementation in admin dashboards and business systems.