首页 > 解决方案 > 在本地 html 视频之上添加视频控件

问题描述

以下链接显示了在浏览器中播放本地视频的一个很好的示例:

http://jsfiddle.net/dsbonev/cCCZ2/

<h1>HTML5 local video file player example</h1>
<div id="message"></div>
<input type="file" accept="video/*"/>
<video controls autoplay></video>

但是,除此之外,我希望允许用户创建他们视频特定片段的“预告片”。在这方面,我想要某种可调节的播放头,如下所示:

在此处输入图像描述

这是一个做同样事情的网站:https ://www.flexclip.com/editor/app?ratio=landscape 。任何关于我将如何构建这样的东西来播放本地文件并允许我选择它的片段的任何见解?

标签: javascriptvideo

解决方案


有一些不同的东西,虽然特定的问题似乎是如何绘制本地视频的预览栏。

本地文件

从您问题中的示例中,您可以看到使用本地文件与使用远程文件并没有太大区别;您可以通过创建本地文件的对象 URL,URL.createObjectURL然后像使用普通视频链接一样使用它。

绘制预览栏

绘制预览栏应该包括寻找视频中的适当时间,然后将该帧绘制到画布上。.currentTime您可以通过设置视频的属性并等待seeked事件来寻找特定点。然后,您可以使用canvasContext.drawImage方法使用视频元素绘制图像。

我还建议为此使用未附加到 DOM 的视频元素,以获得更好的用户体验,并且屏幕上的视频不会跳转到不同的时间。

它可能看起来像这样:

function goToTime(video, time) {
  return new Promise ((resolve) => {
    function cb () {
      video.removeEventListener('seeked', cb);
      resolve();
    }
    video.addEventListener('seeked', cb);
    video.currentTime = time;
  });
}

async function drawPreviewBar(video) {
  const { duration, videoWidth, videoHeight } = video;
  const previewBarFrameWidth = previewBarHeight  * videoWidth / videoHeight;
  const previewBarFrames = previewBarWidth / previewBarFrameWidth;
  for (let i = 0; i < previewBarFrames; i++) {
    await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
    previewBarContext.drawImage(video, 0, 0,
      videoWidth, videoHeight,
      previewBarFrameWidth * i, 0,
      previewBarFrameWidth, previewBarHeight
    );
  }
}

调整/移动选定剪辑的大小

如果要创建 UI 以调整剪辑大小或在剪辑周围移动,可以使用标准拖动处理程序。

例子

这是一个基本示例:

const previewBarWidth = 600;
const previewBarHeight = 40;
const videoElement = document.getElementById('video');
const inputElement = document.getElementById('file-picker');
const invisibleVideo = document.createElement('video');
const previewBarCanvas = document.getElementById('preview-bar-canvas');
previewBarCanvas.width = previewBarWidth;
previewBarCanvas.height = previewBarHeight;
const selector = document.getElementById('selector');
const previewBarContext = previewBarCanvas.getContext('2d');
const topHandle = document.getElementById('selector-top-handle');
const leftHandle = document.getElementById('selector-left-handle');
const rightHandle = document.getElementById('selector-right-handle');
const leftMask = document.getElementById('selector-left-mask');
const rightMask = document.getElementById('selector-right-mask');

var selectorLeft = 0, selectorWidth = previewBarWidth;
var minimumPreviewBarWidth = 80; // may want to dynamically change this based on video duration
var videoLoaded = false;

function goToTime(video, time) {
  return new Promise ((resolve) => {
    function cb () {
      video.removeEventListener('seeked', cb);
      resolve();
    }
    video.addEventListener('seeked', cb);
    video.currentTime = time;
  });
}

async function drawPreviewBar(video) {
  const { duration, videoWidth, videoHeight } = video;
  const previewBarFrameWidth = previewBarHeight  * videoWidth / videoHeight;
  const previewBarFrames = previewBarWidth / previewBarFrameWidth;
  for (let i = 0; i < previewBarFrames; i++) {
    await goToTime(video, i * duration * previewBarFrameWidth / previewBarWidth);
    previewBarContext.drawImage(video, 0, 0, videoWidth, videoHeight, previewBarFrameWidth * i, 0, previewBarFrameWidth, previewBarHeight);
  }
}

function loadVideo(file) {
  var src = URL.createObjectURL(file);
  var loaded = new Promise(function (resolve) {
    invisibleVideo.addEventListener('loadedmetadata', resolve);
  }).then(function () {
    videoLoaded = true;
    document.body.classList.add('loaded');
    return drawPreviewBar(invisibleVideo);
  });
  videoElement.src = src;
  invisibleVideo.src = src;
  return loaded;
}

