mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-10-06 22:32:41 +07:00
feat: Add Embed Info Overlay Component (for embed player only)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user