feat: 集成音频播放组件和视频播放器

This commit is contained in:
2026-02-11 14:49:16 +08:00
parent 9a92e6ffb4
commit c6feeeef8b
48 changed files with 15614 additions and 1584 deletions

View File

@@ -0,0 +1,498 @@
<template>
<view
:class="'iframe' + dataId"
class="find_iframe"
:data-sandbox="sandbox"
:data-allow="allow"
:data-allowfullscreen="allowfullscreen"
:data-frameborder="frameborder"
:data-loadingShow="loadingShow"
:data-console="console"
:data-iframeStyle="iframeStyleString"
:data-iframeClass="iframeClassString"
:data-crossOrigin="crossOrigin"
:data-setDataName="setDataName"
:data-overrideUrlLoadingOptions="overrideUrlLoadingOptionsString"
:ready="ready" :change:ready="ComIframe.readyWatcher"
:isDestroy="isDestroy" :change:isDestroy="ComIframe.destroyWatcher"
:src="src" :change:src="ComIframe.srcWatcher"
:sandbox="sandbox" :change:sandbox="ComIframe.sandboxWatcher"
:allow="allow" :change:allow="ComIframe.allowWatcher"
:allowfullscreen="allowfullscreen" :change:allowfullscreen="ComIframe.allowfullscreenWatcher"
:frameborder="frameborder" :change:frameborder="ComIframe.frameborderWatcher"
:iframeStyle="iframeStyle" :change:iframeStyle="ComIframe.iframeStyleWatcher"
:iframeClass="iframeClass" :change:iframeClass="ComIframe.iframeClassWatcher"
:jump="jump" :change:jump="ComIframe.jumpWatcher"
:assignUrl="assignUrl" :change:assignUrl="ComIframe.assignUrlWatcher"
:loadingStatus="loadingStatus" :change:loadingStatus="ComIframe.loadingWatcher"
:evalJS="evalJSString" :change:evalJS="ComIframe.evalJSWatcher"
:evalCSS="evalCSSString" :change:evalCSS="ComIframe.evalCSSWatcher"
:setData="setDataValue" :change:setData="ComIframe.setDataWatcher"
:overrideUrlLoading="overrideUrlLoadingOptionsString" :change:overrideUrlLoading="ComIframe.overrideUrlLoadingWatcher">
</view>
</template>
<script>
export default {
props: {
dataId: {
type: String,
default () {
return new Date().getTime().toString() + Math.round(Math.random() * 10000)
}
},
src: {//链接
type: String,
default: ''
},
sandbox: {//沙盒模式 原生属性
type: String,
default: 'allow-same-origin allow-scripts allow-forms allow-top-navigation-by-user-activation allow-popups allow-modals'
},
allow: {//允许一些操作 原生属性
type: String,
// default: 'autoplay; fullscreen; encrypted-media; picture-in-picture'
default: ''
},
allowfullscreen: {//是否允许全屏 原生属性
type: Boolean,
default: true
},
//跨域属性 anonymous-它有一个默认值。它定义了将在不传递凭据信息的情况下发送CORS请求 use-credentials-将发送带有凭据、cookie 和证书的 cross-origin 请求
crossOrigin: {
type: String,
default: ''
},
frameborder: {//iframe边框 原生属性
type: String,
default: '0'
},
iframeClass: {//iframe样式
type: [String, Object],
default: ''
},
iframeStyle: {//iframe样式
type: [String, Object],
default: ''
},
loadingShow: {//展示loading
type: Boolean,
default: true
},
console: {//控制console
type: String,
default: 'log warn error'
},
overrideUrlLoadingOptions: {//仅支持app
type: Object,
default: () => {
return {
effect: '',
mode: '',
match: '',
exclude: ''
}
}
}
},
computed: {
overrideUrlLoadingOptionsString () {
return JSON.stringify(this.overrideUrlLoadingOptions)
},
iframeStyleString () {
return JSON.stringify(this.iframeStyle)
},
iframeClassString () {
return JSON.stringify(this.iframeClass)
}
},
data () {
return {
ready: '',
jump: 0,
isDestroy: false,
evalJSString: '',//js字符串
evalCSSString: '',//注入css字符串
assignUrl: '',//跳转链接
loadingStatus: -1,//加载进度提控制
evalJSTask: new Map(),//注入js临时队列表
setDataName: '',
setDataValue: ''
}
},
mounted () {
this.$nextTick(function () {
setTimeout(() => {
this.ready = this.dataId
}, 100)
})
},
methods: {
//返回上一页
back () {
this.jump = 0
this.$nextTick(function () {
this.jump = -1
})
},
//调用内部iframe跳转链接
assign (url) {
this.assignUrl = url
},
//显示进度条加载
showLoading () {
this.loadingStatus = -1
this.$nextTick(function () {
this.loadingStatus = 1
})
},
//关闭进度条加载
hideLoading () {
this.loadingStatus = -1
this.$nextTick(function () {
this.loadingStatus = 0
})
},
//销毁
destroy () {
this.isDestroy = false
this.$nextTick(() => {
this.isDestroy = true
})
},
//注入js
evalJS (str) {
if ( this.evalJSString ) {//如果又正在进行注入的js任务
const id = new Date().getTime().toString() + Math.round(Math.random() * 10000)
this.evalJSTask.set(id, str)//加入等待队列
} else {
this.evalJSString = str
}
},
//注入css
evalCSS (str) {
this.evalCSSString = ''
this.$nextTick(() => {
this.evalCSSString = str
})
},
setData (key, value) {
this.setDataName = key
this.setDataValue = ''
this.$nextTick(() => {
this.setDataValue = value
})
},
loadstart (e) {
this.$emit('loadstart', e)
},
loaded (e) {
this.$emit('loaded', e)
},
callError (e) {
this.$emit('error', e)
},
backError () {
this.$emit('backerror')
},
callDestroy () {
this.$emit('destroyed')
},
overrideUrlLoading (e) {
this.$emit('overrideurlloading', e)
},
message (e) {
this.$emit('message', e)
},
callEvalJS (e) {//注入js回调
this.evalJSString = ''
if ( this.evalJSTask.size ) {//如果等待队列有值并且当前没有正在执行的注入js任务
const firstEntrie = this.evalJSTask.entries().next().value//获取等待队列中的第一个
this.evalJS(firstEntrie[1])//执行任务
this.evalJSTask.delete(firstEntrie[0])//删除等待队列
}
if ( e.status == 'error' ) this.callError(e.message)
}
}
}
</script>
<!-- #ifdef APP-VUE || H5 -->
<script lang="renderjs" type="module" module="ComIframe">
export default {
data () {
return {
dom: null,
iframe: null,
loading: null,
iframeSrc: '',
backTimer: null
}
},
mounted () {
window.addEventListener('message', this.messageListener);
},
beforeDestroy () {
this.destoryIframe()
},
methods: {
messageListener (e) {
this.callMethod('message', {origin: e.origin, data: e.data})
},
destoryIframe () {
window.removeEventListener('message', this.messageListener)
if ( this.loading ) {
this.loading.remove()
this.loading = null
}
if ( this.iframe ) {
this.iframe.remove()
this.iframe = null
}
this.callMethod('callDestroy')
},
showLoadingRender () {
if ( this.loading && this.getData('loadingShow') ) {
this.loading.classList.remove('browser-loading-hide')
this.loading.classList.add('browser-loading-show')
}
},
hideLoadingRender () {
if ( this.loading && this.getData('loadingShow') ) {
this.loading.classList.remove('browser-loading-show')
this.loading.classList.add('browser-loading-hide')
}
},
clearBackTimer () {
if ( this.backTimer ) {
window.clearTimeout(this.backTimer)
this.backTimer = null
}
},
initIframe () {
this.showLoadingRender()
this.callMethod('loadstart', {href: this.iframeSrc})
this.iframe = document.createElement('IFRAME')
this.iframe.setAttribute('class', 'find_iframe_iframe')
this.iframe.setAttribute('frameborder', this.getData('frameborder'))
this.iframe.setAttribute('allow', this.getData('allow'))
this.iframe.setAttribute('allowfullscreen', this.getData('allowfullscreen'))
this.iframe.setAttribute('sandbox', this.getData('sandbox'))
this.iframe.setAttribute('crossOrigin', this.getData('crossOrigin'))
if ( this.getData('iframeStyle') ) this.iframeStyleWatcher(JSON.parse(this.getData('iframeStyle')))//初始化style
if ( this.getData('iframeClass') ) this.iframeClassWatcher(JSON.parse(this.getData('iframeClass')))//初始化style
this.iframe.src = this.iframeSrc
this.iframe.onload = () => {
this.hideLoadingRender()
const iframeWindow = this.iframe.contentWindow
iframeWindow.onbeforeunload = (e) => {
this.clearBackTimer()
this.callMethod('loadstart', e)
this.showLoadingRender()
}
const history = iframeWindow.history
const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
const title = iframeDocument.title
const href = iframeWindow.location.href
const head = iframeDocument.head;
const links = head.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]');//尝试从网页代码中获取favicon.ico
const favicon = links.length > 0 ? links[0].href : href + (href.substring(href.length -1) == '/' ? '' : '/') + 'favicon.ico'//没有获取到默认使用网址拼接favicon.ico
this.callMethod('loaded', {title, href, favicon, history})
}
this.iframe.onerror = (e) => {
this.hideLoadingRender()
this.callMethod('callError', e)
}
this.dom.appendChild(this.iframe)
//控制iframe内部的console
const consoles = this.getData('console').split(' ')
Object.keys(this.iframe.contentWindow.console).forEach(key => {
if ( consoles.indexOf(key) == -1 ) this.iframe.contentWindow.console[key] = function () {}
})
const options = JSON.parse(this.getData('overrideUrlLoadingOptions'))
if ( options && options.mode ) this.overrideUrlLoadingWatcher(this.getData('overrideUrlLoadingOptions'))
},
readyWatcher (newVal) {
if ( newVal ) {
this.dom = document.querySelector('.iframe' + newVal)
this.loading = document.createElement('DIV')
this.loading.setAttribute('class', 'browser-loading-line')
this.dom.appendChild(this.loading)
}
if ( this.iframeSrc && this.dom ) this.initIframe()
},
destroyWatcher (newVal) {
if ( newVal ) this.destoryIframe()
},
srcWatcher (newVal, oldVal) {
this.iframeSrc = newVal
if ( !this.dom ) return
if ( newVal != oldVal ) {
if ( oldVal ) this.destoryIframe()
if ( newVal ) this.initIframe()
}
},
sandboxWatcher (newVal) {
this.iframe && this.iframe.setAttribute('sandbox', newVal)
},
allowWatcher (newVal) {
this.iframe && this.iframe.setAttribute('allow', allow)
},
allowfullscreenWatcher (newVal) {
this.iframe && this.iframe.setAttribute('allowfullscreen', newVal)
},
frameborderWatcher (newVal) {
this.iframe && this.iframe.setAttribute('frameborder', newVal)
},
iframeStyleWatcher (newVal) {
if ( typeof newVal == "string" ) this.iframe && this.iframe.setAttribute('style', newVal)
if ( typeof newVal == "object" ) {
let style = ''
Object.keys(newVal).forEach(key => {
style += key + ':' + newVal[key] + ';'
})
this.iframe && this.iframe.setAttribute('style', style)
}
},
iframeClassWatcher (newVal) {
if ( typeof newVal == "string" ) this.iframe && this.iframe.setAttribute('class', 'find_iframe_iframe ' + newVal)
if ( typeof newVal == "object" ) {
let className = ''
Object.keys(newVal).forEach(key => {
className += newVal[key] ? (key + ' ') : ''
})
this.iframe && this.iframe.setAttribute('class', 'find_iframe_iframe ' + className)
}
},
jumpWatcher (newVal) {
if ( newVal < 0 && this.iframe ) {
this.iframe.contentWindow.history.back()
this.backTimer = window.setTimeout(() => {
this.callMethod('backError')
}, 300)
}
},
assignUrlWatcher (newVal) {
const iframeWindow = this.iframe?.contentWindow
iframeWindow && iframeWindow.window.location.assign(newVal);
},
loadingWatcher (newVal) {
if ( newVal == 0 ) this.hideLoading()
if ( newVal == 1 ) this.showLoading()
},
evalJSWatcher (newVal) {
if ( newVal ) {
try{
const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
const script = iframeDocument.createElement('SCRIPT')
script.innerHTML = newVal
iframeDocument.head.appendChild(script)//插入script标签
this.callMethod('callEvalJS', {status: 'success'})//通知逻辑层成功执行回调
iframeDocument.head.removeChild(script)//执行完script标签后需要移除
}catch(e){
this.callMethod('callEvalJS', {status: 'error', message: JSON.stringify(e)})
}
}
},
evalCSSWatcher (newVal) {
if ( newVal ) {
try{
const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
const style = iframeDocument.createElement('STYLE')
style.innerHTML = newVal
iframeDocument.head.appendChild(style)
}catch(e){
this.callMethod('callError', e)
}
}
},
//给网页值传参
setDataWatcher (newVal) {
if ( newVal ) {
try{
const iframeWindow = this.iframe.contentWindow
const key = this.getData('setDataName')
if ( key ) iframeWindow[key] = newVal
}catch(e){
this.callMethod('callEvalJS', {status: 'error', message: JSON.stringify(e)})
}
}
},
//拦截监听
overrideUrlLoadingWatcher (newVal) {
// #ifdef APP-VUE
const wv = plus.webview.currentWebview()
const options = JSON.parse(newVal)
wv.overrideUrlLoading(options, (e) => {
this.callMethod('overrideUrlLoading', e)
return true;
});
// #endif
},
getData (name) {
const value = this.dom.getAttribute('data-' + name)
if ( ['true', 'false'].includes(value) ) return value == 'false' ? false : true
else if ( /^\d+$/.test(value) ) return Number(value)
else return value
},
callMethod (name, args) {
// #ifndef H5
this.$ownerInstance.callMethod(name, args)
// #endif
// #ifdef H5
this[name](args)
// #endif
}
}
}
</script>
<!-- #endif -->
<style>
@keyframes loading-show {
from {
width: 0;
}
to {
width: 99%;
}
}
@keyframes loading-hide {
from {
width: 0;
opacity: 1;
}
to {
width: 100%;
opacity: 0;
}
}
.find_iframe {
position: relative;
width: 100%;
height: 100%;
}
/deep/ .find_iframe_iframe {
width: 100%;
height: 100%;
}
/deep/ .browser-loading-line {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 3px;
border-radius: 3px;
background-color: #4cd964;
z-index: 1;
touch-action: none;
pointer-events: none;
}
/deep/ .browser-loading-show {
animation: loading-show 5s ease both;
}
/deep/ .browser-loading-hide {
animation: loading-hide .5s linear both;
}
</style>