function updateSelectorBar() {
  selector.style.width = selectorWidth + 'px';
  selector.style.left = selectorLeft + 'px';
  leftMask.style.width = selectorLeft + 'px';
  rightMask.style.left = (selectorLeft + selectorWidth) + 'px';
  rightMask.style.width = (previewBarWidth - selectorWidth - selectorLeft) + 'px';
}

function selectorUpdated() {
  if (!videoLoaded) {
    return;
  }
  var startFraction = selectorLeft / previewBarWidth;
  var durationFraction = selectorWidth / previewBarWidth;
  var startTime = startFraction * invisibleVideo.duration;
  var duration = durationFraction * invisibleVideo.duration;
  var endTime = startTime + duration;
  // do something with startTime, endTime, and duration, maybe;
  videoElement.currentTime = startTime;
}

function addDragHandler (event, cb, ecb) {
  var startX = event.clientX;
  function dragged(e) {
    cb(e.clientX - startX);
  }
  window.addEventListener('mousemove', dragged);
  window.addEventListener('mouseup', function ended() {
    window.removeEventListener('mousemove', dragged);
    window.removeEventListener('mouseup', ended);
    ecb();
  });
}

updateSelectorBar();
topHandle.addEventListener('mousedown', function (e) {
  var startLeft = selectorLeft;
  addDragHandler(e, function (dx) {
    selectorLeft = Math.max(0, Math.min(previewBarWidth - selectorWidth, startLeft + dx));
    updateSelectorBar();
  }, selectorUpdated);
});

leftHandle.addEventListener('mousedown', function (e) {
  var startLeft = selectorLeft;
  var startWidth = selectorWidth;
  addDragHandler(e, function (dx) {
    selectorLeft = Math.max(0, Math.min(selectorLeft + selectorWidth - minimumPreviewBarWidth, startLeft + dx));
    selectorWidth = (startWidth + startLeft - selectorLeft);
    updateSelectorBar();
  }, selectorUpdated);
});

rightHandle.addEventListener('mousedown', function (e) {
  var startWidth = selectorWidth;
  addDragHandler(e, function (dx) {
    selectorWidth = Math.max(minimumPreviewBarWidth, Math.min(previewBarWidth - selectorLeft, startWidth + dx));
    updateSelectorBar();
  }, selectorUpdated);
});

var pendingLoad = Promise.resolve();
inputElement.addEventListener('change', function () {
  let file = inputElement.files[0];
  pendingLoad = pendingLoad.then(function () {
   return loadVideo(file)
  });
});
#video {
  width: 100%;
  height: auto;
}

#preview-bar {
  position: relative;
  margin-top: 10px;
}
.canvas-container {
  position: relative;
  left: 5px;
}

#preview-bar-canvas {
  position: relative;
  top: 2px;

}

#selector {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  border: 2px solid orange;
  border-left-width: 5px;
  border-right-width: 5px;
  border-radius: 3px;
  overflow: show;
  opacity: 0;
  transition: opacity 1s;
  pointer-events: none;
}

.loaded #selector{
  opacity: 1;
  pointer-events: initial;
}

#selector-top-handle {
  position: absolute;
  top: 0;
  height: 10px;
  width: 100%;
  max-width: 30px;
  left: 50%;
  transform: translate(-50%,-100%);
  background: darkgreen;
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  cursor: move;
}

.resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 5px;
  cursor: ew-resize;
  background: darkgreen;
  opacity: 0.75;
  transition: opacity 1s;
}

.resize-handle:hover {
  opacity: 1;
}

.preview-mask {
  position: absolute;
  background: black;
  opacity: 0.6;
  top: 0;
  bottom: 0;
}

#selector-left-mask {
  left: 0;
}

#selector-left-handle {
  left: -5px;
}
#selector-right-handle {
  right: -5px;
}
<input type="file" id="file-picker" />
<video id="video"></video>
<div id="preview-bar">
  <div class="canvas-container">
    <canvas id="preview-bar-canvas"></canvas>
    <div id="selector-left-mask" class="preview-mask"></div>
    <div id="selector-right-mask" class="preview-mask"></div>
  </div>
  <div id ="selector">
     <div id="selector-top-handle"></div>
     <div id="selector-left-handle" class="resize-handle"></div>
     <div id="selector-right-handle" class="resize-handle"></div>
  </div>
</div>


推荐阅读