Vue.js 实战系列之实现视频类WebApp的项目开发——5. 视频播放列表实现

如果想看该实战系列的其他内容,请移步至 Vue.js 实战系列之实现视频类WebApp的项目开发

项目仓库地址,欢迎 Star


实现效果

在这里插入图片描述


功能实现

  1. 路由配置

    设置 推荐 和 关注 的视频播放列表子路由以及解决重复点击导航时,控制台出现报错的问题。

    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import Home from '../views/Home.vue';
    
    Vue.use(VueRouter);
    
    const routes = [
      {
        path: '/',
        redirect: '/index/recommend',
      },
      {
        path: '/index',
        redirect: '/index/recommend',
      },
      {
        path: '/',
        name: 'Home',
        component: Home,
        children: [
          {
            path: '/index',
            name: 'index',
            component: () => import(/* webpackChunkName: "index" */ '../views/index/index.vue'),
            children: [
              {
                path: 'follows',
                name: 'follows',
                component: () => import(/* webpackChunkName: "follows" */ '../views/follow/index.vue'),
                children: [
                  {
                    path: 'reVideoList',
                    name: 'reVideoList',
                    component: () => import(/* webpackChunkName: "VideoList" */ '../common/components/videoList/VideoList.vue'),
                  },
                ],
              },
              {
                path: 'recommend',
                name: 'recommend',
                component: () => import(/* webpackChunkName: "recommend" */ '../views/recommend/index.vue'),
                children: [
                  {
                    path: 'reVideoList',
                    name: 'reVideoList',
                    component: () => import(/* webpackChunkName: "VideoList" */ '../common/components/videoList/VideoList.vue'),
                  },
                ],
              },
            ],
          },
          {
            path: '/friends',
            name: 'friends',
            component: () => import(/* webpackChunkName: "friends" */ '../views/friends/index.vue'),
          },
          {
            path: '/news',
            name: 'news',
            component: () => import(/* webpackChunkName: "news" */ '../views/news/index.vue'),
          },
          {
            path: '/mine',
            name: 'mine',
            component: () => import(/* webpackChunkName: "mine" */ '../views/mine/index.vue'),
          },
        ],
      },
      {
        path: '/release',
        name: 'release',
        component: () => import(/* webpackChunkName: "release" */ '../views/release/index.vue'),
      },
    ];
    
    const router = new VueRouter({
      mode: 'history',
      base: process.env.BASE_URL,
      routes,
    });
    
    // 解决重复点击导航时,控制台出现报错
    const VueRouterPush = VueRouter.prototype.push;
    VueRouter.prototype.push = function push(to) {
      return VueRouterPush.call(this, to).catch(err => err);
    };
    
    export default router;
    
  2. 实现 VideoList 组件
    在这里插入图片描述

    1. 创建 VideoList.vue 组件

      采用 vue-awesome-swiper 实现视频列表的全屏滚动。

      官网: vue-awesome-swiper

      安装:

      npm install swiper vue-awesome-swiper --save
      # or
      yarn add swiper vue-awesome-swiper
      

      使用:

      引入方式分为:Global Registration 和 Local Registration 两种引入方式
      在这里我使用的是在组件中引入,全局引入注册只需要在 main.js 中导入即可,具体使用方式可以去官网查看。

      <template>
        <div class="video-list">
          <swiper ref="mySwiper" :options="swiperOptions">
            <swiper-slide>
              <Videos></Videos>
            </swiper-slide>
            <swiper-slide>Slide 2</swiper-slide>
            <swiper-slide>Slide 3</swiper-slide>
            <swiper-slide>Slide 4</swiper-slide>
            <swiper-slide>Slide 5</swiper-slide>
            <div class="swiper-pagination" slot="pagination"></div>
          </swiper>
        </div>
      </template>
      
      <script>
        import { Swiper, SwiperSlide, directive } from 'vue-awesome-swiper';
        import 'swiper/swiper.min.css';
        import Videos from './Videos.vue';
      
        export default {
          name: 'carrousel',
          components: {
            Swiper,
            SwiperSlide,
            Videos,
          },
          directives: {
            swiper: directive,
          },
          data() {
            return {
              swiperOptions: {
                // 分页器配置
                pagination: {
                  el: '.swiper-pagination',
                  clickable: true,
                },
                // 设定初始化时slide的索引
                initialSlide: 0,
                // Slides的滑动方向,可设置水平(horizontal)或垂直(vertical)
                direction: 'vertical',
                // 鼠标覆盖Swiper时指针形状,设置为true时会变成抓手形状
                grabCursor: true,
                // Swiper使用flexbox布局(display: flex),开启这个设定会在Wrapper上添加等于slides相加的宽或高,
                // 在对flexbox布局的支持不是很好的浏览器中可能需要用到。
                setWrapperSize: true,
                // 自动高度。设置为true时,wrapper和container会随着当前slide的高度而发生变化。
                autoHeight: true,
                // 设置slider容器能够同时显示的slides数量(carousel模式)。
                slidesPerView: 1,
                // 开启鼠标滚轮控制Swiper切换。可设置鼠标选项,或true使用默认值。
                mousewheel: true,
                // 是否开启鼠标控制Swiper切换。设置为true时,能使用鼠标滑轮控制slide滑动。
                mousewheelControl: true,
                // 获取swiper容器的高度。
                height: window.innerHeight, // 因为抖音视频的高度是占满整个屏幕的高度
                // 抵抗率。边缘抵抗力的大小比例。值越小抵抗越大越难将slide拖离边缘,0时完全无法拖离。
                resistanceRatio: 0,
                // 将observe应用于Swiper的祖先元素。当Swiper的祖先元素变化时,例如window.resize,Swiper更新。
                observeParents: true,
              },
              // 标识翻页
              page: 1,
            };
          },
          computed: {
            swiper() {
              return this.$refs.mySwiper.$swiper;
            },
          },
          mounted() {
            console.log('Current Swiper instance object', this.swiper);
          },
        };
      </script>
      
      <style lang="less" scoped>
      .video-list {
        height: 100%;
        .swiper-container {
          height: 100%;
          display: flex;
          background-color: #000;
          color: #fff;
          .swiper-slide {
            display: flex;
            justify-content: center;
            align-items: center;
          }
        }
      }
      </style>
      
    2. 创建 Videos.vue 组件

      1. 采用 vue-video-player 实现视频播放器。

        官网:vue-video-player

        安装:

        npm install vue-video-player --save
        # or
        yarn add vue-video-player
        

        使用:

        引入方式跟上面的插件一样。

      2. 通过 css 的 transform 属性 改变播放按钮的位置

      3. 修改按钮样式 通过 /deep/ 实现样式穿透。

      <template>
        <div class="videos">
          <video-player
            class="video-player-box"
            ref="videoPlayer"
            :options="playerOptions"
            :playsinline="true"
          >
          </video-player>
        </div>
      </template>
      
      <script>
      import 'video.js/dist/video-js.css';
      import { videoPlayer } from 'vue-video-player';
      
      export default {
          components: {
              videoPlayer,
          },
          data() {
            return {
              // videojs options
              playerOptions: {
                  // 默认情况下将会消除任何音频。
                  muted: true,
                  // 如果true,浏览器准备好时开始回放。
                  autoplay: false,
                  // 导致视频一结束就重新开始。
                  loop: true,
                  preload: 'auto',
                  // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器.
                  fluid: true,
                  sources: [{
                      type: 'video/mp4', // 类型
                      src: require('@/assets/videos/sucai.mp4'),
                  }],
                  // poster: '/static/images/author.jpg',
                  // 视频宽度,获取客户端宽度
                  width: document.documentElement.clientWidth,
                  // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
                  notSupportedMessage: '此视频暂无法播放,请稍后再试',
                  controlBar: false,
              },
            };
          },
          mounted() {
              console.log('this is current player instance object', this.player);
          },
      };
      </script>
      
      <style lang="less" scoped>
      .videos {
        height: 100%;
        width: 100%;
        padding-bottom: 50px;
        .video-player-box {
          height: 100%;
          width: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
          /deep/ .vjs-big-play-button {
            position: absolute;
            width: 80px;
            height: 80px;
            border: none;
            background-color: transparent;
            content: none;
            left: 50%;
            top: 55%;
            transform: translate(-50%, -50%);
            .vjs-icon-placeholder {
              font-size: 100px;
              color: rgba(255, 255, 255, 0.7);
            }
          }
          /deep/ .video-js {
            height: calc(100vh - 50px);
          }
        }
      }
      </style>
      
  3. 实现视频播放列表的循环渲染

    1. VideoList.vue 循环渲染

      在原来代码的基础上,修改 swiper-slide 进行 v-for 循环,并且添加模拟数据 dataList,将视频传递给子组件 <Videos/>

      <template>
        <div class="video-list">
          <swiper ref="mySwiper" :options="swiperOptions">
            <swiper-slide v-for="(item , index) in dataList" :key="index">
              <Videos :video="item"></Videos>
            </swiper-slide>
            <div class="swiper-pagination" slot="pagination"></div>
          </swiper>
        </div>
      </template>
      
      <script>
        import { Swiper, SwiperSlide, directive } from 'vue-awesome-swiper';
        import 'swiper/swiper.min.css';
        import Videos from './Videos.vue';
      
        export default {
          name: 'carrousel',
          components: {
            Swiper,
            SwiperSlide,
            Videos,
          },
          directives: {
            swiper: directive,
          },
          data() {
            return {
              dataList: [
                {
                  id: '1',
                  url: 'http://video.jishiyoo.com/3720932b9b474f51a4cf79f245325118/913d4790b8f046bfa1c9a966cd75099f-8ef4af9b34003bd0bc0261cda372521f-ld.mp4',
                },
                {
                  id: '2',
                  url: 'http://video.jishiyoo.com/1eedc49bba7b4eaebe000e3721149807/d5ab221b92c74af8976bd3c1473bfbe2-4518fe288016ee98c8783733da0e2da4-ld.mp4',
                },
                {
                  id: '3',
                  url: 'http://video.jishiyoo.com/549ed372c9d14b029bfb0512ba879055/8e2dc540573d496cb0942273c4a4c78c-15844fe70971f715c01d57c0c6595f45-ld.mp4',
                },
                {
                  id: '4',
                  url: 'http://video.jishiyoo.com/161b9562c780479c95bbdec1a9fbebcc/8d63913b46634b069e13188b03073c09-d25c062412ee3c4a0758b1c48fc8c642-ld.mp4',
                },
              ],
              // 标识翻页
              page: 1,
            };
          },
        };
      </script>
      
    2. Videos.vue 组件视频播放

      Video 是 VideoList 的子组件,通过父子组件传值的方法 在 Videos 组件的 props 属性中接受父组件传过来的值。

      <template>
        <div class="videos">
          <video-player
            class="video-player-box"
            ref="videoPlayer"
            :options="playerOptions"
            :playsinline="true"
          >
          </video-player>
        </div>
      </template>
      
      <script>
      import 'video.js/dist/video-js.css';
      import { videoPlayer } from 'vue-video-player';
      
      export default {
          components: {
              videoPlayer,
          },
          props: ['video'], // 接收父组件传递过来的数据
          data() {
            return {
              // videojs options
              playerOptions: {
                  // ... 其他无关代码省略
                  sources: [{
                      type: 'video/mp4', // 类型
                      src: this.video.url, // 获取VideoList组件传递过来的视频地址
                  }],
              },
            };
          },
      };
      </script>
      

    以上只是部分代码,请自行整合,完整代码见 Github


