play.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <template>
  2. <view class="play-container">
  3. <u-loading-page
  4. :loading="loading"
  5. :bg-color="$colors.bgColor"
  6. :color="$colors.primaryColor"
  7. :loading-color="$colors.primaryColor"
  8. />
  9. <template v-if="!loading">
  10. <!-- 剧集按钮-->
  11. <episode-buttons :episode="episode" @change="handleCollectAndFavChange" />
  12. <!--视频播放-->
  13. <view class="video-box main-center cross-center" :style="{zIndex: isPlaying ? 0 : 998}">
  14. <!--视频容器-->
  15. <swiper
  16. class="swiper"
  17. circular
  18. :vertical="true"
  19. :current="swiperCurrent"
  20. @change="handleSwiperChancge"
  21. >
  22. <swiper-item
  23. v-for="(item, index) in swiperEpisode"
  24. :key="index"
  25. >
  26. <!-- #ifdef MP-TOUTIAO | MP-WEIXIN-->
  27. <!-- 控制按钮 - 播放 -->
  28. <view v-if="!item.isPlaying" class="play-layer main-center cross-center" @tap="handlePlay(item)">
  29. <u-icon name="play-right-fill" size="100rpx" :color="$colors.defaultColor" />
  30. </view>
  31. <!-- 控制按钮 - 暂停 -->
  32. <view v-if="item.isPlaying" class="pause-layer" @tap="handlePause(item, true)" />
  33. <!-- #endif-->
  34. <video
  35. v-if="Object.keys(item).length"
  36. :id="`video${index}`"
  37. :poster="episode.cover_img"
  38. :src="item.url"
  39. style="width: 100%;height: 100%;"
  40. :show-play-btn="video.playBtn"
  41. :show-fullscreen-btn="video.fullscreenBtn"
  42. :controls="video.controls"
  43. :show-progress="video.progress"
  44. object-fit="contain"
  45. @timeupdate="timeupdate"
  46. />
  47. </swiper-item>
  48. </swiper>
  49. </view>
  50. <!--底部-->
  51. <episode-part
  52. :episode="episode"
  53. :is-playing="isPlaying"
  54. :buy-record="buyRecord"
  55. :current-episode="currentEpisode"
  56. @selectEpisode="handleSelectEpisode"
  57. />
  58. <!--充值-->
  59. <recharge
  60. :show.sync="rechargeShow"
  61. type="play"
  62. mode="bottom"
  63. :episode="episode"
  64. :list="currentEpisode"
  65. />
  66. </template>
  67. </view>
  68. </template>
  69. <script>
  70. import { mapState } from 'vuex'
  71. import Recharge from '../../components/Recharge/index'
  72. import EpisodeButtons from './components/EpisodeButtons'
  73. import EpisodePart from './components/EpisodePart'
  74. export default {
  75. name: 'Play',
  76. components: { EpisodePart, EpisodeButtons, Recharge },
  77. data() {
  78. return {
  79. id: null, // 短剧ID
  80. listId: null, // 剧集ID
  81. isPlaying: false, // 是否播放
  82. progress: 0, // 进度条
  83. episode: {}, // 短剧信息
  84. lists: [], // 剧集信息
  85. loading: false, // 数据加载
  86. video: { // 视频配置
  87. controls: true,
  88. progress: true,
  89. fullscreenBtn: false,
  90. playBtn: false
  91. },
  92. buyRecord: [], // 购买记录
  93. rechargeShow: false, // 显示充值
  94. swiperCurrent: 1, // 当前滚动
  95. currentEpisode: {}, // 当前播放剧集
  96. swiperEpisode: { // swiper 剧集
  97. prev: {},
  98. current: {},
  99. next: {}
  100. }
  101. }
  102. },
  103. computed: {
  104. ...mapState({
  105. userInfo: seate => seate.user.info
  106. }),
  107. videoContext() {
  108. const indexArr = ['prev', 'current', 'next']
  109. const swiperKey = indexArr[this.swiperCurrent]
  110. return uni.createVideoContext(`video${swiperKey}`, this)
  111. }
  112. },
  113. watch: {
  114. // 进度条
  115. progress(val) {
  116. if (val >= 100) {
  117. const len = this.lists.length
  118. const indexArr = ['prev', 'current', 'next']
  119. const swiperKey = indexArr[this.swiperCurrent]
  120. if (this.swiperEpisode[swiperKey].sort === this.lists[len - 1].sort) {
  121. this.$u.toast('已全部播放完成')
  122. return 100
  123. }
  124. // 切换
  125. switch (this.swiperCurrent) {
  126. case 0:
  127. this.swiperCurrent = 1
  128. break
  129. case 1:
  130. this.swiperCurrent = 2
  131. break
  132. case 2:
  133. this.swiperCurrent = 3
  134. break
  135. }
  136. this.$forceUpdate()
  137. }
  138. },
  139. swiperCurrent(val) {
  140. const indexArr = ['prev', 'current', 'next']
  141. const swiperKey = indexArr[val]
  142. const dataIndex = this.lists.findIndex(val => {
  143. return this.swiperEpisode[swiperKey].sort === val.sort
  144. })
  145. const len = this.lists.length
  146. let prevIndex, currentIndex, nextIndex
  147. switch (swiperKey) {
  148. case 'prev':
  149. currentIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
  150. nextIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
  151. this.swiperEpisode.current = this.lists[currentIndex]
  152. this.swiperEpisode.next = this.lists[nextIndex]
  153. break
  154. case 'current':
  155. prevIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
  156. nextIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
  157. this.swiperEpisode.prev = this.lists[prevIndex]
  158. this.swiperEpisode.next = this.lists[nextIndex]
  159. break
  160. case 'next':
  161. prevIndex = dataIndex + 1 >= len ? 0 : dataIndex + 1
  162. currentIndex = dataIndex - 1 < 0 ? len - 1 : dataIndex - 1
  163. this.swiperEpisode.current = this.lists[currentIndex]
  164. this.swiperEpisode.prev = this.lists[prevIndex]
  165. break
  166. }
  167. // 暂停其他
  168. this.handlePause(this.currentEpisode, false)
  169. // 当前播放剧集
  170. this.currentEpisode = this.swiperEpisode[swiperKey]
  171. // 播放
  172. this.handlePlay(this.currentEpisode)
  173. },
  174. currentEpisode(val) {
  175. console.log('-->data', val.sort)
  176. }
  177. },
  178. methods: {
  179. // 播放进度
  180. timeupdate({ detail }) {
  181. // currentTime, duration
  182. if (detail.duration) {
  183. this.progress = (detail.currentTime / detail.duration * 100).toFixed(2)
  184. }
  185. },
  186. // 播放
  187. handlePlay(item) {
  188. this.currentEpisode = item
  189. // 检查是否购买
  190. if (!this.checkBeforePlay(item)) {
  191. // 余额是否购买
  192. if (!this.checkOverage(item)) {
  193. this.rechargeShow = true
  194. return
  195. }
  196. // 余额足够 直接购买
  197. this.handleBuy()
  198. return
  199. }
  200. this.isPlaying = true
  201. item.isPlaying = true
  202. this.videoContext.play()
  203. this.watched(this.id, this.currentEpisode.id)
  204. },
  205. // 暂停
  206. handlePause(item, isAll) {
  207. if (!this.isPlaying) return
  208. item.isPlaying = false
  209. this.isPlaying = false
  210. // 展厅其他的
  211. const indexArr = ['prev', 'current', 'next']
  212. const swiperKey = indexArr[this.swiperCurrent]
  213. indexArr.forEach(obj => {
  214. if (swiperKey !== obj || isAll) {
  215. const videoContext = uni.createVideoContext(`video${obj}`, this)
  216. videoContext.pause()
  217. }
  218. })
  219. },
  220. // 选择剧集
  221. handleSelectEpisode(index) {
  222. // 暂停上一个
  223. this.handlePause(this.currentEpisode, true)
  224. const item = this.lists[index]
  225. // 重置SwiperEpisode数据 切换播放
  226. this.swiperCurrent = 1
  227. this.initSwiperEpisode(item.id)
  228. },
  229. // 当前剧集购买记录
  230. async getBuyRecord() {
  231. await this.$api.user.episode.buyRecord(this.id).then(res => {
  232. this.buyRecord = res.data
  233. })
  234. },
  235. // 购买剧集
  236. async handleBuy() {
  237. await this.$api.user.episode.buyHandle(this.id, this.currentEpisode.id).then(async res => {
  238. this.$hideLoading()
  239. if (typeof res.overage !== 'undefined') {
  240. this.rechargeShow = true
  241. } else {
  242. this.$u.toast('购买成功')
  243. await this.getBuyRecord()
  244. this.handlePlay(this.currentEpisode)
  245. this.$api.user.info().then(res => {
  246. this.$store.dispatch('user/info', res.data)
  247. })
  248. }
  249. }).catch(() => {
  250. this.$hideLoading()
  251. })
  252. },
  253. // 滚动 Swiper
  254. handleSwiperChancge({ detail }) {
  255. this.swiperCurrent = detail.current
  256. },
  257. // 播放前检查剧集是否购买/免费
  258. checkBeforePlay(item) {
  259. // 剧集免费 不免费已购买 VIP观看是VIP
  260. if (item.is_free) {
  261. return true
  262. }
  263. if (!item.is_free && this.buyRecord.indexOf(item.id) !== -1) {
  264. return true
  265. }
  266. if (this.episode.is_vip_watch && this.userInfo.info.is_vip) {
  267. return true
  268. }
  269. return false
  270. },
  271. // 检查余额是否足够支付
  272. checkOverage(item) {
  273. return this.userInfo.info.integral >= item.sale_price
  274. },
  275. // 记录观看记录
  276. watched(id, list_id) {
  277. this.$api.user.episode.watched(id, list_id).then(res => {
  278. })
  279. },
  280. // 分享
  281. handleShared(id, list_id) {
  282. this.$api.episode.shared(id, list_id).then(res => {
  283. this.episode.share_count += 1
  284. })
  285. },
  286. handleCollectAndFavChange(data) {
  287. console.log('-->data', data)
  288. if (data.type === 'collect') {
  289. this.episode.user_collect_count += data.num
  290. } else {
  291. this.episode.user_favorite_count += data.num
  292. }
  293. },
  294. // // 初始化 Swiper 剧集
  295. initSwiperEpisode(listId) {
  296. let currentIndex = 0
  297. if (listId) {
  298. currentIndex = this.lists.findIndex(obj => {
  299. return parseInt(listId) === parseInt(obj.id)
  300. })
  301. }
  302. let prevIndex = currentIndex - 1
  303. let nextIndex = currentIndex + 1
  304. const len = this.lists.length
  305. if (parseInt(listId) === 0 || prevIndex < 0) {
  306. prevIndex = len - 1
  307. }
  308. //
  309. if (nextIndex >= len) {
  310. nextIndex = 0
  311. }
  312. this.swiperEpisode = {
  313. prev: this.lists[prevIndex],
  314. current: this.lists[currentIndex],
  315. next: this.lists[nextIndex]
  316. }
  317. console.log('-->data', JSON.stringify(this.swiperEpisode))
  318. this.currentEpisode = this.lists[currentIndex]
  319. this.$nextTick(() => {
  320. this.handlePlay(this.currentEpisode)
  321. })
  322. },
  323. // 获取剧集详情
  324. getEpisode() {
  325. this.loading = true
  326. this.$api.episode.detail(this.id).then(res => {
  327. this.loading = false
  328. this.episode = res.data
  329. uni.setNavigationBarTitle({
  330. title: this.episode.name + (this.episode.status === 0 ? ' | 更新中' : '已完结')
  331. })
  332. this.episode.lists.forEach((obj, index) => {
  333. obj.isPlaying = false
  334. obj.index = index
  335. obj.progress = 0
  336. })
  337. this.lists = this.episode.lists
  338. // 初始化 Swiper 剧集
  339. this.initSwiperEpisode(this.listId)
  340. })
  341. }
  342. },
  343. async onLoad(options) {
  344. this.id = options.id
  345. this.listId = options?.list_id
  346. this.listId = this.listId ? this.listId : 0
  347. await this.getBuyRecord()
  348. this.getEpisode()
  349. },
  350. // 分享
  351. onShareAppMessage(res) {
  352. if (res.from === 'button') { // 来自页面内分享按钮
  353. console.log(res.target)
  354. }
  355. let options = {
  356. title: '',
  357. path: `/pages/episode/play?id=${this.id}`
  358. }
  359. if (this.episode) {
  360. options = {
  361. title: this.episode.name,
  362. path: `/pages/episode/play?id=${this.id}`,
  363. imageUrl: this.episode.cover_img,
  364. desc: this.episode.name,
  365. success: res => {
  366. this.handleShared(this.id, this.currentEpisode.id)
  367. }
  368. }
  369. }
  370. return options
  371. }
  372. }
  373. </script>
  374. <style lang="scss" scoped>
  375. .play-container {
  376. font-size: 28rpx;
  377. .video-box{
  378. position: fixed;
  379. top: 0;
  380. left: 0;
  381. right: 0;
  382. bottom: 0;
  383. .play-layer{
  384. position: fixed;
  385. top: 0;
  386. left: 0;
  387. bottom: 0;
  388. right: 0;
  389. background: transparent;
  390. z-index: 999;
  391. }
  392. .pause-layer{
  393. position: absolute;
  394. top: 0;
  395. left: 0;
  396. bottom: 0;
  397. right: 0;
  398. background: transparent;
  399. z-index: 99;
  400. }
  401. .swiper {
  402. width: 100%;
  403. height: 100vh;
  404. position: relative;
  405. z-index: 99;
  406. .swiper-item {
  407. width: 100%;
  408. height: calc(100vh - #{150rpx});
  409. }
  410. }
  411. .progress-container{
  412. width: 93vw;
  413. background: #fff;
  414. height: 10rpx;
  415. position: fixed;
  416. z-index: 100;
  417. bottom: 160rpx;
  418. .progress{
  419. height: 10rpx;
  420. background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
  421. }
  422. }
  423. }
  424. }
  425. </style>