View File

@@ -0,0 +1,648 @@
<template>
<view class="render-video" :class="'rvideo' + dataId"
:data-route="pageRoute"
:dataId="dataId" :change:dataId="RenderVideo.dataIdWatcher"
:videoParams="videoParams" :change:videoParams="RenderVideo.reloadVideoRender"
:isUnload="isUnload" :change:isUnload="RenderVideo.destroyRender"
:loadDanmuParams="loadDanmuParams" :change:loadDanmuParams="RenderVideo.loadDanmuRender"
:isUnloadDanmu="isUnloadDanmu" :change:isUnloadDanmu="RenderVideo.unloadDanmuRender"
:sendDanmuParams="sendDanmuParams" :change:sendDanmuParams="RenderVideo.sendDanmuRender"
:insertDanmuParams="insertDanmuParams" :change:insertDanmuParams="RenderVideo.insertDanmuRender"
:setVideoParams="setVideoParams" :change:setVideoParams="RenderVideo.setVideoRender"
:reloadCustomParams="reloadCustomParams" :change:reloadCustomParams="RenderVideo.reloadCustomRender"
:updateConfigParams="updateConfigParams" :change:updateConfigParams="RenderVideo.updateConfigRender"
:isPlay="isPlay" :change:isPlay="RenderVideo.playRender"
:isPause="isPause" :change:isPause="RenderVideo.pauseRender"
:isToggle="isToggle" :change:isToggle="RenderVideo.toggleRender"
:seekTime="seekTime" :change:seekTime="RenderVideo.seekRender"
:isOpenFullscreen="isOpenFullscreen" :change:isOpenFullscreen="RenderVideo.openFullscreenRender"
:isExitFullscreen="isExitFullscreen" :change:isExitFullscreen="RenderVideo.exitFullscreenRender"
:captureParams="captureParams" :change:captureParams="RenderVideo.captureRender"
:toastParams="toastParams" :change:toastParams="RenderVideo.showToastRender"
:toolbarParams="toolbarParams" :change:toolbarParams="RenderVideo.showToolbarRender"
:isEnableGesture="isEnableGesture" :change:isEnableGesture="RenderVideo.enableGestureRender"
:isDisableGesture="isDisableGesture" :change:isDisableGesture="RenderVideo.disableGestureRender"
:isUpdateSize="isUpdateSize" :change:isUpdateSize="RenderVideo.updateSizeRender">
<view :class="'rvideoslot' + dataId">
<slot></slot>
</view>
</view>
</template>
<script>
export default {
data () {
return {
dataId: '',
pageRoute: '',//当前页面的路由名称
videoParams: '',//视频参数
isUnload: -1,//是否卸载视频
loadDanmuParams: '',//弹幕配置
isUnloadDanmu: -1,//是否卸载弹幕
sendDanmuParams: '',//发送弹幕
insertDanmuParams: '',//插入弹幕
setVideoParams: '',//设置video属性
reloadCustomParams: '',//重加载自定义配置
updateConfigParams: '',//更新配置
isPlay: -1,//是否播放
isPause: -1,//是否暂停
isToggle: -1,//是否切换
seekTime: -1,//跳转时间
isOpenFullscreen: -1,//是否开启全屏
isExitFullscreen: -1,//是否退出全屏
captureParams: '',//截屏
toastParams: '',//消息提示
toolbarParams: '',//工具栏
isEnableGesture: -1,//是否开启手势事件
isDisableGesture: -1,//是否关闭手势事件
isUpdateSize: -1//是否更新画布尺寸
}
},
mounted() {
this.dataId = new Date().getTime().toString() + Math.round(Math.random() * 10000)
const pages = getCurrentPages()
const page = pages[pages.length-1]
this.pageRoute = page.route
},
methods: {
ready () {
this.emit('message', {ready: true})
},
destroyed () {
this.$emit('destroyed')
},
message (e) {
this.$emit('message', e)
},
emit (name, data) {
this.$emit(name, {
data
})
},
//重加载视频
reloadVideo (params) {
this.videoParams = ''
this.$nextTick(() => {
this.videoParams = params
})
},
//卸载视频
destroy () {
this.isUnload = -1
this.$nextTick(() => {
this.isUnload = 1
})
},
//加载弹幕
loadDanmu (danmu) {
this.loadDanmuParams = ''
this.$nextTick(() => {
this.loadDanmuParams = danmu
})
},
//卸载弹幕
unloadDanmu () {
this.isUnloadDanmu = -1
this.$nextTick(() => {
this.isUnloadDanmu = 1
})
},
//发送弹幕
sendDanmu (danmu, border) {
this.sendDanmuParams = ''
this.$nextTick(() => {
this.sendDanmuParams = {
danmu,
border
}
})
},
//插入弹幕
insertDanmu (danmu) {
this.insertDanmuParams = ''
this.$nextTick(() => {
this.insertDanmuParams = danmu
})
},
//动态修改video属性
setVideo (key, value) {
this.setVideoParams = ''
this.$nextTick(() => {
this.setVideoParams = {
key, value
}
})
},
//重加载自定义配置
reloadCustom (params) {
this.reloadCustomParams = ''
this.$nextTick(() => {
this.reloadCustomParams = params
})
},
//更新配置
updateConfig (params) {
this.updateConfigParams = ''
this.$nextTick(() => {
this.updateConfigParams = params
})
},
//播放视频
play () {
this.isPlay = -1
this.$nextTick(() => {
this.isPlay = 1
})
},
//暂停视频
pause () {
this.isPause = -1
this.$nextTick(() => {
this.isPause = 1
})
},
//播放和暂停视频
toggle () {
this.isToggle = -1
this.$nextTick(() => {
this.isToggle = 1
})
},
/**
* 跳转视频
* @param {Number} time 跳转位置(单位秒)
*/
seek (time) {
this.seekTime = -1
this.$nextTick(() => {
this.seekTime = time
})
},
/**
* 播放和暂停视频
* @param {String} direction 屏幕方向 auto-自动计算 landscape-横屏 portrait-竖屏
*/
openFullscreen (direction) {
this.isOpenFullscreen = -1
this.$nextTick(() => {
this.isOpenFullscreen = direction
})
},
//退出全屏
exitFullscreen () {
this.isExitFullscreen = -1
this.$nextTick(() => {
this.isExitFullscreen = 1
})
},
//截图
capture (data) {
this.captureParams = ''
this.$nextTick(() => {
this.captureParams = data
})
},
//消息提示
showToast (data) {
this.toastParams = ''
this.$nextTick(() => {
this.toastParams = data
})
},
//展示工具栏
showToolbar (data) {
this.toolbarParams = ''
this.$nextTick(() => {
this.toolbarParams = data
})
},
//禁用手势事件
disableGesture () {
this.isDisableGesture = -1
this.$nextTick(() => {
this.isDisableGesture = 1
})
},
//启用手势事件
enableGesture () {
this.isEnableGesture = -1
this.$nextTick(() => {
this.isEnableGesture = 1
})
},
updateSize () {
this.isUpdateSize = -1
this.$nextTick(() => {
this.isUpdateSize = 1
})
}
}
}
</script>
<script lang="renderjs" module="RenderVideo" type="module">
export default {
data () {
return {
dom: null,
domSlot: null,
mp: null
}
},
beforeDestroy() {
this.destroyRender()
},
methods: {
dataIdWatcher (newVal) {
if ( newVal ) {
this.dom = document.querySelector('.rvideo' + newVal)
this.domSlot = document.querySelector('.rvideoslot' + newVal)
this.init()
}
},
async init () {
if ( !window.Hls ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/hls.min.js'), 'hls')
if ( !window.flvjs ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/flv.min.js'), 'flv')
if ( !window.JSMpeg ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/jsmpeg.min.js'), 'JSMpeg')
if ( !window.THREE ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/three.min.js'), 'three')
if ( !window.THREE.OrbitControls ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/OrbitControls.js'), 'OrbitControls')
if ( !window.THREE.DeviceOrientationControls ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/js/DeviceOrientationControls.js'), 'DeviceOrientationControls')
if ( !window.YbSubtitle ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player-subtitle.js'), 'ybPlayerSubtitle')
if ( !window.YbDanmu ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player-danmu.js'), 'ybPlayerDanmu')
if ( !window.YbPano ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player-pano.js'), 'ybPlayerPano')
if ( !window.YbMpeg ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player-mpeg.js'), 'ybPlayerMpeg')
if ( !window.YbPlayer ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player.js'), 'ybPlayer')
if ( !window.YbGesture ) await this.loadScriptInSandbox(this.parseSrc('/uni_modules/yingbing-video/static/html/dist/yb-player-gesture.js'), 'ybPlayerGesture')
this.loadCss()
this.callMethod('ready')
},
loadCss () {
let links = document.getElementsByTagName('link')
let linkarr = []
for ( let i = 0; i < links.length; i++ ) {
if ( links[i].getAttribute('data-id') ) linkarr.push(links[i].getAttribute('data-id'))
}
//判断一下css文件是否已加载避免重复加载
if ( linkarr.indexOf('yb-player') == -1 ) {
const link = document.createElement('LINK')
link.setAttribute('data-id', 'yb-player')
link.rel = 'stylesheet'
link.href = this.parseSrc('/uni_modules/yingbing-video/static/html/css/yb-player.css')
document.head.appendChild(link)
}
//判断一下css文件是否已加载避免重复加载
if ( linkarr.indexOf('yb-player-plugin') == -1 ) {
const link = document.createElement('LINK')
link.setAttribute('data-id', 'yb-player-plugin')
link.rel = 'stylesheet'
link.href = this.parseSrc('/uni_modules/yingbing-video/static/html/css/yb-player-plugin.css')
document.head.appendChild(link)
}
},
request (url) {
return new Promise((resolve, reject) => {
//如果是在线链接表示当前运行为H5直接用XMLHttpRequest请求
if (url.startsWith('http') ) {
// #ifdef H5
var xhr = new XMLHttpRequest()
// #endif
// #ifdef APP-VUE
var xhr = new plus.net.XMLHttpRequest()
// #endif
xhr.onreadystatechange = () => {
if ( xhr.readyState == 4 ) {
if ( xhr.status == 200 ) {
resolve(xhr.responseText)
} else {
resolve(null)
}
xhr.abort()
}
}
xhr.onabort = function () { xhr = null }
xhr.open('GET', url);
xhr.responseType = 'text';
xhr.send();
} else {
//非http协议链接需要使用系统文件API获取文件内容兼容IOS得WkWbview内核
// #ifdef APP-VUE
//将路径开头替换为_www/这是为了兼容ios文件路径读取
plus.io.resolveLocalFileSystemURL(url.replace('./', '_www/'), function( entry ) {
entry.file( function(file){
var fileReader = new plus.io.FileReader();
fileReader.readAsText(file, 'utf-8');
fileReader.onloadend = function(evt) {
resolve(evt.target.result)
}
} );
}, function ( e ) {
resolve(null)
} );
// #endif
// #ifndef APP-VUE
console.log('不支持本地文件访问')
// #endif
}
})
},
parseSrc (path) {
// #ifdef H5
const isHash = window.location.hash
const route = this.dom.getAttribute('data-route')
const pathName = isHash ? window.location.pathname : window.location.pathname.replace(route, '')
return window.location.origin + pathName + path.substring(1)
// #endif
// #ifdef APP-VUE
return '.' + path
// #endif
},
// 安全加载JS库的方法
async loadScriptInSandbox (url, id) {
try {
const response = await this.request(url)
const jsCode = response
// const uni = undefined
// eval('(function () {' + jsCode + '}())')
//创建隔离的执行环境 似乎没有效果
const sandbox = {
window: window,
document: document,
navigator: navigator
// 添加其他必要的全局对象...
};
const code = `
(function(sandbox) {
var window = sandbox.window;
var document = sandbox.document;
var navigator = sandbox.navigator;
var uni = undefined;
${jsCode}
})(sandbox);
`;
// 使用Function构造器创建隔离的执行环境
const executeInSandbox = new Function('sandbox',code);
// 执行代码
executeInSandbox(sandbox);
return { success: true, sandbox };
} catch (error) {
console.error(`加载脚本失败: ${error}`);
// return { success: false, error };
}
},
async loadScriptInDom (src, id) {
return new Promise(resolve => {
const script = document.createElement('SCRIPT')
script.setAttribute('data-id', id)
script.src = src
script.onload = function () {
resolve(true)
}
document.head.appendChild(script)
})
},
//深度克隆数据,避免数据污染
_traverseObject (obj, emitname) {
if(typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
for(let i in obj) {
if(obj.hasOwnProperty(i)){
const value = obj[i]
const emit = {}
emit[emitname] = value
o[i] = i == 'click' ? () => { this.callMethod('message', {data: emit}) } : typeof value === 'object' ? this._traverseObject(value, emitname) : value
}
}
return o;
},
//处理hlsConfig
parseHlsConfig (config = {}) {
Object.keys(config).forEach(key => {
if ( ['xhrSetup'].includes(key) ) {
config[key] = new Function('return (' + config[key] + ')')();
}
if ( ['pLoader', 'fLoader'].includes(key) ) {
config[key] = (new Function(`return ${config[key]}`))();
}
})
return config
},
reloadVideoRender (params) {
if ( !params ) return
this.unloadRender()
const custom = this._traverseObject(params.custom, 'slotclick')
this.mp = new YbPlayer({
container: this.dom,
src: params.src,
segments: params.segments,
title: params.title,
poster: params.poster || undefined,
type: params.type,
three: params.three,
initialTime: params.initialTime,
duration: params.duration,
autoplay: params.autoplay,
preload: params.preload,
muted: params.muted,
playbackRate: params.playbackRate,
loop: params.loop,
isLive: params.isLive,
header: params.header,
controls: params.controls,
height: '100%',
objectFit: params.objectFit,
crossOrigin: params.crossOrigin,
openDirection: params.openDirection,
exitDirection: params.exitDirection,
quality: params.quality,
works: params.works,
workIndex: params.workIndex,
subtitles: params.subtitles,
subtitleIndex: params.subtitleIndex,
custom,
decoder: {
hls: {
loader: Hls,
config: this.parseHlsConfig(params.hlsConfig)
},
flv: {
loader: flvjs,
config: params.flvConfig
},
jsmpeg: {
loader: JSMpeg,
config: params.jsmpegConfig
}
}
})
this.mp.load()
this.mp.loadVideo()
this.mp.loadGestureEvent()
this.mp.appendDom(this.domSlot)
this.mp.onmessage = (data) => {
this.callMethod('message', {data})
}
window.addEventListener('resize', this.updateSizeRender)
},
//动态修改video属性
setVideoRender (newVal) {
if ( !newVal ) return
const { key, value } = newVal
this.mp?.setVideo(key, value)
},
//加载弹幕
loadDanmuRender (danmu) {
if ( !danmu ) return
if ( this.mp ) {
this.mp.setConfig('danmu', YbPlayer.deepClone(danmu))
this.mp.unloadDanmu()
this.mp.loadDanmu()
}
},
//卸载弹幕
unloadDanmuRender (newVal) {
if ( newVal == -1 ) return
this.mp?.unloadDanmu()
},
//发送弹幕
sendDanmuRender (newVal) {
if ( !newVal ) return
const { danmu, border } = newVal
this.mp?.sendDanmu(danmu, border)
},
//插入弹幕
insertDanmuRender (newVal) {
if ( !newVal ) return
this.mp?.insertDanmu(newVal)
},
//更新配置
updateConfigRender (config) {
if ( !config ) return
Object.keys(config).forEach(key => {
this.mp?.setConfig(key, config[key])
})
this.mp?.hideControls()
},
//重加载自定义配置
reloadCustomRender (config) {
if ( !config ) return
const newConfig = this._traverseObject(config, 'slotclick')
Object.keys(newConfig).forEach(key => {
this.mp?.setCustom(key, newConfig[key])
})
this.mp?.unloadCustom()
this.mp?.loadCustom()
},
//播放/暂停
toggleRender (newVal) {
if ( newVal == -1 ) return
this.mp?.toggle()
},
//播放
playRender (newVal) {
if ( newVal == -1 ) return
this.mp?.video?.play()
},
//暂停
pauseRender (newVal) {
if ( newVal == -1 ) return
this.mp?.video?.pause()
},
//跳转
seekRender (time) {
if ( time == -1 ) return
this.mp?.seek(time)
},
//开启全屏
openFullscreenRender (direction) {
if ( direction == -1 ) return
this.mp?.openFullscreen(direction)
},
//退出全屏
exitFullscreenRender (newVal) {
if ( newVal == -1 ) return
this.mp?.exitFullscreen()
},
//消息提示
showToastRender (data) {
if ( !data ) return
this.mp?.showToast(data)
},
//展示工具栏
showToolbarRender (data) {
if ( !data ) return
const newDate = this._traverseObject(data, 'toolclick')
this.mp?.showToolbar(newDate.selector, newDate.list, newDate.checkShow, newDate.checkIndex)
},
//截图
captureRender (data) {
if ( !data ) return
this.mp?.capture(data.type, data.show)
},
//禁用手势事件
disableGestureRender (newVal) {
if ( newVal == -1 ) return
this.mp?.disableGesture()
},
//启用手势事件
enableGestureRender (newVal) {
if ( newVal == -1 ) return
this.mp?.enableGesture()
},
//卸载视频
unloadRender () {
if ( this.mp ) {
if ( this.domSlot && this.dom ) this.dom.appendChild(this.domSlot)
this.mp.unloadDanmu()
this.mp.unloadGestureEvent()
this.mp.unloadVideo()
this.mp.unload()
window.removeEventListener('resize', this.updateSizeRender)
}
},
//卸载视频
destroyRender (newVal) {
if ( newVal == 1 ){
this.unloadRender()
this.callMethod('destroyed')
}
},
//重置画布尺寸
updateSizeRender () {
//重置弹幕画布
this.mp?.refreshDanmu()
//重置3D画布
this.mp?.refreshPano()
},
callMethod (name, args) {
// #ifndef H5
this.$ownerInstance && this.$ownerInstance.callMethod(name, args)
// #endif
// #ifdef H5
this[name] && this[name](args)
// #endif
}
}
}
</script>
<style>
.render-video {
background-color: #000;
width: 100%;
height: 100%;
}
.render-video-slot {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>