vue中使用西瓜播放器xgplayer (封裝)+xgplayer-hls 播放.m3u8格式視頻

1.西瓜播放器官網

http://h5player.bytedance.com/guide/

2.安裝

# 最新穩定版
$ npm install xgplayer對于已有項目也可以通過 CDN 引入,代碼如下:
<script src="//unpkg.byted-static.com/xgplayer/2.31.2/browser/index.js" type="text/javascript"></script>

3.封裝西瓜播放器組件

<template><div class="video-box"><div ref="playerRef" id="video-player" class="video-player"></div></div></template><script setup>import { ref, onMounted, watch } from 'vue';import Player from 'xgplayer';import 'xgplayer/dist/index.min.css';// 定義propsconst props = defineProps({url: {type: String,default: ''},poster: {type: String,default: ""}});// 定義播放器實例和DOM引用const playerRef = ref(null);const player = ref(null);// 定義emitsconst emit = defineEmits(['triggerEvent', 'timeupdate', 'loadingStateChange', 'playEnd', 'videoClick']);// 判斷是否為Apple設備const isAppleDevice = () => {const ua = navigator.userAgent.toLowerCase();return /iphone|ipad|phone|Mac/i.test(ua);};// 初始化播放器const initPlayer = () => {if (!props.url) return console.warn('url is not exist');const config = {el: playerRef.value, // 使用ref代替idurl: props.url,plugins: [window.HlsPlayer],hls: {retryCount: 3, // 重試 3 次,默認值retryDelay: 1000, // 每次重試間隔 1 秒,默認值loadTimeout: 10000, // 請求超時時間為 10 秒,默認值fetchOptions: {// 該參數會透傳給 fetch,默認值為 undefinedmode: 'cors'}},fluid: true,// 倍速播放playbackRate: [2],defaultPlaybackRate: 1,volume: 0.7,playsinline: isAppleDevice(), // IOS設備設置'x5-video-player-type': 'h5', // 微信內置瀏覽器設置'x5-video-orientation': 'portraint',poster: props.poster,// 畫中畫// pip: true,pipConfig: {bottom: 100,right: 100,width: 320,height: 180},// 初始化首幀// videoInit: true,autoplay: true};// 實例化播放器player.value = new Player(config);if (player.value) {// 新增:單擊事件處理const container = player.value.root;container.addEventListener('click', handleContainerClick);// 注冊事件監聽player.value.on('play', () => {emit('triggerEvent', true);});player.value.on('pause', () => {emit('triggerEvent', false);});player.value.on('ended', () => {emit('playEnd');});// 1. 視頻進入等待緩沖狀態player.value.on('waiting', () => {// console.log('視頻緩沖中...');emit('loadingStateChange', { bool: true, time: player.value.currentTime }); // 通知父組件顯示加載狀態});player.value.on('canplay', () => {emit('loadingStateChange', { bool: false, time: player.value.currentTime });});// 監聽播放進度更新player.value.on('timeupdate', () => {emit('timeupdate', { playerVideo: player.value });});setTimeout(() => {forceVideoSize();}, 100); // 延遲100ms確保播放器完全初始化}};// 生命周期鉤子onMounted(() => {initPlayer();});// 監聽url變化watch(() => props.url, (newValue) => {if (!player.value) {initPlayer();return;}player.value.src = newValue;});// 處理容器點擊事件const handleContainerClick = (e) => {// 排除控制欄區域的點擊if (e.target.closest('.xgplayer-control')) return;emit('videoClick', e); // 通知父組件顯示加載狀態};const forceVideoSize = () => {if (!player.value) return;const videoEl = player.value.root.querySelector('video');const container = player.value.root;if (videoEl) {// 完全重置video標簽的樣式videoEl.style.cssText = `width: 100% !important;height: 100% !important;max-width: none !important;max-height: none !important;object-fit: cover !important;position: absolute !important;top: 0 !important;left: 0 !important;bottom: 0 !important;right: 0 !important;margin: 0 !important;padding: 0 !important;border: none !important;`;// 設置播放器容器樣式container.style.cssText = `width: 100% !important;height: 100% !important;max-width: none !important;max-height: none !important;position: relative !important;overflow: hidden !important;margin: 0 !important;padding: 0 !important;`;}};// 組件卸載時銷毀播放器onUnmounted(() => {if (player.value) {player.value.destroy();player.value = null;}});</script><style scoped lang="scss">.video-box {width: 100% !important;height: 100% !important;.video-player {width: 100% !important;height: 100% !important;padding: 0 !important;margin: 0 auto;}}</style>
3.1 .m3u8 格式處理需要在index.html 中引入
3.2 注意 vite項目下,使用依賴import 的方式播放有問題,需要改為全局引入靜態min.js
  <!-- 先引入 xgplayer 核心庫 -->
