video.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <template>
  2. <div class="nut-video" ref="videocon">
  3. <video ref="video" class="nut-videoplayer" :muted="options.muted" :autoplay="options.autoplay"
  4. :loop="options.loop" :poster="options.poster" :controls="options.controls" @error="handleError">
  5. <source v-for="source in sources" :src="source.src" :type="source.type" :key="source.src" />
  6. </video>
  7. <div class="playing-mask" ref="touchMask" v-if="showToolbox && !isDisabled" @click="play"></div>
  8. <div class="nut-video-play-btn" v-if="showToolbox && !isDisabled" ref="palyBtn" v-show="!state.playing"
  9. @click="play"></div>
  10. <div class="nut-video-controller" v-show="showToolbox && !isDisabled"
  11. :class="{'show-control': !state.playing,'hide-control':state.playing}">
  12. <div class="control-play-btn" @click="play"></div>
  13. <div class="current-time">{{ videoSet.displayTime }}</div>
  14. <div class="progress-container">
  15. <div class="progress" ref="progressBar">
  16. <div class="buffered" :style="{width: `${videoSet.loaded}%`}"></div>
  17. <div class="video-ball" :style="{transform: `translate3d(${videoSet.progress.current}px, -50%, 0)`}"
  18. @touchmove.stop.prevent="touchSlidMove($event)" @touchstart.stop="touchSlidSrart($event)"
  19. @touchend.stop="touchSlidEnd($event)">
  20. <div class="move-handle"></div>
  21. </div>
  22. <div class="played" ref="playedBar"></div>
  23. </div>
  24. </div>
  25. <div class="duration-time">{{ videoSet.totalTime }}</div>
  26. <div class="volume" @click="handleMuted" :class="{'muted':state.isMuted}"></div>
  27. <div class="fullscreen-icon" @click="fullScreen"></div>
  28. </div>
  29. <!-- 错误弹窗 -->
  30. <div class="nut-video-error" v-show="state.isError">
  31. <p class="lose">视频加载失败</p>
  32. <p class="retry" @click="retry">点击重试</p>
  33. </div>
  34. </div>
  35. </template>
  36. <script>
  37. import { throttle } from '../../utils/throttle';
  38. export default {
  39. name: 'nut-video',
  40. props: {
  41. sources: {
  42. type: Array,
  43. default() {
  44. return []
  45. }
  46. },
  47. options: {
  48. type: Object,
  49. default() {
  50. return {
  51. autoplay: false, //是否自动播放
  52. volume: 0.5,
  53. poster: '',
  54. loop: false,
  55. controls: true,
  56. muted: false, //是否静音
  57. disabled: false, //禁止操作
  58. playsinline: false, //行内展示
  59. touchPlay: false
  60. };
  61. },
  62. required: true
  63. },
  64. model: {
  65. type: String,
  66. default: ''
  67. }
  68. },
  69. data() {
  70. return {
  71. videoElm: null,
  72. initial: true, //控制封面的显示
  73. showToolbox: false, //控制控制器和标题的显示
  74. // 视频容器元素
  75. player: {
  76. $player: null,
  77. pos: null
  78. },
  79. // progress进度条元素
  80. progressBar: {
  81. progressElm: null, // 进度条DOM对象
  82. pos: null
  83. },
  84. // video控制显示设置
  85. videoSet: {
  86. loaded: 0, // 缓存长度
  87. displayTime: '00:00', // 进度时间
  88. totalTime: '00:00', // 总时间
  89. progress: {
  90. width: 0, // 进度条长度
  91. current: 0 // 进度条当前位置
  92. }
  93. },
  94. state: {
  95. controlShow: true,
  96. vol: 0.5, //音量
  97. currentTime: 0, //当前时间
  98. fullScreen: false,
  99. playing: false, //是否正在播放
  100. isLoading: false,
  101. isEnd: false,
  102. isError: false,
  103. isMuted: false
  104. },
  105. showTouchMask: false,
  106. };
  107. },
  108. computed: {
  109. isDisabled() {
  110. return this.options.disabled
  111. }
  112. },
  113. watch: {
  114. sources: {
  115. handler(newValue, oldValue) {
  116. if (newValue && oldValue && newValue != oldValue) {
  117. this.$nextTick(() => {
  118. this.videoElm.load()
  119. })
  120. }
  121. },
  122. immediate: true
  123. },
  124. options: {
  125. handler(val) {
  126. this.state.isMuted = val.muted ? val.muted : false
  127. },
  128. immediate: true
  129. },
  130. // model: {
  131. // handler(val) {
  132. // if (val) {
  133. // if (val == 'custom') {
  134. // this.state.controlShow = false;
  135. // this.showToolbox = this.options.controls ? true : false
  136. // }
  137. // } else {
  138. // this.showToolbox = false;
  139. // this.state.controlShow = this.options.controls ? true : false
  140. // }
  141. // },
  142. // immediate: true
  143. // }
  144. },
  145. mounted() {
  146. this.init();
  147. },
  148. methods: {
  149. init() {
  150. this.videoElm = this.$el.getElementsByTagName('video')[0];
  151. if (this.options.autoplay) {
  152. this.play();
  153. }
  154. if (this.options.touchPlay) {
  155. this.showTouchMask = true;
  156. }
  157. if (this.options.playsinline) {
  158. this.videoElm.setAttribute('playsinline', this.options.playsinline);
  159. this.videoElm.setAttribute('webkit-playsinline', this.options.playsinline);
  160. this.videoElm.setAttribute('x5-playsinline', this.options.playsinline);
  161. this.videoElm.setAttribute('x5-video-player-type', 'h5');
  162. this.videoElm.setAttribute('x5-video-player-fullscreen', false);
  163. }
  164. this.volumeHandle();
  165. if (this.showToolbox) {
  166. this.customerInit()
  167. } else {
  168. this.videoElm.addEventListener('play', () => {
  169. this.state.playing = true;
  170. this.$emit('play', this.videoElm);
  171. });
  172. this.videoElm.addEventListener('pause', () => {
  173. this.state.playing = false;
  174. this.$emit('pause', this.videoElm);
  175. });
  176. this.videoElm.addEventListener('ended', this.playEnded);
  177. this.videoElm.addEventListener('timeupdate', throttle(this.getPlayTime, 100, 1));
  178. }
  179. },
  180. customerInit() {
  181. const $player = this.$el;
  182. const $progress = this.$el.getElementsByClassName('progress')[0];
  183. // 播放器位置
  184. this.player.$player = $player;
  185. this.progressBar.progressElm = $progress;
  186. this.player.pos = $player.getBoundingClientRect();
  187. this.progressBar.pos = $progress.getBoundingClientRect();
  188. this.videoSet.progress.width = Math.round($progress.getBoundingClientRect().width);
  189. },
  190. play() {
  191. if (this.options.autoplay && this.options.disabled) {
  192. this.state.playing = true;
  193. // this.state.controlShow = false
  194. return false;
  195. }
  196. this.state.playing = !this.state.playing;
  197. if (this.videoElm) {
  198. // 播放状态
  199. if (this.state.playing) {
  200. try {
  201. this.videoElm.play();
  202. // 监听缓存进度
  203. this.videoElm.addEventListener('progress', e => {
  204. this.getLoadTime();
  205. });
  206. // 监听播放进度
  207. this.videoElm.addEventListener('timeupdate', throttle(this.getPlayTime, 100, 1));
  208. // 监听结束
  209. this.videoElm.addEventListener('ended', this.playEnded);
  210. this.$emit('play', this.videoElm);
  211. } catch (e) {
  212. // 捕获url异常出现的错误
  213. this.handleError()
  214. }
  215. }
  216. // 停止状态
  217. else {
  218. this.videoElm.pause();
  219. this.$emit('pause', this.videoElm);
  220. }
  221. }
  222. },
  223. // 音量控制
  224. volumeHandle() {
  225. this.state.vol = this.options.volume;
  226. },
  227. // 静音控制
  228. handleMuted() {
  229. this.state.isMuted = !this.state.isMuted
  230. this.videoElm.muted = this.state.isMuted
  231. },
  232. playEnded() {
  233. this.state.playing = false;
  234. this.state.isEnd = true;
  235. this.state.controlBtnShow = true;
  236. this.videoSet.displayTime = '00:00';
  237. this.videoSet.progress.current = 0;
  238. this.videoElm.currentTime = 0;
  239. this.$emit('playend', this.videoElm);
  240. },
  241. // 数据加载出错
  242. handleError() {
  243. // console.log('error')
  244. this.state.isError = true;
  245. },
  246. fullScreen() {
  247. if (!this.state.fullScreen) {
  248. this.state.fullScreen = true;
  249. this.videoElm.webkitRequestFullScreen();
  250. } else {
  251. this.state.fullScreen = false;
  252. document.webkitCancelFullScreen();
  253. }
  254. // setTimeout(this.initVideo, 200);
  255. },
  256. // 获取播放时间
  257. getPlayTime() {
  258. const percent = this.videoElm.currentTime / this.videoElm.duration;
  259. this.videoSet.progress.current = Math.round(this.videoSet.progress.width * percent);
  260. // 赋值时长
  261. this.videoSet.totalTime = this.timeFormat(this.videoElm.duration);
  262. this.videoSet.displayTime = this.timeFormat(this.videoElm.currentTime);
  263. },
  264. timeFormat(t) {
  265. var h = Math.floor(t / 3600);
  266. if (h < 10) {
  267. h = "0" + h;
  268. }
  269. var m = Math.floor(t % 3600 / 60);
  270. if (m < 10) {
  271. m = "0" + m;
  272. }
  273. var s = Math.round(t % 3600 % 60);
  274. if (s < 10) {
  275. s = "0" + s;
  276. }
  277. var str = '';
  278. if (h != 0) {
  279. str = h + ':' + m + ':' + s;
  280. } else {
  281. str = m + ':' + s;
  282. }
  283. return str;
  284. },
  285. // 获取缓存时间
  286. getLoadTime() {
  287. if (this.videoSet.loaded)
  288. this.videoSet.loaded = (this.videoElm.buffered.end(0) / this.videoElm.duration) * 100;
  289. },
  290. getTime() {
  291. this.videoElm.addEventListener('durationchange', e => {
  292. console.log(e);
  293. });
  294. this.videoElm.addEventListener('progress', e => {
  295. this.videoSet.loaded = (-1 + this.videoElm.buffered.end(0) / this.videoElm.duration) * 100;
  296. });
  297. this.videoSet.len = this.videoElm.duration;
  298. },
  299. // 拖动播放进度
  300. touchSlidSrart(e) { },
  301. touchSlidMove(e) {
  302. let currentX = e.targetTouches[0].pageX;
  303. let offsetX = currentX - this.progressBar.pos.left;
  304. // 边界检测
  305. if (offsetX <= 0) {
  306. offsetX = 0;
  307. }
  308. if (offsetX >= this.videoSet.progress.width) {
  309. offsetX = this.videoSet.progress.width;
  310. }
  311. this.videoSet.progress.current = offsetX;
  312. let percent = this.videoSet.progress.current / this.videoSet.progress.width;
  313. this.videoElm.duration && this.setPlayTime(percent, this.videoElm.duration);
  314. },
  315. touchSlidEnd(e) {
  316. let currentX = e.changedTouches[0].pageX;
  317. let offsetX = currentX - this.progressBar.pos.left;
  318. this.videoSet.progress.current = offsetX;
  319. // 这里的offsetX都是正数
  320. let percent = offsetX / this.videoSet.progress.width;
  321. this.videoElm.duration && this.setPlayTime(percent, this.videoElm.duration);
  322. },
  323. // 设置手动播放时间
  324. setPlayTime(percent, totalTime) {
  325. this.videoElm.currentTime = Math.floor(percent * totalTime);
  326. },
  327. // 点击重新加载
  328. retry() {
  329. console.log('error')
  330. this.state.isError = false;
  331. this.init();
  332. }
  333. },
  334. beforeDestroy() {
  335. }
  336. };
  337. </script>