PlaylistView.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <template>
  2. <el-row v-if="!permissionDenied" class="movie-list">
  3. <el-col v-if="video !== null" :md="15">
  4. <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
  5. <el-card class="box-card">
  6. <div slot="header" class="clearfix">
  7. <el-row>
  8. <router-link style="text-decoration-line: none" target="_blank" :to="`/vod/video/${video.videoId}`">
  9. <el-button style="float: right; padding: 3px 0" type="text">
  10. 原视频
  11. </el-button>
  12. </router-link>
  13. </el-row>
  14. <el-row>
  15. <h3 v-html="video.title" />
  16. </el-row>
  17. <el-row style="color: #999;font-size: 16px;padding-top: 0px;">
  18. <span><i class="el-icon-video-play">{{ video.view }}</i></span>
  19. <span v-html="'&nbsp;&nbsp;&nbsp;&nbsp;'" />
  20. <span><i class="el-icon-s-comment">{{ video.comment }}</i></span>
  21. <span v-html="'&nbsp;&nbsp;&nbsp;&nbsp;'" />
  22. <span><i class="el-icon-watch">{{ video.pubDate }}</i></span>
  23. </el-row>
  24. </div>
  25. <div class="text item">
  26. <div id="dplayer" ref="dplayer" style="height: 480px;" />
  27. </div>
  28. </el-card>
  29. </el-row>
  30. <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
  31. <el-card class="box-card">
  32. <div slot="header" class="clearfix">
  33. <div class="video-data-row">
  34. <el-button
  35. type="danger"
  36. size="mini"
  37. icon="el-icon-collection"
  38. :disabled="isCollected"
  39. @click="collection(video.videoId)"
  40. >
  41. <span>收藏 {{ video.favorite }}</span>
  42. </el-button>
  43. <el-button
  44. type="danger"
  45. size="mini"
  46. icon="el-icon-delete"
  47. @click="deleteVideo(video)"
  48. >
  49. <span>删除</span>
  50. </el-button>
  51. </div>
  52. </div>
  53. <div class="text item">
  54. <!--视频描述行-->
  55. <span class="description" v-html="video.description" />
  56. <el-divider />
  57. <!--视频标签行-->
  58. <div class="v-tag">
  59. <el-tag
  60. v-for="(tag,index) in video.tags"
  61. :key="index"
  62. class="tag"
  63. size="medium"
  64. effect="plain"
  65. >
  66. <router-link style="text-decoration-line: none" target="_blank" :to="`/vod/video/tag/` + tag">
  67. {{ tag }}
  68. </router-link>
  69. </el-tag>
  70. </div>
  71. </div>
  72. </el-card>
  73. </el-row>
  74. </el-col>
  75. <el-col :md="9">
  76. <el-row>
  77. <el-row v-if="showPlaylist" style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
  78. <el-card class="box-card">
  79. <div slot="header" class="clearfix">
  80. <el-row>
  81. <h3>播放列表</h3>
  82. </el-row>
  83. <el-row>
  84. <span>自动播放 <el-switch v-model="autoPlay" /></span>
  85. </el-row>
  86. </div>
  87. <div class="text item">
  88. <el-table
  89. :data="playList.list"
  90. :show-header="false"
  91. height="480"
  92. style="width: 100%"
  93. >
  94. <el-table-column
  95. type="index"
  96. />
  97. <el-table-column
  98. prop="coverUrl"
  99. >
  100. <template slot-scope="scope">
  101. <el-image
  102. lazy
  103. fit="cover"
  104. class="coverImg"
  105. :src="scope.row.coverUrl"
  106. />
  107. </template>
  108. </el-table-column>
  109. <el-table-column
  110. prop="title"
  111. >
  112. <template slot-scope="scope">
  113. <el-button type="text" @click="playItem(scope.row)">
  114. {{ scope.row.title | ellipsis }}
  115. </el-button>
  116. </template>
  117. </el-table-column>
  118. <el-table-column
  119. prop="duration"
  120. >
  121. <template slot-scope="scope">
  122. <span>{{ scope.row.duration }}</span>
  123. </template>
  124. </el-table-column>
  125. </el-table>
  126. </div>
  127. </el-card>
  128. </el-row>
  129. </el-row>
  130. </el-col>
  131. </el-row>
  132. <el-row v-else>
  133. <permission-denied-card :text-object="textObject" />
  134. </el-row>
  135. </template>
  136. <script>
  137. import PermissionDeniedCard from '@/components/card/PermissionDeniedCard'
  138. import flvjs from 'flv.js'
  139. import DPlayer from 'dplayer'
  140. import { videoUrl, videoInfo } from '@/api/video'
  141. import { videoErrorDelete } from '@/api/video_edit'
  142. import { getPlaylistItems } from '@/api/collect'
  143. import { getUserInfo } from '@/api/user'
  144. export default {
  145. name: 'PlaylistView',
  146. components: { PermissionDeniedCard },
  147. filters: {
  148. ellipsis(value) {
  149. if (!value) return ''
  150. const max = 20
  151. if (value.length > max) {
  152. return value.slice(0, max) + '...'
  153. }
  154. return value
  155. }
  156. },
  157. data() {
  158. return {
  159. video: null,
  160. user: null,
  161. isCollected: false,
  162. errorReportForm: {
  163. videoId: null,
  164. errorCode: null
  165. },
  166. permissionDenied: false,
  167. textObject: {
  168. content: '视频',
  169. route: '/video'
  170. },
  171. showPlaylist: true,
  172. autoPlay: false,
  173. playList: {
  174. current: 0,
  175. list: []
  176. },
  177. // **********************************************************************/
  178. flvjs,
  179. DPlayer,
  180. danmaku: {
  181. api: process.env.VUE_APP_SERVER_URL + '/api/comment/danmaku/',
  182. token: 'tnbapp'
  183. },
  184. getUrl: true
  185. }
  186. },
  187. watch: {
  188. // 地址栏 url 发生变化时重新加载本页面
  189. $route() {
  190. this.$router.go()
  191. }
  192. },
  193. created() {
  194. const albumId = this.$route.params.albumId
  195. getPlaylistItems(albumId).then(resp => {
  196. if (resp.code === 0) {
  197. const respData = resp.data
  198. document.title = respData.albumInfo.albumName
  199. this.playList.list = respData.pageList.list
  200. const videoId = this.playList.list[0].videoId
  201. this.getVideoInfo(videoId)
  202. const key = 'myplaylist-' + videoId
  203. const value = localStorage.getItem(key)
  204. if (value != null) {
  205. this.playList = JSON.parse(value)
  206. this.calculateCurrent(videoId)
  207. }
  208. } else {
  209. document.title = '播放列表'
  210. }
  211. })
  212. },
  213. mounted() {
  214. window.addEventListener('beforeunload', this.handleBeforeUnload)
  215. const header = this.$refs.header
  216. if (header !== undefined && header !== null) {
  217. console.log('header -> ' + header)
  218. this.wrapStyle = `height: calc(100vh - ${header.clientHeight + 20}px)`
  219. }
  220. },
  221. methods: {
  222. handleBeforeUnload(event) {
  223. const key = 'myplaylist-' + this.video.videoId
  224. localStorage.removeItem(key)
  225. // event.preventDefault()
  226. },
  227. // 获取视频的详细信息
  228. getVideoInfo(videoId) {
  229. videoInfo(videoId).then(resp => {
  230. if (resp.code === 0) {
  231. this.video = resp.data
  232. this.getVideoUrl(videoId)
  233. this.userId = resp.data.userId
  234. getUserInfo(this.userId).then(resp => {
  235. if (resp.code === 0) {
  236. this.user = resp.data
  237. } else {
  238. this.$notify.error({
  239. message: '用户数据获取失败',
  240. type: 'warning',
  241. duration: 3000
  242. })
  243. }
  244. })
  245. } else if (resp.code === 2) {
  246. this.$router.push('/404')
  247. } else {
  248. this.permissionDenied = true
  249. }
  250. }).catch(error => {
  251. this.$notify.error({
  252. message: error.message,
  253. type: 'warning',
  254. duration: 3000
  255. })
  256. })
  257. },
  258. // 用户点击收藏
  259. collection(videoId) {
  260. },
  261. deleteVideo(video) {
  262. this.$confirm('确定要删除 ' + video.title + '?', '提示', {
  263. confirmButtonText: '确定',
  264. cancelButtonText: '取消',
  265. type: 'warning',
  266. customClass: 'msgbox'
  267. }).then(() => {
  268. const videoId = video.videoId
  269. const errorReportForm = {
  270. videoId: videoId,
  271. errorCode: 4
  272. }
  273. videoErrorDelete(errorReportForm).then(resp => {
  274. if (resp.code === 0) {
  275. this.errorReportForm.errorCode = null
  276. this.$notify({
  277. title: '提示',
  278. message: '视频错误已提交',
  279. type: 'warning',
  280. duration: 3000
  281. })
  282. } else {
  283. this.$notify({
  284. title: '提示',
  285. message: resp.msg,
  286. type: 'warning',
  287. duration: 3000
  288. })
  289. }
  290. }).catch(error => {
  291. this.$notify({
  292. title: '提示',
  293. message: error.message,
  294. type: 'warning',
  295. duration: 3000
  296. })
  297. })
  298. }).catch(() => {
  299. this.$message({
  300. type: 'info',
  301. message: '已取消'
  302. })
  303. })
  304. },
  305. // ****************************************************************************************************************
  306. getVideoUrl(videoId) {
  307. videoUrl(videoId).then(res => {
  308. if (res.code === 0) {
  309. const urlType = res.data.type
  310. if (urlType === 'mp4') {
  311. const urls = res.data.urls
  312. for (const url of urls) {
  313. url.type = 'normal'
  314. }
  315. this.initMp4Player(this.video.userId, videoId, this.video.coverUrl, urls, res.data.currentTime)
  316. } else {
  317. this.$notify.error({
  318. message: '视频 url 类型不合法',
  319. type: 'warning',
  320. duration: 3000
  321. })
  322. }
  323. } else {
  324. this.$notify.error({
  325. message: '视频 url 获取失败',
  326. type: 'warning',
  327. duration: 3000
  328. })
  329. }
  330. }).catch(error => {
  331. this.$notify.error({
  332. message: error.message,
  333. type: 'error',
  334. duration: 3000
  335. })
  336. })
  337. },
  338. initMp4Player(userId, videoId, coverUrl, urls, pos) {
  339. const player = new DPlayer({
  340. container: document.querySelector('#dplayer'),
  341. lang: 'zh-cn',
  342. screenshot: false,
  343. autoplay: false,
  344. volume: 0.1,
  345. mutex: true,
  346. video: {
  347. pic: coverUrl,
  348. defaultQuality: 0,
  349. quality: urls,
  350. hotkey: true
  351. },
  352. danmaku: {
  353. id: videoId,
  354. maximum: 10000,
  355. api: this.danmaku.api,
  356. token: this.danmaku.token,
  357. user: userId,
  358. bottom: '15%',
  359. unlimited: true
  360. }
  361. })
  362. // 设置音量
  363. // player.volume(0.1, true, false)
  364. // 跳转到上次看到的位置
  365. player.seek(pos)
  366. /* 事件绑定 */
  367. player.on('progress', function() {
  368. })
  369. player.on('ended', () => {
  370. this.$message.info('当前视频播放完成')
  371. })
  372. player.on('volumechange', () => {
  373. console.log('声音改变')
  374. })
  375. },
  376. calculateCurrent(videoId) {
  377. for (var i = 0; i < this.playList.list.length; i++) {
  378. if (videoId === this.playList.list[i].videoId) {
  379. this.playList.current = i
  380. const key = 'myplaylist-' + this.video.videoId
  381. localStorage.setItem(key, JSON.stringify(this.playList))
  382. }
  383. }
  384. },
  385. setCurrent(current) {
  386. this.playList.current = current
  387. const key = 'myplaylist-' + this.video.videoId
  388. localStorage.setItem(key, JSON.stringify(this.playList))
  389. },
  390. getNextPath(current) {
  391. this.calculateCurrent(current)
  392. const next = this.playList.current + 1
  393. if (next < this.playList.list.length) {
  394. this.setCurrent(next)
  395. const videoId = this.playList.list[next].videoId
  396. const path = '/vidlist/' + videoId
  397. if (path !== this.$route.path) {
  398. this.$router.push(path)
  399. } else {
  400. console.log(this.playList)
  401. this.$notify.info({
  402. message: '视频列表播放完成',
  403. duration: 3000
  404. })
  405. }
  406. }
  407. },
  408. playItem(item) {
  409. this.getVideoInfo(item.videoId)
  410. }
  411. }
  412. }
  413. </script>
  414. <style scoped>
  415. /*处于手机屏幕时*/
  416. @media screen and (max-width: 768px) {
  417. .movie-list {
  418. padding-top: 8px;
  419. padding-left: 0.5%;
  420. padding-right: 0.5%;
  421. }
  422. .msgbox{
  423. width: 320px !important;
  424. }
  425. }
  426. .movie-list {
  427. padding-top: 5px;
  428. padding-bottom: 5px;
  429. padding-left: 5px;
  430. padding-right: 5px;
  431. }
  432. .clearfix:before,
  433. .clearfix:after {
  434. display: table;
  435. content: "";
  436. }
  437. .clearfix:after {
  438. clear: both;
  439. }
  440. .v-tag {
  441. padding-top: 10px;
  442. }
  443. .tag{
  444. margin-right: 3px;
  445. }
  446. .coverImg {
  447. width: 100%;
  448. height: 90px;
  449. display: block;
  450. }
  451. </style>