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交互方向 从硬盘文件 -> 读取数据 -> 内…