video-box.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <template>
  2. <view class="video-box" v-loading="loading">
  3. <video
  4. v-if="videoUrl"
  5. class="video-box-body"
  6. id="myVideo"
  7. @timeupdate="timeUpdate"
  8. :src="videoUrl"
  9. controls
  10. :initial-time="initial_time"
  11. object-fit="fill"
  12. play-btn-position="center"
  13. @ended="ended"
  14. @tap="videoClick"
  15. @loadedmetadata="loadedmetadata"
  16. >
  17. <cover-view class="video-control">
  18. <cover-view class="multi rate" @tap.stop="showSwitchRate">x {{ currentRate }}</cover-view>
  19. </cover-view>
  20. <cover-view class="multi-list rate" :class="{ active: rateShow }">
  21. <cover-view
  22. v-for="(item, index) in rateList"
  23. :key="index"
  24. class="multi-item rate"
  25. :data-rate="item"
  26. @tap="switchRate"
  27. :class="{ active: item == currentRate }"
  28. >
  29. {{ item }}
  30. </cover-view>
  31. </cover-view>
  32. </video>
  33. <view class="video-box-body" v-else>
  34. <u-empty text="视频地址为空" mode="page"></u-empty>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. export default {
  40. name: 'VideoBox',
  41. props: {
  42. rateList: {
  43. type: Array,
  44. default: () => ['0.5', '1.0', '1.5', '2.0']
  45. },
  46. videoUrl: {
  47. type: String,
  48. default: ''
  49. }
  50. },
  51. data() {
  52. return {
  53. // 加载层
  54. loading: false,
  55. // 视频创建dom
  56. videoContext: uni.createVideoContext('myVideo', this),
  57. // 视频实时时间
  58. initial_time: 0,
  59. // 视频已经播放时间
  60. playedTime: 0,
  61. // 倍速浮层
  62. rateShow: false,
  63. // 默认倍速
  64. currentRate: 1.0,
  65. // 视频实际时间
  66. video_real_time: 0,
  67. // 视频总时长
  68. duration: 0,
  69. // 当前视频选中播放
  70. currentDuration: 0,
  71. // 视频信息
  72. videoInfo: {},
  73. // 选中课程下标
  74. videoIndex: 0
  75. };
  76. },
  77. methods: {
  78. /**
  79. * 加载视频
  80. * @date 2022-10-19
  81. * @returns {any}
  82. */
  83. loadVideo(data) {
  84. this.loading = true;
  85. this.videoInfo = data;
  86. this.initial_time = Number(data.currentDuration) ?? 0;
  87. this.video_real_time = Number(data.playDuration) ?? 0;
  88. this.playedTime = Number(data.playDuration) ?? 0;
  89. },
  90. /**
  91. * 记录时间
  92. * @date 2022-10-19
  93. * @param { Number } index
  94. * @returns {any}
  95. */
  96. recordDuration(index, refresh) {
  97. this.videoIndex = index;
  98. let playDuration = this.video_real_time;
  99. if (this.videoInfo.playDuration > this.video_real_time) {
  100. this.currentDuration = this.video_real_time;
  101. playDuration = this.videoInfo.playDuration;
  102. } else {
  103. this.currentDuration = this.video_real_time;
  104. }
  105. if (this.videoUrl) {
  106. this.$emit('recordDuration', { playDuration, duration: this.duration, currentDuration: this.currentDuration }, index, refresh);
  107. }
  108. },
  109. /**
  110. * 获取视频总时长
  111. * @param {Object} data
  112. */
  113. loadedmetadata(data) {
  114. this.duration = data.detail.duration;
  115. this.loading = false;
  116. },
  117. /**
  118. * 显示倍速浮层
  119. * @date 2022-10-19
  120. * @returns {any}
  121. */
  122. showSwitchRate() {
  123. this.rateShow = true;
  124. },
  125. /**
  126. * 切换倍速
  127. * @param {Object} e
  128. */
  129. switchRate(e) {
  130. let rate = Number(e.currentTarget.dataset.rate);
  131. this.currentRate = rate;
  132. this.rateShow = false;
  133. this.videoContext.playbackRate(rate);
  134. },
  135. /**
  136. * 视频点击
  137. * @param {Object} e
  138. */
  139. videoClick(e) {
  140. this.rateShow = false;
  141. },
  142. /**
  143. * 视频停止
  144. * @date 2022-10-19
  145. * @param {any} e
  146. * @returns {any}
  147. */
  148. pause(e) {
  149. console.log(e);
  150. },
  151. /**
  152. * 控制视频不能快进
  153. * @param {Object} e
  154. */
  155. timeUpdate(e) {
  156. //播放的总时长
  157. let duration = e.detail.duration;
  158. //实时播放进度 秒数
  159. let jumpTime = parseInt(e.detail.currentTime);
  160. //当前视频进度
  161. if (jumpTime - this.playedTime > 3) {
  162. // 差别过大,调用seek方法跳转到实际观看时间
  163. this.videoContext.seek(this.playedTime);
  164. wx.showToast({
  165. title: '未完整看完该视频,不能快进',
  166. icon: 'none',
  167. duration: 2000
  168. });
  169. } else {
  170. this.video_real_time = parseInt(e.detail.currentTime);
  171. this.currentDuration = e.detail.currentTime;
  172. if (this.video_real_time > this.playedTime) {
  173. this.playedTime = this.video_real_time;
  174. }
  175. }
  176. },
  177. /**
  178. * 视频结束
  179. */
  180. ended() {
  181. // 用户把进度条拉到最后,但是实际观看时间不够,跳转回去会自动暂停。
  182. // 这里加个判断。
  183. if (this.videoUrl) {
  184. this.$emit(
  185. 'recordDuration',
  186. { playDuration: this.playedTime, duration: this.duration, currentDuration: this.currentDuration },
  187. this.videoIndex,
  188. true
  189. );
  190. }
  191. }
  192. }
  193. };
  194. </script>
  195. <style lang="scss" scoped>
  196. .video-box {
  197. &-body {
  198. width: 100%;
  199. height: 400rpx;
  200. .video-control {
  201. background-color: rgba(0, 0, 0, 0.1);
  202. height: 90rpx;
  203. position: absolute;
  204. top: 0;
  205. left: 0;
  206. width: 100%;
  207. z-index: 997;
  208. }
  209. .video-wrap {
  210. position: relative;
  211. }
  212. .multi-list.full-screen.vertical {
  213. height: 100vh !important;
  214. padding: 8vh 0;
  215. }
  216. .multi-list.full-screen.horizontal {
  217. height: 100vw !important;
  218. padding: 8vw 0;
  219. }
  220. .multi {
  221. position: absolute;
  222. right: 30rpx;
  223. top: 50%;
  224. transform: translateY(-50%);
  225. z-index: 998;
  226. width: 100rpx;
  227. color: #fff;
  228. padding: 10rpx 0;
  229. text-align: center;
  230. transition: color ease 0.3s;
  231. }
  232. .multi.rate {
  233. right: 30rpx;
  234. }
  235. .multi-list {
  236. position: absolute;
  237. height: 100%;
  238. width: 0;
  239. background-color: rgba(0, 0, 0, 0.65);
  240. top: 0;
  241. right: 0;
  242. transition: width 0.3s ease;
  243. z-index: 999;
  244. box-sizing: border-box;
  245. padding: 20rpx 0;
  246. }
  247. .multi-list.rate {
  248. padding: 25rpx 0;
  249. }
  250. .multi-list.active {
  251. width: 200rpx;
  252. }
  253. .multi-item {
  254. text-align: center;
  255. color: #fff;
  256. line-height: 80rpx;
  257. transition: color ease 0.3s;
  258. }
  259. .multi-item.rate {
  260. line-height: 70rpx;
  261. }
  262. .multi-item:hover,
  263. .multi:hover {
  264. color: #00d8ff;
  265. }
  266. .multi-item.active {
  267. color: #00d8ff;
  268. }
  269. }
  270. }
  271. </style>