Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/seamless-video-switching/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<div id="video-container">
<video id="video-player-1" preload="auto" autoplay></video>
<video id="video-player-2" class="hidden" preload="auto"></video>
</video>
</div>
<script src="index.js"></script>
</body>
</html>
118 changes: 57 additions & 61 deletions examples/seamless-video-switching/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,104 +9,100 @@ let currentVideoIndex = 0;
let visibleVideoPlayer = 1;
let videoFiles = [];

// Helper function to get player element by number
function getPlayer(playerNumber) {
return document.getElementById(`video-player-${playerNumber}`);
}

// Helper function to get the currently visible and hidden players
function getPlayers() {
const visible = getPlayer(visibleVideoPlayer);
const hidden = getPlayer(visibleVideoPlayer === 1 ? 2 : 1);
return { visible, hidden };
}

function preloadNextVideo() {
const nextVideoIndex = (currentVideoIndex + 1) % videoFiles.length;
const { hidden } = getPlayers();
hidden.pause();
hidden.currentTime = 0;
hidden.muted = true;
hidden.src = `${assetsFolder}/${videoFiles[nextVideoIndex]}`;
hidden.load();

hidden.addEventListener('canplay', () => {
hidden.play().then(() => { hidden.pause(); hidden.currentTime = 0; }).catch(() => {});
}, { once: true });
}

// Start hidden player 0.5s before end so it's already playing when the swap happens
function armEarlyStart() {
const { visible } = getPlayers();
visible.addEventListener('timeupdate', function onNearEnd() {
if (visible.duration - visible.currentTime <= 0.5) {
visible.removeEventListener('timeupdate', onNearEnd);
getPlayers().hidden.play().catch(() => {});
}
});
}

function switchToNextVideo() {
currentVideoIndex = (currentVideoIndex + 1) % videoFiles.length;
const { visible: currentPlayer, hidden: nextPlayer } = getPlayers();

const doSwap = () => {
currentPlayer.pause();
currentPlayer.muted = true;
currentPlayer.classList.add('hidden');
nextPlayer.muted = false;
nextPlayer.classList.remove('hidden');
visibleVideoPlayer = visibleVideoPlayer === 1 ? 2 : 1;
preloadNextVideo();
armEarlyStart();
};

// If early start worked, swap is instant; otherwise wait for playing
if (!nextPlayer.paused) {
doSwap();
} else {
nextPlayer.play().catch(e => console.error('Play error:', e));
nextPlayer.addEventListener('playing', doSwap, { once: true });
}
}

async function main() {
console.log('main() - App ready!');

try {
// Load and filter video files
videoFiles = fs.readdirSync(`${rootStoragePath}/${assetsFolder}`);
videoFiles = videoFiles.filter(filename => !filename.startsWith('.'));
videoFiles.sort();

// console.log('Video files:', videoFiles);


if (videoFiles.length === 0) {
console.error(`No video files found in ${rootStoragePath}/${assetsFolder}`);
return;
}

const player1 = getPlayer(1);
const player2 = getPlayer(2);

// Initialize first video

player1.src = `${assetsFolder}/${videoFiles[0]}`;
player1.currentTime = 0;
player1.muted = false; // Ensure audio is enabled for first player
player2.muted = true; // Mute the second player initially
console.log('Player 1 loaded:', videoFiles[0]);

// Set up video ended listeners for both players
player1.muted = false;
player2.muted = true;

player1.addEventListener('ended', () => switchToNextVideo());
player2.addEventListener('ended', () => switchToNextVideo());

// Preload next video once first video starts playing

player1.addEventListener('playing', () => {
console.log('Player 1 playing');
preloadNextVideo();
armEarlyStart();
}, { once: true });

} catch (e) {
console.error('Error in main():', e);
}
}

// Preload the next video in the hidden player
function preloadNextVideo() {
const nextVideoIndex = (currentVideoIndex + 1) % videoFiles.length;
const { hidden } = getPlayers();
const filePath = `${assetsFolder}/${videoFiles[nextVideoIndex]}`;

console.log(`Preloading: ${videoFiles[nextVideoIndex]}`);

// Reset and load the next video
hidden.pause();
hidden.currentTime = 0;
hidden.muted = true; // Ensure hidden player is muted
hidden.src = filePath;
hidden.load();
}

// Switch to the next video seamlessly
function switchToNextVideo() {
currentVideoIndex = (currentVideoIndex + 1) % videoFiles.length;
const { visible: currentPlayer, hidden: nextPlayer } = getPlayers();

console.log(`Switching to: ${videoFiles[currentVideoIndex]}`);

// Ensure next player starts from beginning
nextPlayer.currentTime = 0;

// Start playing the next video (while still hidden)
nextPlayer.play().catch(e => console.error('Play error:', e));

// Switch visibility and audio immediately - the video is already preloaded
currentPlayer.pause();
currentPlayer.muted = true; // Mute the outgoing player
currentPlayer.classList.add('hidden');

nextPlayer.muted = false; // Unmute the incoming player
nextPlayer.classList.remove('hidden');

// Toggle which player is visible
visibleVideoPlayer = visibleVideoPlayer === 1 ? 2 : 1;

// Preload the next video in the now-hidden player
preloadNextVideo();
}

// Call main when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
Expand Down
Loading