| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- <template>
- <div
- ref="container"
- class="jessibuca-container"
- :class="{'jessibuca-fullscreen': fullscreen}"
- @dblclick="fullscreenSwich"
- >
- <div class="jessibuca-player-wrapper"></div>
- <div id="buttonsBox" class="buttons-box">
- <div class="buttons-box-left">
- <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" />
- <i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" />
- <i class="iconfont icon-stop jessibuca-btn" @click="destroy" />
- <i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()" />
- <i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()" />
- </div>
- <div class="buttons-box-right">
- <span class="jessibuca-btn">{{ kBps }} kb/s</span>
- <!-- <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
- <!-- <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
- <i
- class="iconfont icon-camera1196054easyiconnet jessibuca-btn"
- style="font-size: 1rem !important"
- @click="screenshot"
- />
- <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick" />
- <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich" />
- <i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich" />
- </div>
- </div>
- </div>
- </template>
- <!-- #ifdef H5 -->
- <script setup>
- import { ref, watch, nextTick, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
- import { useRoute } from 'vue-router'
- // Props
- const props = defineProps({
- videoUrl: String,
- error: String,
- hasAudio: Boolean,
- height: [String, Number]
- })
- // 获取当前实例 UID (用于管理多个播放器实例)
- const instance = getCurrentInstance()
- const uid = instance.uid
- // 获取路由
- const route = useRoute()
- // 模板引用
- const container = ref(null)
- // 响应式数据
- const playing = ref(false)
- const isNotMute = ref(false)
- const quieting = ref(false)
- const fullscreen = ref(false)
- const loaded = ref(false)
- const speed = ref(0)
- const performance = ref('')
- const kBps = ref(0)
- const btnDom = ref(null)
- const videoInfo = ref(null)
- const volume = ref(1)
- const rotate = ref(0)
- const vod = ref(true)
- const forceNoOffscreen = ref(false)
- const playerWidth = ref(0)
- const playerHeight = ref(0)
- const parentNodeResizeObserver = ref(null)
- // 全局播放器实例存储
- const jessibucaPlayer = {}
- // 判断是否处于全屏状态
- const isFullscreen = () => {
- // #ifdef H5
- return document.fullscreenElement ||
- document.msFullscreenElement ||
- document.mozFullScreenElement ||
- document.webkitFullscreenElement || false
- // #endif
- // #ifndef H5
- return false
- // #endif
- }
- // 更新播放器 DOM 尺寸
- const updatePlayerDomSize = () => {
- const dom = container.value
- if (!dom) return
- if (!parentNodeResizeObserver.value) {
- // #ifdef H5
- parentNodeResizeObserver.value = new ResizeObserver(() => {
- updatePlayerDomSize()
- })
- parentNodeResizeObserver.value.observe(dom.parentNode)
- // #endif
- }
-
- // 获取父容器尺寸
- const boxWidth = dom.parentNode.clientWidth
- const boxHeight = dom.parentNode.clientHeight
-
- // 检查是否处于全屏状态
- const isFullscreenState = isFullscreen()
- let width, height
-
- if (isFullscreenState) {
- // 全屏模式,使用窗口尺寸
- // #ifdef H5
- width = window.innerWidth
- height = window.innerHeight
- // #endif
- } else {
- // 非全屏模式,使用16:9比例
- width = boxWidth
- height = (9 / 16) * width
-
- // 如果计算出的高度超过容器高度,则以容器高度为基准重新计算宽度
- if (boxHeight > 0 && height > boxHeight) {
- height = boxHeight
- width = height * 16 / 9
- }
- }
-
- // 限制尺寸不超过视口
- // #ifdef H5
- const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
- if (!isFullscreenState && height > clientHeight) {
- height = clientHeight
- width = (16 / 9) * height
- }
- // #endif
-
- playerWidth.value = width
- playerHeight.value = height
-
- // 应用尺寸到容器
- dom.style.width = `${width}px`
- dom.style.height = `${height}px`
-
- // 如果播放器存在,更新播放器尺寸
- if (playing.value && jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].resize(width, height)
- }
- }
- // 公共方法:调整大小
- const resize = () => {
- updatePlayerDomSize()
- }
- // 创建播放器
- const create = () => {
- // #ifdef H5
- // H5 环境使用 CDN 加载 decoder
- const decoderUrl = '/jessibuca/decoder.js'
- // #endif
-
- // #ifdef MP-WEIXIN
- // 微信小程序不支持 jessibuca,这里不应该被调用
- console.warn('Jessibuca is not supported in WeChat Mini Program')
- return
- // #endif
- const options = {
- container: container.value,
- autoWasm: true,
- background: '',
- controlAutoHide: false,
- debug: false,
- // #ifdef H5
- decoder: decoderUrl,
- // #endif
- // #ifndef H5
- decoder: '',
- // #endif
- forceNoOffscreen: false,
- hasAudio: typeof props.hasAudio === 'undefined' ? true : props.hasAudio,
- heartTimeout: 5,
- heartTimeoutReplay: true,
- heartTimeoutReplayTimes: 3,
- hiddenAutoPause: false,
- hotKey: true,
- isFlv: false,
- isFullResize: false,
- isNotMute: isNotMute.value,
- isResize: true,
- keepScreenOn: true,
- loadingText: '请稍等, 视频加载中......',
- loadingTimeout: 10,
- loadingTimeoutReplay: true,
- loadingTimeoutReplayTimes: 3,
- openWebglAlignment: false,
- operateBtns: {
- fullscreen: false,
- screenshot: false,
- play: false,
- audio: false,
- record: false
- },
- recordType: 'mp4',
- rotate: 0,
- showBandwidth: false,
- supportDblclickFullscreen: false,
- timeout: 10,
- useMSE: true,
- useWCS: false,
- useWebFullScreen: true,
- videoBuffer: 0.1,
- wasmDecodeErrorReplay: true,
- wcsUseVideoRender: true
- }
- console.log('Jessibuca -> options: ', options)
-
- // #ifdef H5
- jessibucaPlayer[uid] = new window.Jessibuca({ ...options })
- const jessibuca = jessibucaPlayer[uid]
-
- jessibuca.on('pause', () => {
- playing.value = false
- })
- jessibuca.on('play', () => {
- playing.value = true
- })
- jessibuca.on('fullscreen', (msg) => {
- fullscreen.value = msg
- })
- jessibuca.on('mute', (msg) => {
- isNotMute.value = !msg
- })
- jessibuca.on('performance', (perf) => {
- let show = '卡顿'
- if (perf === 2) {
- show = '非常流畅'
- } else if (perf === 1) {
- show = '流畅'
- }
- performance.value = show
- })
- jessibuca.on('kBps', (kbps) => {
- kBps.value = Math.round(kbps)
- })
- jessibuca.on('videoInfo', (msg) => {
- console.log('Jessibuca -> videoInfo: ', msg)
- })
- jessibuca.on('audioInfo', (msg) => {
- console.log('Jessibuca -> audioInfo: ', msg)
- })
- jessibuca.on('error', (msg) => {
- console.log('Jessibuca -> error: ', msg)
- })
- jessibuca.on('timeout', (msg) => {
- console.log('Jessibuca -> timeout: ', msg)
- })
- jessibuca.on('loadingTimeout', (msg) => {
- console.log('Jessibuca -> timeout: ', msg)
- })
- jessibuca.on('delayTimeout', (msg) => {
- console.log('Jessibuca -> timeout: ', msg)
- })
- jessibuca.on('playToRenderTimes', (msg) => {
- console.log('Jessibuca -> playToRenderTimes: ', msg)
- })
- // #endif
- }
- // 播放按钮点击
- const playBtnClick = () => {
- play(props.videoUrl)
- }
- // 播放
- const play = (url) => {
- console.log('Jessibuca -> url: ', url)
- if (jessibucaPlayer[uid]) {
- destroy()
- }
- create()
-
- // #ifdef H5
- jessibucaPlayer[uid].on('play', () => {
- playing.value = true
- loaded.value = true
- quieting.value = jessibucaPlayer[uid].quieting
- })
- if (jessibucaPlayer[uid].hasLoaded()) {
- jessibucaPlayer[uid].play(url)
- } else {
- jessibucaPlayer[uid].on('load', () => {
- jessibucaPlayer[uid].play(url)
- })
- }
- // #endif
- }
- // 暂停
- const pause = () => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].pause()
- }
- // #endif
- playing.value = false
- performance.value = ''
- }
- // 截图
- const screenshot = () => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].screenshot()
- }
- // #endif
- }
- // 静音
- const mute = () => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].mute()
- }
- // #endif
- }
- // 取消静音
- const cancelMute = () => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].cancelMute()
- }
- // #endif
- }
- // 销毁播放器
- const destroy = () => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].destroy()
- }
- if (document.getElementById('buttonsBox') == null && btnDom.value) {
- container.value.appendChild(btnDom.value)
- }
- jessibucaPlayer[uid] = null
- // #endif
- playing.value = false
- performance.value = ''
- }
- // 全屏切换
- const fullscreenSwich = () => {
- const isFull = isFullscreen()
-
- if (!isFull) {
- // 进入全屏
- try {
- const containerEl = container.value
-
- // #ifdef H5
- // 尝试使用HTML5全屏API
- if (containerEl.requestFullscreen) {
- containerEl.requestFullscreen()
- } else if (containerEl.webkitRequestFullscreen) {
- containerEl.webkitRequestFullscreen()
- } else if (containerEl.msRequestFullscreen) {
- containerEl.msRequestFullscreen()
- } else if (containerEl.mozRequestFullScreen) {
- containerEl.mozRequestFullScreen()
- } else {
- // 如果原生API不可用,使用Jessibuca的全屏API
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].setFullscreen(true)
- }
- }
- // #endif
-
- // 设置全屏标志
- fullscreen.value = true
- } catch (e) {
- console.error('全屏切换失败:', e)
- }
- } else {
- // 退出全屏
- try {
- // #ifdef H5
- if (document.exitFullscreen) {
- document.exitFullscreen()
- } else if (document.webkitExitFullscreen) {
- document.webkitExitFullscreen()
- } else if (document.msExitFullscreen) {
- document.msExitFullscreen()
- } else if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen()
- } else {
- // 如果原生API不可用,使用Jessibuca的全屏API
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].setFullscreen(false)
- }
- }
- // #endif
-
- // 设置全屏标志
- fullscreen.value = false
- } catch (e) {
- console.error('退出全屏失败:', e)
- }
- }
-
- // 重新计算尺寸
- setTimeout(() => {
- updatePlayerDomSize()
- }, 300)
- }
- // 监听 videoUrl 变化
- watch(() => props.videoUrl, (val) => {
- nextTick(() => {
- play(val)
- })
- }, { immediate: true })
- // 组件挂载时
- onMounted(() => {
- // #ifdef H5
- const paramUrl = decodeURIComponent(route.params.url || '')
- nextTick(() => {
- updatePlayerDomSize()
- window.onresize = updatePlayerDomSize
- if (typeof props.videoUrl === 'undefined' && paramUrl) {
- play(paramUrl)
- }
- btnDom.value = document.getElementById('buttonsBox')
- })
- // #endif
- })
- // 组件卸载前
- onBeforeUnmount(() => {
- // #ifdef H5
- if (jessibucaPlayer[uid]) {
- jessibucaPlayer[uid].destroy()
- }
- if (parentNodeResizeObserver.value) {
- parentNodeResizeObserver.value.disconnect()
- }
- // #endif
- playing.value = false
- loaded.value = false
- performance.value = ''
- })
- // 暴露方法供父组件调用
- defineExpose({
- resize,
- play,
- pause,
- destroy,
- screenshot
- })
- </script>
- <!-- #endif -->
- <style>
- .jessibuca-container {
- width: 100%;
- height: 100%;
- background-color: #000000;
- margin: 0 auto;
- position: relative;
- overflow: hidden;
- }
- .jessibuca-container.jessibuca-fullscreen {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw !important;
- height: 100vh !important;
- z-index: 9999;
- }
- .jessibuca-player-wrapper {
- width: 100%;
- padding-top: 56.25%;
- position: relative;
- }
- .buttons-box {
- width: 100%;
- height: 28px;
- background-color: rgba(43, 51, 63, 0.7);
- position: absolute;
- display: -webkit-box;
- display: -ms-flexbox;
- display: flex;
- left: 0;
- bottom: 0;
- user-select: none;
- z-index: 10;
- }
- .jessibuca-btn {
- width: 20px;
- color: rgb(255, 255, 255);
- line-height: 27px;
- margin: 0px 10px;
- padding: 0px 2px;
- cursor: pointer;
- text-align: center;
- font-size: 0.8rem !important;
- }
- .buttons-box-right {
- position: absolute;
- right: 0;
- }
- </style>
|