Skip to content

Instantly share code, notes, and snippets.

@perryism
Created March 5, 2026 05:54
Show Gist options
  • Select an option

  • Save perryism/c5704d7090b7045c9f752cd293fe358c to your computer and use it in GitHub Desktop.

Select an option

Save perryism/c5704d7090b7045c9f752cd293fe358c to your computer and use it in GitHub Desktop.
video player reel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local Video Player</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.app-wrapper {
display: flex;
gap: 20px;
max-width: 1400px;
width: 100%;
height: 90vh;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 40px;
flex: 1;
display: flex;
flex-direction: column;
min-width: 400px;
position: relative;
}
.resize-handle {
position: absolute;
background: transparent;
z-index: 10;
}
.resize-handle-right {
right: 0;
top: 0;
bottom: 0;
width: 10px;
cursor: ew-resize;
}
.resize-handle-right:hover {
background: rgba(102, 126, 234, 0.2);
}
.resize-handle-right::after {
content: '';
position: absolute;
right: 2px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 40px;
background: #667eea;
border-radius: 2px;
opacity: 0;
transition: opacity 0.2s ease;
}
.resize-handle-right:hover::after {
opacity: 0.6;
}
.container.resizing {
user-select: none;
}
.reel-sidebar {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 20px;
width: 300px;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.reel-sidebar.dragover {
background: #f8f9ff;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.4);
transform: scale(1.02);
}
.reel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
}
.reel-title {
font-size: 1.2em;
font-weight: 600;
color: #333;
}
.reel-count {
background: #667eea;
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85em;
font-weight: 500;
}
.reel-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.reel-list::-webkit-scrollbar {
width: 6px;
}
.reel-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.reel-list::-webkit-scrollbar-thumb {
background: #667eea;
border-radius: 10px;
}
.reel-item {
background: #f8f9ff;
border-radius: 10px;
padding: 12px;
cursor: move;
transition: all 0.3s ease;
border: 2px solid transparent;
position: relative;
user-select: none;
}
.reel-item:hover {
background: #e8ebff;
transform: translateX(-5px);
}
.reel-item.active {
border-color: #667eea;
background: #e8ebff;
}
.reel-item.dragging {
opacity: 0.5;
cursor: grabbing;
}
.reel-item.drag-over {
border-top: 3px solid #667eea;
}
.reel-item-name {
font-size: 0.9em;
color: #333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 5px;
}
.reel-item-duration {
font-size: 0.75em;
color: #999;
}
.reel-item-remove {
position: absolute;
top: 8px;
right: 8px;
background: #ff4757;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 0.8em;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.reel-item:hover .reel-item-remove {
display: flex;
}
.reel-item-remove:hover {
background: #d63447;
transform: scale(1.1);
}
.reel-controls {
padding-top: 15px;
border-top: 2px solid #f0f0f0;
}
.loop-toggle {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: #f8f9ff;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s ease;
}
.loop-toggle:hover {
background: #e8ebff;
}
.loop-toggle input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.loop-toggle label {
cursor: pointer;
color: #333;
font-size: 0.9em;
font-weight: 500;
}
.clear-reel-btn {
margin-top: 10px;
background: #ff4757;
color: white;
border: none;
padding: 10px;
border-radius: 8px;
cursor: pointer;
font-size: 0.9em;
font-weight: 500;
transition: background 0.3s ease;
}
.clear-reel-btn:hover {
background: #d63447;
}
.ffmpeg-section {
margin-top: 15px;
padding-top: 15px;
border-top: 2px solid #f0f0f0;
}
.ffmpeg-title {
font-size: 0.95em;
font-weight: 600;
color: #333;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 5px;
}
.ffmpeg-command {
background: #2d2d2d;
color: #f8f8f2;
padding: 12px;
border-radius: 8px;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
font-size: 0.75em;
overflow-x: auto;
margin-bottom: 10px;
position: relative;
}
.ffmpeg-command code {
display: block;
white-space: pre-wrap;
word-break: break-all;
}
.copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: #667eea;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85em;
transition: background 0.2s ease;
}
.copy-btn:hover {
background: #764ba2;
}
.copy-btn.copied {
background: #2ecc71;
}
.mylist-section {
margin-top: 10px;
}
.mylist-title {
font-size: 0.85em;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.mylist-content {
background: #2d2d2d;
color: #f8f8f2;
padding: 12px;
border-radius: 8px;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
font-size: 0.75em;
max-height: 200px;
overflow-y: auto;
position: relative;
}
.mylist-content::-webkit-scrollbar {
width: 6px;
}
.mylist-content::-webkit-scrollbar-track {
background: #1a1a1a;
border-radius: 10px;
}
.mylist-content::-webkit-scrollbar-thumb {
background: #667eea;
border-radius: 10px;
}
.mylist-content code {
display: block;
white-space: pre;
color: #a6e22e;
}
.ffmpeg-empty {
text-align: center;
color: #999;
padding: 20px;
font-size: 0.85em;
font-style: italic;
}
.reel-empty {
text-align: center;
color: #999;
padding: 40px 20px;
font-size: 0.9em;
}
.reel-drop-hint {
text-align: center;
color: #667eea;
padding: 20px;
font-size: 0.85em;
background: #f8f9ff;
border-radius: 8px;
margin-bottom: 10px;
border: 2px dashed #667eea;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2em;
}
.drop-zone {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 60px 20px;
text-align: center;
background: #f8f9ff;
transition: all 0.3s ease;
cursor: pointer;
margin-bottom: 30px;
}
.drop-zone.dragover {
background: #e8ebff;
border-color: #764ba2;
transform: scale(1.02);
}
.drop-zone-icon {
font-size: 4em;
margin-bottom: 20px;
}
.drop-zone-text {
color: #667eea;
font-size: 1.2em;
font-weight: 500;
}
.drop-zone-subtext {
color: #999;
margin-top: 10px;
font-size: 0.9em;
}
.drop-zone-hint {
color: #764ba2;
margin-top: 15px;
font-size: 0.85em;
font-weight: 500;
}
.video-container {
display: none;
margin-top: 20px;
position: relative;
}
.video-container.active {
display: block;
}
.video-wrapper {
position: relative;
width: 100%;
resize: both;
overflow: hidden;
min-width: 300px;
min-height: 200px;
max-width: 100%;
}
.video-wrapper-resizable {
border: 2px solid transparent;
border-radius: 12px;
transition: border-color 0.2s ease;
}
.video-wrapper-resizable:hover {
border-color: rgba(102, 126, 234, 0.3);
}
video {
width: 100%;
height: 100%;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
background: #000;
display: block;
object-fit: contain;
}
#preloadVideo {
position: absolute;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
width: 100%;
border-radius: 10px;
}
.video-transition {
transition: opacity 0.3s ease-in-out;
}
.video-info {
margin-top: 15px;
padding: 15px;
background: #f8f9ff;
border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.video-name {
color: #333;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.video-controls {
display: flex;
gap: 10px;
}
.change-video-btn, .resize-btn {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 0.9em;
font-weight: 500;
transition: background 0.3s ease;
white-space: nowrap;
}
.change-video-btn:hover, .resize-btn:hover {
background: #764ba2;
}
.resize-btn.active {
background: #764ba2;
}
.resize-corner {
position: absolute;
bottom: 5px;
right: 5px;
width: 20px;
height: 20px;
cursor: nwse-resize;
z-index: 100;
}
.resize-corner::before {
content: '';
position: absolute;
bottom: 2px;
right: 2px;
width: 0;
height: 0;
border-style: solid;
border-width: 0 0 12px 12px;
border-color: transparent transparent rgba(102, 126, 234, 0.5) transparent;
}
.resize-corner:hover::before {
border-color: transparent transparent rgba(102, 126, 234, 0.8) transparent;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="app-wrapper">
<div class="container" id="mainContainer">
<div class="resize-handle resize-handle-right" id="resizeHandle"></div>
<h1>🎬 Local Video Player</h1>
<div class="drop-zone" id="dropZone">
<div class="drop-zone-icon">📹</div>
<div class="drop-zone-text">Drag & Drop your videos here</div>
<div class="drop-zone-subtext">or click to browse</div>
<div class="drop-zone-hint">💡 Select multiple videos to create a reel</div>
</div>
<input type="file" id="fileInput" accept="video/*" multiple class="hidden">
<div class="video-container" id="videoContainer">
<div class="video-wrapper" id="videoWrapper">
<video id="videoPlayer" controls></video>
<video id="preloadVideo" muted></video>
<div class="resize-corner" id="resizeCorner"></div>
</div>
<div class="video-info">
<span class="video-name" id="videoName"></span>
<div class="video-controls">
<button class="resize-btn" id="resizeBtn">📐 Resize</button>
<button class="change-video-btn" id="changeVideoBtn">Add More Videos</button>
</div>
</div>
</div>
</div>
<div class="reel-sidebar" id="reelSidebar">
<div class="reel-header">
<span class="reel-title">📼 Video Reel</span>
<span class="reel-count" id="reelCount">0</span>
</div>
<div class="reel-drop-hint" id="reelDropHint">
💡 Drop videos here or drag to reorder
</div>
<div class="reel-list" id="reelList">
<div class="reel-empty">Drop videos to start building your reel</div>
</div>
<div class="reel-controls">
<div class="loop-toggle">
<input type="checkbox" id="loopToggle" checked>
<label for="loopToggle">Loop Reel</label>
</div>
<button class="clear-reel-btn" id="clearReelBtn">Clear All</button>
</div>
<div class="ffmpeg-section" id="ffmpegSection">
<div class="ffmpeg-title">⚙️ FFmpeg Command</div>
<div id="ffmpegContent">
<div class="ffmpeg-empty">Add videos to generate ffmpeg command</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const videoContainer = document.getElementById('videoContainer');
const videoPlayer = document.getElementById('videoPlayer');
const preloadVideo = document.getElementById('preloadVideo');
const videoName = document.getElementById('videoName');
const changeVideoBtn = document.getElementById('changeVideoBtn');
const reelList = document.getElementById('reelList');
const reelCount = document.getElementById('reelCount');
const loopToggle = document.getElementById('loopToggle');
const clearReelBtn = document.getElementById('clearReelBtn');
const reelSidebar = document.getElementById('reelSidebar');
const reelDropHint = document.getElementById('reelDropHint');
const mainContainer = document.getElementById('mainContainer');
const resizeHandle = document.getElementById('resizeHandle');
const videoWrapper = document.getElementById('videoWrapper');
const resizeCorner = document.getElementById('resizeCorner');
const resizeBtn = document.getElementById('resizeBtn');
const ffmpegContent = document.getElementById('ffmpegContent');
// State
let videoReel = [];
let currentVideoIndex = 0;
let draggedItemIndex = null;
let isTransitioning = false;
let isResizing = false;
let resizeMode = 'none'; // 'container' or 'video'
let startX, startY, startWidth, startHeight;
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
reelSidebar.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Highlight drop zone when dragging over it
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.remove('dragover');
}, false);
});
// Highlight reel sidebar when dragging files over it
reelSidebar.addEventListener('dragenter', (e) => {
// Only highlight if dragging files from outside (not reordering)
if (e.dataTransfer.types.includes('Files')) {
reelSidebar.classList.add('dragover');
}
});
reelSidebar.addEventListener('dragover', (e) => {
if (e.dataTransfer.types.includes('Files')) {
reelSidebar.classList.add('dragover');
}
});
reelSidebar.addEventListener('dragleave', (e) => {
// Check if we're leaving the sidebar entirely
if (e.target === reelSidebar || !reelSidebar.contains(e.relatedTarget)) {
reelSidebar.classList.remove('dragover');
}
});
reelSidebar.addEventListener('drop', (e) => {
reelSidebar.classList.remove('dragover');
if (e.dataTransfer.types.includes('Files')) {
handleDrop(e);
}
});
// Handle dropped files
dropZone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
// Handle click to browse
dropZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
handleFiles(e.target.files);
});
// Handle add more videos button
changeVideoBtn.addEventListener('click', () => {
fileInput.click();
});
// Handle clear reel button
clearReelBtn.addEventListener('click', () => {
if (videoReel.length > 0 && confirm('Are you sure you want to clear all videos?')) {
videoReel = [];
currentVideoIndex = 0;
updateReelUI();
videoPlayer.pause();
videoPlayer.src = '';
dropZone.style.display = 'block';
videoContainer.classList.remove('active');
}
});
function handleFiles(files) {
const videoFiles = Array.from(files).filter(file => file.type.startsWith('video/'));
if (videoFiles.length === 0) {
alert('Please select valid video files.');
return;
}
const wasEmpty = videoReel.length === 0;
// Add videos to reel
videoFiles.forEach(file => {
const videoData = {
id: Date.now() + Math.random(),
file: file,
name: file.name,
url: URL.createObjectURL(file),
duration: null
};
videoReel.push(videoData);
});
updateReelUI();
// If this is the first video, start playing
if (wasEmpty) {
currentVideoIndex = 0;
playVideo(0);
} else {
// If we added videos while playing, update preload
preloadNextVideo();
}
}
function updateReelUI() {
reelCount.textContent = videoReel.length;
if (videoReel.length === 0) {
reelList.innerHTML = '<div class="reel-empty">Drop videos to start building your reel</div>';
return;
}
reelList.innerHTML = '';
videoReel.forEach((video, index) => {
const reelItem = document.createElement('div');
reelItem.className = 'reel-item';
reelItem.draggable = true;
reelItem.dataset.index = index;
if (index === currentVideoIndex) {
reelItem.classList.add('active');
}
const itemName = document.createElement('div');
itemName.className = 'reel-item-name';
itemName.textContent = `${index + 1}. ${video.name}`;
const itemDuration = document.createElement('div');
itemDuration.className = 'reel-item-duration';
itemDuration.textContent = video.duration ? formatDuration(video.duration) : 'Loading...';
const removeBtn = document.createElement('button');
removeBtn.className = 'reel-item-remove';
removeBtn.textContent = '×';
removeBtn.onclick = (e) => {
e.stopPropagation();
removeVideo(index);
};
reelItem.appendChild(itemName);
reelItem.appendChild(itemDuration);
reelItem.appendChild(removeBtn);
// Click to play
reelItem.onclick = (e) => {
if (e.target !== removeBtn) {
playVideo(index);
}
};
// Drag and drop for reordering
reelItem.addEventListener('dragstart', handleDragStart);
reelItem.addEventListener('dragend', handleDragEnd);
reelItem.addEventListener('dragover', handleDragOver);
reelItem.addEventListener('drop', handleReelItemDrop);
reelItem.addEventListener('dragleave', handleDragLeave);
reelList.appendChild(reelItem);
});
// Update ffmpeg command
updateFFmpegCommand();
}
function updateFFmpegCommand() {
if (videoReel.length === 0) {
ffmpegContent.innerHTML = '<div class="ffmpeg-empty">Add videos to generate ffmpeg command</div>';
return;
}
// Generate mylist.txt content
const mylistContent = videoReel.map(video => `file '${video.name}'`).join('\n');
// Create the HTML for the ffmpeg section
const html = `
<div class="ffmpeg-command">
<button class="copy-btn" onclick="copyToClipboard('ffmpeg-cmd', this)">Copy</button>
<code id="ffmpeg-cmd">ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4</code>
</div>
<div class="mylist-section">
<div class="mylist-title">📄 mylist.txt</div>
<div class="mylist-content">
<button class="copy-btn" onclick="copyToClipboard('mylist-content', this)">Copy</button>
<code id="mylist-content">${mylistContent}</code>
</div>
</div>
`;
ffmpegContent.innerHTML = html;
}
// Copy to clipboard function
window.copyToClipboard = function(elementId, button) {
const element = document.getElementById(elementId);
const text = element.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = '✓ Copied';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard');
});
}
// Drag and drop handlers for reordering
function handleDragStart(e) {
draggedItemIndex = parseInt(e.target.dataset.index);
e.target.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.target.innerHTML);
}
function handleDragEnd(e) {
e.target.classList.remove('dragging');
// Remove all drag-over classes
document.querySelectorAll('.reel-item').forEach(item => {
item.classList.remove('drag-over');
});
}
function handleDragOver(e) {
if (draggedItemIndex === null) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const targetIndex = parseInt(e.currentTarget.dataset.index);
if (targetIndex !== draggedItemIndex) {
e.currentTarget.classList.add('drag-over');
}
}
function handleDragLeave(e) {
e.currentTarget.classList.remove('drag-over');
}
function handleReelItemDrop(e) {
if (draggedItemIndex === null) return;
e.stopPropagation();
e.currentTarget.classList.remove('drag-over');
const targetIndex = parseInt(e.currentTarget.dataset.index);
if (draggedItemIndex !== targetIndex) {
// Reorder the array
const draggedVideo = videoReel[draggedItemIndex];
videoReel.splice(draggedItemIndex, 1);
videoReel.splice(targetIndex, 0, draggedVideo);
// Update current video index
if (currentVideoIndex === draggedItemIndex) {
currentVideoIndex = targetIndex;
} else if (draggedItemIndex < currentVideoIndex && targetIndex >= currentVideoIndex) {
currentVideoIndex--;
} else if (draggedItemIndex > currentVideoIndex && targetIndex <= currentVideoIndex) {
currentVideoIndex++;
}
updateReelUI();
}
draggedItemIndex = null;
}
function playVideo(index, autoplay = true) {
if (index < 0 || index >= videoReel.length || isTransitioning) return;
currentVideoIndex = index;
const video = videoReel[index];
// Set the video source
videoPlayer.src = video.url;
videoName.textContent = video.name;
// Show video container and hide drop zone
dropZone.style.display = 'none';
videoContainer.classList.add('active');
// Update duration when metadata is loaded
if (!video.duration) {
videoPlayer.addEventListener('loadedmetadata', () => {
video.duration = videoPlayer.duration;
updateReelUI();
}, { once: true });
}
// Preload metadata for smoother start
videoPlayer.preload = 'auto';
// Play video when ready
if (autoplay) {
videoPlayer.play().catch(err => {
console.log('Autoplay prevented:', err);
});
}
updateReelUI();
// Preload next video
preloadNextVideo();
}
function preloadNextVideo() {
if (videoReel.length === 0) return;
let nextIndex = currentVideoIndex + 1;
// Handle looping
if (nextIndex >= videoReel.length) {
if (loopToggle.checked) {
nextIndex = 0;
} else {
return; // Don't preload if not looping and at the end
}
}
const nextVideo = videoReel[nextIndex];
if (nextVideo) {
preloadVideo.src = nextVideo.url;
preloadVideo.preload = 'auto';
preloadVideo.load();
// Update duration for next video if not set
if (!nextVideo.duration) {
preloadVideo.addEventListener('loadedmetadata', () => {
nextVideo.duration = preloadVideo.duration;
updateReelUI();
}, { once: true });
}
}
}
function transitionToNextVideo() {
if (videoReel.length === 0 || isTransitioning) return;
let nextIndex = currentVideoIndex + 1;
// If we've reached the end
if (nextIndex >= videoReel.length) {
if (loopToggle.checked) {
nextIndex = 0;
} else {
// Stop at the end
currentVideoIndex = videoReel.length - 1;
return;
}
}
isTransitioning = true;
// Swap the videos for seamless transition
const nextVideo = videoReel[nextIndex];
// Update current index and UI
currentVideoIndex = nextIndex;
videoName.textContent = nextVideo.name;
updateReelUI();
// Quick switch to preloaded video
videoPlayer.src = nextVideo.url;
videoPlayer.currentTime = 0;
videoPlayer.play().then(() => {
isTransitioning = false;
// Preload the next video after this one
preloadNextVideo();
}).catch(err => {
console.log('Playback error:', err);
isTransitioning = false;
});
}
function removeVideo(index) {
if (index < 0 || index >= videoReel.length) return;
// Revoke object URL to free memory
URL.revokeObjectURL(videoReel[index].url);
videoReel.splice(index, 1);
if (videoReel.length === 0) {
videoPlayer.pause();
videoPlayer.src = '';
dropZone.style.display = 'block';
videoContainer.classList.remove('active');
currentVideoIndex = 0;
} else if (index === currentVideoIndex) {
// If we removed the current video, play the next one (or previous if it was the last)
currentVideoIndex = Math.min(index, videoReel.length - 1);
playVideo(currentVideoIndex);
} else if (index < currentVideoIndex) {
// Adjust current index if we removed a video before it
currentVideoIndex--;
}
updateReelUI();
}
function formatDuration(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// Handle video end - play next video seamlessly
videoPlayer.addEventListener('ended', () => {
transitionToNextVideo();
});
// Preload next video when current video is halfway through
videoPlayer.addEventListener('timeupdate', () => {
if (videoPlayer.duration && videoPlayer.currentTime > videoPlayer.duration / 2) {
// Ensure next video is preloaded by halfway point
if (!preloadVideo.src || preloadVideo.readyState < 2) {
preloadNextVideo();
}
}
});
// ===== RESIZE FUNCTIONALITY =====
// Toggle resize mode
resizeBtn.addEventListener('click', () => {
if (resizeMode === 'video') {
// Disable video resize mode
resizeMode = 'none';
resizeBtn.classList.remove('active');
videoWrapper.classList.remove('video-wrapper-resizable');
videoWrapper.style.resize = 'none';
resizeCorner.style.display = 'none';
} else {
// Enable video resize mode
resizeMode = 'video';
resizeBtn.classList.add('active');
videoWrapper.classList.add('video-wrapper-resizable');
videoWrapper.style.resize = 'both';
resizeCorner.style.display = 'block';
}
});
// Container resize (drag right edge)
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
resizeMode = 'container';
startX = e.clientX;
startWidth = mainContainer.offsetWidth;
mainContainer.classList.add('resizing');
e.preventDefault();
});
// Video resize (drag corner)
resizeCorner.addEventListener('mousedown', (e) => {
isResizing = true;
resizeMode = 'video';
startX = e.clientX;
startY = e.clientY;
startWidth = videoWrapper.offsetWidth;
startHeight = videoWrapper.offsetHeight;
e.preventDefault();
e.stopPropagation();
});
// Mouse move handler
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
if (resizeMode === 'container') {
const deltaX = e.clientX - startX;
const newWidth = startWidth + deltaX;
// Set min and max width
if (newWidth >= 400 && newWidth <= window.innerWidth - 400) {
mainContainer.style.flex = `0 0 ${newWidth}px`;
}
} else if (resizeMode === 'video') {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
const newWidth = startWidth + deltaX;
const newHeight = startHeight + deltaY;
// Set min dimensions
if (newWidth >= 300) {
videoWrapper.style.width = `${newWidth}px`;
}
if (newHeight >= 200) {
videoWrapper.style.height = `${newHeight}px`;
}
}
});
// Mouse up handler
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
mainContainer.classList.remove('resizing');
if (resizeMode === 'container') {
resizeMode = 'none';
}
}
});
// Prevent text selection while resizing
document.addEventListener('selectstart', (e) => {
if (isResizing) {
e.preventDefault();
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment