videoPlay.vue 10 KB

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