diff --git a/frontend-tools/video-js/src/VideoJS.css b/frontend-tools/video-js/src/VideoJS.css index c6227285..5229a6ab 100644 --- a/frontend-tools/video-js/src/VideoJS.css +++ b/frontend-tools/video-js/src/VideoJS.css @@ -71,10 +71,10 @@ transition: all 0.2s ease !important; } -.vjs-autoplay-toggle:hover { +/* .vjs-autoplay-toggle:hover { color: #ff4444 !important; transform: scale(1.1) !important; -} +} */ .vjs-autoplay-toggle .vjs-autoplay-icon { width: 1.2em; @@ -360,6 +360,17 @@ visibility: visible !important; } +/* Hide menus/tooltips when chapters overlay is open */ +.video-js.chapters-open .vjs-menu, +.video-js.chapters-open .vjs-menu.vjs-lock-showing, +.video-js.chapters-open .vjs-hover-display, +.video-js.chapters-open .vjs-time-tooltip, +.video-js.chapters-open .vjs-progress-holder .vjs-mouse-display { + display: none !important; + opacity: 0 !important; + visibility: hidden !important; +} + /* Responsive adjustments */ @media (max-width: 768px) { .video-container { @@ -681,3 +692,55 @@ text-align: center !important; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8) !important; } +button{cursor: pointer;} + +body{ font-family: Arial, Helvetica, sans-serif; box-sizing:border-box;} +.video-wrapper{ position:relative; font-family:Arial; height:calc(100vh - 16px);} +.video-box{ height:100%;} +.video-wrapper .video-box .video-js{ padding:0; height:100% !important;} + +.video-chapter{ position:absolute; top:10px; width:min(360px, calc(100% - 20px)); border:1px solid rgba(255, 255, 255, 0.12); border-radius:12px; +height:calc(100% - 80px); background:rgba(18, 18, 18, 0.96); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); overflow:hidden; +box-shadow: 0 12px 30px rgba(0,0,0,0.45); right:10px;} +.chapter-head{ padding:12px 8px 10px 16px; position:sticky; top:0; left:0; background:linear-gradient(180deg, rgba(28,28,28,0.95), rgba(18,18,18,0.95)); +border-bottom:1px solid rgba(255,255,255,0.08); z-index:2; } +.playlist-title{ display:flex; align-items:center; gap:10px; } +.chapter-title{ width: auto; flex: 1; min-width: 0; } +.chapter-title h3{ margin:0; padding:0;} +.chapter-title h3 a{color:#fff; font-size: 18px; line-height: 26px; font-weight: 700; text-decoration: none; +white-space: nowrap; text-overflow: ellipsis; height: 28px; overflow: hidden; display: block;} +.chapter-title p{ margin:4px 0 0; padding:0; color:#bdbdbd; font-size:12px; font-weight:400; line-height:15px;} +.chapter-title p a{ color:#bdbdbd; font-size:12px; font-weight:400; line-height:15px; text-decoration: none;} + +.chapter-close{ width:40px; margin-left:auto; display:flex; align-items:center; justify-content:flex-end;} +.chapter-close button{ background:transparent; color:#fff; border: 0; width: 40px; height: 40px; padding: 0; display: flex; +align-items: center; justify-content: center; border-radius:8px;} +.chapter-close button:hover{ background:rgba(255,255,255,0.1);} +.playlist-action-menu{ display:none; justify-content:space-between; gap:10px;} +.playlist-action-menu button{ background:transparent; border: 0; width: 40px; height: 40px; padding: 0; display: flex; +align-items: center; justify-content: center; align-items:center; border-radius:100px; } +.playlist-action-menu button:hover{ background:rgba(0, 0, 0, 0.1);} +.start-action{ display:flex;} + +.chapter-body{ height:calc(100% - 59px); overflow:auto; } +.chapter-body ul{ margin:0; padding:0;} +.playlist-items a{ padding:12px; display:flex; align-items:center; text-decoration:none; gap:12px; width:100%; box-sizing:border-box;} +.playlist-items a:hover{background:rgba(255,255,255,0.06);} +.playlist-items.selected a{ background:rgba(255,255,255,0.14);} +.playlist-drag-handle{ width:24px; display:flex; justify-content:center; color:#e0e0e0; font-size:12px;} +.thumbnail-meta{ flex:1; min-width:0; padding:0;} +.thumbnail-meta h4{ margin:0 2px 4px 0; font-size:14px; line-height:20px; font-weight:600; overflow:hidden; text-overflow:ellipsis; +color:#fff; white-space:normal; max-height:40px; -webkit-line-clamp: 2; line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical;} +.thumbnail-meta .meta-sub{ display:flex; gap:8px; align-items:center; } +.thumbnail-meta .meta-sub .meta-dynamic{ color:#bdbdbd; font-size:12px; line-height:18px; } +.thumbnail-action button{ border:0; background:transparent; color:#fff; opacity:0;} +.playlist-items a:hover .thumbnail-action button{ opacity:1;} + +/* custom scrollbar for chapter list */ +.chapter-body::-webkit-scrollbar{ width:10px; } +.chapter-body::-webkit-scrollbar-thumb{ background:rgba(255,255,255,0.18); border-radius:8px; } +.chapter-body::-webkit-scrollbar-track{ background:transparent; } + +.video-box .video-js .vjs-control-bar .vjs-spacer-control{ margin-left:auto;} +.video-js .vjs-control-bar .settings-item-svg{ display:flex;} +.video-js .vjs-control-bar .settings-item-svg svg{width:auto !important; height: auto !important; transform:inherit !important;} \ No newline at end of file diff --git a/frontend-tools/video-js/src/components/chapter/ChapterList.jsx b/frontend-tools/video-js/src/components/chapter/ChapterList.jsx new file mode 100644 index 00000000..4d9ce26b --- /dev/null +++ b/frontend-tools/video-js/src/components/chapter/ChapterList.jsx @@ -0,0 +1,156 @@ +import React from 'react'; + +function ChapterList() { + const playlistData = [ + { + id: 1, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: true + }, + { + id: 2, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + }, + { + id: 3, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + }, + { + id: 4, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + }, + { + id: 5, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + }, + { + id: 6, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + } + , + { + id: 7, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + } + , + { + id: 8, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + } + , + { + id: 9, + title: "Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge", + channel: "Physics Wallah - Alakh Pandey", + duration: "40:13", + thumbnail: "https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw", + selected: false + } + +]; + + + return ( +
+
+
+
+

12 chapter 1 II Electri charges and Fields JEE MAINS/NEET

+

Physics Wallah - Alakh Pandey 1 / 17

+
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ +
+
+ ); +} + +export default ChapterList; + diff --git a/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js b/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js index 67ef0a3d..995879d4 100644 --- a/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js +++ b/frontend-tools/video-js/src/components/controls/AutoplayToggleButton.js @@ -62,7 +62,11 @@ class AutoplayToggleButton extends Button { updateIcon() { if (this.isAutoplayEnabled) { // Simple text icon for now - this.iconSpan.innerHTML = ``; + this.iconSpan.innerHTML = ` + + + +`; // Only update element properties if element exists if (this.el()) { this.el().title = 'Autoplay is on'; @@ -74,7 +78,12 @@ class AutoplayToggleButton extends Button { } } else { // Simple text icon for now - this.iconSpan.innerHTML = ``; + this.iconSpan.innerHTML = ` + + + + +`; // Only update element properties if element exists if (this.el()) { this.el().title = 'Autoplay is off'; diff --git a/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js index f3db81ec..2986bd5f 100644 --- a/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js +++ b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js @@ -11,6 +11,9 @@ class CustomChaptersOverlay extends Component { this.chaptersData = options.chaptersData || []; this.overlay = null; this.chaptersList = null; + this.seriesTitle = options.seriesTitle || 'Chapters'; + this.channelName = options.channelName || ''; + this.thumbnail = options.thumbnail || ''; // Bind methods this.createOverlay = this.createOverlay.bind(this); @@ -39,101 +42,124 @@ class CustomChaptersOverlay extends Component { position: absolute; top: 0; right: 0; - width: 300px; + width: 100%; height: 100%; - background: linear-gradient(180deg, rgba(20, 20, 30, 0.95) 0%, rgba(40, 40, 50, 0.95) 100%); - color: white; z-index: 1000; display: none; - overflow-y: auto; - box-shadow: -4px 0 20px rgba(0, 0, 0, 0.5); + pointer-events: none; /* allow clicks only on inner panel */ + background: rgba(0, 0, 0, 0.35); `; + // Build container + const container = document.createElement('div'); + container.className = 'video-chapter'; + container.style.pointerEvents = 'auto'; + this.overlay.appendChild(container); + // Create header const header = document.createElement('div'); - header.style.cssText = ` - background: rgba(0, 0, 0, 0.8); - padding: 20px; - text-align: center; - font-weight: bold; - font-size: 14px; - letter-spacing: 2px; - border-bottom: 2px solid #4a90e2; - position: sticky; - top: 0; + header.className = 'chapter-head'; + container.appendChild(header); + + const playlistTitle = document.createElement('div'); + playlistTitle.className = 'playlist-title'; + header.appendChild(playlistTitle); + + const chapterTitle = document.createElement('div'); + chapterTitle.className = 'chapter-title'; + chapterTitle.innerHTML = ` +

${this.seriesTitle}

+

${this.channelName} 1 / ${this.chaptersData.length}

`; - header.textContent = 'CHAPTERS'; - this.overlay.appendChild(header); + playlistTitle.appendChild(chapterTitle); // Create close button - const closeBtn = document.createElement('div'); - closeBtn.style.cssText = ` - position: absolute; - top: 15px; - right: 15px; - width: 25px; - height: 25px; - background: rgba(0, 0, 0, 0.6); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - font-size: 16px; - z-index: 10; + const chapterClose = document.createElement('div'); + chapterClose.className = 'chapter-close'; + const closeBtn = document.createElement('button'); + closeBtn.setAttribute('aria-label', 'Close chapters'); + closeBtn.innerHTML = ` + + + `; - closeBtn.textContent = '×'; closeBtn.onclick = () => { this.overlay.style.display = 'none'; + const el = this.player().el(); + if (el) el.classList.remove('chapters-open'); }; - this.overlay.appendChild(closeBtn); + chapterClose.appendChild(closeBtn); + playlistTitle.appendChild(chapterClose); // Create chapters list - this.chaptersList = document.createElement('div'); - this.chaptersList.style.cssText = ` - padding: 10px 0; - `; + const body = document.createElement('div'); + body.className = 'chapter-body'; + container.appendChild(body); + + const list = document.createElement('ul'); + body.appendChild(list); + this.chaptersList = list; // Add chapters from data - this.chaptersData.forEach((chapter) => { - const chapterItem = document.createElement('div'); - chapterItem.style.cssText = ` - padding: 15px 20px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - cursor: pointer; - transition: background 0.2s ease; - font-size: 14px; - line-height: 1.4; - `; - chapterItem.textContent = chapter.text; + this.chaptersData.forEach((chapter, index) => { + const li = document.createElement('li'); + const item = document.createElement('div'); + item.className = `playlist-items ${index === 0 ? 'selected' : ''}`; - // Add hover effect - chapterItem.onmouseenter = () => { - chapterItem.style.background = 'rgba(74, 144, 226, 0.2)'; - }; - chapterItem.onmouseleave = () => { - chapterItem.style.background = 'transparent'; - }; + const anchor = document.createElement('a'); + anchor.href = '#'; + anchor.onclick = (e) => e.preventDefault(); - // Add click handler - chapterItem.onclick = () => { + const drag = document.createElement('div'); + drag.className = 'playlist-drag-handle'; + drag.textContent = index === 0 ? '▶' : String(index + 1); + + const meta = document.createElement('div'); + meta.className = 'thumbnail-meta'; + // compute duration + const totalSec = Math.max(0, Math.floor((chapter.endTime || chapter.startTime) - chapter.startTime)); + const hh = Math.floor(totalSec / 3600); + const mm = Math.floor((totalSec % 3600) / 60); + const ss = totalSec % 60; + const timeStr = hh > 0 + ? `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')}` + : `${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')}`; + const titleEl = document.createElement('h4'); + titleEl.textContent = chapter.text; + const sub = document.createElement('div'); + sub.className = 'meta-sub'; + const dynamic = document.createElement('span'); + dynamic.className = 'meta-dynamic'; + dynamic.textContent = this.channelName; + dynamic.setAttribute('data-duration', timeStr); + sub.appendChild(dynamic); + meta.appendChild(titleEl); + meta.appendChild(sub); + + const action = document.createElement('div'); + action.className = 'thumbnail-action'; + const btn = document.createElement('button'); + btn.innerHTML = ` + + + `; + action.appendChild(btn); + + // Click to seek + item.onclick = () => { this.player().currentTime(chapter.startTime); this.overlay.style.display = 'none'; - - // Update active state - this.chaptersList.querySelectorAll('div').forEach((item) => { - item.style.background = 'transparent'; - item.style.fontWeight = 'normal'; - }); - chapterItem.style.background = 'rgba(74, 144, 226, 0.4)'; - chapterItem.style.fontWeight = 'bold'; + this.updateActiveItem(index); }; - this.chaptersList.appendChild(chapterItem); + anchor.appendChild(drag); + anchor.appendChild(meta); + anchor.appendChild(action); + item.appendChild(anchor); + li.appendChild(item); + this.chaptersList.appendChild(li); }); - this.overlay.appendChild(this.chaptersList); - // Add to player playerEl.appendChild(this.overlay); @@ -155,10 +181,20 @@ class CustomChaptersOverlay extends Component { toggleOverlay() { if (!this.overlay) return; + const el = this.player().el(); if (this.overlay.style.display === 'none' || !this.overlay.style.display) { this.overlay.style.display = 'block'; + if (el) el.classList.add('chapters-open'); + // hide any open menus + try { + this.player().el().querySelectorAll('.vjs-menu').forEach((m) => { + m.classList.remove('vjs-lock-showing'); + m.style.display = 'none'; + }); + } catch (e) {} } else { this.overlay.style.display = 'none'; + if (el) el.classList.remove('chapters-open'); } } @@ -166,7 +202,7 @@ class CustomChaptersOverlay extends Component { if (!this.chaptersList || !this.chaptersData) return; const currentTime = this.player().currentTime(); - const chapterItems = this.chaptersList.querySelectorAll('div'); + const chapterItems = this.chaptersList.querySelectorAll('.playlist-items'); chapterItems.forEach((item, index) => { const chapter = this.chaptersData[index]; @@ -174,12 +210,33 @@ class CustomChaptersOverlay extends Component { currentTime >= chapter.startTime && (index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime); + const handle = item.querySelector('.playlist-drag-handle'); + const dynamic = item.querySelector('.meta-dynamic'); if (isPlaying) { - item.style.borderLeft = '4px solid #10b981'; - item.style.paddingLeft = '16px'; + item.classList.add('selected'); + if (handle) handle.textContent = '▶'; + if (dynamic) dynamic.textContent = dynamic.getAttribute('data-duration') || ''; } else { - item.style.borderLeft = 'none'; - item.style.paddingLeft = '20px'; + item.classList.remove('selected'); + if (handle) handle.textContent = String(index + 1); + if (dynamic) dynamic.textContent = this.channelName; + } + }); + } + + updateActiveItem(activeIndex) { + const items = this.chaptersList.querySelectorAll('.playlist-items'); + items.forEach((el, idx) => { + const handle = el.querySelector('.playlist-drag-handle'); + const dynamic = el.querySelector('.meta-dynamic'); + if (idx === activeIndex) { + el.classList.add('selected'); + if (handle) handle.textContent = '▶'; + if (dynamic) dynamic.textContent = dynamic.getAttribute('data-duration') || ''; + } else { + el.classList.remove('selected'); + if (handle) handle.textContent = String(idx + 1); + if (dynamic) dynamic.textContent = this.channelName; } }); } @@ -188,6 +245,8 @@ class CustomChaptersOverlay extends Component { if (this.overlay) { this.overlay.remove(); } + const el = this.player().el(); + if (el) el.classList.remove('chapters-open'); super.dispose(); } } diff --git a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css index 8192c7f6..7230fada 100644 --- a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css +++ b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css @@ -31,7 +31,7 @@ bottom: 60px; right: 20px; width: 250px; - height: 600px; + height: 400px; background: rgba(28, 28, 28, 0.95); color: white; border-radius: 7px; @@ -59,6 +59,8 @@ border-bottom: 1px solid rgba(255, 255, 255, 0.1); transition: background 0.2s ease; } +.custom-settings-overlay .settings-left span.vjs-icon-placeholder {transform: inherit !important;} + .settings-item:last-child { border-bottom: none; @@ -80,6 +82,18 @@ flex-direction: column; } +/* Quality submenu mirrors speed submenu */ +.quality-submenu { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(28, 28, 28, 0.95); + display: none; + flex-direction: column; +} + /* Submenu header */ .submenu-header { padding: 12px 16px; @@ -110,8 +124,50 @@ .speed-option.active { background: rgba(255, 255, 255, 0.1); } + +/* Quality option styling */ +.quality-option { + padding: 12px 16px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + transition: background 0.2s ease; +} + +.quality-option:hover { + background: rgba(255, 255, 255, 0.05); +} + +.quality-option.active { + background: rgba(255, 255, 255, 0.1); +} + +/* Settings row left/right layout like YouTube */ +.settings-left { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.settings-right { + display: inline-flex; + align-items: center; + gap: 8px; +} .vjs-icon-cog:before { font-size: 20px !important; position: relative; top: -5px !important; } + +/* HD superscript badge for 1080p */ +sup.hd-badge { + font-size: 10px; + line-height: 1; + margin-left: 6px; + background: #e53935; + color: #fff; + padding: 1px 4px; + border-radius: 3px; +} diff --git a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js index 28edd5d0..53eaf849 100644 --- a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js +++ b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js @@ -1,263 +1,567 @@ // components/controls/CustomSettingsMenu.js -import videojs from 'video.js'; -import './CustomSettingsMenu.css'; -import UserPreferences from '../../utils/UserPreferences'; +import videojs from "video.js"; +import "./CustomSettingsMenu.css"; +import UserPreferences from "../../utils/UserPreferences"; // Get the Component base class from Video.js -const Component = videojs.getComponent('Component'); +const Component = videojs.getComponent("Component"); class CustomSettingsMenu extends Component { - constructor(player, options) { - super(player, options); + constructor(player, options) { + super(player, options); - this.settingsButton = null; - this.settingsOverlay = null; - this.speedSubmenu = null; - this.userPreferences = options?.userPreferences || new UserPreferences(); + this.settingsButton = null; + this.settingsOverlay = null; + this.speedSubmenu = null; + this.qualitySubmenu = null; + this.userPreferences = options?.userPreferences || new UserPreferences(); + this.providedQualities = options?.qualities || null; - // Bind methods - this.createSettingsButton = this.createSettingsButton.bind(this); - this.createSettingsOverlay = this.createSettingsOverlay.bind(this); - this.positionButton = this.positionButton.bind(this); - this.toggleSettings = this.toggleSettings.bind(this); - this.handleSpeedChange = this.handleSpeedChange.bind(this); - this.handleClickOutside = this.handleClickOutside.bind(this); + // Bind methods + this.createSettingsButton = this.createSettingsButton.bind(this); + this.createSettingsOverlay = this.createSettingsOverlay.bind(this); + this.positionButton = this.positionButton.bind(this); + this.toggleSettings = this.toggleSettings.bind(this); + this.handleSpeedChange = this.handleSpeedChange.bind(this); + this.handleQualityChange = this.handleQualityChange.bind(this); + this.getAvailableQualities = this.getAvailableQualities.bind(this); + this.handleClickOutside = this.handleClickOutside.bind(this); - // Initialize after player is ready - this.player().ready(() => { - this.createSettingsButton(); - this.createSettingsOverlay(); - this.setupEventListeners(); - }); - } + // Initialize after player is ready + this.player().ready(() => { + this.createSettingsButton(); + this.createSettingsOverlay(); + this.setupEventListeners(); + }); + } - createSettingsButton() { - const controlBar = this.player().getChild('controlBar'); + createSettingsButton() { + const controlBar = this.player().getChild("controlBar"); - // Hide default playback rate button - const playbackRateButton = controlBar.getChild('playbackRateMenuButton'); - if (playbackRateButton) { - playbackRateButton.hide(); - } + // Do NOT hide default playback rate button to avoid control bar layout shifts - // Create settings button - this.settingsButton = controlBar.addChild('button', { - controlText: 'Settings', - className: 'vjs-settings-button', - }); + // Create settings button + this.settingsButton = controlBar.addChild("button", { + controlText: "Settings", + className: "vjs-settings-button", + }); - // Style the settings button (gear icon) - const settingsButtonEl = this.settingsButton.el(); - settingsButtonEl.innerHTML = ` + // Style the settings button (gear icon) + const settingsButtonEl = this.settingsButton.el(); + settingsButtonEl.innerHTML = ` `; - // Position the settings button at the end of the control bar - this.positionButton(); + // Position the settings button at the end of the control bar + this.positionButton(); - // Add click handler - this.settingsButton.on('click', this.toggleSettings); - } + // Add click handler + this.settingsButton.on("click", this.toggleSettings); + } - createSettingsOverlay() { - const controlBar = this.player().getChild('controlBar'); + createSettingsOverlay() { + const controlBar = this.player().getChild("controlBar"); - // Create settings overlay - this.settingsOverlay = document.createElement('div'); - this.settingsOverlay.className = 'custom-settings-overlay'; + // Create settings overlay + this.settingsOverlay = document.createElement("div"); + this.settingsOverlay.className = "custom-settings-overlay"; - // Get current preferences for display - const currentPlaybackRate = this.userPreferences.getPreference('playbackRate'); - const currentQuality = this.userPreferences.getPreference('quality'); + // Get current preferences for display + const currentPlaybackRate = + this.userPreferences.getPreference("playbackRate"); + const currentQuality = this.userPreferences.getPreference("quality"); - // Format playback rate for display - const playbackRateLabel = currentPlaybackRate === 1 ? 'Normal' : `${currentPlaybackRate}`; - const qualityLabel = currentQuality.charAt(0).toUpperCase() + currentQuality.slice(1); + // Format playback rate for display + const playbackRateLabel = + currentPlaybackRate === 1 ? "Normal" : `${currentPlaybackRate}`; + const qualities = this.getAvailableQualities(); + const activeQuality = + qualities.find((q) => q.value === currentQuality) || qualities[0]; + const qualityLabelHTML = + activeQuality?.displayLabel || + activeQuality?.label || + (currentQuality ? String(currentQuality) : "Auto"); - // Settings menu content - this.settingsOverlay.innerHTML = ` -
Settings
- -
- Playback speed - ${playbackRateLabel} -
- -
- Quality - ${qualityLabel} -
- `; + // Settings menu content + this.settingsOverlay.innerHTML = ` +
Settings
+ +
+ + + + + Playback speed + + ${playbackRateLabel} + + +
+ +
+ + + + + Quality + + ${qualityLabelHTML} + + +
+`; - // Create speed submenu - this.createSpeedSubmenu(); + // Create speed submenu + this.createSpeedSubmenu(); - // Add to control bar - controlBar.el().appendChild(this.settingsOverlay); - } + // Create quality submenu + this.createQualitySubmenu(qualities, activeQuality?.value); - createSpeedSubmenu() { - const speedOptions = [ - { label: '0.25', value: 0.25 }, - { label: '0.5', value: 0.5 }, - { label: '0.75', value: 0.75 }, - { label: 'Normal', value: 1 }, - { label: '1.25', value: 1.25 }, - { label: '1.5', value: 1.5 }, - { label: '1.75', value: 1.75 }, - { label: '2', value: 2 }, - ]; + // Add to control bar + controlBar.el().appendChild(this.settingsOverlay); + } - this.speedSubmenu = document.createElement('div'); - this.speedSubmenu.className = 'speed-submenu'; + createSpeedSubmenu() { + const speedOptions = [ + { label: "0.25", value: 0.25 }, + { label: "0.5", value: 0.5 }, + { label: "0.75", value: 0.75 }, + { label: "Normal", value: 1 }, + { label: "1.25", value: 1.25 }, + { label: "1.5", value: 1.5 }, + { label: "1.75", value: 1.75 }, + { label: "2", value: 2 }, + ]; - // Get current playback rate for highlighting - const currentRate = this.userPreferences.getPreference('playbackRate'); + this.speedSubmenu = document.createElement("div"); + this.speedSubmenu.className = "speed-submenu"; - this.speedSubmenu.innerHTML = ` + // Get current playback rate for highlighting + const currentRate = this.userPreferences.getPreference("playbackRate"); + + this.speedSubmenu.innerHTML = ` ${speedOptions - .map( - (option) => ` -
+ .map( + (option) => ` +
${option.label} - ${option.value === currentRate ? '' : ''} + ${option.value === currentRate ? '' : ""}
` - ) - .join('')} + ) + .join("")} `; - this.settingsOverlay.appendChild(this.speedSubmenu); + this.settingsOverlay.appendChild(this.speedSubmenu); + } + + createQualitySubmenu(qualities, currentValue) { + this.qualitySubmenu = document.createElement("div"); + this.qualitySubmenu.className = "quality-submenu"; + + const header = ` + + `; + + const optionsHtml = qualities + .map( + (q) => ` +
+ ${q.displayLabel || q.label} + ${q.value === currentValue ? '' : ""} +
+ ` + ) + .join(""); + + this.qualitySubmenu.innerHTML = header + optionsHtml; + this.settingsOverlay.appendChild(this.qualitySubmenu); + } + + getAvailableQualities() { + // Priority: provided options -> MEDIA_DATA JSON -> player sources -> default + const desiredOrder = [ + "auto", + "144p", + "240p", + "360p", + "480p", + "720p", + "1080p", + ]; + + if ( + Array.isArray(this.providedQualities) && + this.providedQualities.length + ) { + return this.sortAndDecorateQualities( + this.providedQualities, + desiredOrder + ); } - positionButton() { - const controlBar = this.player().getChild('controlBar'); - const fullscreenToggle = controlBar.getChild('fullscreenToggle'); + try { + const md = typeof window !== "undefined" ? window.MEDIA_DATA : null; + const jsonQualities = md?.data?.qualities; + if (Array.isArray(jsonQualities) && jsonQualities.length) { + // Expected format: [{label: '1080p', value: '1080p', src: '...'}] + const normalized = jsonQualities.map((q) => ({ + label: q.label || q.value || "Auto", + value: (q.value || q.label || "auto").toString().toLowerCase(), + src: q.src || q.url || q.href, + type: q.type || "video/mp4", + })); + return this.sortAndDecorateQualities(normalized, desiredOrder); + } + } catch (e) { + // ignore + } - if (this.settingsButton && fullscreenToggle) { - // Small delay to ensure all buttons are created - setTimeout(() => { - const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle); - controlBar.removeChild(this.settingsButton); - controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1); - console.log('✓ Settings button positioned after fullscreen toggle'); - }, 50); + // Derive from player's current sources + const sources = this.player().currentSources + ? this.player().currentSources() + : this.player().currentSrc(); + if (Array.isArray(sources) && sources.length > 0) { + const mapped = sources.map((s, idx) => { + const label = + s.label || + s.res || + this.inferLabelFromSrc(s.src) || + (idx === 0 ? "Auto" : `Source ${idx + 1}`); + const value = String(label).toLowerCase(); + return { label, value, src: s.src, type: s.type || "video/mp4" }; + }); + return this.sortAndDecorateQualities(mapped, desiredOrder); + } + + // Default fallback + // Build full ordered list without src so UI is consistent; switching will require src in JSON + const fallback = desiredOrder.map((v) => ({ + label: v === "auto" ? "Auto" : v, + value: v, + })); + return this.sortAndDecorateQualities(fallback, desiredOrder); + } + + sortAndDecorateQualities(list, desiredOrder) { + const orderIndex = (val) => { + const i = desiredOrder.indexOf(String(val).toLowerCase()); + return i === -1 ? 999 : i; + }; + const decorated = list + .map((q) => { + const val = (q.value || q.label || "").toString().toLowerCase(); + const baseLabel = q.label || q.value || ""; + const is1080 = val === "1080p"; + const displayLabel = is1080 + ? `${baseLabel} HD` + : baseLabel; + return { ...q, value: val, label: baseLabel, displayLabel }; + }) + .sort((a, b) => orderIndex(a.value) - orderIndex(b.value)); + + // Ensure all desired labels appear at least once (even if not provided), for consistent menu + const have = new Set(decorated.map((q) => q.value)); + desiredOrder.forEach((val) => { + if (!have.has(val)) { + const baseLabel = val === "auto" ? "Auto" : val; + const displayLabel = + val === "1080p" + ? `${baseLabel} HD` + : baseLabel; + decorated.push({ label: baseLabel, value: val, displayLabel }); + } + }); + // Re-sort after pushing missing + decorated.sort((a, b) => orderIndex(a.value) - orderIndex(b.value)); + return decorated; + } + + inferLabelFromSrc(src) { + if (!src) return null; + // Try to detect typical resolution markers in file name or query string + const match = /(?:_|\.|\/)\D*(1440p|1080p|720p|480p|360p|240p|144p)/i.exec( + src + ); + if (match && match[1]) return match[1].toUpperCase(); + const m2 = /(\b\d{3,4})p\b/i.exec(src); + if (m2 && m2[1]) return `${m2[1]}p`; + return null; + } + + positionButton() { + const controlBar = this.player().getChild("controlBar"); + const fullscreenToggle = controlBar.getChild("fullscreenToggle"); + + if (this.settingsButton && fullscreenToggle) { + // Small delay to ensure all buttons are created + setTimeout(() => { + const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle); + controlBar.removeChild(this.settingsButton); + controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1); + console.log("✓ Settings button positioned after fullscreen toggle"); + }, 50); + } + } + + setupEventListeners() { + // Settings item clicks + this.settingsOverlay.addEventListener("click", (e) => { + e.stopPropagation(); + + if (e.target.closest('[data-setting="playback-speed"]')) { + this.speedSubmenu.style.display = "flex"; + this.qualitySubmenu.style.display = "none"; + } + + if (e.target.closest('[data-setting="quality"]')) { + this.qualitySubmenu.style.display = "flex"; + this.speedSubmenu.style.display = "none"; + } + }); + + // Speed submenu header (back button) + this.speedSubmenu + .querySelector(".submenu-header") + .addEventListener("click", () => { + this.speedSubmenu.style.display = "none"; + }); + + // Quality submenu header (back button) + this.qualitySubmenu + .querySelector(".submenu-header") + .addEventListener("click", () => { + this.qualitySubmenu.style.display = "none"; + }); + + // Speed option clicks + this.speedSubmenu.addEventListener("click", (e) => { + const speedOption = e.target.closest(".speed-option"); + if (speedOption) { + const speed = parseFloat(speedOption.dataset.speed); + this.handleSpeedChange(speed, speedOption); + } + }); + + // Quality option clicks + this.qualitySubmenu.addEventListener("click", (e) => { + const qualityOption = e.target.closest(".quality-option"); + if (qualityOption) { + const value = qualityOption.dataset.quality; + this.handleQualityChange(value, qualityOption); + } + }); + + // Close menu when clicking outside + document.addEventListener("click", this.handleClickOutside); + + // Add hover effects + this.settingsOverlay.addEventListener("mouseover", (e) => { + const item = e.target.closest(".settings-item, .speed-option"); + if (item && !item.style.background.includes("0.1")) { + item.style.background = "rgba(255, 255, 255, 0.05)"; + } + }); + + this.settingsOverlay.addEventListener("mouseout", (e) => { + const item = e.target.closest(".settings-item, .speed-option"); + if (item && !item.style.background.includes("0.1")) { + item.style.background = "transparent"; + } + }); + } + + toggleSettings(e) { + e.stopPropagation(); + const isVisible = this.settingsOverlay.style.display === "block"; + this.settingsOverlay.style.display = isVisible ? "none" : "block"; + this.speedSubmenu.style.display = "none"; // Hide submenu when main menu toggles + if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none"; + } + + handleSpeedChange(speed, speedOption) { + // Update player speed + this.player().playbackRate(speed); + + // Save preference + this.userPreferences.setPreference("playbackRate", speed); + + // Update UI + this.speedSubmenu.querySelectorAll(".speed-option").forEach((opt) => { + opt.classList.remove("active"); + opt.style.background = "transparent"; + const check = opt.querySelector(".checkmark"); + if (check) check.remove(); + }); + + speedOption.classList.add("active"); + speedOption.style.background = "rgba(255, 255, 255, 0.1)"; + speedOption.insertAdjacentHTML( + "beforeend", + '' + ); + + // Update main menu display + const currentSpeedDisplay = + this.settingsOverlay.querySelector(".current-speed"); + const speedLabel = speed === 1 ? "Normal" : `${speed}`; + currentSpeedDisplay.textContent = speedLabel; + + // Close only the speed submenu (keep overlay open) + this.speedSubmenu.style.display = "none"; + + console.log("Playback speed preference saved:", speed); + } + + handleQualityChange(value, qualityOption) { + const qualities = this.getAvailableQualities(); + const selected = qualities.find((q) => String(q.value) === String(value)); + + // Save preference + this.userPreferences.setQualityPreference(value); + + // Update UI + this.qualitySubmenu.querySelectorAll(".quality-option").forEach((opt) => { + opt.classList.remove("active"); + opt.style.background = "transparent"; + const check = opt.querySelector(".checkmark"); + if (check) check.remove(); + }); + + qualityOption.classList.add("active"); + qualityOption.style.background = "rgba(255, 255, 255, 0.1)"; + qualityOption.insertAdjacentHTML( + "beforeend", + '' + ); + + // Update main menu display + const currentQualityDisplay = + this.settingsOverlay.querySelector(".current-quality"); + currentQualityDisplay.innerHTML = + selected?.displayLabel || selected?.label || String(value); + + // Perform source switch if we have src defined + if (selected?.src) { + const player = this.player(); + const wasPaused = player.paused(); + const currentTime = player.currentTime(); + const rate = player.playbackRate(); + + // Try to preserve active subtitle track + const textTracks = player.textTracks(); + let activeSubtitleLang = null; + for (let i = 0; i < textTracks.length; i++) { + const track = textTracks[i]; + if (track.kind === "subtitles" && track.mode === "showing") { + activeSubtitleLang = track.language; + break; } - } + } - setupEventListeners() { - // Settings item clicks - this.settingsOverlay.addEventListener('click', (e) => { - e.stopPropagation(); + console.log("Switching quality to", selected.label, selected.src); - if (e.target.closest('[data-setting="playback-speed"]')) { - this.speedSubmenu.style.display = 'flex'; - } - }); + player.addClass("vjs-changing-resolution"); + player.src({ src: selected.src, type: selected.type || "video/mp4" }); - // Speed submenu header (back button) - this.speedSubmenu.querySelector('.submenu-header').addEventListener('click', () => { - this.speedSubmenu.style.display = 'none'; - }); - - // Speed option clicks - this.speedSubmenu.addEventListener('click', (e) => { - const speedOption = e.target.closest('.speed-option'); - if (speedOption) { - const speed = parseFloat(speedOption.dataset.speed); - this.handleSpeedChange(speed, speedOption); - } - }); - - // Close menu when clicking outside - document.addEventListener('click', this.handleClickOutside); - - // Add hover effects - this.settingsOverlay.addEventListener('mouseover', (e) => { - const item = e.target.closest('.settings-item, .speed-option'); - if (item && !item.style.background.includes('0.1')) { - item.style.background = 'rgba(255, 255, 255, 0.05)'; - } - }); - - this.settingsOverlay.addEventListener('mouseout', (e) => { - const item = e.target.closest('.settings-item, .speed-option'); - if (item && !item.style.background.includes('0.1')) { - item.style.background = 'transparent'; - } - }); - } - - toggleSettings(e) { - e.stopPropagation(); - const isVisible = this.settingsOverlay.style.display === 'block'; - this.settingsOverlay.style.display = isVisible ? 'none' : 'block'; - this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles - } - - handleSpeedChange(speed, speedOption) { - // Update player speed - this.player().playbackRate(speed); - - // Save preference - this.userPreferences.setPreference('playbackRate', speed); - - // Update UI - document.querySelectorAll('.speed-option').forEach((opt) => { - opt.classList.remove('active'); - opt.style.background = 'transparent'; - opt.querySelector('span:last-child')?.remove(); - }); - - speedOption.classList.add('active'); - speedOption.style.background = 'rgba(255, 255, 255, 0.1)'; - speedOption.insertAdjacentHTML('beforeend', ''); - - // Update main menu display - const currentSpeedDisplay = this.settingsOverlay.querySelector('.current-speed'); - const speedLabel = speed === 1 ? 'Normal' : `${speed}`; - currentSpeedDisplay.textContent = speedLabel; - - // Hide menus - this.settingsOverlay.style.display = 'none'; - this.speedSubmenu.style.display = 'none'; - - console.log('Playback speed preference saved:', speed); - } - - handleClickOutside(e) { - if ( - this.settingsOverlay && - this.settingsButton && - !this.settingsOverlay.contains(e.target) && - !this.settingsButton.el().contains(e.target) - ) { - this.settingsOverlay.style.display = 'none'; - this.speedSubmenu.style.display = 'none'; - } - } - - dispose() { - // Remove event listeners - document.removeEventListener('click', this.handleClickOutside); - - // Remove DOM elements - if (this.settingsOverlay) { - this.settingsOverlay.remove(); + const onLoaded = () => { + // Restore time, rate, subtitles + try { + player.playbackRate(rate); + } catch (e) {} + try { + if (!isNaN(currentTime)) player.currentTime(currentTime); + } catch (e) {} + if (!wasPaused) { + player.play().catch(() => {}); } - super.dispose(); + // Restore subtitles + if (activeSubtitleLang) { + const tt = player.textTracks(); + for (let i = 0; i < tt.length; i++) { + const t = tt[i]; + if (t.kind === "subtitles") { + t.mode = + t.language === activeSubtitleLang ? "showing" : "disabled"; + } + } + } + + // Ensure Subtitles (CC) button remains visible after source switch + try { + const controlBar = player.getChild("controlBar"); + const names = [ + "subtitlesButton", + "textTrackButton", + "subsCapsButton", + ]; + for (const n of names) { + const btn = controlBar && controlBar.getChild(n); + if (btn) { + if (typeof btn.show === "function") btn.show(); + const el = btn.el && btn.el(); + if (el) { + el.style.display = ""; + el.style.visibility = ""; + } + } + } + } catch (e) { + // noop + } + + player.removeClass("vjs-changing-resolution"); + player.off("loadedmetadata", onLoaded); + }; + + player.on("loadedmetadata", onLoaded); } + + // Close overlay to avoid covering the CC button + if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none"; + this.settingsOverlay.style.display = "none"; + + console.log("Quality preference saved:", value); + } + + handleClickOutside(e) { + if ( + this.settingsOverlay && + this.settingsButton && + !this.settingsOverlay.contains(e.target) && + !this.settingsButton.el().contains(e.target) + ) { + this.settingsOverlay.style.display = "none"; + this.speedSubmenu.style.display = "none"; + if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none"; + } + } + + dispose() { + // Remove event listeners + document.removeEventListener("click", this.handleClickOutside); + + // Remove DOM elements + if (this.settingsOverlay) { + this.settingsOverlay.remove(); + } + + super.dispose(); + } } // Set component name for Video.js -CustomSettingsMenu.prototype.controlText_ = 'Settings Menu'; +CustomSettingsMenu.prototype.controlText_ = "Settings Menu"; // Register the component with Video.js -videojs.registerComponent('CustomSettingsMenu', CustomSettingsMenu); +videojs.registerComponent("CustomSettingsMenu", CustomSettingsMenu); export default CustomSettingsMenu; diff --git a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx index 81f0e314..decb9d5c 100644 --- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx +++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx @@ -26,556 +26,561 @@ function VideoJSPlayer() { typeof window !== 'undefined' && window.MEDIA_DATA ? window.MEDIA_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: { - url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/fe4933d67b884d4da507dd60e77f7438.VID_20200909_141053.mp4sprites.jpg', - frame: { width: 160, height: 90, seconds: 10 }, - }, - siteUrl: '', - nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO', - }, + 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: { + url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/fe4933d67b884d4da507dd60e77f7438.VID_20200909_141053.mp4sprites.jpg', + frame: { width: 160, height: 90, seconds: 10 }, + }, + siteUrl: '', + nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO', + chaptersData: [ + { startTime: 0, endTime: 5, text: 'Start111' }, + { startTime: 5, endTime: 10, text: 'Introduction - EuroHPC' }, + { startTime: 10, endTime: 15, text: 'Planning - EuroHPC' }, + ], + }, [] ); // Define chapters as JSON object // Note: The sample-chapters.vtt file is no longer needed as chapters are now loaded from this JSON const chaptersData = mediaData.chaptersData; -// [ -// { startTime: 0, endTime: 5, text: 'Start111' }, -// { startTime: 5, endTime: 10, text: 'Introduction - EuroHPC' }, -// { startTime: 10, endTime: 15, text: 'Planning - EuroHPC' }, -// ]; + /* [ + { startTime: 0, endTime: 5, text: 'Start111' }, + { startTime: 5, endTime: 10, text: 'Introduction - EuroHPC' }, + { startTime: 10, endTime: 15, text: 'Planning - EuroHPC' }, + ]; */ // Get video data from mediaData const currentVideo = useMemo( @@ -588,21 +593,66 @@ function VideoJSPlayer() { nextLink: mediaData?.nextLink || null, sources: mediaData.data?.original_media_url ? [ - { - src: mediaData.siteUrl + mediaData.data.original_media_url, - type: 'video/mp4', - }, - ] + { + src: mediaData.siteUrl + mediaData.data.original_media_url, + type: 'video/mp4', + }, + ] : [ - { - src: '/videos/sample-video.mp4', - type: 'video/mp4', - }, - ], + { + src: '/videos/sample-video.mp4', + type: 'video/mp4', + }, + ], }), [mediaData] ); + // Compute available qualities. Prefer JSON (mediaData.data.qualities), otherwise build a full ordered list using the current source. + const availableQualities = useMemo(() => { + const desiredOrder = ['auto', '144p', '240p', '360p', '480p', '720p', '1080p']; + + const normalize = (arr) => { + const norm = arr.map((q) => ({ + label: q.label || q.value || 'Auto', + value: (q.value || q.label || 'auto').toString().toLowerCase(), + src: q.src || q.url || q.href, + type: q.type || 'video/mp4', + })); + // ensure all desired present + const have = new Set(norm.map((q) => q.value)); + desiredOrder.forEach((v) => { + if (!have.has(v)) { + norm.push({ label: v === 'auto' ? 'Auto' : v, value: v }); + } + }); + // sort + const idx = (v) => { + const i = desiredOrder.indexOf(String(v).toLowerCase()); + return i === -1 ? 999 : i; + }; + norm.sort((a, b) => idx(a.value) - idx(b.value)); + return norm; + }; + + const jsonList = mediaData?.data?.qualities; + if (Array.isArray(jsonList) && jsonList.length) { + return normalize(jsonList); + } + + // Build from current source + const baseSrc = (currentVideo?.sources && currentVideo.sources[0]?.src) || null; + const type = (currentVideo?.sources && currentVideo.sources[0]?.type) || 'video/mp4'; + + const buildFromBase = desiredOrder.map((v) => ({ + label: v === 'auto' ? 'Auto' : v, + value: v, + src: baseSrc || undefined, + type, + })); + return normalize(buildFromBase); + }, [mediaData, currentVideo]); + // Get related videos from mediaData instead of static data const relatedVideos = useMemo(() => { if (!mediaData?.data?.related_media) { @@ -858,16 +908,16 @@ function VideoJSPlayer() { // Picture-in-picture toggle button pictureInPictureToggle: true, - // Playback rate menu button - playbackRateMenuButton: true, + // Remove default playback speed dropdown from control bar + playbackRateMenuButton: false, // Descriptions button descriptionsButton: true, - // Subtitles button - subtitlesButton: false, + // Subtitles (CC) button should be visible + subtitlesButton: true, - // Captions button (disabled to avoid duplicate) + // Captions button (keep disabled to avoid duplicate with subtitles) captionsButton: false, // Audio track button @@ -982,6 +1032,7 @@ function VideoJSPlayer() { // END: Add subtitle tracks // BEGIN: Chapters Implementation + console.log('chaptersData1', chaptersData); if (chaptersData && chaptersData.length > 0) { const chaptersTrack = playerRef.current.addTextTrack('chapters', 'Chapters', 'en'); // Add cues to the chapters track @@ -1036,8 +1087,8 @@ function VideoJSPlayer() { userPreferences: userPreferences.current, }); // Add it after the play button - const playToggleIndex = controlBar.children().indexOf(playToggle); - controlBar.addChild(autoplayToggleButton, {}, playToggleIndex + 1); + const fullscreenToggleIndex = controlBar.children().indexOf(fullscreenToggle); + controlBar.addChild(autoplayToggleButton, {}, fullscreenToggleIndex - 1); // Store reference for later use customComponents.current.autoplayToggleButton = autoplayToggleButton; @@ -1204,10 +1255,14 @@ function VideoJSPlayer() { } // END: Move chapters button after fullscreen toggle + console.log('chaptersData', chaptersData); // BEGIN: Add Chapters Overlay Component if (chaptersData && chaptersData.length > 0) { customComponents.current.chaptersOverlay = new CustomChaptersOverlay(playerRef.current, { chaptersData: chaptersData, + seriesTitle: mediaData?.data?.title || 'Chapters', + channelName: mediaData?.data?.user || mediaData?.data?.author_name || 'Channel', + thumbnail: mediaData?.data?.thumbnail_url || mediaData?.data?.author_thumbnail || '', }); } else { console.log('⚠ No chapters data available for overlay'); @@ -1217,6 +1272,7 @@ function VideoJSPlayer() { // BEGIN: Add Settings Menu Component customComponents.current.settingsMenu = new CustomSettingsMenu(playerRef.current, { userPreferences: userPreferences.current, + qualities: availableQualities, }); // END: Add Settings Menu Component diff --git a/frontend-tools/video-js/src/main.jsx b/frontend-tools/video-js/src/main.jsx index 68d41f9a..760de73e 100644 --- a/frontend-tools/video-js/src/main.jsx +++ b/frontend-tools/video-js/src/main.jsx @@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client'; import './VideoJS.css'; import VideoJS from './VideoJS.jsx'; +// import ChapterList from './components/chapter/ChapterList.jsx'; // Mount the components when the DOM is ready const mountComponents = () => { @@ -11,7 +12,12 @@ const mountComponents = () => { const root = createRoot(rootContainer); root.render( - +
+
+ +
+ {/* */} +
); }