feat: Add Embed Info Overlay Component (for embed player only)

This commit is contained in:
Yiannis Christodoulou 2025-09-22 23:17:52 +03:00
parent a89ee7b0e0
commit 68fa1efe87
7 changed files with 389 additions and 812 deletions

View File

@ -1654,3 +1654,111 @@ button.vjs-button > .vjs-icon-placeholder:before {
font-size: 1.4em !important;
}
}
/* Embed Info Overlay Styles */
.vjs-embed-info-overlay {
position: absolute !important;
top: 10px !important;
left: 10px !important;
z-index: 1000 !important;
display: flex !important;
align-items: center !important;
gap: 10px !important;
background: rgba(0, 0, 0, 0.7) !important;
padding: 8px 12px !important;
border-radius: 8px !important;
backdrop-filter: blur(4px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
max-width: calc(100% - 40px) !important;
box-sizing: border-box !important;
transition: opacity 0.3s ease-in-out !important;
font-family: Arial, sans-serif !important;
}
.vjs-embed-info-overlay .embed-avatar-container {
flex-shrink: 0 !important;
width: 32px !important;
height: 32px !important;
border-radius: 50% !important;
overflow: hidden !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
}
.vjs-embed-info-overlay .embed-avatar-container a {
display: block !important;
width: 100% !important;
height: 100% !important;
text-decoration: none !important;
}
.vjs-embed-info-overlay .embed-avatar-container img {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
display: block !important;
}
.vjs-embed-info-overlay .embed-title-container {
flex: 1 !important;
min-width: 0 !important;
overflow: hidden !important;
}
.vjs-embed-info-overlay .embed-title-container a,
.vjs-embed-info-overlay .embed-title-container span {
color: #fff !important;
text-decoration: none !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 1.3 !important;
display: block !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
transition: color 0.2s ease !important;
}
.vjs-embed-info-overlay .embed-title-container a:hover {
color: #009931 !important;
}
/* Responsive styles for smaller screens */
@media (max-width: 768px) {
.vjs-embed-info-overlay {
top: 8px !important;
left: 8px !important;
padding: 6px 10px !important;
gap: 8px !important;
max-width: calc(100% - 32px) !important;
}
.vjs-embed-info-overlay .embed-avatar-container {
width: 28px !important;
height: 28px !important;
}
.vjs-embed-info-overlay .embed-title-container a,
.vjs-embed-info-overlay .embed-title-container span {
font-size: 13px !important;
}
}
@media (max-width: 480px) {
.vjs-embed-info-overlay {
top: 6px !important;
left: 6px !important;
padding: 5px 8px !important;
gap: 6px !important;
max-width: calc(100% - 24px) !important;
}
.vjs-embed-info-overlay .embed-avatar-container {
width: 24px !important;
height: 24px !important;
}
.vjs-embed-info-overlay .embed-title-container a,
.vjs-embed-info-overlay .embed-title-container span {
font-size: 12px !important;
}
}

View File

@ -0,0 +1,251 @@
// components/overlays/EmbedInfoOverlay.js
import videojs from 'video.js';
// Get the Component base class from Video.js
const Component = videojs.getComponent('Component');
class EmbedInfoOverlay extends Component {
constructor(player, options) {
super(player, options);
this.authorName = options.authorName || 'Unknown';
this.authorProfile = options.authorProfile || '';
this.authorThumbnail = options.authorThumbnail || '';
this.videoTitle = options.videoTitle || 'Video';
this.videoUrl = options.videoUrl || '';
// Initialize after player is ready
this.player().ready(() => {
this.createOverlay();
});
}
createEl() {
const el = document.createElement('div');
el.className = 'vjs-embed-info-overlay';
return el;
}
createOverlay() {
const playerEl = this.player().el();
const overlay = this.el();
// Set overlay styles for positioning at top left
overlay.style.cssText = `
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.7);
padding: 8px 12px;
border-radius: 8px;
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1);
max-width: calc(100% - 40px);
box-sizing: border-box;
transition: opacity 0.3s ease-in-out;
`;
// Create avatar container
if (this.authorThumbnail) {
const avatarContainer = document.createElement('div');
avatarContainer.className = 'embed-avatar-container';
avatarContainer.style.cssText = `
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 50%;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.2);
`;
if (this.authorProfile) {
const avatarLink = document.createElement('a');
avatarLink.href = this.authorProfile;
avatarLink.target = '_blank';
avatarLink.rel = 'noopener noreferrer';
avatarLink.title = this.authorName;
avatarLink.style.cssText = `
display: block;
width: 100%;
height: 100%;
`;
const avatarImg = document.createElement('img');
avatarImg.src = this.authorThumbnail;
avatarImg.alt = this.authorName;
avatarImg.title = this.authorName;
avatarImg.style.cssText = `
width: 100%;
height: 100%;
object-fit: cover;
display: block;
`;
// Handle image load error
avatarImg.onerror = () => {
avatarImg.style.display = 'none';
avatarContainer.style.display = 'none';
};
avatarLink.appendChild(avatarImg);
avatarContainer.appendChild(avatarLink);
} else {
const avatarImg = document.createElement('img');
avatarImg.src = this.authorThumbnail;
avatarImg.alt = this.authorName;
avatarImg.title = this.authorName;
avatarImg.style.cssText = `
width: 100%;
height: 100%;
object-fit: cover;
display: block;
`;
// Handle image load error
avatarImg.onerror = () => {
avatarImg.style.display = 'none';
avatarContainer.style.display = 'none';
};
avatarContainer.appendChild(avatarImg);
}
overlay.appendChild(avatarContainer);
}
// Create title container
const titleContainer = document.createElement('div');
titleContainer.className = 'embed-title-container';
titleContainer.style.cssText = `
flex: 1;
min-width: 0;
overflow: hidden;
`;
if (this.videoUrl) {
const titleLink = document.createElement('a');
titleLink.href = this.videoUrl;
titleLink.target = '_blank';
titleLink.rel = 'noopener noreferrer';
titleLink.textContent = this.videoTitle;
titleLink.title = this.videoTitle;
titleLink.style.cssText = `
color: #fff;
text-decoration: none;
font-size: 14px;
font-weight: 500;
line-height: 1.3;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.2s ease;
`;
// Add hover effect
titleLink.addEventListener('mouseenter', () => {
titleLink.style.color = '#009931';
});
titleLink.addEventListener('mouseleave', () => {
titleLink.style.color = '#fff';
});
titleContainer.appendChild(titleLink);
} else {
const titleText = document.createElement('span');
titleText.textContent = this.videoTitle;
titleText.title = this.videoTitle;
titleText.style.cssText = `
color: #fff;
font-size: 14px;
font-weight: 500;
line-height: 1.3;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
titleContainer.appendChild(titleText);
}
overlay.appendChild(titleContainer);
// Append overlay to player
playerEl.appendChild(overlay);
// Hide overlay during user inactivity (like controls)
this.setupAutoHide();
}
setupAutoHide() {
const player = this.player();
const overlay = this.el();
// Show/hide with controls
player.on('useractive', () => {
overlay.style.opacity = '1';
});
player.on('userinactive', () => {
overlay.style.opacity = '0.7';
});
// Always show when paused
player.on('pause', () => {
overlay.style.opacity = '1';
});
// Hide during fullscreen controls fade
player.on('fullscreenchange', () => {
setTimeout(() => {
if (player.isFullscreen()) {
overlay.style.opacity = player.userActive() ? '1' : '0.7';
} else {
overlay.style.opacity = '1';
}
}, 100);
});
}
// Method to update overlay content if needed
updateContent(options) {
if (options.authorName) this.authorName = options.authorName;
if (options.authorProfile) this.authorProfile = options.authorProfile;
if (options.authorThumbnail) this.authorThumbnail = options.authorThumbnail;
if (options.videoTitle) this.videoTitle = options.videoTitle;
if (options.videoUrl) this.videoUrl = options.videoUrl;
// Recreate overlay with new content
const overlay = this.el();
overlay.innerHTML = '';
this.createOverlay();
}
show() {
this.el().style.display = 'flex';
}
hide() {
this.el().style.display = 'none';
}
dispose() {
// Clean up any event listeners or references
const overlay = this.el();
if (overlay && overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
super.dispose();
}
}
// Register the component with Video.js
videojs.registerComponent('EmbedInfoOverlay', EmbedInfoOverlay);
export default EmbedInfoOverlay;

View File

@ -146,7 +146,7 @@ class EndScreenOverlay extends Component {
// Generate a placeholder image using a service or create a data URL
// For now, we'll use a simple colored placeholder based on the title
const colors = [
'#FF6B6B',
'#009931',
'#4ECDC4',
'#45B7D1',
'#96CEB4',

View File

@ -5,6 +5,7 @@ import 'video.js/dist/video-js.css';
// Import the separated components
import EndScreenOverlay from '../overlays/EndScreenOverlay';
import AutoplayCountdownOverlay from '../overlays/AutoplayCountdownOverlay';
import EmbedInfoOverlay from '../overlays/EmbedInfoOverlay';
import ChapterMarkers from '../markers/ChapterMarkers';
import SpritePreview from '../markers/SpritePreview';
import NextVideoButton from '../controls/NextVideoButton';
@ -34,6 +35,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
: {
data: {
// COMMON
title: 'Modi tempora est quaerat numquam',
author_name: 'Markos Gogoulos',
author_profile: '/user/markos/',
author_thumbnail: '/media/userlogos/2024/10/02/markos.jpeg',
url: 'https://demo.mediacms.io/view?m=7dedcb56bde9463dbc0766768a99be0f',
poster_url:
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
chapter_data: [
@ -1035,10 +1041,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// AUDIO
/*media_type: 'audio',
original_media_url:
'https://videojs.mediacms.io/media/original/user/markos/174be7a1ecb04850a6927a0af2887ccc.SizzlaHardGround.mp3',
hls_info: {},
encodings_info: {},*/
original_media_url:
'https://videojs.mediacms.io/media/original/user/markos/174be7a1ecb04850a6927a0af2887ccc.SizzlaHardGround.mp3',
hls_info: {},
encodings_info: {},*/
},
// other
@ -1046,7 +1052,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/fe4933d67b884d4da507dd60e77f7438.VID_20200909_141053.mp4sprites.jpg',
frame: { width: 160, height: 90, seconds: 10 },
},
siteUrl: '',
siteUrl: 'https://demo.mediacms.io',
nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO',
urlAutoplay: true,
urlMuted: false,
@ -1287,6 +1293,12 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
return {
id: mediaData.data?.friendly_token || 'default-video',
title: mediaData.data?.title || 'Video',
author_name: mediaData.data?.author_name || 'Unknown',
author_profile: mediaData.data?.author_profile ? mediaData.siteUrl + mediaData.data.author_profile : '',
author_thumbnail: mediaData.data?.author_thumbnail
? mediaData.siteUrl + mediaData.data.author_thumbnail
: '',
url: mediaData.data?.url ? mediaData.siteUrl + mediaData.data.url : '',
poster: mediaData.data?.poster_url ? mediaData.siteUrl + mediaData.data.poster_url : '',
previewSprite: mediaData?.previewSprite || {},
related_media: mediaData.data?.related_media || [],
@ -2376,6 +2388,18 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}
// END: Add Chapters Overlay Component
// BEGIN: Add Embed Info Overlay Component (for embed player only)
if (isEmbedPlayer) {
customComponents.current.embedInfoOverlay = new EmbedInfoOverlay(playerRef.current, {
authorName: currentVideo.author_name,
authorProfile: currentVideo.author_profile,
authorThumbnail: currentVideo.author_thumbnail,
videoTitle: currentVideo.title,
videoUrl: currentVideo.url,
});
}
// END: Add Embed Info Overlay Component
// BEGIN: Add Settings Menu Component
customComponents.current.settingsMenu = new CustomSettingsMenu(playerRef.current, {
userPreferences: userPreferences.current,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long