videoPlay.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <!--
  2. * @FileDescription: 播放器
  3. * @Author: 黄琪勇
  4. * @Date:2024-04-03 18:30:09
  5. -->
  6. <template>
  7. <div
  8. @keydown="props.listenWinEvents || handleVideoKeydown"
  9. @mousemove="handleVideoMousemove"
  10. class="videoPlay"
  11. :class="{ isHideController: !isShowController }"
  12. tabindex="-1"
  13. >
  14. <video class="videoPlayBox" @click="handlePlay" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
  15. <div class="videoController" @click.stop>
  16. <div class="timeController">{{ `${formatTime(timeController.currentTime)} / ${formatTime(timeController.duration)}` }}</div>
  17. <el-slider
  18. class="sliderController"
  19. @change="handleTimeChange"
  20. @mousedown="handleSilderMousedown"
  21. v-model="timeController.currentTimeSilder"
  22. :max="timeController.duration"
  23. :format-tooltip="
  24. (value:number) => {
  25. return formatTime(value)
  26. }
  27. "
  28. />
  29. <div class="playController">
  30. <div class="leftPlayController">
  31. <img @click="handlePlay" :src="require(`./img/${playController.type === 'play' ? 'iconPause' : 'iconPlay'}.png`)" />
  32. <img @click="handleLoop" :src="require(`./img/${playController.loop ? 'iconLoopActive' : 'iconLoop'}.png`)" />
  33. <el-popover placement="top" trigger="click" :teleported="false" popper-class="palySpeedPopover">
  34. <template #reference>
  35. <img src="./img//iconSpeed.png" />
  36. </template>
  37. <div class="sliderSpeedCon">
  38. <img @click="handlePalySpeed(playController.speedStep)" src="./img/jia.png" />
  39. <el-slider
  40. class="sliderSpeed"
  41. @change="handlePalySpeedChange"
  42. v-model="playController.palySpeed"
  43. vertical
  44. :step="playController.speedStep"
  45. :max="playController.maxSpeed"
  46. :min="playController.minSpeed"
  47. />
  48. <img @click="handlePalySpeed(-playController.speedStep)" src="./img/jian.png" />
  49. </div>
  50. </el-popover>
  51. </div>
  52. <div class="rightPlayController"></div>
  53. </div>
  54. </div>
  55. <slot></slot>
  56. </div>
  57. </template>
  58. <script setup lang="ts">
  59. import TCPlayer from "tcplayer.js"
  60. import "tcplayer.js/dist/tcplayer.min.css"
  61. import { onMounted, onUnmounted, ref, reactive } from "vue"
  62. import { UUID } from "@/libs/tools"
  63. import { formatTime } from "./tools"
  64. const props = defineProps<{
  65. listenWinEvents?: boolean
  66. }>()
  67. const emits = defineEmits<{
  68. (e: "ready"): void //播放器初始化完成
  69. }>()
  70. const videoId = "video" + UUID()
  71. let playerVm: Record<string, any>
  72. /* 时间控制器 */
  73. const timeController = reactive({
  74. currentTime: 0, // 当前时间
  75. duration: 0, // 总时长
  76. currentTimeSilder: 0,
  77. isDrag: false
  78. })
  79. /* 播放控制器 */
  80. const playController = reactive<{
  81. type: "play" | "pause"
  82. loop: boolean
  83. minSpeed: number
  84. maxSpeed: number
  85. speedStep: number
  86. palySpeed: number
  87. }>({
  88. type: "pause", //play|pause
  89. loop: false,
  90. minSpeed: 0.5,
  91. maxSpeed: 1.5,
  92. speedStep: 0.1,
  93. palySpeed: 1
  94. })
  95. /* 是否显示控制器 */
  96. const isShowController = ref(true)
  97. let _showTimer: any
  98. onMounted(() => {
  99. initVideo()
  100. if (props.listenWinEvents) {
  101. document.addEventListener("keydown", handleVideoKeydown)
  102. }
  103. })
  104. onUnmounted(() => {
  105. if (props.listenWinEvents) {
  106. document.removeEventListener("keydown", handleVideoKeydown)
  107. }
  108. playerVm?.dispose()
  109. })
  110. /**
  111. * 初始化播放器
  112. */
  113. function initVideo() {
  114. playerVm = TCPlayer(videoId, {
  115. controls: false,
  116. autoplay: true,
  117. loop: false
  118. })
  119. // 初始化完成
  120. playerVm.on("ready", () => {
  121. console.log("播放器初始化完成")
  122. emits("ready")
  123. })
  124. // 开始加载数据时
  125. playerVm.on("loadstart", () => {
  126. // 重新设置播放倍速 因为切换视频播放倍速会重置
  127. playerVm.playbackRate(playController.palySpeed)
  128. })
  129. //总时长变化时候
  130. playerVm.on("durationchange", () => {
  131. //duration和currentTime 时间向上取整Math.ceil
  132. timeController.duration = Math.ceil(playerVm.duration())
  133. })
  134. //当前播放时间变化
  135. playerVm.on("timeupdate", () => {
  136. //duration和currentTime 时间向上取整Math.ceil
  137. timeController.currentTime = Math.ceil(playerVm.currentTime())
  138. if (!timeController.isDrag) {
  139. timeController.currentTimeSilder = timeController.currentTime
  140. }
  141. })
  142. playerVm.on("play", () => {
  143. playController.type = "play"
  144. })
  145. playerVm.on("pause", () => {
  146. playController.type = "pause"
  147. })
  148. }
  149. /**
  150. * 播放 需要在ready之后调用
  151. */
  152. function playVideo({ src }: { src: string }) {
  153. playerVm?.src(src)
  154. showController()
  155. }
  156. /* 时间控制器 */
  157. function handleTimeChange(value: number | number[]) {
  158. playerVm.currentTime(value || 0)
  159. timeController.isDrag = false
  160. }
  161. function handleSilderMousedown() {
  162. timeController.isDrag = true
  163. }
  164. /* 播放控制器 */
  165. function handlePlay() {
  166. playController.type === "pause" ? playerVm.play() : playerVm.pause()
  167. showController()
  168. }
  169. // 暂停
  170. function pauseVideo() {
  171. playerVm.pause()
  172. showController()
  173. }
  174. function handleLoop() {
  175. playController.loop ? playerVm.loop(false) : playerVm.loop(true)
  176. playController.loop = playerVm.loop()
  177. }
  178. function handlePalySpeedChange(value: number | number[]) {
  179. playerVm.playbackRate(value)
  180. }
  181. function handlePalySpeed(value: number) {
  182. const palySpeed = parseFloat((playController.palySpeed + value).toFixed(1))
  183. if (palySpeed > playController.maxSpeed || palySpeed < playController.minSpeed) {
  184. return
  185. }
  186. playController.palySpeed = palySpeed
  187. handlePalySpeedChange(palySpeed)
  188. }
  189. /* 是否显示控制器 */
  190. function handleVideoKeydown(e: KeyboardEvent) {
  191. console.log("handleVideoKeydown")
  192. const key = e.key
  193. if (key === " ") {
  194. handlePlay()
  195. }
  196. }
  197. function handleVideoMousemove() {
  198. console.log("handleVideoMousemove")
  199. showController()
  200. }
  201. function showController() {
  202. isShowController.value = true
  203. _showTimer && clearTimeout(_showTimer)
  204. _showTimer = setTimeout(tryHideController, 3000)
  205. }
  206. function tryHideController() {
  207. if (playController.type === "play") {
  208. isShowController.value = false
  209. }
  210. }
  211. defineExpose({
  212. playVideo,
  213. pauseVideo
  214. })
  215. </script>
  216. <style lang="scss" scoped>
  217. .videoPlay {
  218. width: 100%;
  219. height: 100%;
  220. position: relative;
  221. overflow: hidden;
  222. :deep(.videoPlayBox) {
  223. &.tcp-skin .tcp-right-click-popup-menu {
  224. display: none;
  225. }
  226. }
  227. .videoPlayBox {
  228. width: 100%;
  229. height: 100%;
  230. }
  231. &.isHideController {
  232. cursor: none;
  233. .videoController {
  234. opacity: 0;
  235. transform: translateY(100%);
  236. }
  237. }
  238. .videoController {
  239. position: absolute;
  240. width: 100%;
  241. left: 0;
  242. bottom: 0;
  243. padding: 0 30px 15px;
  244. background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
  245. color: #fff;
  246. transition: all 0.5s;
  247. .timeController {
  248. font-weight: 500;
  249. font-size: 22px;
  250. color: #ffffff;
  251. line-height: 30px;
  252. }
  253. & > :deep(.sliderController.el-slider) {
  254. --el-slider-button-wrapper-offset: -12px;
  255. --el-slider-button-wrapper-size: 28px;
  256. --el-slider-height: 4px;
  257. --el-slider-border-radius: 2px;
  258. --el-slider-main-bg-color: #ff8057;
  259. --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
  260. height: 28px;
  261. .el-slider__button {
  262. border: none;
  263. &:hover {
  264. transform: scale(1.1);
  265. }
  266. }
  267. }
  268. .playController {
  269. display: flex;
  270. justify-content: space-between;
  271. .leftPlayController {
  272. margin-left: -10px;
  273. display: flex;
  274. & > img {
  275. cursor: pointer;
  276. width: 48px;
  277. height: 48px;
  278. margin-right: 26px;
  279. }
  280. & > :deep(.palySpeedPopover.el-popover.el-popper) {
  281. min-width: initial;
  282. width: 65px !important;
  283. height: 293px;
  284. background: url("./img/bg.png") no-repeat;
  285. background-size: 100% 100%;
  286. box-shadow: none;
  287. border: none;
  288. padding: 15px 0 22px;
  289. .el-popper__arrow {
  290. display: none;
  291. }
  292. .sliderSpeedCon {
  293. width: 100%;
  294. height: 100%;
  295. display: flex;
  296. justify-content: center;
  297. align-items: center;
  298. flex-direction: column;
  299. & > img {
  300. cursor: pointer;
  301. flex-shrink: 0;
  302. width: 33px;
  303. height: 35px;
  304. }
  305. .sliderSpeed.el-slider {
  306. flex-grow: 1;
  307. padding: 14px 0;
  308. --el-slider-button-wrapper-offset: -11px;
  309. --el-slider-button-wrapper-size: 26px;
  310. --el-slider-height: 6px;
  311. --el-slider-border-radius: 4px;
  312. --el-slider-main-bg-color: #ff8057;
  313. --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
  314. .el-slider__button {
  315. border: none;
  316. &:hover {
  317. transform: scale(1.1);
  318. }
  319. }
  320. }
  321. }
  322. }
  323. }
  324. }
  325. }
  326. }
  327. </style>