React and Vue enable efficient video embedding through component-based rendering that binds sources, events, and states reactively, supporting native HTML5 playback or third-party iframes without performance hits. Embed local files via <video> for custom controls or use libraries like React Player (React) and Vue Video Player (Vue) for unified handling of YouTube, Vimeo, HLS, and DASH streams. This helps in reducing code duplication across platforms while maintaining accessibility and responsive layouts.

Prerequisites

  • JavaScript and HTML/CSS Knowledge: DOM events, media attributes, and responsive styling for video containers.
  • React or Vue Fundamentals: Components, props, state management (useState in React, data() in Vue), and lifecycle (useEffect/mounted).
  • Node.js and npm: LTS v18+ for package management; VS Code with extensions (ES7 React/JS snippets for React, Volar for Vue).
  • Video Assets: Local MP4/WebM files in public/ or URLs/API keys for embeds (YouTube API, Vimeo Pro).
  • Set up a React Project using this guide: Creating a React App
  • Set up a Vue Project using this guide: Quick Start

Embed Basic Video in Vue Components

Create src/components/VideoEmbed.jsx with useRef for the video element and event handlers:

code
import React, { useRef } from 'react';

const VideoEmbed = ({ src, width = 640, height = 360 }) => {
const videoRef = useRef(null);

const handlePlay = () => console.log('Playing');
const handleLoad = () => console.log('Loaded');

return (
<div>
<video
ref={videoRef}
src={src}
controls
crossOrigin="anonymous"
width={width}
height={height}
onPlay={handlePlay}
onLoadedData={handleLoad}
preload="metadata"
/>
</div>
);
};

export default VideoEmbed;

Use in App.jsx: <VideoEmbed src="/sample.mp4" />. Place files in public/.

Explanation:

  • crossOrigin="anonymous": Allows cross-origin requests without credentials for the video resource.
  • onPlay={handlePlay}: Event handler that logs 'Playing' when the video starts playing.
  • onLoadedData={handleLoad}: Event handler that logs 'Loaded' when enough data is loaded to play the video.
Banner for Video Embedding

Embed Third-Party Videos (YouTube/Vimeo) in React

Create src/components/VideoEmbed.vue with :src binding and @event directives:

<template> <div> <video ref="video" :src="src" controls crossorigin="anonymous" :width="width" :height="height" @play="handlePlay" @loadeddata="handleLoad" preload="metadata" /> </div> </template> <script> export default { name: 'VideoEmbed', props: { src: { type: String, required: true }, width: { type: Number, default: 640 }, height: { type: Number, default: 360 } }, methods: { handlePlay() { console.log('Playing'); }, handleLoad() { console.log('Loaded'); } } }; </script> Add CSS in src/App.css: .player-wrapper { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; } .react-player { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }. Use in App.jsx: <ThirdPartyEmbed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" /> or url="https://vimeo.com/123456789".

Explanation:

  • crossOrigin="anonymous": Allows cross-origin requests without credentials for the video resource.
  • onPlay={handlePlay}: Event handler that logs 'Playing' when the video starts playing.
  • onLoadedData={handleLoad}: Event handler that logs 'Loaded' when enough data is loaded to play the video.

Embed Third-Party Videos (YouTube/Vimeo) in Vue

Install vue-youtube for YouTube and vue-vimeo-player for Vimeo via npm install vue-youtube vue-vimeo-player. Create src/components/ThirdPartyEmbed.vue using iframes for simplicity, or integrate libraries:

code
<template>
<div class="player-wrapper">
<iframe
v-if="isYouTube"
:src="`https://www.youtube.com/embed/${videoId}?modestbranding=1`"
width="100%"
height="360"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
<iframe
v-else-if="isVimeo"
:src="`https://player.vimeo.com/video/${videoId}`"
width="100%"
height="360"
frameborder="0"
allow="autoplay; fullscreen; picture-in-picture"
allowfullscreen
></iframe>
</div>
</template>

<script>
export default {
name: 'ThirdPartyEmbed',
props: {
url: { type: String, required: true }
},
computed: {
videoId() {
if (this.isYouTube) return this.url.split('v=')[1]?.split('&')[0];
if (this.isVimeo) return this.url.split('/').pop();
return '';
},
isYouTube() { return this.url.includes('youtube.com') || this.url.includes('youtu.be'); },
isVimeo() { return this.url.includes('vimeo.com'); }
}
};
</script>

<style>
.player-wrapper { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; }
.player-wrapper iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>

Use in App.vue: <ThirdPartyEmbed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" /> or url="https://vimeo.com/123456789".

