Просмотр исходного кода

添加直播页面和播放器

reghao 2 лет назад
Родитель
Сommit
45545e71a6
3 измененных файлов с 484 добавлено и 0 удалено
  1. 160 0
      src/components/LivePlayer.vue
  2. 6 0
      src/router/index.js
  3. 318 0
      src/views/home/LivePage.vue

+ 160 - 0
src/components/LivePlayer.vue

@@ -0,0 +1,160 @@
+<template>
+  <div id="dplayer" ref="dplayer" style="height: 480px;" />
+</template>
+
+<script>
+import { videoUrl } from '@/api/video'
+import SocketInstance from '@/utils/ws/socket-instance'
+
+import flvjs from 'flv.js'
+import DPlayer from 'dplayer'
+
+export default {
+  name: 'VideoPlayer',
+  props: {
+    videoProp: {
+      type: Object,
+      default: () => null
+    }
+  },
+  data() {
+    return {
+      flvjs,
+      DPlayer,
+      danmaku: {
+        api: '//api.reghao.cn/api/comment/danmaku/',
+        token: 'bili'
+      },
+      getUrl: true
+    }
+  },
+  created() {
+  },
+  mounted() {
+    //SocketInstance.connect()
+
+    const videoId = this.videoProp.videoId
+    if (this.getUrl) {
+      // this.getVideoUrl(videoId)
+      const url = 'https://live.reghao.cn/live/cam'
+      this.initFlvPlayer(videoId, this.videoProp.coverUrl, url)
+    }
+  },
+  methods: {
+    getVideoUrl(videoId) {
+      videoUrl(videoId).then(res => {
+        if (res.code === 0) {
+          const urlType = res.data.type
+          if (urlType === 'mp4') {
+            const urls = res.data.urls
+            for (const url of urls) {
+              url.type = 'normal'
+            }
+            const autoPlay = false
+            this.initMp4Player(this.videoProp.userId, videoId, this.videoProp.coverUrl, urls, res.data.currentTime, autoPlay)
+          } else if (urlType === 'flv') {
+            const urls = res.data.urls
+            const url = urls[0].url
+            this.initFlvPlayer(videoId, this.videoProp.coverUrl, url)
+          } else {
+            this.$notify.error({
+              message: '视频 url 类型不合法',
+              type: 'warning',
+              duration: 3000
+            })
+          }
+        } else {
+          this.$notify.error({
+            message: '视频 url 获取失败',
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify.error({
+          message: '视频 url 获取失败',
+          type: 'warning',
+          duration: 3000
+        })
+      })
+    },
+    danmakuConfig() {
+      // TODO 获取弹幕配置,将 videoUrl 作为本函数的回调
+    },
+    initMp4Player(userId, videoId, coverUrl, urls, pos, autoPlay) {
+      const player = new DPlayer({
+        container: document.querySelector('#dplayer'),
+        lang: 'zh-cn',
+        logo: '/logo.png',
+        screenshot: false,
+        autoplay: autoPlay,
+        volume: 0.1,
+        mutex: true,
+        video: {
+          pic: coverUrl,
+          defaultQuality: 0,
+          quality: urls
+        },
+        danmaku: {
+          id: videoId,
+          maximum: 10000,
+          api: this.danmaku.api,
+          token: this.danmaku.token,
+          user: userId,
+          bottom: '15%',
+          unlimited: true
+        }
+      })
+
+      // 设置音量
+      //player.volume(0.1, true, false)
+      // 跳转到上次看到的位置
+      player.seek(pos)
+
+      /* 事件绑定 */
+      player.on('progress', function() {
+        // SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
+      })
+
+      player.on('ended', () => {
+        // SocketInstance.send({ videoId: videoId, currentTime: player.video.currentTime })
+        const path = this.$route.path
+        const nextPath = '/video/gz5RYkw1zn'
+        if (path !== nextPath) {
+          this.$router.push(nextPath)
+        } else {
+          console.log('视频播放完成')
+        }
+      })
+
+      player.on('volumechange', () => {
+        console.log('声音改变')
+      })
+    },
+    initFlvPlayer(videoId, coverUrl, videoUrl) {
+      const dp = new DPlayer({
+        container: document.getElementById('dplayer'),
+        live: true,
+        danmaku: false,
+        video: {
+          url: videoUrl,
+          type: 'customFlv',
+          customType: {
+            customFlv: function(video, player) {
+              const flvPlayer = flvjs.createPlayer({
+                type: 'flv',
+                url: video.src
+              })
+              flvPlayer.attachMediaElement(video)
+              flvPlayer.load()
+            }
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style>
+</style>

+ 6 - 0
src/router/index.js

@@ -7,6 +7,7 @@ const TimelineIndex = () => import('views/home/Timeline')
 const StatusPage = () => import('views/home/Status')
 const VideoIndex = () => import('views/home/Video')
 const VideoPage = () => import('views/home/VideoPage')
+const LivePage = () => import('views/home/LivePage')
 const AudioIndex = () => import('views/home/Audio')
 const AudioPage = () => import('views/home/AudioPage')
 const ImageIndex = () => import('views/home/Image')
@@ -106,6 +107,11 @@ const routes = [
     name: 'VideoPage',
     component: VideoPage
   },
+  {
+    path: '/live/:id',
+    name: 'LivePage',
+    component: LivePage
+  },
   {
     path: '/audio',
     name: 'AudioIndex',

+ 318 - 0
src/views/home/LivePage.vue

@@ -0,0 +1,318 @@
+<template>
+  <el-row class="movie-list">
+    <el-col :md="15">
+      <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <el-row>
+              <h3 v-html="video.title" />
+            </el-row>
+            <el-row style="color: #999;font-size: 16px;padding-top: 0px;">
+              <span><i class="el-icon-video-play">{{ video.viewCount }}</i></span>
+            </el-row>
+          </div>
+          <div class="text item">
+            <live-player :video-prop="video"/>
+          </div>
+        </el-card>
+      </el-row>
+      <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
+        <el-card class="box-card">
+          <div slot="header" class="clearfix">
+            <div class="video-data-row">
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-collection"
+                :disabled="isCollected"
+                @click="collection(video.videoId)"
+              >
+                <span>收藏 {{ video.collectCount }}</span>
+              </el-button>
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-thumb"
+                :disabled="isCollected"
+                @click="collection(video.videoId)"
+              >
+                <span>喜欢 {{ video.collectCount }}</span>
+              </el-button>
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-share"
+                :disabled="isCollected"
+                @click="collection(video.videoId)"
+              >
+                <span>分享 {{ video.collectCount }}</span>
+              </el-button>
+              <el-button
+                type="danger"
+                size="mini"
+                icon="el-icon-download"
+                @click="getDownloadUrl(video.videoId)"
+              >
+                <span>下载</span>
+              </el-button>
+            </div>
+          </div>
+          <div class="text item">
+            <!--视频描述行-->
+            <span class="description" v-html="video.description"/>
+          </div>
+        </el-card>
+      </el-row>
+    </el-col>
+    <el-col :md="9">
+      <el-row>
+        <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
+          <user-avatar-card :userAvatar="user" />
+        </el-row>
+        <el-row v-if="showPlaylist" style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
+          <el-card class="box-card">
+            <div slot="header" class="clearfix">
+              <el-row>
+                <h3>播放列表</h3>
+              </el-row>
+              <el-row>
+                <span>自动播放 <el-switch v-model="autoPlay"/></span>
+              </el-row>
+            </div>
+            <div class="text item">
+              <el-table
+                :data="similarVideos"
+                style="width: 100%"
+              >
+                <el-table-column
+                  prop="title">
+                  <template slot-scope="scope">
+                    <router-link target="_blank" :to="`/video/${scope.row.videoId}`">
+                      <span>{{scope.row.videoId}}</span>
+                    </router-link>
+                  </template>
+                </el-table-column>
+                <el-table-column
+                  prop="coverUrl">
+                  <template slot-scope="scope">
+                    <span>10:00</span>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+          </el-card>
+        </el-row>
+        <el-row style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
+          <el-card class="box-card">
+            <div slot="header" class="clearfix">
+              <el-row>
+                <h3>推荐直播</h3>
+              </el-row>
+            </div>
+            <div class="text item">
+              <el-row v-for="(item,index) in similarVideos" :key="index" class="item">
+                <video-card :video="item" />
+              </el-row>
+            </div>
+          </el-card>
+        </el-row>
+      </el-row>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+import LivePlayer from 'components/LivePlayer'
+import Comment from 'components/comment/Comment'
+import VideoCard from 'components/card/VideoCard'
+import UserAvatarCard from '@/components/card/UserAvatarCard'
+
+import {similarVideo, videoInfo, videoErrorReport, downloadVideo} from '@/api/video'
+import { collectVideo } from "@/api/collect";
+import {getUserInfo} from "@/api/user";
+
+export default {
+  name: 'LivePage',
+  components: { Comment, VideoCard, LivePlayer, UserAvatarCard },
+  data() {
+    return {
+      video: {
+        videoId: 'jk1024',
+        title: '我的直播',
+        description: '我的直播',
+        viewCount: 10,
+        collectCount: 10,
+      },
+      user: {
+        userId: 10001,
+        screenName: '浩',
+        avatarUrl: '',
+        following: 1024,
+        follower: 1024,
+      },
+      videoProp: {
+        info: null,
+        autoPlay: false,
+        playlist: []
+      },
+      similarVideos: [],
+      isCollected: false,
+      showErrorReportDialog: false,
+      errorReportForm: {
+        videoId: null,
+        errorCode: null
+      },
+      showPlaylist: false,
+      autoPlay: false,
+    }
+  },
+  watch: {
+    // 地址栏 url 发生变化时重新加载本页面
+    $route(){
+      this.$router.go()
+    }
+  },
+  created() {
+    const id = this.$route.params.id
+    console.log('id -> ' + id)
+    /*this.getVideoInfo(videoId)
+    this.getSimilarVideos(videoId)*/
+  },
+  methods: {
+    // 获取视频的详细信息
+    getVideoInfo(videoId) {
+      videoInfo(videoId).then(res => {
+        if (res.code === 0) {
+          this.videoProp = res.data
+
+          this.video = res.data
+          document.title = res.data.title
+          this.userId = res.data.userId
+          getUserInfo(this.userId).then(res => {
+            if (res.code === 0) {
+              this.user = res.data
+            } else {
+              this.$notify.error({
+                message: '用户数据获取失败',
+                type: 'warning',
+                duration: 3000
+              })
+            }
+          })
+        } else {
+          this.$notify.error({
+            message: '视频数据获取失败',
+            type: 'warning',
+            duration: 3000
+          })
+          this.$router.push('/404')
+        }
+      }).catch(error => {
+        this.$notify.error({
+          message: error.message,
+          type: 'warning',
+          duration: 3000
+        })
+      })
+    },
+    // 用户点击收藏
+    collection(videoId) {
+      collectVideo({videoId: videoId}).then(res => {
+        if (res.code === 0) {
+          this.$notify.success({
+            title: '视频已收藏',
+            duration: 2000
+          })
+        } else {
+        }
+      })
+    },
+    getDownloadUrl(videoId) {
+      let filename
+      downloadVideo(videoId).then(res => {
+        if (res.code === 0) {
+          const downloadUrl = res.data
+          fetch(downloadUrl.url, {
+            headers: {
+              Authorization: 'Bearer ' + downloadUrl.token
+            },
+            method: 'GET',
+            credentials: 'include',
+          }).then(res => {
+            /*
+            遍历 formdata
+            for (const key of res.headers.keys()) {
+              console.log(key + ' : ' + res.headers.get(key))
+            }*/
+            const header = res.headers.get('Content-Disposition');
+            const parts = header.split(';');
+            filename = parts[1].split('=')[1];
+            return res.blob()
+          }).then(data => {
+              const blobUrl = window.URL.createObjectURL(data);
+              const a = document.createElement('a');
+              a.download = filename;
+              a.href = blobUrl;
+              a.click();
+          }).catch(e => {
+            this.$notify({
+              title: '提示',
+              message: '视频下载失败',
+              type: 'warning',
+              duration: 3000
+            })
+          })
+        } else {
+          this.$notify({
+            title: '提示',
+            message: res.msg,
+            type: 'warning',
+            duration: 3000
+          })
+        }
+      }).catch(error => {
+        this.$notify({
+          title: '提示',
+          message: error.message,
+          type: 'error',
+          duration: 3000
+        })
+      })
+    },
+  }
+}
+</script>
+
+<style scoped>
+/*处于手机屏幕时*/
+@media screen and (max-width: 768px) {
+  .movie-list {
+    padding-top: 8px;
+    padding-left: 0.5%;
+    padding-right: 0.5%;
+  }
+}
+
+.movie-list {
+  padding-top: 15px;
+  padding-left: 5%;
+  padding-right: 5%;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.v-tag {
+  padding-top: 10px;
+}
+.tag{
+  margin-right: 3px;
+}
+</style>