问题记录

  1. Vue 重复点击相同路由,出现 Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/index/recommend/reVideoList".

    问题描述:重复点击导航时,控制台出现如上报错信息。

    问题原因:产生这个错误信息的原因很简单,就是因为重复点击相同的路由造成的。在 vue router v3.1 版本之后 ,回调形式改成 Promise api了,返回的是 Promise,如果没有捕获到错误,控制台始终会出现如上图的警告。

    解决方案:

    1. 方案一、降低 router 版本
      npm i vue-router@3.0 -S

    2. 在 router/index.js 文件夹下增加下列代码

      // src/router/index.js
      import Vue from 'vue';
      import VueRouter from 'vue-router';
      Vue.use(VueRouter);		
      const router = new Router({
        // routes...配置信息
      })
       
      const VueRouterPush = VueRouter.prototype.push;
      VueRouter.prototype.push = function push(to) {
        return VueRouterPush.call(this, to).catch(err => err);
      };
      
    3. 捕获异常

      this.$router.push(route).catch(err => {
        console.log('输出报错',err)
      })
      
    4. 跳转时,判断跳转路由和当前路由是否一致,避免重复跳转产生问题

      toXXX (item) {
        if (this.$route.path !== item.url) {
          this.$router.push({ path: item.url })
        }
      }
      
    5. 补齐router第三个参数

      // 补齐router.push()的第三个参数
      this.$router.push(route, () => {}, (e) => {
          console.log('输出报错',e) 
      })
      
  2. 使用 videojs 播放视频提示 (CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED) No compatible source was found for this video.

    问题描述:使用 Videos 播放本地视频时无法播放。

    解决方案:

    1. 将文件放到服务器上,就是别用本地文件的方式打开;

    2. 播放本地视频文件,使用 require 导入即可;

      require('@/assets/videos/index/01.mp4'),
      
    3. 用的是 chrome,将网站的 flash 设置成默认允许,然后刷新下,就可以了。

    4. 如果没有服务器,可以使用下面文件中提供的播放源播放。

  3. 如何更改 Video 组件的播放按钮的样式?

    在 vue 组件中,在 style 设置为 scoped 的时候,里面在写样式对子组件是不生效的,如果想让某些样式对所有子组件都生效,可以使用 /deep/ 深度选择器来实现样式穿透。


总结

通过本章节,可以轻松实现视频列表公共组件的封装。

下面几点是在开发项目时需要注意的点:

  1. 使用 vue-awesome-swiper 插件实现了全屏滚动;
  2. 使用 vue-video-player 插件实现了移动端视频播放;
  3. 采用 /deep/ 实现样式穿透;
  4. 采用 transform 实现位置的移动
  5. 使用第三方组件时是否采用全局注册的方式。

上一章节: 4. 顶部导航条实现

下一章节: 6. 首页视频详情实现

项目整体介绍:Vue.js 项目实战之实现视频播放类WebApp的项目开发(仿抖音app)


项目仓库地址,欢迎 Star。

有任何问题欢迎评论区留言讨论。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值