参考这个
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>时间轴剪辑播放器</title> <style> body { font-family: sans-serif; margin: 20px; } #preview { width: 600px; height: 340px; background: #000; display: flex; align-items: center; justify-content: center; } #preview img, #preview video { max-width: 100%; max-height: 100%; } #timelineWrapper { position: relative; width: 100%; margin-top: 20px; overflow-x: auto; } #timeline { display: flex; height: 100px; border: 2px dashed #ccc; padding: 10px; position: relative; min-width: 600px; } .clip { width: 100px; height: 80px; margin-right: 5px; border: 1px solid #999; background-size: cover; background-position: center; position: relative; } #pointer { position: absolute; width: 2px; background: red; top: 0; bottom: 0; left: 0; cursor: ew-resize; } #controls { margin-top: 20px; } button { margin-right: 10px; } </style> </head> <body> <h2>剪辑时间轴播放器</h2> <div id="preview"></div> <div id="timelineWrapper"> <div id="timeline" ondragover="event.preventDefault()" ondrop="handleDrop(event)"> <div id="pointer"></div> </div> </div> <div id="controls"> <button onclick="playSequence()">播放</button> <button onclick="pauseSequence()">暂停</button> <button onclick="stopSequence()">停止</button> </div> <script> const timeline = document.getElementById('timeline'); const pointer = document.getElementById('pointer'); const preview = document.getElementById('preview'); let clips = []; let totalDuration = 0; let currentIndex = 0; let currentTime = 0; let isPaused = false; let isStopped = false; let pointerInterval = null; function handleDrop(event) { event.preventDefault(); const files = Array.from(event.dataTransfer.files); files.forEach(file => { const url = URL.createObjectURL(file); const ext = file.name.split('.').pop().toLowerCase(); let type = ''; if (['mp4', 'webm', 'ogg'].includes(ext)) { type = 'video'; const video = document.createElement('video'); video.src = url; video.addEventListener('loadedmetadata', () => { const duration = video.duration; addClip(type, url, duration); }); } else if (['png', 'jpg', 'jpeg', 'gif'].includes(ext)) { type = 'image'; addClip(type, url, 5); // 图片固定播放 5s } }); } function addClip(type, src, duration) { clips.push({ type, src, duration }); const div = document.createElement('div'); div.className = 'clip'; if (type === 'image') { div.style.backgroundImage = `url(${src})`; } else { div.style.background = '#333 url(play-icon.png) center center no-repeat'; } timeline.appendChild(div); updateTotalDuration(); } function updateTotalDuration() { totalDuration = clips.reduce((sum, clip) => sum + clip.duration, 0); } function playSequence(startTime = 0) { if (clips.length === 0) return; isPaused = false; isStopped = false; // 如果是从中间跳转 if (startTime > 0) { [currentIndex, currentTime] = findClipByGlobalTime(startTime); } startPointerAnimation(startTime); playClipAt(currentIndex, currentTime); } function pauseSequence() { isPaused = true; const video = preview.querySelector('video'); if (video) video.pause(); clearInterval(pointerInterval); } function stopSequence() { isStopped = true; clearInterval(pointerInterval); preview.innerHTML = ''; currentIndex = 0; currentTime = 0; pointer.style.left = `0px`; } function findClipByGlobalTime(globalTime) { let time = 0; for (let i = 0; i < clips.length; i++) { const next = time + clips[i].duration; if (globalTime < next) { return [i, globalTime - time]; } time = next; } return [clips.length - 1, 0]; } function playClipAt(index, offset = 0) { if (index >= clips.length || isStopped) return; const clip = clips[index]; preview.innerHTML = ''; if (clip.type === 'image') { const img = document.createElement('img'); img.src = clip.src; preview.appendChild(img); setTimeout(() => { if (!isPaused && !isStopped) { currentIndex++; playClipAt(currentIndex); } }, (clip.duration - offset) * 1000); } else if (clip.type === 'video') { const video = document.createElement('video'); video.src = clip.src; video.autoplay = true; video.controls = false; video.currentTime = offset; preview.appendChild(video); video.onended = () => { if (!isPaused && !isStopped) { currentIndex++; playClipAt(currentIndex); } }; } } function startPointerAnimation(startAt = 0) { const timelineWidth = timeline.scrollWidth; pointerInterval = setInterval(() => { if (isPaused || isStopped) return; // 计算当前总播放时间 let elapsed = 0; for (let i = 0; i < currentIndex; i++) { elapsed += clips[i].duration; } const video = preview.querySelector('video'); if (video) { elapsed += video.currentTime; } else { elapsed += currentTime; } if (elapsed >= totalDuration) { clearInterval(pointerInterval); return; } pointer.style.left = `${(elapsed / totalDuration) * timelineWidth}px`; }, 50); } pointer.addEventListener('mousedown', (e) => { e.preventDefault(); clearInterval(pointerInterval); isPaused = true; const rect = timeline.getBoundingClientRect(); const onMove = (moveEvent) => { let x = moveEvent.clientX - rect.left; x = Math.max(0, Math.min(x, timeline.scrollWidth)); pointer.style.left = `${x}px`; }; const onUp = (upEvent) => { let x = upEvent.clientX - rect.left; x = Math.max(0, Math.min(x, timeline.scrollWidth)); const percent = x / timeline.scrollWidth; const jumpTime = percent * totalDuration; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); stopSequence(); // 先停止 playSequence(jumpTime); // 再跳转播放 }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }); </script> </body> </html>
网友回复
腾讯混元模型广场里都是混元模型的垂直小模型,如何api调用?
为啥所有的照片分辨率提升工具都会修改照片上的图案细节?
js如何在浏览器中将webm视频的声音分离为单独音频?
微信小程序如何播放第三方域名url的mp4视频?
ai多模态大模型能实时识别视频中的手语为文字吗?
如何远程调试别人的chrome浏览器获取调试信息?
为啥js打开新网页window.open设置窗口宽高无效?
浏览器中js的navigator.mediaDevices.getDisplayMedia屏幕录像无法录制SpeechSynthesisUtterance产生的说话声音?
js中mediaRecorder如何录制window.speechSynthesis声音音频并下载?
python如何直接获取抖音短视频的音频文件url?