视频(Video)实现技术

重要通知

类目简介

基本概况

延迟加载视频库

  • vanilla-lazyload https://github.com/verlok/vanilla-lazyload
  • lozad.js https://github.com/ApoorvSaxena/lozad.js
  • yall.js https://github.com/malchata/yall.js
  • react-lazyload https://github.com/twobin/react-lazyload

视频格式MIME类型

  • MP4 = 带有 H.264 视频编码和 AAC 音频编码的 MPEG 4 文件
  • WebM = 带有 VP8 视频编码和 Vorbis 音频编码的 WebM 文件
  • Ogg = 带有 Theora 视频编码和 Vorbis 音频编码的 Ogg 文件
----------------------------------------------
	Format	    MIME-type
----------------------------------------------
	MP4					video/mp4
	WebM				video/webm
	Ogg					video/ogg
----------------------------------------------

代码示例

const loadVideoData = function (url) {
  return new Promise((resolve, reject) => {
    const video = document.createElement("video");
    video.src = url;
    video.onloadeddata = function (e) {
      resolve(video);
    };
    video.onerror = function () {
      reject(e);
    };
  });
};

const url = "https://test.ysunlight.com/Reference/video/war.mp4";
loadVideoData(url).then((video) => {
  video.autoplay = true;
  video.controls = true;
  video.muted = true;
  window.setTimeout(() => {
    video.play();
  });

  video.onplay = function () {
    window.setInterval(() => {
      ctx.drawImage(video, 0, 0, width, height);
    }, 10);
  };

  document.getElementById("closeMuted").onclick = function () {
    video.muted = false;
  };
});

MIME-type

----------------------------------------------------------------------------------
  
----------------------------------------------------------------------------------
  ['video/wmv', 'video/mp4', 'video/avi', 'video/flv', 'video/mpg', 'video/mkv']
  ['video/mp4', ' video/webm', 'video/ogg', 'video/flv'] 


Video                            Type	           Extension	MIME Type
MPEG-4	                         .mp4	           video/mp4
Ogg Video	                       .ogv	           video/ogg
Flash Video	                     .flv	           video/x-flv
A/V Interleave	                 .avi	           video/x-msvideo
Microsoft Windows Media	         .wmv	           video/x-ms-wmv
RealMedia Variable Bitrate	     .rmvb	         application/vnd.rn-realmedia-vbr
Waveform Audio File Format (WAV) .wav	           audio/x-wav


  video/mpeg	mp2
  video/mpeg	mpa
  video/mpeg	mpe
  video/mpeg	mpeg
  video/mpeg	mpg
  video/mpeg	mpv2
  video/quicktime	mov
  video/quicktime	qt
  video/x-la-asf	lsf
  video/x-la-asf	lsx
  video/x-ms-asf	asf
  video/x-ms-asf	asr
  video/x-ms-asf	asx
  video/x-msvideo	avi
  video/x-sgi-movie	movie
----------------------------------------------------------------------------------

视频格式

[.avi]:全称为Audio Video Interleaved,即音频视频交错格式。它于1992年被Microsoft公司推出。 [.mp4]: [.wmv]:全称为Windows Media Video,也是微软推出的一种采用独立编码方式并且可以直接在网上实时观看视频节目的文件压缩格式。 [.flv]:由Adobe Flash延伸出来的的一种流行网络视频封装格式。 [.mpg] [.mpeg]:MPEG格式, 全称为Moving Picture Experts Group,即运动图像专家组格式,该专家组建于1988年,专门负责为CD建立视频和音频标准,而成员都是为视频、音频及系统领域的技术专家。 [.mkv]:是一种新的多媒体封装格式,这个封装格式可把多种不同编码的视频及16条或以上不同格式的音频和语言不同的字幕封装到一个Matroska Media档内。它也是其中一种开放源代码的多媒体封装格式。Matroska同时还可以提供非常好的交互功能,而且比MPEG的方便、强大。

[.mpg]: [.vob]: [.mov]:美国Apple公司开发的一种视频格式,默认的播放器是苹果的QuickTime。

[.mpe]: [.dat]: [.vob]: [.asf]: [.3gp]: [.rm] [.rmvb]:Real Networks公司所制定的音频视频压缩规范称为Real Media。

