play.vue 12 KB

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