mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
feat: Add Embed Info Overlay Component (for embed player only)
This commit is contained in:
parent
a89ee7b0e0
commit
68fa1efe87
@ -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,
|
||||
|
||||
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
Loading…
x
Reference in New Issue
Block a user