Explanation:

  • iframe (YouTube): The embedded YouTube player using the extracted videoId, with modest branding enabled and appropriate permissions set.
  • iframe (Vimeo): The embedded Vimeo player using the extracted videoId, with autoplay and fullscreen permissions enabled.
  • player-wrapper: A CSS container that maintains a 16:9 aspect ratio for responsive iframe embedding by using padding and absolute positioning.

Add Custom Controls and Events

In React, extend VideoEmbed.jsx with useState for state, useRef/useEffect for listeners. Update to:

code
import React, { useState, useRef, useEffect } from 'react';

const VideoEmbed = ({ src }) => {
const videoRef = useRef(null);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(1);

useEffect(() => {
const video = videoRef.current;
if (video) {
const updateProgress = () => setCurrentTime(video.currentTime);
const setDur = () => setDuration(video.duration);
video.addEventListener('timeupdate', updateProgress);
video.addEventListener('loadedmetadata', setDur);
return () => {
video.removeEventListener('timeupdate', updateProgress);
video.removeEventListener('loadedmetadata', setDur);
};
}
}, [src]);

const seek = (time) => videoRef.current.currentTime = parseFloat(time);
const setVol = (vol) => {
const volumeVal = parseFloat(vol);
setVolume(volumeVal);
videoRef.current.volume = volumeVal;
};

return (
<div>
<video ref={videoRef} src={src} preload="metadata" />
<input
type="range"
value={currentTime}
min={0}
max={duration}
onChange={(e) => seek(e.target.value)}
/>
<input
type="range"
value={volume}
min={0}
max={1}
step={0.1}
onChange={(e) => setVol(e.target.value)}
/>
</div>
);
};

export default VideoEmbed;

Explanation:

  • seek(time): Function to jump the video playback to a specified time in seconds.
  • setVol(vol): Function to update the video volume both in state and on the video element.
  • First input (range slider): Controls and displays the playback position, allowing the user to seek through the video timeline.
  • Second input (range slider): Controls and displays the volume level, allowing the user to adjust the video sound.

Use in App.jsx: <VideoEmbed src="/sample.mp4" />. In Vue, update VideoEmbed.vue with data(), methods, and bindings:

code
<template>
<div>
<video ref="video" :src="src" preload="metadata" @timeupdate="updateProgress" @loadedmetadata="setDuration" />
<input type="range" v-model="currentTime" :min="0" :max="duration" @change="seek" />
<input type="range" v-model="volume" min="0" max="1" step="0.1" @change="setVolume" />
</div>
</template>

<script>
export default {
props: { src: { type: String, required: true } },
data() {
return { currentTime: 0, duration: 0, volume: 1 };
},
methods: {
setDuration() {
this.duration = this.$refs.video.duration;
},
updateProgress() {
this.currentTime = this.$refs.video.currentTime;
},
seek() {
this.$refs.video.currentTime = this.currentTime;
},
setVolume() {
this.$refs.video.volume = this.volume;
}
}
};
</script>

Use in App.vue: <VideoEmbed src="/sample.mp4" />.

Explanation:

  • video ref ("video"): A Vue ref attached to the <video> element to directly access and control the video DOM API.
  • setDuration(): Method that sets duration by reading the video element's duration once metadata has loaded.
  • updateProgress(): Method that updates currentTime based on the video element"s current playback time during playback.
  • seek(): Method that sets the video"s playback position to the selected currentTime when the range input changes.

Handle Streaming (HLS/DASH) in React

Install react-player via npm install react-player. Create src/components/StreamingVideo.jsx:

code
import React from 'react';
import ReactPlayer from 'react-player';

const StreamingVideo = ({ url, width = '100%', height = 360 }) => {
const handleError = (e) => console.error('Stream error:', e);

return (
<div className="player-wrapper">
<ReactPlayer
url={url}
width={width}
height={height}
controls={true}
playing={true}
config={{
hls: { withCredentials: false },
dash: { options: { lowLatencyMode: true } },
file: { attributes: { crossOrigin: 'anonymous' } }
}}
onError={handleError}
/>
</div>
);
};

export default StreamingVideo;

Add responsive CSS in src/App.css: .player-wrapper { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; } .react-player { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }. For HLS, use url="https://example.com/stream.m3u8"; for DASH, url="https://example.com/stream.mpd". Import and use in the App.jsx: <StreamingVideo url="https://example.com/stream.m3u8" />. Ensure CORS is enabled on the streaming server for cross-origin playback.

Explanation:

  • ReactPlayer controls: Enables native playback controls for the video.
  • ReactPlayer playing: Starts the video playback automatically when the player loads.
  • ReactPlayer config: Configuration object for streaming protocols and player behavior:
  • hls.withCredentials: Controls whether to send credentials with HLS requests, set to false here.
  • dash.options.lowLatencyMode: Enables low latency mode for DASH streaming.
  • file.attributes.crossOrigin: Sets the CORS policy to anonymous for loaded media files.