视频(Video)

<video> 元素提供了 播放、暂停和音量控件来控制视频,用于在 HTML 或者 XHTML 文档中嵌入媒体播放器,用于支持文档内的视频播放。

注意事项

现代浏览器已经屏蔽了视频自动播放时携带音量的问题,即若设置自动播放需静音模式。

视频格式的MIME类型

  • MP4:type="video/mp4",带有 H.264 视频编码和 AAC 音频编码的 MPEG 4 文件。
  • Ogg:type="video/ogg",带有 VP8 视频编码和 Vorbis 音频编码的 WebM 文件。
  • WebM:type="video/webm",带有 Theora 视频编码和 Vorbis 音频编码的 Ogg 文件。
<video controls>
  <source src="video.ogg" type="video/ogg">
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
</video>

<script type="text/javascript">
  const file = document.createElement('input');
  file.setAttribute('type', 'file');
  file.setAttribute('accept', 'video/mp4,video/webm,video/ogg');
  file.click();
</script>

相关类库

  • FlvPlayer: https://github.com/zhw2590582/FlvPlayer
  • FLV: https://github.com/bilibili/flv.js
  • Node-Media-Server:https://github.com/illuspas/Node-Media-Server
  • cyberplayer
  • JWPlayer
  • hls.js

相关样式

video {
  -webkit-playsinline: true;
}
video::-webkit-media-controls-fullscreen-button {
  display: none;
}

事件列表

const video = document.getElementsById('video')[0];

// 当浏览器开始查找音频/视频时触发
video.addEventListener('loadstart', (e) => {
  console.log(e.type);
}, false);

// 当音频/视频的时长已更改时触发
video.addEventListener('durationchange', (e) => {
  console.log(e.type);
}, false);

// 当浏览器已加载音频/视频的元数据时触发
video.addEventListener('loadedmetadata', (e) => {
  console.log(e.type);
}, false);

// 当浏览器已加载音频/视频的当前帧时触发
video.addEventListener('loadeddata', (e) => {
  console.log(e.type);
}, false);

// 当浏览器正在下载音频/视频时触发
video.addEventListener('progress', (e) => {
  console.log(e.type);
}, false);

// 当浏览器可以开始播放音频/视频时触发
video.addEventListener('canplay', (e) => {
  console.log(e.type);
}, false);

// 当浏览器可在不因缓冲而停顿的情况下进行播放时触发
video.addEventListener('canplaythrough', (e) => {
  console.log(e.type);
}, false);

场景代码

import { fetchFile } from '/public/file/file.js';
import { ReaderVideoData } from '/public/module/video.js';


const videoArray = [];

async function initVideo() {
  const path = await fetchFile('https://test.ysunlight.com/Reference/video/Format/video.mp4');
  const videoElement = await ReaderVideoData(path, 'video/mp4');
  console.log(videoElement.duration);

  // 获取视频指定范围时间轴每帧缩略图
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  canvas.setAttribute('width', videoElement.videoWidth);
  canvas.setAttribute('height', videoElement.videoHeight);


  // 当用户已移动/跳跃到音频/视频中的新位置时触发
  let delayRender = null;
  videoElement.onseeked = () => {
    delayRender && delayRender();
  }
  
  for (let i = 0; i < videoElement.duration; i++) {
    if (i == 0) continue;

    videoElement.currentTime = i;
    // 保障onseeked事件已经触发
    await new Promise(r => delayRender = r);
    context.drawImage(videoElement, 0, 0, videoElement.videoWidth, videoElement.videoHeight);
    videoArray.push(canvas.toDataURL());
  }

  console.log(videoArray);
}
initVideo();


// 防止死缓
window.setTimeout(function() {
  var total = Math.floor(dynamicPlayer.duration);
  var audioControl = function() {
    var time = dynamicPlayer.currentTime;
    // 防止死缓
    console.log(time);
    if (time >= total) {
      current.attr("data-state", "false");
      button.attr("src", path + controlArray[0]);
      current.removeClass('audio-play');
      dynamicPlayer.pause();
      window.clearInterval(interval);
    }
  };
  interval = window.setInterval(audioControl, 1000);
}, 150);




