courseVideo.nvue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <template>
  2. <view style="position: relative;">
  3. <view class="status_bar" :style="{height: statusBarHeight + 'px',width: windowWidth + 'px'}"></view>
  4. <view class="video-box" :style="{width: windowWidth + 'px'}">
  5. <video :style="{width: windowWidth + 'px',height: '422rpx'}" @tap="toggleBarChange" ref="myVideo2"
  6. id="myVideo2" :src="pickCatalog.videoUrl" :enable-progress-gesture="data.isFast==1"
  7. :enable-danmu="false" :danmu-btn="false" :initial-time="studyTimes" @play="onPlayVideo"
  8. @pause="onPauseVideo" :poster="poster" objectFit="contain" :show-progress="data.isFast==1"
  9. :title="pickCatalog.courseName" :controls="true" :show-fullscreen-btn="true"
  10. @controlstoggle="toggleBarChange" @timeupdate="onTimeUpdate" @fullscreenchange="onFullscreenChange"
  11. @waiting="onWaiting()" @ended="endedPlayNext()" @canplay="onVideoCanPlay"
  12. @canplaythrough="onVideoCanPlayThrough" @loadeddata="onLoadeddata" @loadedmetadata="onLoadedMetadata"
  13. @error="errorvideo">
  14. <!-- 疗法 -->
  15. <view
  16. v-if="showTherapy&&fullScreen"
  17. :class="fullScreen && direction == 'horizontal' ? 'horizontal':'horizontal'" @tap.stop>
  18. <view class="goods" @tap.stop="goToPro(showTherapyItem.packageId)">
  19. <image :src="showTherapyItem.imgUrl" mode="aspectFill" class="goodsimg"></image>
  20. <view class="goodsname"><text class="goodsname-txt">{{showTherapyItem.packageName}}</text>
  21. </view>
  22. </view>
  23. <view class="es-w-40 es-h-40" style="position: absolute;right:0rpx;top:0rpx;z-index:999">
  24. <image class="es-w-40 es-h-40" src="@/static/images/close40.png" @tap.stop="closeTherapy">
  25. </image>
  26. </view>
  27. </view>
  28. </video>
  29. <image class="back-img" @click="navBack" src="@/static/image/hall/back_white_icon.png"></image>
  30. </view>
  31. <view class="audiobox" v-if="showAudio" :style="{width: windowWidth + 'px',height: audioboxHeight + 'px'}" @click.stop>
  32. <view class="audiobox-title" :style="{width: windowWidth + 'px',paddingTop: paddingTop + 'px'}" @click="navBack">
  33. <uni-icons type="left" size="20" color="#fff"></uni-icons>
  34. <text class="textOne audiobox-text">{{pickCatalog.title}}</text>
  35. </view>
  36. <view class="es es-ver es-ac es-pc">
  37. <view class="es es-ac es-pc">
  38. <image class="es-icon-52 es-icon-play-last" src="/static/images/other/video/play-last.png" @click="audioBtnEvent('endedPlayPrev')"></image>
  39. <image class="es-icon-88 es-ml-50 es-mr-50"
  40. :src="audioPlayIng?'/static/images/other/video/play-stop.png':'/static/image/hall/video_icon.png'" @click="audioBtnEvent('playAudioAction')">
  41. </image>
  42. <image class="es-icon-52 es-icon-play-next" src="/static/images/other/video/play-next.png" @click="endedPlayNext('click')"></image>
  43. </view>
  44. <view class="audiobox-time audiobox-text">
  45. <text class="audiobox-text">{{playTime}} / {{formatSeconds(pickCatalog.seconds)}}</text>
  46. </view>
  47. </view>
  48. <view class="audiobox-video" :style="{width: windowWidth + 'px'}">
  49. <view class="audiobox-video-btn"><text class="audiobox-text" @click="audioBtnEvent('backPlayVideo')">返回视频</text></view>
  50. </view>
  51. </view>
  52. </view>
  53. </template>
  54. <script>
  55. import { formatSeconds } from '@/utils/tools.js'
  56. export default {
  57. data() {
  58. return {
  59. formatSeconds,
  60. showAudio: false,
  61. audioPlayIng: false,
  62. statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
  63. windowWidth: uni.getSystemInfoSync().windowWidth,
  64. audioboxHeight: 211,
  65. paddingTop: 15,
  66. videoContext: null,
  67. data: {
  68. isFast: false,
  69. },
  70. pickCatalog: {
  71. videoUrl: "https://tcpv.ylrzcloud.com/course/20241014/1728890026184.mp4",
  72. thumbnail: "",
  73. duration: 0,
  74. videoId: 0,
  75. seconds: 0,
  76. studyTime: null
  77. },
  78. studyTimes: 0,
  79. poster: "",
  80. fullScreen: false,
  81. direction: "",
  82. packageJsonList: [],
  83. showTherapy: false,
  84. showTherapyItem: {},
  85. currentTime: 0,
  86. nodeTime: 0,
  87. playTime:"00:00",
  88. }
  89. },
  90. onLoad() {
  91. this.paddingTop = this.statusBarHeight > 0 ? this.statusBarHeight : this.statusBarHeight + uni.upx2px(30)
  92. this.audioboxHeight = uni.upx2px(425) + this.statusBarHeight
  93. uni.$on('getPickCatalog', (data) => {
  94. this.pickCatalog = data
  95. this.packageJsonList = data.packageJson ? JSON.parse(data.packageJson) : []
  96. })
  97. uni.$on('getDataInfo', (data) => {
  98. this.data = data
  99. })
  100. uni.$on('studyTimes', (val) => {
  101. this.studyTimes = val
  102. })
  103. uni.$on('poster', (val) => {
  104. this.poster = val
  105. })
  106. uni.$on('playVideo', (val) => {
  107. this.videoContext.play();
  108. })
  109. uni.$on('pauseVideo', (val) => {
  110. this.videoContext.pause();
  111. })
  112. uni.$on('seekVideo', (val) => {
  113. this.videoContext.seek(val);
  114. })
  115. uni.$on('playTime', (val) => {
  116. this.playTime = val
  117. })
  118. uni.$on('changeData', (data) => {
  119. this.showAudio = data.showAudio
  120. this.audioPlayIng = data.audioPlayIng
  121. })
  122. },
  123. onReady() {
  124. this.videoContext = this.$refs.myVideo2
  125. },
  126. onUnload() {
  127. uni.$off('getPickCatalog')
  128. uni.$off('getDataInfo')
  129. uni.$off('studyTimes')
  130. uni.$off('poster')
  131. uni.$off('playVideo')
  132. uni.$off('pauseVideo')
  133. uni.$off('seekVideo')
  134. uni.$off('playTime')
  135. uni.$off('changeData')
  136. this.videoContext = null
  137. },
  138. methods: {
  139. navBack() {
  140. uni.$emit("navBack");
  141. },
  142. errorvideo(e) {
  143. console.log("播发错误", e)
  144. },
  145. toggleBarChange(event) {
  146. this.showFullScreenBtn = !this.showFullScreenBtn;
  147. },
  148. onPlayVideo() { //视频播放器事件
  149. uni.$emit("onPlayVideo");
  150. },
  151. onPauseVideo() {
  152. //this.isPlayIng=false;
  153. },
  154. onTimeUpdate(res) { //在视频播放时,检查缓存的变化,估算新增的已加载字节数并累加到 dataUsage 中。
  155. uni.$emit("onTimeUpdate", res);
  156. this.currentTime = Math.round(res.detail.currentTime)
  157. uni.$u.throttle(this.checkTherapy, 1000, true);
  158. },
  159. checkTherapy() {
  160. let node = this.packageJsonList.filter(item => item.duration == this.currentTime)
  161. if (node && node.length > 0) {
  162. this.nodeTime = node[0].duration
  163. this.showTherapyItem = node[0]
  164. if (!this.showTherapy) {
  165. this.showTherapy = true
  166. }
  167. } else {
  168. if (this.showTherapy && this.nodeTime && this.nodeTime > this.currentTime) {
  169. this.showTherapy = false
  170. }
  171. }
  172. },
  173. onFullscreenChange(event) {
  174. this.fullScreen = event.detail.fullScreen
  175. // direction取为 vertical 或 horizontal
  176. this.direction = event.detail.direction
  177. // 检查是否全屏
  178. if (!event.detail.fullScreen) {
  179. // 退出全屏,锁定竖屏
  180. plus.screen.lockOrientation('portrait-primary');
  181. }
  182. },
  183. onWaiting(res) {
  184. uni.$emit("onWaiting");
  185. //this.showToast("qxj onWaiting:"+JSON.stringify(res));
  186. },
  187. onProgress(event) {
  188. //console.log("qxj onProgress:"+JSON.stringify(event));
  189. //uni.showToast({title:event.detail.buffered,icon: 'none'});
  190. },
  191. endedPlayNext(type) {
  192. console.log('视频播放完成,开始下一个');
  193. uni.$emit("endedPlayNext",type);
  194. },
  195. onVideoCanPlay(res) {
  196. console.log('视频可以开始播放,但可能需要缓冲');
  197. //this.showToast("qxj onVideoCanPlay:"+JSON.stringify(res));
  198. },
  199. onVideoCanPlayThrough(res) {
  200. console.log('视频可以无暂停地播放');
  201. //this.showToast("qxj onVideoCanPlay:"+JSON.stringify(res));
  202. },
  203. onLoadeddata(res) {
  204. //this.showToast("qxj onLoadeddata res:"+JSON.stringify(res));
  205. console.log('视频缓冲完成');
  206. },
  207. onLoadedMetadata(event) { //在视频元数据加载完成时初始化。
  208. uni.$emit("onLoadedMetadata", event);
  209. },
  210. closeTherapy() {
  211. this.showTherapy = false
  212. },
  213. goToPro(productId) {
  214. this.videoContext.exitFullScreen()
  215. this.showTherapy = false
  216. setTimeout(() => {
  217. uni.navigateTo({
  218. url: "/pages/store/packageDetails?packageId=" + productId
  219. });
  220. }, 500);
  221. },
  222. audioBtnEvent(event) {
  223. uni.$emit("audioBtnEvent",event);
  224. }
  225. }
  226. }
  227. </script>
  228. <style lang="scss" scoped>
  229. .status_bar {
  230. background-color: #000;
  231. }
  232. .audiobox {
  233. // width: 100%;
  234. height: 422rpx;
  235. position: absolute;
  236. top: 0;
  237. left: 0;
  238. background-color: #333;
  239. z-index: 991;
  240. display: flex;
  241. flex-direction: column;
  242. align-items: center;
  243. justify-content: center;
  244. &-text {
  245. font-family: PingFang SC, PingFang SC;
  246. font-weight: 400;
  247. font-size: 26rpx;
  248. color: #fff;
  249. }
  250. &-video {
  251. position: absolute;
  252. bottom: 40rpx;
  253. left: 0;
  254. z-index: 992;
  255. display: flex;
  256. flex-direction: row;
  257. align-items: center;
  258. justify-content: center;
  259. }
  260. &-video-btn {
  261. background-color: rgba(255, 255, 255, 0.3);
  262. padding: 8rpx 20rpx;
  263. border-radius: 50rpx;
  264. }
  265. &-title {
  266. position: absolute;
  267. top: 0;
  268. left: 0;
  269. z-index: 992;
  270. padding: 30rpx 40rpx;
  271. // padding-top: calc(var(--status-bar-height) + 30rpx);
  272. width: 100%;
  273. box-sizing: border-box;
  274. background: linear-gradient(to top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
  275. color: #ffff;
  276. display: flex;
  277. flex-direction: row;
  278. overflow: hidden;
  279. align-items: center;
  280. box-sizing: border-box;
  281. .textOne {
  282. flex: 1;
  283. overflow: hidden;
  284. verflow: hidden;
  285. word-break: break-all; /* break-all(允许在单词内换行 */
  286. text-overflow: ellipsis; /* 超出部分省略号 */
  287. -webkit-box-orient: vertical; /* 设置或检索伸缩盒对象的子元素的排列方式 */
  288. -webkit-line-clamp: 1; /* 显示的行数 */
  289. lines:1;
  290. }
  291. }
  292. &-time {
  293. margin-top: 18rpx;
  294. font-size: 24rpx;
  295. }
  296. }
  297. .video-box {
  298. height: 422rpx;
  299. overflow: hidden;
  300. position: relative;
  301. }
  302. .back-img {
  303. width: 50rpx;
  304. height: 50rpx;
  305. position: absolute;
  306. top: 20rpx;
  307. left: 34rpx;
  308. z-index: 999;
  309. }
  310. .horizontal {
  311. width: 130px;
  312. height: 150px;
  313. position: absolute;
  314. bottom: 50px;
  315. left: 20px;
  316. z-index: 999;
  317. display: flex;
  318. flex-direction: column;
  319. align-items: center;
  320. justify-content: flex-end;
  321. background-color: transparent;
  322. .es-w-40 {
  323. width: 20px;
  324. }
  325. .es-h-40 {
  326. height: 20px;
  327. }
  328. .goods {
  329. width: 120px;
  330. height: 140px;
  331. border-radius: 10px;
  332. background-color: rgba(255, 255, 255, 0.4);
  333. overflow: hidden;
  334. display: flex;
  335. flex-direction: column;
  336. align-items: center;
  337. justify-content: center;
  338. }
  339. .goodsimg {
  340. width: 100px;
  341. height: 100px;
  342. border-radius: 10px;
  343. }
  344. .goodsname {
  345. width: 100px;
  346. box-sizing: border-box;
  347. padding-top: 7px;
  348. verflow: hidden;
  349. word-break: break-all;
  350. /* break-all(允许在单词内换行 */
  351. text-overflow: ellipsis;
  352. /* 超出部分省略号 */
  353. -webkit-box-orient: vertical;
  354. /* 设置或检索伸缩盒对象的子元素的排列方式 */
  355. -webkit-line-clamp: 1;
  356. /* 显示的行数 */
  357. lines: 1;
  358. }
  359. .goodsname-txt {
  360. font-size: 12px;
  361. text-overflow: ellipsis;
  362. /* 超出部分省略号 */
  363. lines: 1;
  364. }
  365. }
  366. .therapybox {
  367. width: 290rpx;
  368. height: 350rpx;
  369. position: absolute;
  370. bottom: 100rpx;
  371. left: 40rpx;
  372. z-index: 999;
  373. display: flex;
  374. flex-direction: column;
  375. align-items: center;
  376. justify-content: flex-end;
  377. background-color: transparent;
  378. .goods {
  379. width: 270rpx;
  380. height: 330rpx;
  381. border-radius: 12rpx;
  382. background-color: rgba(255, 255, 255, 0.4);
  383. overflow: hidden;
  384. display: flex;
  385. flex-direction: column;
  386. align-items: center;
  387. justify-content: center;
  388. }
  389. .goodsimg {
  390. width: 240rpx;
  391. height: 240rpx;
  392. border-radius: 12rpx;
  393. }
  394. .goodsname {
  395. width: 240rpx;
  396. box-sizing: border-box;
  397. padding-top: 14rpx;
  398. verflow: hidden;
  399. word-break: break-all;
  400. /* break-all(允许在单词内换行 */
  401. text-overflow: ellipsis;
  402. /* 超出部分省略号 */
  403. -webkit-box-orient: vertical;
  404. /* 设置或检索伸缩盒对象的子元素的排列方式 */
  405. -webkit-line-clamp: 1;
  406. /* 显示的行数 */
  407. lines: 1;
  408. }
  409. .goodsname-txt {
  410. font-size: 26rpx;
  411. text-overflow: ellipsis;
  412. /* 超出部分省略号 */
  413. lines: 1;
  414. }
  415. }
  416. </style>