h5使用video标签简单实现播放视频

2,467 阅读1分钟

近期做了一个需求,h5 页面,展示视频列表,点击视频,全屏播放。开始还以为全屏是不是需要读写 app 的标题栏,事实证明多想了,video 标签本身全屏的时候,可以让视频之外的地方全黑,ios 和 android 都一样。

但 ios 播放视频的时候,会自动将 video 放在最高层,然后全屏播放。

为了统一效果,播放视频的时候,统一用一个黑色遮罩层。

video,其实本身还是挺多事的,本文只是简单的播放,以后再涉及 video 的时候,心里有个数。video 的所有属性和事件可以参考MDN 的介绍

如果是 PC 端的,推荐DPlayer。移动端,效果可能没那么好。

播放效果和简单逻辑

video_h5

可能全屏效果比很多插件要好些,不然样式问题,也是闹心。

实现其实没啥大逻辑。

播放组件关键逻辑就是,播放的时候,先检测是否能播放,若不能则显示加载中。

播放组件的逻辑:

  • 判断是否能播放,不能播放显示加载中
  • 播放中出错的话,提示
  • 播放结束,通知父组件

video_h5_3

列表的逻辑:

  • 展示列表
  • 传递必要属性
  • 接受子组件的事件

video_h5_1

列表项组件项组件的逻辑:

  • 属于展示组件,额外增加一个图片失败的监测

video_h5_2

附注代码

videoPlayer 的代码

<template>
  <div class="video-box" v-if="isShowPlayer">
    <video
      controls
      :src="info.url"
      :poster="info.cover"
      ref="video"
      @canplay="canPlay"
      @error="error"
      @ended="ended"
      controlslist="nodownload"
      disablePictureInPicture
    ></video>
    <div class="close-box" @click="clickClose">
      <img
        src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/close.png"
        alt="关闭"
      />
    </div>
    <div class="loadingBox" v-if="isLoading">
      <img
        class="loading-icon"
        src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/loading.gif"
        alt="正在加载..."
      />
    </div>
  </div>
</template>
<script>
export default {
  props: {
    info: { required: true, type: Object }, // url cover属性
    isShowPlayer: { default: false }
  },
  data() {
    return {
      isCanPlay: false,
      isLoading: false
    };
  },
  mounted() {
    if (this.isCanPlay) {
      this.$refs.video.play();
      return;
    }
    this.isLoading = true;
  },

  methods: {
    canPlay() {
      this.isCanPlay = true;
      this.isLoading = false;
      this.$refs.video.play();
    },
    error() {
      alert("视频出错了,请联系客服人员");
      this.showLoading = false;
    },
    ended() {
      this.$emit("ended");
    },
    clickClose() {
      this.$emit("update:isShowPlayer", false);
    }
  }
};
</script>
<style scoped>
.video-box {
  position: fixed;
  z-index: 9999;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #000;
}
video {
  width: 100%;
}
.close-box {
  position: fixed;
  z-index: 10000;
  top: 0px;
  right: 0;
  padding: 20px;
}
.close-box img {
  width: 24px;
}
.loadingBox {
  position: fixed;
  z-index: 9999;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
}
.loading-icon {
  width: 50px;
  padding: 10px;
  border-radius: 10px;
  background-color: rgba(0, 0, 0);
}
</style>

列表 的代码

<template>
  <div class="paper">
    <div class="list">
      <play-item
        :info="item"
        class="item"
        @clickItem="clickItem(index)"
        v-for="(item, index) in playList"
        :key="index"
      ></play-item>
    </div>
    <videoPlayer
      v-if="isShowPlayer"
      :isShowPlayer.sync="isShowPlayer"
      :info="curVideo"
      @ended="ended"
    ></videoPlayer>
    <div class="loadingBox" v-if="isLoading">
      <img
        class="loading-icon"
        src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/loading.gif"
        alt="正在加载..."
      />
    </div>
  </div>
</template>

<script>
import PlayItem from "./components/PlayItem";
import VideoPlayer from "./components/VideoPlayer";
export default {
  components: { PlayItem, VideoPlayer },
  data() {
    return {
      // 播放列表
      playList: [],
      // 是否正在加载
      isLoading: true,
      // 是否显示播放器
      isShowPlayer: false,
      // 当前播放视频的索引
      curVideoIndex: null
    };
  },
  computed: {
    curVideo() {
      return this.playList[this.curVideoIndex];
    }
  },
  mounted() {
    this.getPlayList();
  },
  methods: {
    getPlayList() {
      setTimeout(() => {
        this.isLoading = false;
        const list = [
          {
            title: "黑色毛衣",
            url:
              "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video.mp4"
          },
          {
            title: "发如雪",
            url:
              "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video2.mp4"
          },
          {
            title: "千里之外",
            url:
              "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/video.mp4"
          }
        ];
        list.forEach(
          item =>
            (item.cover =
              "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/cover.png")
        );
        this.playList = list;
      }, 20);
    },
    clickItem(index) {
      this.curVideoIndex = index;
      this.isShowPlayer = true;
    },
    ended() {
      const isLast = this.curVideoIndex === this.playList.length - 1;
      if (isLast) {
        alert("这是最后一个视频了~~");
        return;
      }
      this.curVideoIndex++;
    }
  }
};
</script>

<style scoped>
.list {
  padding: 16px;
}
.item {
  margin-bottom: 16px;
}
.loadingBox {
  position: fixed;
  z-index: 9999;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
}
.loading-icon {
  width: 50px;
  padding: 10px;
  border-radius: 10px;
  background-color: rgba(0, 0, 0);
}
</style>

列表项组件 的代码

<template>
  <div class="play-item-box" @click="clickItem">
    <div class="left">
      <img class="resource" :src="info.cover" @error="errorImg" alt="" />
      <img
        class="play-icon"
        src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/play_icon.png"
        alt=""
      />
    </div>
    <div class="right">
      <div class="title">
        {{ info.title }}
      </div>
      <div class="time">发送时间:2021-10-21 10:10:10</div>
    </div>
  </div>
</template>

<script>
export default {
  name: "playItem",
  props: { info: Object },
  data() {
    return {
      defaultCover:
        "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/cover.png"
    };
  },
  methods: {
    clickItem() {
      this.$emit("clickItem");
    },
    errorImg() {
      this.info.coverImage = this.defaultCover;
    }
  }
};
</script>

<style scoped>
.play-item-box {
  display: flex;
  border-radius: 6px;
}
.left {
  width: 33%;
  position: relative;
  height: 70px;
}
.left .resource {
  display: block;
  width: 100%;
  border-radius: 6px;
  height: 100%;
}
.play-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  width: 33px;
}
.right {
  flex: 1;
  margin-left: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 2px 0;
}
.title {
  font-size: 16px;
  font-weight: bold;
  color: #333;
  line-height: 1.3;
}
.time {
  font-size: 12px;
  color: #999;
}
.video {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  opacity: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 11;
}
</style>