//视频播放器
videoPlayer(e) {
  e = e || window.event;
  const node = e.currentTarget;
  const url = node.getAttribute('data-src');

  if (!Brower.ios) {
    const videoOtherID = document.getElementById("videoOtherID");
    videoOtherID.setAttribute("class", "controls");
    videoOtherID.src = url;
    videoOtherID.autoplay = true;
    videoOtherID.controls = 'controls';
    videoOtherID.play();
    return ;
  }   

  const videoIOSID = document.getElementById("videoIOSID");
  videoIOSID.setAttribute("class", "controls");
  videoIOSID.src = url;
  videoIOSID.loop = true;
  videoIOSID.preload = 'auto';
  videoIOSID.autoplay = true;
  videoIOSID.controls = 'controls';
  videoIOSID.setAttribute('x5-video-player-type', "h5");
  videoIOSID.setAttribute('x5-video-player-fullscreen', "true");
  videoIOSID.play();
  //监听退出全屏
}

CSS

video::-webkit-media-controls{ 
  display:none !important;
}


video::-internal-media-controls-overflow-button{ 
  display: none !important;
}


video::-webkit-media-controls-fullscreen-button {display: none;}
video::-webkit-media-controls-play-button {display:none;}
video::-webkit-media-controls-timeline {display:none;}
video::-webkit-media-controls-current-time-display{display:none;}
video::-webkit-media-controls-time-remaining-display {display:none;}
video::-webkit-media-controls-toggle-closed-captions-button {display:none;}

/** 隐藏声音icon */
video::-webkit-media-controls-mute-button {display:none;}
video::-webkit-media-controls-volume-slider {display:none;}


/* 隐藏Chrome/Safari的三点菜单 */
video::-webkit-media-controls-overflow-button {display: none;}
/* 隐藏Edge的三点菜单 */
video::-webkit-media-controls-overflow-menu-button {display: none;}
<!-- controlslist="nodownload noplaybackrate"用于取消更多控件弹窗中的下载功能和播放速度功能,disablePictureInPicture用于隐藏画中画功能。当这三个功能都被隐藏后,右下角的三点图标就会自动消失。 -->
<video controls controlslist="nodownload noplaybackrate" disablePictureInPicture>
    <source src="your-video-source.mp4" type="video/mp4">
</video>






<!--解决方案:禁止点击播放就会自动全屏   H5 video标签在ISO系统 Safari浏览器中 点击播放时,会自动全屏-->
<video 
  playsinline 
  webkit-playsinline="true" 
  controls
>
  <source src="video.mp4" type="video/mp4">
</video>

常见问题

问题原因:video标签在iPhone14.7与14.8.1版本上层级很高,导致遮挡其他定位元素层级低于video标签。

解决方案:通过CSS中transform: translateZ(0);实现提高层级。

HTML5VideoEditor

  • 搜狐云剪辑,360快剪辑,爱奇艺在线非编
  • 腾讯云剪:https://yunjian.qq.com/
  • 阿里裁剪:https://vod.console.aliyun.com/
  • 用于智能无损视频编辑、屏幕捕获和视频调试的轻量级软件:https://www.solveigmm.com/
  • HTML5 Video Editor:https://www.solveigmm.com/en/products/html5-cloud-video-editing/
  • html5-videoEditor:https://github.com/braveheartCMS/html5-videoEditor

VideoPlayer

initVideoData(duration, videoElement) {
    this.video.defaultTimes = duration;
    this.video.times = duration;

// 获取视频指定范围时间轴每帧缩略图
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.setAttribute('width', videoElement.videoWidth);
canvas.setAttribute('height', videoElement.videoHeight);

let delayRender = null;
videoElement.onseeked = () => {
  delayRender && delayRender();
}

videoElement.addEventListener('loadeddata', async () => {
  for (let i = 0; i < this.video.limit; i += 1) {
    const times = Math.ceil(this.video.axisDefaultValue / 10) * i;
    videoElement.currentTime = this;

    // 解决设置视频新位置延迟性渲染问题
    await new Promise(r => delayRender = r);

    context.drawImage(videoElement, 0, 0, videoElement.videoWidth, videoElement.videoHeight);
    this.video.axis.push(canvas.toDataURL());
  }
}, false);
}

