video-box.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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);
  87. this.video_real_time = Number(data.playDuration);
  88. this.playedTime = Number(data.playDuration);
  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. this.$emit('recordDuration', { playDuration, duration: this.duration, currentDuration: this.currentDuration }, index, refresh);
  106. },
  107. /**
  108. * 获取视频总时长
  109. * @param {Object} data
  110. */
  111. loadedmetadata(data) {
  112. this.duration = data.detail.duration;
  113. this.loading = false;
  114. },
  115. /**
  116. * 显示倍速浮层
  117. * @date 2022-10-19
  118. * @returns {any}
  119. */
  120. showSwitchRate() {
  121. this.rateShow = true;
  122. },
  123. /**
  124. * 切换倍速
  125. * @param {Object} e
  126. */
  127. switchRate(e) {
  128. let rate = Number(e.currentTarget.dataset.rate);
  129. this.currentRate = rate;
  130. this.rateShow = false;
  131. this.videoContext.playbackRate(rate);
  132. },
  133. /**
  134. * 视频点击
  135. * @param {Object} e
  136. */
  137. videoClick(e) {
  138. this.rateShow = false;
  139. },
  140. /**
  141. * 视频停止
  142. * @date 2022-10-19
  143. * @param {any} e
  144. * @returns {any}
  145. */
  146. pause(e) {
  147. console.log(e);
  148. },
  149. /**
  150. * 控制视频不能快进
  151. * @param {Object} e
  152. */
  153. timeUpdate(e) {
  154. //播放的总时长
  155. let duration = e.detail.duration;
  156. //实时播放进度 秒数
  157. let jumpTime = parseInt(e.detail.currentTime);
  158. //当前视频进度
  159. if (jumpTime - this.playedTime > 3) {
  160. // 差别过大,调用seek方法跳转到实际观看时间
  161. this.videoContext.seek(this.playedTime);
  162. wx.showToast({
  163. title: '未完整看完该视频,不能快进',
  164. icon: 'none',
  165. duration: 2000
  166. });
  167. } else {
  168. this.video_real_time = parseInt(e.detail.currentTime);
  169. this.currentDuration = e.detail.currentTime
  170. if (this.video_real_time > this.playedTime) {
  171. this.playedTime = this.video_real_time;
  172. }
  173. }
  174. },
  175. /**
  176. * 视频结束
  177. */
  178. ended() {
  179. // 用户把进度条拉到最后,但是实际观看时间不够,跳转回去会自动暂停。
  180. // 这里加个判断。
  181. this.$emit(
  182. 'recordDuration',
  183. { playDuration: this.playedTime, duration: this.duration, currentDuration: this.currentDuration },
  184. this.videoIndex,
  185. false
  186. );
  187. }
  188. }
  189. };
  190. </script>
  191. <style lang="scss" scoped>
  192. .video-box {
  193. &-body {
  194. width: 100%;
  195. height: 400rpx;
  196. .video-control {
  197. background-color: rgba(0, 0, 0, 0.1);
  198. height: 90rpx;
  199. position: absolute;
  200. top: 0;
  201. left: 0;
  202. width: 100%;
  203. z-index: 997;
  204. }
  205. .video-wrap {
  206. position: relative;
  207. }
  208. .multi-list.full-screen.vertical {
  209. height: 100vh !important;
  210. padding: 8vh 0;
  211. }
  212. .multi-list.full-screen.horizontal {
  213. height: 100vw !important;
  214. padding: 8vw 0;
  215. }
  216. .multi {
  217. position: absolute;
  218. right: 30rpx;
  219. top: 50%;
  220. transform: translateY(-50%);
  221. z-index: 998;
  222. width: 100rpx;
  223. color: #fff;
  224. padding: 10rpx 0;
  225. text-align: center;
  226. transition: color ease 0.3s;
  227. }
  228. .multi.rate {
  229. right: 30rpx;
  230. }
  231. .multi-list {
  232. position: absolute;
  233. height: 100%;
  234. width: 0;
  235. background-color: rgba(0, 0, 0, 0.65);
  236. top: 0;
  237. right: 0;
  238. transition: width 0.3s ease;
  239. z-index: 999;
  240. box-sizing: border-box;
  241. padding: 20rpx 0;
  242. }
  243. .multi-list.rate {
  244. padding: 25rpx 0;
  245. }
  246. .multi-list.active {
  247. width: 200rpx;
  248. }
  249. .multi-item {
  250. text-align: center;
  251. color: #fff;
  252. line-height: 80rpx;
  253. transition: color ease 0.3s;
  254. }
  255. .multi-item.rate {
  256. line-height: 70rpx;
  257. }
  258. .multi-item:hover,
  259. .multi:hover {
  260. color: #00d8ff;
  261. }
  262. .multi-item.active {
  263. color: #00d8ff;
  264. }
  265. }
  266. }
  267. </style>