fix: init Plyr before HLS manifest loads to avoid bare video flash
All checks were successful
CI / test (push) Successful in 45s

Create Plyr instance immediately in start() so the styled player UI
appears right away. Quality and audio controls are injected once the
HLS manifest is ready, running doAdd() directly when player.ready is
already true instead of waiting on the 'ready' event that already fired.
This commit is contained in:
2026-05-03 13:45:00 -05:00
parent 07ca635a84
commit b9248cfe2d

View File

@@ -150,7 +150,7 @@
* Create custom quality control in Plyr controls * Create custom quality control in Plyr controls
*/ */
function addCustomQualityControl(player, qualityLabels) { function addCustomQualityControl(player, qualityLabels) {
player.on('ready', () => { function doAdd() {
console.log('Adding custom quality control...'); console.log('Adding custom quality control...');
const controls = player.elements.container.querySelector('.plyr__controls'); const controls = player.elements.container.querySelector('.plyr__controls');
@@ -238,14 +238,21 @@
} }
console.log('Custom quality control added'); console.log('Custom quality control added');
}); }
// Run immediately if Plyr is already ready, otherwise wait
if (player.ready) {
doAdd();
} else {
player.on('ready', doAdd);
}
} }
/** /**
* Create custom audio tracks control in Plyr controls * Create custom audio tracks control in Plyr controls
*/ */
function addCustomAudioTracksControl(player, hlsInstance) { function addCustomAudioTracksControl(player, hlsInstance) {
player.on('ready', () => { function doAdd() {
console.log('Adding custom audio tracks control...'); console.log('Adding custom audio tracks control...');
const controls = player.elements.container.querySelector('.plyr__controls'); const controls = player.elements.container.querySelector('.plyr__controls');
@@ -397,52 +404,32 @@
}); });
console.log('Custom audio tracks control added'); console.log('Custom audio tracks control added');
}); }
// Run immediately if Plyr is already ready, otherwise wait
if (player.ready) {
doAdd();
} else {
player.on('ready', doAdd);
}
} }
/** /**
* Initialize Plyr with HLS quality options * Main initialization
*/ */
function initPlyrWithQuality(hlsInstance) { async function start() {
console.log('Starting Plyr with HLS...');
if (typeof hls_manifest_url === 'undefined' || !hls_manifest_url) {
console.error('No HLS manifest URL available');
return;
}
// Initialize Plyr immediately so the player UI shows right away
// instead of a bare <video> element while the manifest loads.
const video = document.getElementById('js-video-player'); const video = document.getElementById('js-video-player');
if (video) {
if (!hlsInstance || !hlsInstance.levels || hlsInstance.levels.length === 0) { plyrInstance = new Plyr(video, {
console.error('HLS not ready');
return;
}
if (!video) {
console.error('Video element not found');
return;
}
console.log('HLS levels available:', hlsInstance.levels.length);
const sortedLevels = [...hlsInstance.levels].sort((a, b) => b.height - a.height);
const seenHeights = new Set();
const uniqueLevels = [];
sortedLevels.forEach((level) => {
if (!seenHeights.has(level.height)) {
seenHeights.add(level.height);
uniqueLevels.push(level);
}
});
const qualityLabels = ['auto'];
uniqueLevels.forEach((level) => {
const originalIndex = hlsInstance.levels.indexOf(level);
const label = level.height + 'p';
if (!window.hlsQualityMap[label]) {
qualityLabels.push(label);
window.hlsQualityMap[label] = originalIndex;
}
});
console.log('Quality labels:', qualityLabels);
const playerOptions = {
autoplay: autoplayActive, autoplay: autoplayActive,
disableContextMenu: false, disableContextMenu: false,
captions: { captions: {
@@ -472,64 +459,64 @@
src: typeof storyboard_url !== 'undefined' && storyboard_url !== null ? [storyboard_url] : [], src: typeof storyboard_url !== 'undefined' && storyboard_url !== null ? [storyboard_url] : [],
}, },
settings: ['captions', 'speed', 'loop'], settings: ['captions', 'speed', 'loop'],
tooltips: { tooltips: { controls: true },
controls: true, });
},
};
console.log('Creating Plyr...');
try {
plyrInstance = new Plyr(video, playerOptions);
console.log('Plyr instance created');
window.plyrInstance = plyrInstance; window.plyrInstance = plyrInstance;
addCustomQualityControl(plyrInstance, qualityLabels);
addCustomAudioTracksControl(plyrInstance, hlsInstance);
if (plyrInstance.eventListeners) { if (plyrInstance.eventListeners) {
plyrInstance.eventListeners.forEach(function(eventListener) { plyrInstance.eventListeners.forEach(function(eventListener) {
if (eventListener.type === 'dblclick') { if (eventListener.type === 'dblclick') {
eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options); eventListener.element.removeEventListener(
eventListener.type, eventListener.callback, eventListener.options);
} }
}); });
} }
plyrInstance.started = false; plyrInstance.started = false;
plyrInstance.once('playing', function(){this.started = true}); plyrInstance.once('playing', function(){ this.started = true; });
if (typeof data !== 'undefined' && data.time_start != 0) { if (typeof data !== 'undefined' && data.time_start != 0) {
video.addEventListener('loadedmetadata', function() { video.addEventListener('loadedmetadata', function() {
video.currentTime = data.time_start; video.currentTime = data.time_start;
}); });
} }
console.log('Plyr init complete');
} catch (e) {
console.error('Failed to initialize Plyr:', e);
}
}
/**
* Main initialization
*/
async function start() {
console.log('Starting Plyr with HLS...');
if (typeof hls_manifest_url === 'undefined' || !hls_manifest_url) {
console.error('No HLS manifest URL available');
return;
} }
try { try {
const hlsInstance = await initHLS(hls_manifest_url); const hlsInstance = await initHLS(hls_manifest_url);
initPlyrWithQuality(hlsInstance); // Manifest is ready — add quality and audio controls
addCustomQualityControl(plyrInstance, buildQualityLabels(hlsInstance));
addCustomAudioTracksControl(plyrInstance, hlsInstance);
} catch (error) { } catch (error) {
console.error('Failed to initialize:', error); console.error('Failed to initialize HLS:', error);
} }
} }
/**
* Build quality labels from HLS levels
*/
function buildQualityLabels(hlsInstance) {
const qualityLabels = ['auto'];
if (!hlsInstance || !hlsInstance.levels) return qualityLabels;
const sortedLevels = [...hlsInstance.levels].sort((a, b) => b.height - a.height);
const seenHeights = new Set();
sortedLevels.forEach((level) => {
if (!seenHeights.has(level.height)) {
seenHeights.add(level.height);
const originalIndex = hlsInstance.levels.indexOf(level);
const label = level.height + 'p';
if (!window.hlsQualityMap[label]) {
qualityLabels.push(label);
window.hlsQualityMap[label] = originalIndex;
}
}
});
return qualityLabels;
}
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start); document.addEventListener('DOMContentLoaded', start);
} else { } else {