// 获取视频长度
getVideoTimes(url) {
const scope = this;
this.video.url = yrl;

const videoElement = document.createElement('video');
videoElement.src = url;

videoElement.addEventListener('loadedmetadata', () => {
  const duration = videoElement.duration;

  if (duration === Infinity) {
    videoElement.currentTime = 10000000 * Math.random();
    videoElement.ondurationchange = function() {
      this.ontimeupdate = function() {
        return;
      }
      videoElement.currentTime = 10000000 * Math.random();
      videoElement.currentTime = 0;

      scope.initVideoData(videoElement.duration, videoElement);
    }
    return;
  }

  scope.initVideoData(duration, videoElement);
}, false);
}

mencoder

流媒体播放解决方案

并行视频实时转播与分发、视频录像分布存储、视频服务器和视频录像服务器的分布存储与资源共享。

Node-Media-Server

GitHub:https://github.com/illuspas/Node-Media-Server 一个 Node.js 实现的RTMP/HTTP/WebSocket/HLS/DASH流媒体服务器

代码案例

  <body data-url="https://test.ysunlight.com"> 
    <video controls src="http://127.0.0.1:8088"></video>
  </body>
  <script src="./test.js"></script>
  <script>
    let video = document.getElementsByTagName('video')[0];
    //http://127.0.0.1:7901/video



/**

    ajax({
      path: 'http://127.0.0.1:7901/video',
      method: 'get',
      dataType: 'blob',
      data: {
        count: 0
      },
      success(res) {
        let url = URL.createObjectURL(res);
        console.log(url);
        //video.src = url;
        //video.src = "../material/download.mp4";
      },
      fail(err) {
        //
      }
    });



 */
  </script>
(function(global, factory) {
  typeof exports == 'object' && typeof module == 'object' ? module.exports = factory() : typeof define == 'function' && define.amd ? define([], factory) : typeof exports == 'object' ? exports.ajax = factory : global.ajax = factory;
})(window, function(options) {'use strict';
  options = options || {};
  options.base = options.base || "";
  options.path = options.path || "";
  options.method = (options.method || 'GET').toUpperCase();
  options.async = options.async || true;
  options.timeout = options.timeout || 15000;
  options.size = options.size || 0;
  options.header = options.header || {};
  //设置为"json"时,responseText与responseXML就不能读取,否则报错
  options.dataType = options.dataType || '';
  options.data = options.data || {};

  options.base = options.base.match(/\/$/g) == null ? (options.base + '/') : options.base;
  
  //url
  var url = "";
  if (options.path.match(/^(https?:\/\/)/g) != null) {
    url = options.path;
  } else {
    options.path = options.path.match(/^\//g) == null ? options.path : options.path.replace(/^\//g, '');
    url = options.base + options.path;
  }


  /**
   * XMLHttpRequest
   */
  var xhr = null;
  if (window.ActiveXObject) {
    //IE6、IE7
    xhr = new ActiveXObject("Microsoft.xhr");
  } 
  else if (window.XMLHttpRequest) {
    //IE7+, Firefox, Chrome, Opera, Safari 
    xhr = new XMLHttpRequest();
  }
  else {
    throw new Error("it don’t support XMLHttpRequest.");
  }

  /**
   * 响应
   * 
   */

  //time out
  var TIMEOUT = null;

  if (options.dataType) {
    xhr.responseType = options.dataType.toLowerCase();
  }

  /**
   * 下载阶段
   */
  xhr.onloadstart = function(e) {
    console.log(e);
  }
  xhr.onprogress = function(e) {
    console.log(e);
  }
  xhr.onload = function(e) {
    console.log(e);
  }
  xhr.onloadend = function(e) {
    console.log(e);
  }
  xhr.ontimeout = function(e) {
    console.log(e);
  }
  xhr.onabort = function(e) {
    console.log(e);
  }
  xhr.onerror = function(e) {
    console.log(e);
  }

  xhr.upload.onloadstart = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }
  /**
   * 上传进度设计
   * 
   */
  xhr.upload.onprogress = function(e) {
    let size = options.size;
    let scale = size/100;

    let pros = e.loaded;
    let value = 0 + "%";
  
    let current = (pros/scale).toFixed(2);
  
    value = current + "%";
  
    if (pros >= size) {
      value = "100%";
    }
    options.progress && options.progress(value);
  }
  xhr.upload.onload = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }
  xhr.upload.onloadend = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }
  xhr.upload.ontimeout = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }
  xhr.upload.onabort = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }
  xhr.upload.onerror = function(e) {
    e['upload'] = e['type'];
    console.log(e);
  }


  /**
   * 上传阶段
   * 
   */
  //request state
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      //complete
      window.clearTimeout(TIMEOUT);
      if (options.complete) {
        options.complete();
      }
      if (xhr.status == 200) {
        //success
        if (options.success) {
          options.success(xhr.response);
        }
        return ;
      }
      //fail
      if (options.fail) {
        options.fail({state: xhr.status, msg: xhr.responseText});
      }
    }
  }

  //转换数据为PATH参数
  var transArguments = function(data) {
    data = data || {};
    var _path = "";
    for (var i in data) {
      _path += i + '=' + encodeURIComponent(data[i]) + '&';
    }
    _path = _path.replace(/&$/g, '');
    return _path;
  }


  //GET
  if (options.method == 'GET') {
    var _url = url + '?' + transArguments(options.data);
    xhr.open(options.method, _url, true);
    for (var key in options.header) {
      xhr.setRequestHeader(key, options.header[key]);
    }
    xhr.send(null);
  }


  //POST
  if (options.method == 'POST') {
    xhr.open(options.method, url, true);

    //上传文件清空Content-type, 否则会异常
    if (options.type !== 'upload') {
      xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    }

    for (var key in options.header) {
      xhr.setRequestHeader(key, options.header[key]);
    }

    //上传文件
    if (options.type === 'upload') {
      xhr.send(options.data);
    } else {
      xhr.send(transArguments(options.data));
    }
  }
  

  //超时
  TIMEOUT = window.setTimeout(function () {
    xhr.abort();
    //完成
    options.complete && options.complete({});

    //失败
    options.fail && options.fail({state: 408, msg: 'timeout'});

  }, options.timeout);


});

