seatonwan9
2025-08-28 b9ec065e68c535ea2e534189738626f8d1ff276a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<template>
  <div
    class="m-video"
    :class="{ 'u-video-hover': !hidden }"
    :style="`width: ${width}; height: ${height};`"
  >
    <!-- -->
    <video
      ref="veo"
      class="video"
      :style="`object-fit: ${fit};`"
      :src="src"
      :poster="veoPoster"
      :autoplay="autoplay"
      :controls="!originPlay && controls"
      :controlslist="controlslist"
      :loop="loop"
      :muted="autoplay || muted"
      :preload="preload"
      crossorigin="anonymous"
      @loadeddata="poster ? () => false : getPoster()"
      @pause="showPlay ? onPause() : () => false"
      @playing="showPlay ? onPlaying() : () => false"
      @click.prevent.once="onPlay"
      v-bind="$attrs"
    >
      您的浏览器不支持video标签。
    </video>
    <span
      v-show="originPlay || showPlay"
      class="m-icon-play"
      :class="{ hidden: hidden }"
    >
      <svg class="u-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 34">
        <path
          d="M28.26,11.961L11.035,0.813C7.464-1.498,3,1.391,3,6.013v21.974c0,4.622,4.464,7.511,8.035,5.2L28.26,22.039
          C31.913,19.675,31.913,14.325,28.26,11.961z"
        ></path>
      </svg>
    </span>
    <!-- :width="width"
      :height="height" -->
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface Props {
  src: string // 视频文件地址,支持网络地址 https 和相对地址
  poster?: string // 视频封面地址,支持网络地址 https 和相对地址
  second?: number // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面
  width?: any // 视频播放器宽度,单位px
  height?: any // 视频播放器高度,单位px
  autoplay?: boolean // 视频就绪后是否马上播放,优先级高于preload
  controls?: boolean // 是否向用户显示控件,比如进度条,全屏等
  loop?: boolean // 视频播放完成后,是否循环播放
  muted?: boolean // 是否静音
  preload?: 'auto' | 'metadata' | 'none' // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略
  showPlay?: boolean // 播放暂停时是否显示播放器中间的暂停图标
  fit?: 'none' | 'fill' | 'contain' | 'cover' // video的poster默认图片和视频内容缩放规则
  controlslist?: string // video标签的controlslist属性,用于隐藏视频播放器的默认控件
}
const props = withDefaults(defineProps<Props>(), {
  src: '',
  poster: '',
  second: 0.5,
  width: '94vw',
  height: '80vh',
  /*
    参考 MDN 自动播放指南:https://developer.mozilla.org/zh-CN/docs/Web/Media/Autoplay_guide
    Autoplay 功能
    据新政策,媒体内容将在满足以下至少一个的条件下自动播放:
    1.音频被静音或其音量设置为 0
    2.用户和网页已有交互行为(包括点击、触摸、按下某个键等等)
    3.网站已被列入白名单;如果浏览器确定用户经常与媒体互动,这可能会自动发生,也可能通过首选项或其他用户界面功能手动发生
    4.自动播放策略应用到<iframe>或者其文档上
    autoplay:由于目前在最新版的Chrome浏览器(以及所有以Chromium为内核的浏览器)中,
    已不再允许自动播放音频和视频。就算你为video或audio标签设置了autoplay属性也一样不能自动播放!
    解决方法:设置视频 autoplay 时,视频必须设置为静音 muted: true 即可实现自动播放,
    然后用户可以使用控制栏开启声音,类似某宝商品自动播放的宣传视频逻辑
  */
  autoplay: false,
  controls: true,
  loop: false,
  muted: false,
  controlslist: '', //隐藏下载按钮nodownload
  /*
    preload可选属性:
    auto: 一旦页面加载,则开始加载视频;
    metadata: 当页面加载后仅加载视频的元数据(例如长度),建议使用metadata,以便视频自动获取第一帧作为封面poster
    none: 页面加载后不应加载视频
  */
  preload: 'auto',
  showPlay: true,
  /*
    fit可选属性:
    none: 保存原有内容,不进行缩放;
    fill: 不保持原有比例,内容拉伸填充整个内容容器;
    contain: 保存原有比例,内容以包含方式缩放;
    cover: 保存原有比例,内容以覆盖方式缩放
  */
  fit: 'contain',
})
// 参考文档:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video
const veoPoster = ref(props.poster)
const originPlay = ref(true)
const hidden = ref(false) // 是否隐藏播放器中间的播放按钮
// 为模板引用标注类型
const veo = ref()
// const veo = ref<HTMLVideoElement | null>(null) // 声明一个同名的模板引用
 
/*
  loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发
  preload为none时不会触发
*/
function getPoster() {
  // 在未设置封面时,自动截取视频0.5s对应帧作为视频封面
  // 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图
  veo.value.currentTime = props.second
  // 创建canvas元素
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  // canvas画图
  canvas.width = veo.value.videoWidth
  canvas.height = veo.value.videoHeight
  ctx?.drawImage(veo.value, 0, 0, canvas.width, canvas.height)
  // 把canvas转成base64编码格式
  veoPoster.value = canvas.toDataURL('image/png')
}
function onPlay() {
  if (originPlay.value) {
    veo.value.currentTime = 0
    originPlay.value = false
  }
  if (props.autoplay) {
    veo.value?.pause()
  } else {
    hidden.value = true
    veo.value?.play()
  }
}
const onPause=()=>{
  hidden.value = false
}
const onPlaying=()=>{
  hidden.value = true
}
defineExpose({onPause})
onMounted(() => {
  if (props.autoplay) {
    hidden.value = true
    originPlay.value = false
  }
  /*
    自定义设置播放速度,经测试:
    在vue2中需设置:this.$refs.veo.playbackRate = 2
    在vue3中需设置:veo.value.defaultPlaybackRate = 2
  */
  // veo.value.defaultPlaybackRate = 2
})
</script>
 
<style lang="scss" scoped>
.m-video {
  display: inline-block;
  position: relative;
  background: #000;
  cursor: pointer;
  .video {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    object-fit: cover;
  }
  .m-icon-play {
    display: inline-block;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.7);
    pointer-events: none;
    opacity: 0.8;
    transition: opacity 0.3s;
    .u-svg {
      display: inline-block;
      fill: #fff;
      width: 22px;
      height: 33px;
      margin-top: 13px;
      margin-left: 20px;
    }
  }
  .hidden {
    opacity: 0;
  }
}
.u-video-hover {
  &:hover {
    .m-icon-play {
      opacity: 1;
    }
  }
}
</style>