feat: Related media at the end of video

This commit is contained in:
Yiannis Christodoulou 2025-07-21 19:45:19 +03:00
parent 11465523b1
commit a66d823d1d
3 changed files with 723 additions and 118 deletions

View File

@ -74,14 +74,14 @@
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 91%; height: calc(100% - 46px);
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
display: none; display: none;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 1000; z-index: 1000;
padding: 0 20px; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
@ -95,44 +95,51 @@
.vjs-related-videos-grid { .vjs-related-videos-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr);
gap: 12px; gap: 0;
max-width: 800px;
width: 100%; width: 100%;
margin: 0 auto; height: 100%;
margin: 0;
} }
.vjs-related-video-item { .vjs-related-video-item {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
border-radius: 8px; border-radius: 0;
overflow: hidden; overflow: hidden;
transition: transform 0.2s ease; transition: none;
background: #1a1a1a; background: #1a1a1a;
border: 1px solid #000;
} }
.vjs-related-video-item:hover { .vjs-related-video-item:hover {
transform: scale(1.05); transform: none;
} }
.vjs-related-video-thumbnail { .vjs-related-video-thumbnail {
width: 100%; width: 100%;
height: 120px; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
border-radius: 0;
} }
.vjs-related-video-overlay { .vjs-related-video-overlay {
position: absolute; position: absolute;
bottom: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8)); bottom: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.9));
color: white; color: white;
padding: 10px; padding: 12px;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
display: flex;
flex-direction: column;
justify-content: flex-start;
height: 100%;
} }
.vjs-related-video-item:hover .vjs-related-video-overlay { .vjs-related-video-item:hover .vjs-related-video-overlay {
@ -142,14 +149,21 @@
.vjs-related-video-title { .vjs-related-video-title {
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
line-height: 1.3;
color: white;
margin-bottom: 4px; margin-bottom: 4px;
line-height: 1.2; }
.vjs-related-video-meta {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
} }
.vjs-related-video-author { .vjs-related-video-author {
font-size: 12px; font-size: 12px;
color: #ccc; color: #ccc;
margin-bottom: 2px;
} }
.vjs-related-video-views { .vjs-related-video-views {
@ -157,6 +171,30 @@
color: #aaa; color: #aaa;
} }
.vjs-related-video-author::after {
content: "•";
margin-left: 8px;
color: #666;
}
.vjs-related-video-duration {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 2px 6px;
font-size: 11px;
font-weight: bold;
border-radius: 2px;
opacity: 0;
transition: opacity 0.3s ease;
}
.vjs-related-video-item:hover .vjs-related-video-duration {
opacity: 1;
}
/* Keep entire control bar active when video ends */ /* Keep entire control bar active when video ends */
.video-js.vjs-ended .vjs-control-bar { .video-js.vjs-ended .vjs-control-bar {
opacity: 1 !important; opacity: 1 !important;
@ -290,18 +328,20 @@
} }
.vjs-related-videos-grid { .vjs-related-videos-grid {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(auto, 1fr); grid-template-rows: repeat(2, 1fr);
gap: 8px; gap: 0;
max-width: 100%; width: 100%;
height: 100%;
} }
.vjs-end-screen-overlay { .vjs-end-screen-overlay {
padding: 0 15px; padding: 0;
height: calc(100% - 46px);
} }
.vjs-related-video-thumbnail { .vjs-related-video-thumbnail {
height: 100px; height: 100%;
} }
.vjs-chapter-floating-tooltip { .vjs-chapter-floating-tooltip {
@ -317,16 +357,20 @@
} }
.vjs-related-videos-grid { .vjs-related-videos-grid {
grid-template-columns: 1fr; grid-template-columns: repeat(2, 1fr);
gap: 6px; grid-template-rows: repeat(3, 1fr);
gap: 0;
width: 100%;
height: 100%;
} }
.vjs-end-screen-overlay { .vjs-end-screen-overlay {
padding: 0 10px; padding: 0;
height: calc(100% - 46px);
} }
.vjs-related-video-thumbnail { .vjs-related-video-thumbnail {
height: 120px; height: 100%;
} }
/* Make sure video info panel is responsive */ /* Make sure video info panel is responsive */

View File

@ -13,8 +13,7 @@ class EndScreenOverlay extends Component {
super(player, options); super(player, options);
// Now set the instance property after super() completes // Now set the instance property after super() completes
this.relatedVideos = this.relatedVideos = options && options.relatedVideos ? options.relatedVideos : [];
options && options.relatedVideos ? options.relatedVideos : [];
// console.log( // console.log(
// 'EndScreenOverlay created with', // 'EndScreenOverlay created with',
@ -25,14 +24,16 @@ class EndScreenOverlay extends Component {
createEl() { createEl() {
// Get relatedVideos from options since createEl is called during super() // Get relatedVideos from options since createEl is called during super()
const relatedVideos = const relatedVideos = this.options_ && this.options_._relatedVideos ? this.options_._relatedVideos : [];
this.options_ && this.options_._relatedVideos
? this.options_._relatedVideos // Limit items based on screen size
: []; const isMobile = window.innerWidth <= 768;
const maxItems = isMobile ? 6 : 12;
const videosToShow = relatedVideos.slice(0, maxItems);
// console.log( // console.log(
// 'Creating end screen with', // 'Creating end screen with',
// relatedVideos.length, // videosToShow.length,
// 'related videos' // 'related videos'
// ); // );
@ -46,12 +47,8 @@ class EndScreenOverlay extends Component {
}); });
// Create video items // Create video items
if ( if (videosToShow && Array.isArray(videosToShow) && videosToShow.length > 0) {
relatedVideos && videosToShow.forEach((video) => {
Array.isArray(relatedVideos) &&
relatedVideos.length > 0
) {
relatedVideos.forEach((video) => {
const videoItem = this.createVideoItem(video); const videoItem = this.createVideoItem(video);
grid.appendChild(videoItem); grid.appendChild(videoItem);
}); });
@ -91,6 +88,11 @@ class EndScreenOverlay extends Component {
}); });
title.textContent = video.title; title.textContent = video.title;
// Create meta container for author and views
const meta = videojs.dom.createEl('div', {
className: 'vjs-related-video-meta',
});
const author = videojs.dom.createEl('div', { const author = videojs.dom.createEl('div', {
className: 'vjs-related-video-author', className: 'vjs-related-video-author',
}); });
@ -101,13 +103,37 @@ class EndScreenOverlay extends Component {
}); });
views.textContent = video.views; views.textContent = video.views;
// Add author and views to meta container
meta.appendChild(author);
meta.appendChild(views);
// Add duration display (positioned absolutely in bottom right)
const duration = videojs.dom.createEl('div', {
className: 'vjs-related-video-duration',
});
// Format duration from seconds to MM:SS
const formatDuration = (seconds) => {
if (!seconds || seconds === 0) return '';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
duration.textContent = formatDuration(video.duration);
// Structure: title at top, meta at bottom
overlay.appendChild(title); overlay.appendChild(title);
overlay.appendChild(author); overlay.appendChild(meta);
overlay.appendChild(views);
item.appendChild(thumbnail); item.appendChild(thumbnail);
item.appendChild(overlay); item.appendChild(overlay);
// Add duration to the item (positioned absolutely)
if (video.duration && video.duration > 0) {
item.appendChild(duration);
}
// Add click handler // Add click handler
item.addEventListener('click', () => { item.addEventListener('click', () => {
window.location.href = `/view?m=${video.id}`; window.location.href = `/view?m=${video.id}`;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState, useMemo } from 'react'; import React, { useEffect, useRef, useMemo } from 'react';
import videojs from 'video.js'; import videojs from 'video.js';
import 'video.js/dist/video-js.css'; import 'video.js/dist/video-js.css';
@ -22,7 +22,538 @@ function VideoJSPlayer() {
typeof window !== 'undefined' && window.MEDIA_DATA typeof window !== 'undefined' && window.MEDIA_DATA
? window.MEDIA_DATA ? window.MEDIA_DATA
: { : {
data: {}, data: {
related_media: [
{
friendly_token: 'jgLkic37V',
url: 'http://localhost/view?m=jgLkic37V',
api_url: 'http://localhost/api/v1/media/jgLkic37V',
user: 'admin',
title: 'Screenshot20250312at15.58.35.png',
description: '',
add_date: '2025-03-12T14:28:24.757807Z',
views: 5,
media_type: 'image',
state: 'public',
duration: 0,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/ad827f53568c4565b69ee59da88966c8.Screenshot20250312at15.58.35.png.jpg',
is_reviewed: true,
preview_url: null,
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: null,
},
{
friendly_token: '54Ip78oPK',
url: 'http://localhost/view?m=54Ip78oPK',
api_url: 'http://localhost/api/v1/media/54Ip78oPK',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) 2',
description: '',
add_date: '2025-05-19T01:22:10.354407+01:00',
views: 8,
media_type: 'video',
state: 'public',
duration: 1,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/ca19044d26af4eada1669c3373c771cb_nhCvKcl.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/ca19044d26af4eada1669c3373c771cb.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '0.6MB',
},
{
friendly_token: '0VpZtWduU',
url: 'http://localhost/view?m=0VpZtWduU',
api_url: 'http://localhost/api/v1/media/0VpZtWduU',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) (Trimmed)',
description: '',
add_date: '2025-05-19T01:30:55.374863+01:00',
views: 13,
media_type: 'video',
state: 'public',
duration: 6,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/46b2bec8d79f496682640ce99b5c53af_RA16Oba.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/46b2bec8d79f496682640ce99b5c53af.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '4.0MB',
},
{
friendly_token: 'efM2aLP4P',
url: 'http://localhost/view?m=efM2aLP4P',
api_url: 'http://localhost/api/v1/media/efM2aLP4P',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) 1',
description: '',
add_date: '2025-05-19T01:22:08.117300+01:00',
views: 11,
media_type: 'video',
state: 'public',
duration: 8,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/c27f570a84d64cdc857f87c50dace993_Ma3ap4p.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/c27f570a84d64cdc857f87c50dace993.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 2,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '4.8MB',
},
{
friendly_token: '8RUHRjnji',
url: 'http://localhost/view?m=8RUHRjnji',
api_url: 'http://localhost/api/v1/media/8RUHRjnji',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed)',
description: '',
add_date: '2025-05-19T01:29:48.308159+01:00',
views: 7,
media_type: 'video',
state: 'public',
duration: 7,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/884cac6a306e4ae9995726fcd3c7aa49_fT7pnM4.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '4.6MB',
},
{
friendly_token: 'N5Vpnqfpf',
url: 'http://localhost/view?m=N5Vpnqfpf',
api_url: 'http://localhost/api/v1/media/N5Vpnqfpf',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov',
description: '',
add_date: '2025-05-19T01:15:03.594646+01:00',
views: 11,
media_type: 'video',
state: 'public',
duration: 37,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/e549ebb4b1dc41a48cf76ebccf592836_1xqZHHc.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/e549ebb4b1dc41a48cf76ebccf592836.tmpu6x4f3js.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '22.3MB',
},
{
friendly_token: 'MmyuYCEfC',
url: 'http://localhost/view?m=MmyuYCEfC',
api_url: 'http://localhost/api/v1/media/MmyuYCEfC',
user: 'admin',
title: 'JmailLogIdeasforYiannis.mp4',
description: 'check the 00:12 and then the 00:39',
add_date: '2025-03-12T02:37:42Z',
views: 49,
media_type: 'video',
state: 'public',
duration: 112,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/3d4e6f1c7e014720bc6390ed9d61aba5_BppzBiz.JmailLogIdeasforYiannis.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/3d4e6f1c7e014720bc6390ed9d61aba5.tmpcjd0bvr5.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '40.6MB',
},
{
friendly_token: 'E8ia0lfir',
url: 'http://localhost/view?m=E8ia0lfir',
api_url: 'http://localhost/api/v1/media/E8ia0lfir',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed)',
description: '',
add_date: '2025-05-19T01:22:27.251447+01:00',
views: 12,
media_type: 'video',
state: 'public',
duration: 37,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/8572a325a9d649f79cc60181fbece78c_0H5arHy.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/8572a325a9d649f79cc60181fbece78c.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '22.4MB',
},
{
friendly_token: 'eZAl84zg6',
url: 'http://localhost/view?m=eZAl84zg6',
api_url: 'http://localhost/api/v1/media/eZAl84zg6',
user: 'admin',
title: '20257855hd_1920_1080_60fps.mp4',
description: '',
add_date: '2025-03-12T02:35:59.779098Z',
views: 29,
media_type: 'video',
state: 'public',
duration: 90,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/ed9c61a72c0445debfbedee011b6eba1_8HjWGZX.20257855hd_1920_1080_60fps.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/ed9c61a72c0445debfbedee011b6eba1.tmpil72jnbv.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '64.4MB',
},
{
friendly_token: '6WShYNxZx',
url: 'http://localhost/view?m=6WShYNxZx',
api_url: 'http://localhost/api/v1/media/6WShYNxZx',
user: 'admin',
title: 'SampleVideo_1280x720_30mb.mp4 test abc',
description: 'test 123 yiannis',
add_date: '2025-04-14T00:00:00+01:00',
views: 43,
media_type: 'video',
state: 'public',
duration: 171,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/c868df836ac34688b876e741b58ada93_LRvu388.SampleVideo_1280x720_30mb.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/c868df836ac34688b876e741b58ada93.tmpyl_r01_l.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 3,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '31.5MB',
},
{
friendly_token: 'SOgLTsrAH',
url: 'http://localhost/view?m=SOgLTsrAH',
api_url: 'http://localhost/api/v1/media/SOgLTsrAH',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) (Trimmed) 1',
description: '',
add_date: '2025-05-19T01:31:11.057879+01:00',
views: 8,
media_type: 'video',
state: 'public',
duration: 2,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/46c75315947e4a5f9dd464c1721206fd_6KJzqBH.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/46c75315947e4a5f9dd464c1721206fd.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '1.5MB',
},
{
friendly_token: 'PoDk009ue',
url: 'http://localhost/view?m=PoDk009ue',
api_url: 'http://localhost/api/v1/media/PoDk009ue',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov',
description: '',
add_date: '2025-05-19T01:17:44.296445+01:00',
views: 12,
media_type: 'video',
state: 'public',
duration: 37,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/fd1d31460e614ca9b4645228e62c9eec_DB8nan8.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '22.4MB',
},
{
friendly_token: 'b7e06rUz5',
url: 'http://localhost/view?m=b7e06rUz5',
api_url: 'http://localhost/api/v1/media/b7e06rUz5',
user: 'admin',
title: 'Proto.cyTimesheet03_03_202507_03_2025.pdf',
description: '',
add_date: '2025-03-12T14:29:15.245599Z',
views: 16,
media_type: 'pdf',
state: 'public',
duration: 0,
thumbnail_url: null,
is_reviewed: true,
preview_url: null,
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: null,
},
{
friendly_token: 'wUu76zL5a',
url: 'http://localhost/view?m=wUu76zL5a',
api_url: 'http://localhost/api/v1/media/wUu76zL5a',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) 2 (Trimmed)',
description: '',
add_date: '2025-05-19T01:28:52.829907+01:00',
views: 2,
media_type: 'video',
state: 'public',
duration: 0,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/1b3e7dc2431b46a5b7e40b297662137a_1V0DJz5.ca19044d26af4eada1669c3373c771cb.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/1b3e7dc2431b46a5b7e40b297662137a.ca19044d26af4eada1669c3373c771cb.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '0.3MB',
},
{
friendly_token: 'JBjMHLfLZ',
url: 'http://localhost/view?m=JBjMHLfLZ',
api_url: 'http://localhost/api/v1/media/JBjMHLfLZ',
user: 'admin',
title: 'att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov (Trimmed) (Trimmed) 2',
description: '',
add_date: '2025-05-19T01:31:11.848403+01:00',
views: 19,
media_type: 'video',
state: 'public',
duration: 4,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/7759ca3e0ba1482aa9149201bd14139d_ISBMqD4.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.att.24Cz35hRdjJ9FV0RlxCPVVtc6XVAdJvrU4eb8Gkykc.mov.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/7759ca3e0ba1482aa9149201bd14139d.884cac6a306e4ae9995726fcd3c7aa49.fd1d31460e614ca9b4645228e62c9eec.tmpjohj96hu.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '2.5MB',
},
{
friendly_token: 'w13x5uUXc',
url: 'http://localhost/view?m=w13x5uUXc',
api_url: 'http://localhost/api/v1/media/w13x5uUXc',
user: 'admin',
title: 'Web357202503112025_05_33_AM.png',
description: '',
add_date: '2025-03-12T14:27:55.078360Z',
views: 3,
media_type: 'image',
state: 'public',
duration: 0,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/1da967bcdca540efa1d2362af2f3a34b.Web357202503112025_05_33_AM.png.jpg',
is_reviewed: true,
preview_url: null,
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: null,
},
{
friendly_token: 'sZxPpdon5',
url: 'http://localhost/view?m=sZxPpdon5',
api_url: 'http://localhost/api/v1/media/sZxPpdon5',
user: 'admin',
title: 'JmailLogIdeasforYiannis.mp4 (Trimmed)',
description: 'check the 00:12 and then the 00:39',
add_date: '2025-05-15T02:16:19.580745+01:00',
views: 9,
media_type: 'video',
state: 'public',
duration: 17,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/7191a59cbfcd402ebed400e604a76840_5WRh0cp.3d4e6f1c7e014720bc6390ed9d61aba5.JmailLogIdeasforYiannis.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/7191a59cbfcd402ebed400e604a76840.3d4e6f1c7e014720bc6390ed9d61aba5.tmpcjd0bvr5.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '4.5MB',
},
{
friendly_token: 'TCycDZROI',
url: 'http://localhost/view?m=TCycDZROI',
api_url: 'http://localhost/api/v1/media/TCycDZROI',
user: 'admin',
title: 'JmailLogIdeasforYiannis.mp4 (Trimmed)',
description: 'check the 00:12 and then the 00:39',
add_date: '2025-05-15T02:14:58.855935+01:00',
views: 8,
media_type: 'video',
state: 'public',
duration: 112,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/5cbfbe93d06c453487c65d9d54749318_CUIZl8y.3d4e6f1c7e014720bc6390ed9d61aba5.JmailLogIdeasforYiannis.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/5cbfbe93d06c453487c65d9d54749318.3d4e6f1c7e014720bc6390ed9d61aba5.tmpcjd0bvr5.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 1,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '40.6MB',
},
{
friendly_token: 'YjGJafibO',
url: 'http://localhost/view?m=YjGJafibO',
api_url: 'http://localhost/api/v1/media/YjGJafibO',
user: 'admin',
title: 'file_example_MP4_480_1_5MG.mp4',
description: '',
add_date: '2025-04-14T00:07:11.074523+01:00',
views: 18,
media_type: 'video',
state: 'public',
duration: 30,
thumbnail_url:
'http://localhost/media/original/thumbnails/user/admin/e76f4fff2b3d42adbadb0f1204b21b81_s8EEvSE.file_example_MP4_480_1_5MG.mp4.jpg',
is_reviewed: true,
preview_url:
'/media/encoded/1/admin/e76f4fff2b3d42adbadb0f1204b21b81.tmp04azp_tc.gif',
author_name: '',
author_profile: 'http://localhost/user/admin/',
author_thumbnail: 'http://localhost/media/userlogos/user.jpg',
encoding_status: 'success',
likes: 2,
dislikes: 0,
reported_times: 0,
featured: false,
user_featured: false,
size: '1.6MB',
},
],
},
previewSprite: { previewSprite: {
url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/fe4933d67b884d4da507dd60e77f7438.VID_20200909_141053.mp4sprites.jpg', url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/fe4933d67b884d4da507dd60e77f7438.VID_20200909_141053.mp4sprites.jpg',
frame: { width: 160, height: 90, seconds: 10 }, frame: { width: 160, height: 90, seconds: 10 },
@ -54,6 +585,7 @@ function VideoJSPlayer() {
title: mediaData.data?.title || 'Video', title: mediaData.data?.title || 'Video',
poster: mediaData.siteUrl + mediaData.data?.poster_url || '', poster: mediaData.siteUrl + mediaData.data?.poster_url || '',
previewSprite: mediaData?.previewSprite || {}, previewSprite: mediaData?.previewSprite || {},
related_media: mediaData.data?.related_media || [],
sources: mediaData.data?.original_media_url sources: mediaData.data?.original_media_url
? [ ? [
{ {
@ -71,81 +603,30 @@ function VideoJSPlayer() {
[mediaData] [mediaData]
); );
// Mock related videos data (would come from API) // Get related videos from mediaData instead of static data
const [relatedVideos] = useState([ const relatedVideos = useMemo(() => {
{ if (!mediaData?.data?.related_media) {
id: 'Otbc37Yj4', return [];
title: 'Amazing Ocean Depths', }
author: 'Marine Explorer',
views: '2.1M views', return mediaData.data.related_media
thumbnail: 'https://picsum.photos/320/180?random=1', .slice(0, 12) // Limit to maximum 12 items
category: 'nature', .map((media) => ({
}, id: media.friendly_token,
{ title: media.title,
id: 'Kt9m2Pv8x', author: media.user || media.author_name || 'Unknown',
title: 'Deep Sea Creatures', views: `${media.views} views`,
author: 'Aquatic Life', thumbnail: media.thumbnail_url || media.author_thumbnail,
views: '854K views', category: media.media_type,
thumbnail: 'https://picsum.photos/320/180?random=2', url: media.url,
category: 'nature', duration: media.duration,
}, size: media.size,
{ likes: media.likes,
id: 'Ln5q8Bw3r', dislikes: media.dislikes,
title: 'Coral Reef Paradise', add_date: media.add_date,
author: 'Ocean Films', description: media.description,
views: '1.7M views', }));
thumbnail: 'https://picsum.photos/320/180?random=3', }, [mediaData]);
category: 'nature',
},
{
id: 'Mz4x7Cy9p',
title: 'Underwater Adventure',
author: 'Sea Documentaries',
views: '3.2M views',
thumbnail: 'https://picsum.photos/320/180?random=4',
category: 'nature',
},
{
id: 'Nx8v2Fk6w',
title: 'Marine Wildlife',
author: 'Nature Plus',
views: '967K views',
thumbnail: 'https://picsum.photos/320/180?random=5',
category: 'nature',
},
{
id: 'Py7t4Mn1q',
title: 'Ocean Mysteries',
author: 'Discovery Zone',
views: '1.4M views',
thumbnail: 'https://picsum.photos/320/180?random=6',
category: 'nature',
},
{
id: 'Qw5e8Rt2n',
title: 'Whales and Dolphins',
author: 'Ocean Planet',
views: '2.8M views',
thumbnail: 'https://picsum.photos/320/180?random=7',
category: 'nature',
},
{
id: 'Uv3k9Lp7m',
title: 'Tropical Fish Paradise',
author: 'Aquatic World',
views: '1.2M views',
thumbnail: 'https://picsum.photos/320/180?random=8',
category: 'nature',
},
{
id: 'Zx6c4Mn8b',
title: 'Deep Ocean Exploration',
author: 'Marine Science',
views: '3.7M views',
thumbnail: 'https://picsum.photos/320/180?random=9',
category: 'nature',
},
]);
// Function to navigate to next video // Function to navigate to next video
const goToNextVideo = () => { const goToNextVideo = () => {
@ -652,6 +1133,55 @@ function VideoJSPlayer() {
} }
// END: Add chapter markers to progress control // END: Add chapter markers to progress control
// BEGIN: Move CC (subtitles) and PiP buttons to the right side
setTimeout(() => {
// Find the subtitles/captions button (CC button)
const possibleTextTrackButtons = ['subtitlesButton', 'captionsButton', 'subsCapsButton'];
let textTrackButton = null;
for (const buttonName of possibleTextTrackButtons) {
const button = controlBar.getChild(buttonName);
if (button) {
textTrackButton = button;
console.log(`Found text track button: ${buttonName}`);
break;
}
}
// Find the picture-in-picture button
const pipButton = controlBar.getChild('pictureInPictureToggle');
// Move buttons to the right side in desired order
if (fullscreenToggle) {
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
let insertIndex = fullscreenIndex;
// Move PiP button first (will be rightmost before fullscreen)
if (pipButton) {
try {
controlBar.removeChild(pipButton);
controlBar.addChild(pipButton, {}, insertIndex);
console.log('✓ Picture-in-Picture button moved to right side');
insertIndex = controlBar.children().indexOf(fullscreenToggle); // Update index
} catch (e) {
console.log('✗ Failed to move PiP button:', e);
}
}
// Move CC button (will be before PiP)
if (textTrackButton) {
try {
controlBar.removeChild(textTrackButton);
controlBar.addChild(textTrackButton, {}, insertIndex);
console.log('✓ CC button moved to right side');
} catch (e) {
console.log('✗ Failed to move CC button:', e);
}
}
}
}, 100);
// END: Move CC (subtitles) and PiP buttons to the right side
// BEGIN: Move chapters button after fullscreen toggle // BEGIN: Move chapters button after fullscreen toggle
if (chaptersButton && fullscreenToggle) { if (chaptersButton && fullscreenToggle) {
try { try {
@ -1053,7 +1583,12 @@ function VideoJSPlayer() {
}; };
}, []); }, []);
return <video ref={videoRef} className="video-js vjs-default-skin" tabIndex="0" />; return (
<>
<video ref={videoRef} className="video-js vjs-default-skin" tabIndex="0" />
{JSON.stringify(currentVideo.related_media)}
</>
);
} }
export default VideoJSPlayer; export default VideoJSPlayer;