Handle Streaming (HLS/DASH) in Vue

Install hls.js via npm install hls.js and dashjs via npm install dashjs. For HLS, create src/components/StreamingVideo.vue:

code
<template>
<video ref="video" controls width="640" height="360"></video>
</template>

<script>
import Hls from 'hls.js';

export default {
props: { src: String },
mounted() {
if (Hls.isSupported()) {
this.hls = new Hls();
this.hls.loadSource(this.src);
this.hls.attachMedia(this.$refs.video);
} else if (this.$refs.video.canPlayType('application/vnd.apple.mpegurl')) {
this.$refs.video.src = this.src;
}
},
beforeUnmount() {
if (this.hls) this.hls.destroy();
}
};
</script>

Explanation:

  • this.hls.loadSource(src): Loads the provided HLS video source into the Hls.js player.
  • this.hls.attachMedia(videoElement): Attaches the Hls.js player to the video element to manage playback.
  • video.canPlayType('application/vnd.apple.mpegurl'): Checks if the browser can natively play HLS streams (e.g., Safari).
  • video.src = src: Sets the video element"s source directly when native HLS playback is supported.
  • hls.destroy(): Cleans up and releases resources used by the Hls.js player before the component is unmounted.

For DASH streams, similarly use dash.js:

code
<template>
<video ref="video" controls width="640" height="360"></video>
</template>

<script>
import dashjs from 'dashjs';

export default {
props: { src: String },
mounted() {
this.player = dashjs.MediaPlayer().create();
this.player.initialize(this.$refs.video, this.src, true);
},
beforeUnmount() {
if (this.player) this.player.reset();
}
};
</script>

Use in App.vue: <StreamingVideo src="https://example.com/stream.m3u8" /> or .mpd.

Explanation:

  • dashjs.MediaPlayer().create(): Creates a new instance of a DASH.js media player.
  • player.initialize(videoElement, src, autoplay): Initializes the player with the video element and source, and optionally starts playback immediately.
  • player.reset(): Releases resources and cleans up the DASH.js player before the component is unmounted.

Optimize and Secure Embeds

To ensure that embedded videos perform well across different devices and remain secure, follow the best practices for optimization and security.

Optimize Performance

Use IntersectionObserver for lazy loading with preload="none". In React (VideoEmbed.jsx):

In React (VideoEmbed.jsx):

code
import React, { useRef, useEffect } from 'react';

const VideoEmbed = ({ src }) => {
const videoRef = useRef(null);

const handleIntersection = (entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
videoRef.current.load();
}
};

useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, { threshold: 0.5 });
if (videoRef.current) {
observer.observe(videoRef.current);
}
return () => observer.disconnect();
}, []);

return <video ref={videoRef} src={src} preload="none" controls />;
};

Explanation:

  • videoRef.current.load(): Triggers the browser to load the video when it enters the viewport.
  • new IntersectionObserver(handleIntersection, { threshold: 0.5 }): Creates an observer that monitors the video element and calls handleIntersection when at least 50% of it is visible in the viewport.
  • preload="none": Prevents the video from loading automatically until it is manually triggered or enters view.

In Vue (VideoEmbed.vue):

code
<template>
<video ref="video" :src="src" preload="none" controls />
</template>

<script>
export default {
mounted() {
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) {
this.$refs.video.load();
}
}, { threshold: 0.5 });
observer.observe(this.$refs.video);
},
beforeUnmount() {
// Observer disconnect handled externally if needed
}
};
</script>

Set preload="metadata" for essentials. Use MP4/H.264 for compatibility; serve lower resolutions (720p/480p) for mobile.

Explanation:

  • this.$refs.video.load(): Triggers the video element to begin loading only when it becomes at least 50% visible in the viewport.
  • preload="none": Prevents the browser from preloading the video until manually triggered, reducing unnecessary network usage.
  • new IntersectionObserver(..., { threshold: 0.5 }): Creates an observer that activates when at least half of the video is in view, enabling lazy loading.

Secure Embeds

Use HTTPS for sources: <video src="https://example.com/video.mp4" controls />.

Set server headers: res.setHeader('X-Frame-Options', 'SAMEORIGIN'); or CSP.

Add crossOrigin="anonymous": <video src={src} controls crossOrigin="anonymous" />. Disable controls: In React, <ReactPlayer url={url} controls={false} />; in Vue, <video controls="false" /> or library equivalent.

Use platform privacy settings and API keys. For premium, integrate DRM like Widevine/FairPlay.