<script src="https://unpkg.com/xgplayer@latest/dist/index.min.js"></script> 
<!-- 再引入 xgplayer-hls 插件 -->
<script src="https://unpkg.com/xgplayer-hls@latest/dist/index.min.js"></script> 

4.父組件使用

<template><swiper :circular="state.circular" class="m-tiktok-video-swiper" @change="swiperChange"@animationfinish="animationfinish" :current="state.current" :vertical="true" duration="300"><swiper-item v-for="(item, index) in state.originList" :key="index"><view class="swiper-item"v-if="index == state.current || index + 1 == state.current || index - 1 == state.current"><xgplayerVideo  class="m-tiktok-video-player" :url="item.src":poster="poster"v-if="index == state.current && item.src" @playEnd="ended"@loadingStateChange="onwaiting"@triggerEvent="onTriggerEvent"@timeupdate="onTimeupdate"@videoClick="onVideoClick"></xgplayerVideo><slot :item="item"></slot></view></swiper-item></swiper>
</template><script lang="ts" setup>
import { reactive, ref, getCurrentInstance, watch, nextTick } from "vue";
import type { ComponentInternalInstance, PropType } from "vue";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import { getPlayUrl } from "@/api/home";
import { trackEvent } from "@/utils/common";
import xgplayerVideo from "./xgplayerVideo.vue"
const _this = getCurrentInstance() as ComponentInternalInstance;export interface IvideoItem {/*** 視頻鏈接*/src: string;/*** 海報封面*/poster?: string;
}const emits = defineEmits(["change","ended","swiperchange","videoClicks"
]);const props = defineProps({poster: {type: String,default: "",},/*** 當前播放劇集(索引值)*/current: {type: [Number, String],default: 0,},/*** 視頻列表*/videoList: {type: Array as PropType<IvideoItem[]>,default: () => [],},/*** 是否循環播放一個視頻*/loop: {type: Boolean,default: true,},/*** 顯示原生控制欄*/controls: {type: Boolean,default: true,},/*** 是否自動播放*/autoplay: {type: Boolean,default: true,},/*** 是否自動滾動播放*/autoChange: {type: Boolean,default: true,},
});const state = reactive({circular: false,originList: [] as any, // 源數據originIndex: 0, // 記錄源數據的下標current: 0,videoContexts: [] as any,isFirstLoad: true,isPause: true,bufferStartTime:0
});
const VideoPlayer = ref([])
const videoDomDate = ref({})
const reportedTimes = ref(new Set()); // 記錄已上報的時間點const animationfinish = async () => { };function ended() {trackEvent("play", {id: props.videoList[0].videoId,level: props.current,status:'finish'})// 自動切換下一個視頻if (props.autoChange) {state.current = state.originIndex + 1;}emits("ended");
}
/*** 初始一個顯示的swiper數據* @originIndex 從源數據的哪個開始顯示默認0*/
async function initSwiperData(originIndex = state.originIndex) {// 確保索引有效if (originIndex < 0 || originIndex >= state.originList.length) {console.warn("無效的視頻索引:", originIndex);return;}const index = originIndex;// 延遲播放當前視頻,確保DOM已更新await nextTick();console.log("播放視頻:", index, props.videoList[index]);// handleCoverClick(index);// 數據改變emits("change", {index: originIndex,detail: state.originList[originIndex],});}
// 視頻緩沖
const onwaiting = (val) => {// state = true 開始緩沖// state = false 緩沖結束if (val.bool) {state.bufferStartTime = Date.now()trackEvent("play_stop", {id: props.videoList[0].videoId,level: props.current,stop: Number(val.time.toFixed(2)),time:0});} else {if (state.bufferStartTime) {const bufferTime = (Date.now() - state.bufferStartTime) / 1000;trackEvent("play_stop", {id: props.videoList[0].videoId,level: props.current,stop: Number(val.time.toFixed(2)),time:bufferTime});}}}
// 播放暫停
const onTriggerEvent = (boo) => {console.log(boo);
}
// 點擊屏幕
const onVideoClick = (e) => {emits("videoClicks", e);
}// 每隔10秒上報一次
const onTimeupdate = (val) => {if (!val.playerVideo || val.playerVideo.paused) return;const currentTime = Math.floor(val.playerVideo.currentTime);// 視頻總時長const duration = Math.floor(val.playerVideo.duration || 0);// 只處理0秒和10的倍數秒數,且不超過視頻總時長if ((currentTime === 0 || currentTime % 10 === 0) && currentTime <= duration && !reportedTimes.value.has(currentTime)) {if (props.current == 1) {trackEvent("play_progress", {id: props.videoList[0].videoId,level: props.current,progress: currentTime});}reportedTimes.value.add(currentTime);// console.log(`上報: ${currentTime}秒`);}}
// var hls = new Hls();
const handleCoverClick = async(index) => {
// if (Hls.isSupported()) {// 創建新的HLS實例// if (hls) {//     hls.destroy()//  }//           // 暫停視頻(確保沒有播放中的實例)//     if (videoDomDate.value.videoDom) {//         videoDomDate.value.videoDom.pause();//     }//       // 加載.m3u8視頻源//         console.log('HLS媒體已附加',state.originList[index].src,state.current);//         if(state.originList[index].src){//             hls.loadSource(state.originList[index].src); // 假設item.src是.m3u8格式的URL//         }//         // 關聯HLS實例與視頻元素// // 關聯HLS實例與視頻元素//     if (videoDomDate.value.videoDom) {//         hls.attachMedia(videoDomDate.value.videoDom); // 假設DomVideoPlayer暴露了video元素//       }//         console.log('開始進入',videoDomDate.value);//        hls.on(Hls.Events.MEDIA_ATTACHED, () => {//         console.log(videoDomDate.value.videoDom,',準備播放');//         videoDomDate.value.videoDom.play(); // 開始播放視頻//        });VideoPlayer.value.forEach(item => {console.log(item);if (item) {const num = Number(item.$ownerInstance.$el.getAttribute('data-index'))console.log(num,'99');if (num === index) {item.play()} else {state.isPause = trueitem.toSeek(0)item.pause();}}})
// }
};/*** swiper滑動時候*/
async function swiperChange(event: any) {const { current } = event.detail;state.current = current;state.originIndex = current;// 確保視頻源已加載// if (!state.originList[current].src) {//     const url = await getVideoUrl(props.videoList[current].videoUrl);//     state.originList[current].src = url;// }initSwiperData();emits("swiperchange", current);console.log("swiper切換:", current);
}
async function getVideoUrl(videoUrl: string) {try {if (videoUrl) {const {data: { url },} = await getPlayUrl({videoUrl,});return url;}} catch (error) {console.error("獲取視頻URL失敗:", error);throw error;}
}
// 監聽props.current變化
watch(() => props.current,async () => {console.log(props.current, props.videoList);if (props.videoList?.length) {const i = props.videoList.findIndex((item: any) => item.num == props.current);if (i > -1) {state.current = i;state.originIndex = i;state.originList = props.videoList;const url = await getVideoUrl(props.videoList[i].videoHls);console.log(url, '=============>');state.originList[i].src = url;// 埋點trackEvent("play", {id: props.videoList[0].videoId,level: props.current,status:'start'})// Meta Pixel 用于衡量 Facebook廣告 的效果//#ifdef H5window.fbq('track', 'ViewContent',{content_ids :props.videoList[0].videoId});//#endifif (state.isFirstLoad || !state.videoContexts?.length) {initSwiperData();}}}},{immediate: true,deep: true}
);
function jumpToVideo(index) {if (index >= 0 && index < state.originList.length) {state.current = index;state.originIndex = index;// initSwiperData(index);}
}let loadTimer: any = null;
onLoad(() => {// 為了首次只加載一條視頻(提高首次加載性能),延遲加載后續視頻loadTimer = setTimeout(() => {state.isFirstLoad = false;clearTimeout(loadTimer);}, 5000);
});onUnload(() => {clearTimeout(loadTimer);
});defineExpose({initSwiperData,jumpToVideo,
});
</script><style lang="scss">
.m-tiktok-video-swiper,
.m-tiktok-video-player {width: 100%;height: 100%;background-color: #000;
}.m-tiktok-video-swiper {.swiper-item {width: 100%;height: 100%;position: relative;}.m-tiktok-video-poster {display: block;opacity: 1;visibility: visible;position: absolute;left: 0;top: 0;background-position: center center;background-color: #000;background-size: 100% auto;background-repeat: no-repeat;transition: opacity 0.3s ease, visibility 0.3s ease;pointer-events: none;width: 100%;height: 100%;}.iszan {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 99;}
}
</style>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914596.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914596.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914596.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2025-07-15通過邊緣線檢測圖像里的主體有沒有出血

本節觀點&#xff1a;一個好的提問就已經解決了問題的90%。 對于問題的描述正確與否決定了解決問題的方法和路徑&#xff0c;所以我們在AI時代必須要學會正確的描述問題和表達問題&#xff0c;否則即使有AI輔助也是很難精準的解決問題。 我的問題&#xff1a; 如何利用代碼從圖…

【Docker基礎】Dockerfile指令速覽:文件與目錄操作指令詳解

目錄 引言 1 ADD&#xff1a;高級文件復制與解壓 1.1 指令簡介 1.2 語法 1.3 功能詳解 1.4 使用場景 1.5 執行流程 1.6 示例 1.7 注意事項 2 WORKDIR&#xff1a;設置工作目錄 2.1 指令簡介 2.2 語法 2.3 使用場景 2.4 創建流程 2.5 示例 2.6 注意事項 3 VOLU…

Python 程序設計講義(2):Python 概述

Python 程序設計講義&#xff08;2&#xff09;&#xff1a;Python 概述 一、Python 語言的發展史 Python 語言誕生于 1990 年。 2002 年 10 月&#xff0c;Python2.0 正式發布。 2008 年 12 月&#xff0c;Python3.0 正式發布。 Python3.0 在語法層面和解釋器內部做了很多重大…

多租戶SaaS系統中設計安全便捷的跨租戶流程共享

四維協同架構??,結合動態授權、加密傳輸、行為審計和智能策略 一、??權限控制體系?? 1. ??動態權限模型? ? 2. ??授權策略實現?? ??RBAC+ABAC混合模型?? 在流程表增加shared_tenants字段存儲授權信息,結合屬性動態校驗: CREATE TABLE workflow_process…

Spring Ioc Bean 到底是什么

Bean 到底是什么&#xff1f; 簡單來說&#xff0c;Spring Bean 就是一個由 Spring IoC 容器負責創建、管理和裝配的 Java 對象。 它不是一種新的技術&#xff0c;它本質上還是一個普普通通的 Java 對象&#xff08;POJO - Plain Old Java Object&#xff09;&#xff0c;但它的…

【PCIe 總線及設備入門學習專欄 5.1.1 -- PCIe PERST# 信號的作用】

文章目錄 PCIe PERSTN#PERST# 信號作用概述簡要定義PERST# 的關鍵功能PERST# 的時序圖示意Synopsys PCIe EP IP 中 PERST# 的作用關鍵信號接口典型復位流程示例代碼(Verilog for Synopsys PCIe)PERST# 使用場景舉例(Synopsys PCIe EP)1. 系統上電初始化2. 熱復位特定設備3.…

使用python的pillow模塊將圖片轉化為灰度圖,獲取值和修改值

使用python的pillow模塊可以將圖片轉化為灰度圖&#xff0c; 可以獲取灰度圖的特定點值&#xff0c;區域值&#xff0c; 修改值并保存到圖片 圖片轉換為灰度圖 from PIL import Image# 打開圖片 image Image.open("d://python//2//1.jpg")gray_image image.convert…

記憶力訓練day41

通常是一個地點記2組詞 數字和人體記憶宮殿更注重 即時性&#xff1b;地點記憶宮殿是長久性

自動微分模塊

一.前言本章節我們是要學習梯隊計算&#xff0c;?動微分&#xff08;Autograd&#xff09;模塊對張量做了進?步的封裝&#xff0c;具有?動求導功能。?動微分模塊是構成神經?絡 訓練的必要模塊&#xff0c;在神經?絡的反向傳播過程中&#xff0c;Autograd 模塊基于正向計算…

深度學習·目標檢測和語義分割基礎

邊緣框 不是標準的x&#xff0c;y坐標軸。邊緣框三種表示&#xff1a;左上右下下坐標&#xff0c;左上坐標長寬&#xff0c;中心坐標長寬 COCO 目標檢測數據集的格式&#xff1a;注意一個圖片有多個物體&#xff0c;使用csv或者文件夾結構的格式不可取。 錨框算法 生成很多…

ffmpeg音視頻處理大綱

FFmpeg 是一個功能強大的開源音視頻處理工具集&#xff0c;其核心代碼以 C 語言實現。下面從源碼角度分析 FFmpeg 如何實現轉碼、壓縮、提取、截取、拼接、合并和錄屏等功能&#xff1a; 一、FFmpeg 核心架構與數據結構 FFmpeg 的源碼結構圍繞以下核心組件展開&#xff1a; lib…

網絡安全小練習

一、docker搭建 1.安裝 2.改變鏡像源&#xff08;推薦國內鏡像源&#xff1a;阿里云鏡像源&#xff09; 登錄阿里云容器鏡像源服務&#xff08; 阿里云登錄 - 歡迎登錄阿里云&#xff0c;安全穩定的云計算服務平臺 &#xff09; 復制系統分配的專屬地址 配置 sudo mkdir …

數據結構——順序表的相關操作

一、順序表基礎認知?1.順序表的定義與特點?順序表是數據結構中一種線性存儲結構&#xff0c;它將數據元素按照邏輯順序依次存儲在一片連續的物理內存空間中。簡單來說&#xff0c;就是用一段地址連續的存儲單元依次存放線性表的元素&#xff0c;且元素之間的邏輯關系通過物理…

2025最新國產用例管理工具評測:Gitee Test、禪道、藍凌測試、TestOps 哪家更懂研發協同?

在快節奏的 DevOps 時代&#xff0c;測試用例管理已不再是 QA 的獨角戲&#xff0c;而是穿透需求—開發—測試—交付全流程的核心樞紐。想象一下&#xff0c;如果用例結構混亂&#xff0c;覆蓋不全&#xff0c;甚至丟失版本變更歷史&#xff0c;不僅協作亂&#xff0c;還影響交…

在線評測系統開發交流

https://space.bilibili.com/700332132?spm_id_from333.788.0.0 實驗內容爬蟲Web系統設計數據分析實驗指導爬蟲Web系統設計自然語言處理與信息檢索數據可視化評分標準FAQ實驗二&#xff1a;在線評測系統實驗概述實驗內容Step1&#xff1a;題目管理Step2&#xff1a;題目評測S…

Linux操作系統從入門到實戰(十)Linux開發工具(下)make/Makefile的推導過程與擴展語法

Linux操作系統從入門到實戰&#xff08;十&#xff09;Linux開發工具&#xff08;下&#xff09;make/Makefile的推導過程與擴展語法前言一、 make/Makefile的推導過程1. 先看一個完整的Makefile示例2. make的工作流程&#xff08;1&#xff09;尋找Makefile文件&#xff08;2&…

NFS磁盤共享

步驟&#xff1a;注意事項?&#xff1a;確保服務端防火墻關閉&#xff0c;或者允許2049端口通信&#xff0c;客戶端需具備讀寫權限。服務器端安裝NFS服務器&#xff1a;sudo apt-get install nfs-kernel-server # Debian/Ubuntu sudo yum install nfs-utils # Ce…

ORA-06413: 連接未打開

System.Data.OracleClient.OracleException:ORA-06413: 連接未打開 oracle 報錯 ORA-06413: 連接未打開 db.Open();的報錯鏈接未打開&#xff0c;System.Data.OracleClient.OracleException HResult0x80131938 MessageORA-06413: 連接未打開 關于ORA-06413錯誤&#xff08;…

【PCIe 總線及設備入門學習專欄 5.1.2 -- PCIe EP core_rst_n 與 app_rst_n】

文章目錄 app_rst_n 和 core_rst_n 的作用1. core_rst_n — PCIe 控制器內部邏輯復位作用控制方式2. app_rst_n — 應用層/用戶邏輯復位作用特點兩者關系圖示:示例流程(Synopsys EP)rst_sync[3] 的作用詳解(復位同步邏輯)為什么使用 rst_sync[3]?圖示說明Synopsys 官方手…

Python初學者筆記第二十期 -- (文件IO)

第29節課 文件IO 在編程中&#xff0c;文件 I/O&#xff08;輸入/輸出&#xff09;允許程序與外部文件進行數據交互。Python 提供了豐富且易用的文件 I/O 操作方法&#xff0c;能讓開發者輕松實現文件的讀取、寫入和修改等操作。 IO交互方向 從硬盤文件 -> 讀取數據 -> 內…