代码示例

<video controls src="http://127.0.0.1:8088/video"></video>
const express = require('express');
const app = express();

const fs = require("fs");
const zlib = require('zlib');

app.get('/', function (req, res) {
  res.send('Hello World');
});

//流视频
app.get('/video', function (req, res) {
  console.log(req.headers.range);

  let readerStream = fs.createReadStream('D:/devpt/project/server/item/video/material/download.mp4');
  readerStream.pipe(res);
  readerStream.on('end', function() {
    res.end();
  });
});


const port = 8088;
const server = app.listen(port, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log("应用实例,访问地址为 http://%s:%s", host, port)

});

互动视频编辑技术

官网:https://www.ctrlvideo.com/ 开放平台:https://open.ctrlvideo.com/ API接口:https://open.ctrlvideo.com/doc/detail/100004

智令互动(深圳)科技有限公司

互动视频云服务平台:视频剪辑系统、互动视频封装系统、互动视频内容发布系统、互动视频代理系统、互动视频播控系统

姜磊本人是腾讯4级专家,腾讯M族专家团团长; CEO侯亮拥有十年以上互联网内容、营销从业经验,曾任职网易、迅雷,服务过宝洁、箭牌、嘉士伯、日产等国内外500强企业; CTO张瑞圣拥有16年IT领域工作经验,为音视频领域专家,精通流媒体架构、机器视觉、视频AI等; COO肖国良,拥有十五年以上互联网行业营销、运营经验,曾任前程无忧(51job)销售高管、巴乐兔租房平台(Baletu)联合创始人,拥有多次创业经验。

第三方相关库

xgplayer

西瓜视频播放器

GitHub:https://github.com/bytedance/xgplayer 官网:https://v2.h5player.bytedance.com/

flv.js

具有 H.264 + AAC / MP3 编解码器播放功能的 FLV 容器 多部分分段视频播放 HTTP FLV 低延迟直播流播放 FLV over WebSocket 实时流播放 兼容 Chrome、FireFox、Safari 10、IE11 和 Edge 极低的开销,并由您的浏览器加速硬件!

GitHub:https://github.com/bilibili/flv.js

hls.js

videojs

videojs:https://docs.videojs.com/ Vue版本:https://docs.videojs.com/tutorial-vue.html

Last Updated:
Contributors: 709992523