Files
ZY-Player/src/components/Play.vue
2020-11-21 16:22:06 +08:00

1654 lines
56 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="play">
<div class="box">
<div class="title">
<span v-if="this.right.list.length > 1"> {{(video.info.index + 1)}} </span>{{name}}
</div>
<div class="player">
<div id="xgplayer"></div>
</div>
<div class="more" v-if="!video.iptv" :key="Boolean(video.iptv)">
<span class="zy-svg" @click="otherEvent" v-show="name !== ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="coloursIconTitle">
<title id="coloursIconTitle">换源</title>
<circle cx="12" cy="9" r="5"></circle>
<circle cx="9" cy="14" r="5"></circle>
<circle cx="15" cy="14" r="5"></circle>
</svg>
</span>
<span class="zy-svg" @click="nextEvent" v-show="right.list.length > 1">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="forwardIconTitle">
<title id="forwardIconTitle">下一集</title>
<path d="M10 14.74L3 19V5l7 4.26V5l12 7-12 7v-4.26z"></path>
</svg>
</span>
<span class="zy-svg" @click="listEvent" :class="right.type === 'list' ? 'active' : ''" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="dashboardIconTitle">
<title id="dashboardIconTitle">播放列表</title>
<rect width="20" height="20" x="2" y="2"></rect>
<path d="M11 7L17 7M11 12L17 12M11 17L17 17"></path>
<line x1="7" y1="7" x2="7" y2="7"></line>
<line x1="7" y1="12" x2="7" y2="12"></line>
<line x1="7" y1="17" x2="7" y2="17"></line>
</svg>
</span>
<span class="zy-svg" @click="historyEvent" :class="right.type === 'history' ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="timeIconTitle">
<title id="timeIconTitle">历史记录</title>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span class="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="favouriteIconTitle">
<title id="favouriteIconTitle">收藏</title>
<path d="M12,21 L10.55,19.7051771 C5.4,15.1242507 2,12.1029973 2,8.39509537 C2,5.37384196 4.42,3 7.5,3 C9.24,3 10.91,3.79455041 12,5.05013624 C13.09,3.79455041 14.76,3 16.5,3 C19.58,3 22,5.37384196 22,8.39509537 C22,12.1029973 18.6,15.1242507 13.45,19.7149864 L12,21 Z"></path>
</svg>
</span>
<span class="zy-svg" @click="detailEvent" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="feedIconTitle">
<title id="feedIconTitle">详情</title>
<circle cx="7.5" cy="7.5" r="2.5"></circle>
<path d="M22 13H2"></path>
<path d="M18 6h-5m5 3h-5"></path>
<path d="M5 2h14a3 3 0 0 1 3 3v17H2V5a3 3 0 0 1 3-3z"></path>
</svg>
</span>
<span class="zy-svg" @click="miniEvent" v-show="right.list.length > 0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="diamondIconTitle">
<title id="diamondIconTitle">精简模式</title>
<path d="M12 20L3 11M12 20L21 11M12 20L8 11M12 20L16 11M3 11L7 5M3 11H8M7 5L8 11M7 5H12M17 5L21 11M17 5L16 11M17 5H12M21 11H16M8 11H16M8 11L12 5M16 11L12 5"></path>
</svg>
</span>
<span class="zy-svg" @click="playWithExternalPalyerEvent" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle" >使用第三方播放器</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span class="zy-svg" @click="shareEvent" v-show="right.list.length > 0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="qrIconTitle">
<title id="qrIconTitle">分享</title>
<rect x="10" y="3" width="7" height="7" transform="rotate(90 10 3)"></rect>
<rect width="1" height="1" transform="matrix(-1 0 0 1 7 6)"></rect>
<rect x="10" y="14" width="7" height="7" transform="rotate(90 10 14)"></rect>
<rect x="6" y="17" width="1" height="1"></rect>
<rect x="14" y="20" width="1" height="1"></rect>
<rect x="17" y="17" width="1" height="1"></rect>
<rect x="14" y="14" width="1" height="1"></rect>
<rect x="20" y="17" width="1" height="1"></rect>
<rect x="20" y="14" width="1" height="1"></rect>
<rect x="20" y="20" width="1" height="1"></rect>
<rect x="21" y="3" width="7" height="7" transform="rotate(90 21 3)"></rect>
<rect x="17" y="6" width="1" height="1"></rect>
</svg>
</span>
<span class="zy-svg" @click="showShortcutEvent" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="sendIconTitle">
<title id="sendIconTitle">快捷键指南</title>
<polygon points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"></polygon>
</svg>
</span>
<span class="zy-svg" @click="issueEvent" v-show="right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="infoIconTitle">
<title id="infoIconTitle">复制调试信息</title>
<path d="M12,12 L12,15"></path>
<line x1="12" y1="9" x2="12" y2="9"></line>
<circle cx="12" cy="12" r="10"></circle>
</svg>
</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0" @click="historyItemEvent(right.history[0])">上次播放到{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}}</span>
</div>
<div class="more" v-if="video.iptv" :key="Boolean(video.iptv)">
<span class="zy-svg" @click="channelListShow = !channelListShow">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="dashboardIconTitle">
<title id="dashboardIconTitle">频道列表</title>
<rect width="20" height="20" x="2" y="2"></rect>
<path d="M11 7L17 7M11 12L17 12M11 17L17 17"></path>
<line x1="7" y1="7" x2="7" y2="7"></line>
<line x1="7" y1="12" x2="7" y2="12"></line>
<line x1="7" y1="17" x2="7" y2="17"></line>
</svg>
</span>
<span class="zy-svg" @click="otherEvent">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="coloursIconTitle">
<title id="coloursIconTitle">换源</title>
<circle cx="12" cy="9" r="5"></circle>
<circle cx="9" cy="14" r="5"></circle>
<circle cx="15" cy="14" r="5"></circle>
</svg>
</span>
<span class="zy-svg" @click="miniEvent">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="diamondIconTitle">
<title id="diamondIconTitle">精简模式</title>
<path d="M12 20L3 11M12 20L21 11M12 20L8 11M12 20L16 11M3 11L7 5M3 11H8M7 5L8 11M7 5H12M17 5L21 11M17 5L16 11M17 5H12M21 11H16M8 11H16M8 11L12 5M16 11L12 5"></path>
</svg>
</span>
<span class="zy-svg" @click="playWithExternalPalyerEvent">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle" >使用第三方播放器</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span class="zy-svg" @click="showShortcutEvent">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="sendIconTitle">
<title id="sendIconTitle">快捷键指南</title>
<polygon points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"></polygon>
</svg>
</span>
</div>
</div>
<transition name="slideX">
<div v-if="right.show" class="list">
<div class="list-top">
<span class="list-top-title" v-if="right.type === 'list'">播放列表</span>
<span class="list-top-title" v-if="right.type === 'history'">历史记录</span>
<span class="list-top-title" v-if="right.type === 'shortcut'">快捷键指南{{ this.video.iptv ? '(直播时部分功能不可用)' : '' }}</span>
<span class="list-top-title" v-if="right.type === 'other'">同组其他源的视频</span>
<span class="list-top-title" v-if="right.type === 'sources'">该频道可用源</span>
<span class="list-top-close zy-svg" @click="closeListEvent">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<ul v-if="right.type === 'list'" class="list-item" v-on-clickaway="closeListEvent">
<li v-if="right.list.length > 0" @click="exportM3u8">导出</li>
<li v-if="right.list.length === 0">无数据</li>
<li @click="listItemEvent(j)" :class="video.info.index === j ? 'active' : ''" v-for="(i, j) in right.list" :key="j">{{i | ftName(j)}}</li>
</ul>
<ul v-if="right.type === 'history'" class="list-history" v-on-clickaway="closeListEvent">
<li v-if="right.history.length > 0" @click="clearAllHistory">清空</li>
<li v-if="right.history.length === 0">无数据</li>
<li @click="historyItemEvent(m)" :class="video.info.id === m.ids ? 'active' : ''" v-for="(m, n) in right.history" :key="n"><span class="title" :title="'【' + m.site + '】' + m.name + ' 第' + (m.index+1) + '集'">【{{m.site}}】{{m.name}} 第{{m.index+1}}集</span><span @click.stop="removeHistoryItem(m)" class="detail-delete">删除</span></li>
</ul>
<ul v-if="right.type === 'shortcut'" class="list-shortcut" v-on-clickaway="closeListEvent">
<li v-for="(m, n) in right.shortcut" :key="n"><span class="title">{{m.desc}} -- [ {{m.key}} ]</span></li>
</ul>
<ul v-if="right.type === 'other'" class="list-other" v-on-clickaway="closeListEvent">
<li v-if="right.other.length === 0">无数据</li>
<li @click="otherItemEvent(m)" v-for="(m, n) in right.other" :key="n"><span class="title">{{m.name}} - [{{m.site.name}}]</span></li>
</ul>
<ul v-if="right.type === 'sources'" class="list-channels" v-on-clickaway="closeListEvent">
<li v-if="right.sources.length === 0">当前频道已关闭</li>
<li v-for="(channel, index) in right.sources" :key="index">
<span @click="playChannel(channel)" class="title">{{ channel.id === video.iptv.id ? channel.name + '[当前]' : channel.name }}</span>
<span @click="disableChannel(channel)" class="btn" title="关闭频道">隐藏</span>
</li>
</ul>
</div>
</div>
</transition>
<transition name="slideX">
<div v-if="channelListShow" class="list">
<div class="list-top">
<span class="list-top-title">频道列表</span>
<span class="list-top-close zy-svg" @click="channelListShow = false">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<el-autocomplete
clearable
size="small"
v-model.trim="searchTxt"
value-key="keywords"
:fetch-suggestions="querySearch"
:popper-append-to-body="false"
popper-class="popper"
placeholder="搜索"
@keyup.enter.native="searchAndRecord">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-autocomplete>
<el-tree ref="channelTree"
:data="channelListForShow"
:props="defaultProps"
accordion
:filter-node-method="filterNode"
@node-click="handleNodeClick">
</el-tree>
</div>
</div>
</transition>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { star, history, setting, shortcut, mini, channelList, iptvSearch, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import Player from 'xgplayer'
import HlsJsPlayer from 'xgplayer-hls.js'
import mt from 'mousetrap'
import { directive as onClickaway } from 'vue-clickaway'
import { exec, execFile } from 'child_process'
const { remote, clipboard } = require('electron')
const win = remote.getCurrentWindow()
const PinyinMatch = require('pinyin-match')
const VIDEO_DETAIL_CACHE = {}
const addPlayerBtn = function (event, svg, attrs) {
const player = this
const util = Player.util
const controlEl = player.controls
const btnConfig = player.config[event]
if (btnConfig) {
const btnName = 'xg-btn-' + event
const btn = util.createDom(btnName, svg || btnConfig.svg, attrs || {}, btnName)
controlEl.appendChild(btn)
const ev = ['click', 'touchend']
ev.forEach(item => {
btn.addEventListener(item, function (e) {
e.preventDefault()
e.stopPropagation()
player.emit(event)
}, false)
})
}
}
const addPlayerView = function (event, tpl, attrs) {
const player = this
const util = Player.util
const rootEl = player.root
const viewConfig = player.config[event]
if (viewConfig) {
const viewName = 'xg-view-' + event
const view = util.createDom(viewName, tpl, attrs || {}, viewName)
rootEl.appendChild(view)
const ev = ['click', 'touchend']
ev.forEach(item => {
view.addEventListener(item, function (e) {
e.preventDefault()
e.stopPropagation()
player.emit(event)
}, false)
})
}
}
export default {
name: 'play',
data () {
return {
xg: null,
right: {
show: false,
type: '',
list: [],
history: [],
shortcut: [],
other: [],
sources: [],
currentTime: 0
},
config: {
id: 'xgplayer',
url: '',
lang: 'zh-cn',
width: '100%',
height: '100%',
autoplay: false,
videoInit: true,
screenShot: true,
keyShortcut: 'off',
crossOrigin: true,
cssFullscreen: true,
defaultPlaybackRate: 1,
playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5],
playPrev: true,
playNextOne: true,
videoStop: true,
showList: true,
showHistory: true,
videoTitle: true,
airplay: true,
closeVideoTouch: true,
ignores: ['cssFullscreen']
},
state: {
showList: false,
showHistory: false
},
name: '',
length: 0,
timer: null,
scroll: false,
isStar: false,
isTop: false,
miniMode: false,
mainWindowBounds: {},
searchTxt: '',
searchRecordList: [],
channelList: [],
channelListForShow: [],
channelListShow: false,
defaultProps: {
label: 'label',
children: 'children'
}
}
},
filters: {
ftName (e, n) {
const num = e.split('$')
if (num.length > 1) {
return e.split('$')[0]
} else {
return `${(n + 1)}`
}
}
},
directives: {
onClickaway: onClickaway
},
computed: {
view: {
get () {
return this.$store.getters.getView
},
set (val) {
this.SET_VIEW(val)
}
},
video: {
get () {
return this.$store.getters.getVideo
},
set (val) {
this.SET_VIDEO(val)
}
},
detail: {
get () {
return this.$store.getters.getDetail
},
set (val) {
this.SET_DETAIL(val)
}
},
share: {
get () {
return this.$store.getters.getShare
},
set (val) {
this.SET_SHARE(val)
}
},
appState: {
get () {
return this.$store.getters.getAppState
},
set (val) {
this.SET_APPSTATE(val)
}
},
setting () {
return this.$store.getters.getSetting
}
},
watch: {
view () {
this.right.show = false
this.right.type = ''
if (this.view === 'Play') {
this.getChannelList()
if (this.video.key === '' && !this.video.iptv) this.channelListShow = true
}
},
video: {
handler () {
this.getUrls()
},
deep: true
},
setting: {
handler () {
this.changeSetting()
},
deep: true
},
name () {
const span = document.querySelector('.xg-view-videoTitle span')
if (!span) {
return
}
if (this.right.list.length > 1) {
span.innerText = `『第 ${this.video.info.index + 1} 集』${this.name}`
} else {
span.innerText = `${this.name}`
}
},
searchTxt () {
if (this.searchTxt === '清除历史记录...') {
this.clearSearchRecords()
this.searchTxt = ''
}
this.searchEvent()
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_APPSTATE']),
handleNodeClick (node) {
if (node.channel) {
this.playChannel(node.channel)
}
},
filterNode (value, data) {
if (!value) return true
return data.label.toLowerCase().includes(value.toLowerCase()) || PinyinMatch.match(data.label, value)
},
querySearch (queryString, cb) {
var searchRecordList = this.searchRecordList.slice(0, -1)
var results = queryString ? searchRecordList.filter(this.createFilter(queryString)) : this.searchRecordList
// 调用 callback 返回建议列表的数据
cb(results)
},
createFilter (queryString) {
return (item) => {
return (item.keywords.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
}
},
getSearchRecordList () {
iptvSearch.all().then(res => {
this.searchRecordList = res.reverse()
this.searchRecordList.push({ id: this.searchRecordList.length + 1, keywords: '清除历史记录...' })
})
},
addSearchRecord () {
const wd = this.searchTxt
if (wd) {
iptvSearch.find({ keywords: wd }).then(res => {
if (!res) {
iptvSearch.add({ keywords: wd })
}
this.getSearchRecordList()
})
}
},
clearSearchRecords () {
iptvSearch.clear().then(res => {
this.getSearchRecordList()
})
},
searchEvent () {
this.$refs.channelTree.filter(this.searchTxt)
},
searchAndRecord () {
this.addSearchRecord()
this.searchEvent()
},
async getUrls () {
if (this.video.key === '') {
if (!this.video.iptv) this.channelListShow = true
return false
}
this.name = ''
if (this.timer !== null) {
clearInterval(this.timer)
this.timer = null
}
if (this.xg && this.xg.hasStart) {
this.xg.pause()
}
if (this.video.iptv) {
// 是直播源,直接播放
this.playChannel(this.video.iptv)
} else {
this.channelListShow = false
const index = this.video.info.index | 0
var time = this.video.info.time
if (!time) {
// 如果video.info.time没有设定的话从历史中读取时间进度
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
if (db.index === index) {
time = db.time
}
}
}
this.playVideo(index, time)
}
},
disableChannel (channel) {
const index = this.right.sources.indexOf(channel)
this.right.sources.splice(index, 1)
const ele = this.channelList.find(e => e.id === channel.channelID)
const origin = ele.channels.find(e => e.id === channel.id)
origin.isActive = false
ele.isActive = ele.channels.some(e => e.isActive)
channelList.remove(ele.id)
channelList.add(ele)
this.getChannelList()
},
playChannel (channel) {
if (channel.channels) {
this.right.sources = channel.channels.filter(e => e.isActive)
channel = channel.prefer ? channel.channels.find(e => e.id === channel.prefer) : channel.channels.filter(e => e.isActive)[0]
} else {
const ele = this.channelList.find(e => e.id === channel.channelID)
ele.prefer = channel.id
channelList.remove(ele.id)
channelList.add(ele)
this.getChannelList()
this.right.sources = ele.channels.filter(e => e.isActive)
}
this.video.iptv = channel
this.name = channel.name
this.xg.src = channel.url
this.xg.play()
document.querySelector('xg-btn-showhistory').style.display = 'none'
document.querySelector('.xgplayer-playbackrate').style.display = 'none'
},
playVideo (index = 0, time = 0) {
document.querySelector('xg-btn-showhistory').style.display = 'block'
document.querySelector('.xgplayer-playbackrate').style.display = 'inline-block'
this.fetchM3u8List().then(m3u8Arr => {
this.xg.src = m3u8Arr[index]
if (time !== 0) {
this.xg.play()
this.xg.once('playing', () => {
this.xg.currentTime = time
})
} else {
this.xg.play()
}
this.videoPlaying()
this.xg.once('ended', () => {
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > index)) {
this.video.info.time = 0
this.video.info.index++
}
this.xg.off('ended')
})
})
},
fetchM3u8List () {
return new Promise((resolve) => {
const cacheKey = this.video.key + '@' + this.video.info.id
if (VIDEO_DETAIL_CACHE[cacheKey]) {
this.name = VIDEO_DETAIL_CACHE[cacheKey].name
resolve(VIDEO_DETAIL_CACHE[cacheKey].list)
}
zy.detail(this.video.key, this.video.info.id).then(res => {
this.name = res.name
const dd = res.dl.dd
const type = Object.prototype.toString.call(dd)
let m3u8Txt = []
if (type === '[object Array]') {
for (const i of dd) {
if (i._t.indexOf('m3u8') >= 0) {
m3u8Txt = i._t.split('#')
}
}
} else {
m3u8Txt = dd._t.split('#')
}
this.right.list = m3u8Txt
const m3u8Arr = []
for (const i of m3u8Txt) {
const j = i.split('$')
if (j.length > 1) {
for (let m = 0; m < j.length; m++) {
if (j[m].indexOf('.m3u8') >= 0 && j[m].startsWith('http')) {
m3u8Arr.push(j[m])
break
}
}
} else {
m3u8Arr.push(j[0])
}
}
VIDEO_DETAIL_CACHE[cacheKey] = {
list: m3u8Arr,
name: res.name
}
resolve(m3u8Arr)
})
})
},
async videoPlaying () {
this.changeVideo()
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
const doc = {
site: db.site,
ids: db.ids,
name: db.name,
type: db.type,
year: db.year,
index: this.video.info.index,
time: db.time,
detail: this.video.detail
}
history.update(db.id, doc)
} else {
const doc = {
site: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
type: this.video.info.type,
year: this.video.info.year,
index: this.video.info.index,
time: '',
detail: this.video.detail
}
history.add(doc)
}
this.updateStar()
this.timerEvent()
},
changeVideo () {
win.setProgressBar(-1)
this.checkStar()
this.checkTop()
},
timerEvent () {
this.timer = setInterval(async () => {
const endTime = this.xg.duration
const currentTime = this.xg.currentTime
const progress = parseFloat((currentTime / endTime).toFixed(2))
win.setProgressBar(progress)
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
const doc = { ...db }
doc.time = this.xg.currentTime
doc.duration = this.xg.duration
delete doc.id
history.update(db.id, doc)
}
}, 10000)
},
prevEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.prefer === this.video.iptv.id)
if (index >= 1) {
var channel = this.channelList[index - 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是第一个频道了。')
}
} else {
if (this.video.info.index >= 1) {
this.video.info.index--
this.video.info.time = 0
} else {
this.$message.warning('这已经是第一集了。')
}
}
},
nextEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.prefer === this.video.iptv.id)
if (index < (this.channelList.length - 1)) {
var channel = this.channelList[index + 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是最后一个频道了。')
}
} else {
if (this.video.info.index < (this.right.list.length - 1)) {
this.video.info.index++
this.video.info.time = 0
} else {
this.$message.warning('这已经是最后一集了。')
}
}
},
listEvent () {
if (this.right.type === 'list') {
this.right.show = false
this.right.type = ''
} else {
this.right.show = true
this.right.type = 'list'
}
},
historyEvent () {
if (this.right.type === 'history') {
this.right.show = false
this.right.type = ''
} else {
this.right.show = true
this.right.type = 'history'
}
this.getAllhistory()
},
getAllhistory () {
history.all().then(res => {
this.right.history = res.reverse()
})
},
async updateStar () {
const info = this.video.info
const db = await star.find({ key: this.video.key, ids: info.id })
if (db) {
db.index = info.index
star.update(db.id, db)
}
},
async starEvent () {
const info = this.video.info
const db = await star.find({ key: this.video.key, ids: info.id })
if (db) {
star.remove(db.id).then(res => {
if (res) {
this.$message.warning('取消收藏失败')
} else {
this.$message.success('取消收藏成功')
this.isStar = false
}
})
} else {
zy.detail(this.video.key, info.id).then(detailRes => {
const docs = {
key: this.video.key,
ids: info.id,
name: info.name,
detail: detailRes,
index: info.index
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
this.isStar = true
})
})
}
},
detailEvent () {
this.detail = {
show: true,
key: this.video.key,
info: this.video.info
}
},
async miniEvent () {
this.mainWindowBounds = JSON.parse(JSON.stringify(win.getBounds()))
let miniWindowBounds
await mini.find().then(res => { if (res) miniWindowBounds = res.bounds })
if (!miniWindowBounds) miniWindowBounds = { x: win.getPosition()[0], y: win.getPosition()[1], width: 550, height: 340 }
win.setBounds(miniWindowBounds)
this.xg.getCssFullscreen()
this.miniMode = true
},
async exitMiniEvent () {
await mini.find().then(res => {
let doc = {}
doc = {
id: 0,
bounds: win.getBounds()
}
if (res) {
mini.update(doc)
} else {
mini.add(doc)
}
})
win.setBounds(this.mainWindowBounds)
this.xg.exitCssFullscreen()
this.miniMode = false
},
shareEvent () {
this.share = {
show: true,
key: this.video.key,
info: this.video.info
}
},
issueEvent () {
const info = {
video: this.video,
list: this.right.list,
m3u8List: VIDEO_DETAIL_CACHE[this.video.key + '@' + this.video.info.id] || [],
playerError: this.xg.error || '',
playerState: this.xg.readyState || '',
networkState: this.xg.networkState || ''
}
clipboard.writeText(JSON.stringify(info, null, 4))
this.$message.success('视频信息复制成功')
},
playWithExternalPalyerEvent () {
const fs = require('fs')
if (this.video.iptv) {
var externalPlayer = this.setting.externalPlayer
if (!externalPlayer) {
this.$message.error('请设置第三方播放器路径')
return
}
if (fs.existsSync(externalPlayer)) {
execFile(externalPlayer, [this.video.iptv.url])
} else {
exec(externalPlayer, [this.video.iptv.url])
}
return
}
this.fetchM3u8List().then(m3u8Arr => {
var externalPlayer = this.setting.externalPlayer
if (!externalPlayer) {
this.$message.error('请设置第三方播放器路径')
// 在线播放该视频
var link = 'https://www.m3u8play.com/?play=' + m3u8Arr[this.video.info.index]
const open = require('open')
open(link)
} else {
var m3uFile = this.generateM3uFile(this.video.info.name, m3u8Arr, this.video.info.index)
if (fs.existsSync(externalPlayer)) {
execFile(externalPlayer, [m3uFile])
} else {
exec(externalPlayer, [m3uFile])
}
}
})
},
generateM3uFile (fileName, m3u8Arr, startIndex) {
const path = require('path')
const os = require('os')
const fs = require('fs')
var filePath = path.join(os.tmpdir(), fileName + '.m3u')
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
}
var str = '#EXTM3U' + os.EOL
for (let ind = startIndex; ind < m3u8Arr.length; ind++) {
str += `#EXTINF: -1, 第${ind + 1}` + os.EOL
str += m3u8Arr[ind] + os.EOL
}
str += '#EXT-X-ENDLIST' + os.EOL
fs.writeFileSync(filePath, str)
return filePath
},
async checkStar () {
const db = await star.find({ key: this.video.key, ids: this.video.info.id })
if (db) {
this.isStar = true
} else {
this.isStar = false
}
},
checkTop () {
this.isTop = this.appState.windowIsOnTop
},
closeListEvent () {
this.right.show = false
this.right.type = ''
},
exportM3u8 () {
const m3u8Arr = []
for (const i of this.right.list) {
const j = i.split('$')
let link, name
if (j.length > 1) {
for (let m = 0; m < j.length; m++) {
if (j[m].indexOf('.m3u8') >= 0 && j[m].startsWith('http')) {
link = j[m]
break
}
}
name = j[0]
} else {
name = `${m3u8Arr.length + 1}`
link = j[0]
}
m3u8Arr.push({
name: name,
link: link
})
}
let m3u8Content = '#EXTM3U'
for (const item of m3u8Arr) {
m3u8Content += `#EXTINF:-1, ${item.name}\n${item.link}`
}
const blob = new Blob([m3u8Content], { type: 'application/vnd.apple.mpegurl' })
const downloadElement = document.createElement('a')
const href = window.URL.createObjectURL(blob)
downloadElement.href = href
downloadElement.download = `${this.name}.m3u8`
document.body.appendChild(downloadElement)
downloadElement.click()
document.body.removeChild(downloadElement)
window.URL.revokeObjectURL(href)
},
clearAllHistory () {
history.clear().then(res => {
this.right.history = []
})
},
listItemEvent (n) {
if (this.video.iptv) {
var channel = this.channelList[n]
// 是直播源,直接播放
this.playChannel(channel)
} else {
this.video.info.time = 0
this.video.info.index = n
this.right.show = false
this.right.type = ''
}
},
historyItemEvent (e) {
this.video = {
key: e.site,
info: {
id: e.ids,
name: e.name,
type: e.type,
year: e.year,
index: e.index,
time: e.time
}
}
this.right.show = false
this.right.type = ''
},
removeHistoryItem (e) {
history.remove(e.id).then(res => {
this.getAllhistory()
}).catch(err => {
this.$message.warning('删除历史记录失败, 错误信息: ' + err)
})
},
async getOtherSites () {
this.right.other = []
const currentSite = await sites.find({ key: this.video.key })
sites.all().then(sitesRes => {
// 排除已关闭的源和当前源
for (const siteItem of sitesRes.filter(x => x.isActive && x.group === currentSite.group && x.key !== this.video.key)) {
zy.search(siteItem.key, this.name).then(searchRes => {
const type = Object.prototype.toString.call(searchRes)
if (type === '[object Array]') {
searchRes.forEach(async item => {
const detailRes = item
detailRes.key = siteItem.key
detailRes.site = siteItem
this.right.other.push(detailRes)
})
}
if (type === '[object Object]') {
const detailRes = searchRes
detailRes.key = siteItem.key
detailRes.site = siteItem
this.right.other.push(detailRes)
}
})
}
})
},
otherEvent (m) {
if (!this.video.iptv) {
this.right.type = 'other'
this.getOtherSites()
this.right.currentTime = this.xg.currentTime
} else {
this.channelListShow = false
this.right.type = 'sources'
}
this.right.show = true
},
async otherItemEvent (e) {
// 打开当前播放的剧集index, 定位到当前的时间
this.video = { key: e.key, info: { id: e.id, name: e.name, site: e.site, index: this.video.info.index, time: this.right.currentTime } }
},
mtEvent () {
setting.find().then(res => {
if (res.shortcut) {
shortcut.all().then(res => {
for (const i of res) {
mt.bind(i.key, () => {
if (this.view === 'Play') {
this.shortcutEvent(i.name)
}
})
}
})
} else {
shortcut.all().then(res => {
for (const i of res) {
mt.unbind(i.key)
}
})
}
})
},
shortcutEvent (e) {
if (e === 'playAndPause') {
if (this.xg) {
if (this.xg.paused) {
this.xg.play()
// 继续播放时,隐藏进度条
win.setProgressBar(-1)
} else {
this.xg.pause()
}
}
return false
}
if (e === 'forward') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime += parseInt(this.setting.forwardTimeInSec)
}
return false
}
if (e === 'back') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime -= parseInt(this.setting.forwardTimeInSec)
}
return false
}
if (e === 'volumeUp') {
if (this.xg && this.xg.volume < 0.9) {
this.xg.volume += 0.1
}
return false
}
if (e === 'volumeDown') {
if (this.xg && this.xg.volume > 0.2) {
this.xg.volume -= 0.1
}
return false
}
if (e === 'mute') {
if (this.xg) {
this.xg.volume = 0
}
return false
}
if (e === 'top') {
if (this.appState.windowIsOnTop) {
win.setAlwaysOnTop(false)
this.appState.windowIsOnTop = false
} else {
win.setAlwaysOnTop(true)
this.appState.windowIsOnTop = true
}
return false
}
if (e === 'fullscreen') {
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
} else {
this.xg.getFullscreen(this.xg.root)
}
return false
}
if (e === 'escape') {
if (this.miniMode) {
this.exitMiniEvent()
}
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
this.xg.exitCssFullscreen()
}
return false
}
if (e === 'next') {
this.nextEvent()
return false
}
if (e === 'prev') {
this.prevEvent()
return false
}
if (e === 'home') {
if (this.xg && !this.xg.paused) {
this.xg.currentTime = 0
}
return false
}
if (e === 'end') {
if (this.xg && !this.xg.paused) {
const endTime = this.xg.duration
this.xg.currentTime = endTime
}
return false
}
if (e === 'opacityUp') {
const num = win.getOpacity()
if (num > 0.1) {
win.setOpacity(num - 0.1)
}
return false
}
if (e === 'opacityDown') {
const num = win.getOpacity()
if (num < 1) {
win.setOpacity(num + 0.1)
}
return false
}
if (e === 'playbackRateUp') {
if (this.xg && !this.xg.paused) {
const rate = this.xg.playbackRate
this.xg.playbackRate = rate + 0.25
this.$message.info('当前播放速度为: ' + this.xg.playbackRate)
}
return false
}
if (e === 'playbackRateDown') {
if (this.xg && !this.xg.paused) {
const rate = this.xg.playbackRate
if (rate > 0.25) {
this.xg.playbackRate = rate - 0.25
this.$message.info('当前播放速度为: ' + this.xg.playbackRate)
}
}
return false
}
if (e === 'mini') {
this.miniEvent()
return false
}
},
changeSetting () {
this.mtEvent()
},
toggleList () {
if (this.state.showList) {
document.querySelector('xg-btn-showlist ul').style.display = 'none'
this.state.showList = false
} else {
this.refreshList()
document.querySelector('xg-btn-showlist ul').style.display = 'block'
this.state.showList = true
}
},
refreshList () {
let ul = document.querySelector('xg-btn-showlist ul')
if (!ul) {
ul = document.createElement('ul')
document.querySelector('xg-btn-showlist').appendChild(ul)
ul.addEventListener('click', (ev) => {
ev = ev || window.event
const target = ev.target || ev.srcElement // target表示在事件冒泡中触发事件的源元素在IE中是srcElement
if (target.nodeName.toLowerCase() === 'li') {
this.listItemEvent(parseInt(target.dataset.index))
}
})
}
ul.style.display = 'none'
let li = ''
if (this.video.iptv) {
// 直播频道列表
let index = 0
this.channelList.forEach(e => {
if (e.prefer === this.video.iptv.id) {
li += `<li class="selected" data-index="${index}" title="${e.name}">${e.name}</li>`
} else {
li += `<li data-index="${index}" title="${e.name}">${e.name}</li>`
}
index += 1
})
} else {
if (this.right.list.length === 0) {
li = '<li>无数据</li>'
} else {
for (let index = 0; index < this.right.list.length; index++) {
const item = this.right.list[index]
const num = item.split('$')
let title
if (num.length > 1) {
title = num[0]
} else {
title = `${(index + 1)}`
}
if (index === this.video.info.index) {
li += `<li class="selected" data-index="${index}" title="${title}">${title}</li>`
} else {
li += `<li data-index="${index}" title="${title}">${title}</li>`
}
}
}
}
ul.innerHTML = li
},
toggleHistory () {
if (this.state.showHistory) {
document.querySelector('xg-btn-showhistory ul').style.display = 'none'
this.state.showHistory = false
} else {
this.refreshHistory()
document.querySelector('xg-btn-showhistory ul').style.display = 'block'
this.state.showHistory = true
}
},
refreshHistory () {
this.getAllhistory()
let ul = document.querySelector('xg-btn-showhistory ul')
if (!ul) {
ul = document.createElement('ul')
document.querySelector('xg-btn-showhistory').appendChild(ul)
ul.addEventListener('click', (ev) => {
ev = ev || window.event
const target = ev.target || ev.srcElement // target表示在事件冒泡中触发事件的源元素在IE中是srcElement
if (target.nodeName.toLowerCase() === 'li') {
this.historyItemEvent(this.right.history[parseInt(target.dataset.index)])
}
})
}
ul.style.display = 'none'
let li = ''
if (this.right.history.length === 0) {
li = '<li>无数据</li>'
} else if (!this.video.iptv) {
window.historyItemEvent = this.historyItemEvent.bind(this)
for (let index = 0; index < this.right.history.length; index++) {
const item = this.right.history[index]
const text = `${item.site}${item.name}${item.index + 1}`
if (this.video.info.id === item.ids) {
li += `<li class="selected" data-index="${index}" title="${text}">${text}</li>`
} else {
li += `<li data-index="${index}" title="${text}">${text}</li>`
}
}
}
ul.innerHTML = li
},
async getChannelList () {
await channelList.all().then(res => {
this.channelList = res.filter(e => e.isActive)
this.channelListForShow = []
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
groups.forEach(g => {
var doc = {
label: g,
children: this.channelList.filter(x => x.group === g).map(i => { return { label: i.name, channel: i } })
}
this.channelListForShow.push(doc)
})
})
},
bindEvent () {
this.xg.on('exitFullscreen', () => {
if (this.miniMode) this.xg.getCssFullscreen()
})
this.xg.on('volumechange', () => {
this.config.volume = this.xg.volume.toFixed(2)
setting.find().then(res => { res.volume = this.config.volume; setting.update(res) })
})
this.xg.on('playNextOne', () => {
this.nextEvent()
})
this.xg.on('playPrev', () => {
this.prevEvent()
})
this.xg.on('showList', () => {
this.toggleList()
})
this.xg.on('showHistory', () => {
this.toggleHistory()
})
this.xg.on('videoStop', () => {
if (this.miniMode) this.exitMiniEvent()
this.videoStop()
})
const ev = ['click', 'touchend', 'mousemove']
let timerID
ev.forEach(item => {
this.xg.root.addEventListener(item, () => {
if (this.xg && this.xg.fullscreen) {
const videoTitle = document.querySelector('.xg-view-videoTitle')
videoTitle.style.display = 'block'
clearTimeout(timerID)
timerID = setTimeout(() => {
// 播放中自动消失
if (this.xg && !this.xg.paused) {
videoTitle.style.display = 'none'
}
}, 3000)
}
})
})
this.xg.on('exitFullscreen', () => {
document.querySelector('.xg-view-videoTitle').style.display = 'none'
})
},
videoStop () {
win.setProgressBar(-1)
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
}
clearInterval(this.timer)
this.video.key = ''
this.xg.src = ''
this.config.src = ''
this.xg.destroy(false)
this.xg = null
this.name = ''
this.right.list = []
this.getAllhistory()
setTimeout(() => {
this.xg = new HlsJsPlayer(this.config)
this.playerInstall()
this.bindEvent()
}, 1000)
},
minMaxEvent () {
win.on('minimize', () => {
if (this.xg && this.xg.hasStart) {
this.xg.pause()
}
})
win.on('restore', () => {
// 不知为何在if clause里直接使用this.xg.hasStart居然就不工作不得其解。
var hasStart = this.xg.hasStart
if (this.xg && hasStart) {
this.xg.play()
}
})
},
playerInstall () {
Player.install('playPrev', function () {
addPlayerBtn.bind(this, 'playPrev', '<svg t="1595866093990" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3657" style="width: 20px;height: 20px;margin-top: 11px;margin-left: 9px;" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M98.583851 3.180124h190.807453a31.801242 31.801242 0 0 1 31.801243 31.801242v387.021118L902.201242 10.176398l11.130435-7.632299A31.801242 31.801242 0 0 1 957.217391 31.801242v960.397516a31.801242 31.801242 0 0 1-43.885714 29.257143l-11.130435-7.632299L321.192547 601.997516V989.018634a31.801242 31.801242 0 0 1-31.801243 31.801242H98.583851a31.801242 31.801242 0 0 1-31.801242-31.801242v-954.037268a31.801242 31.801242 0 0 1 31.801242-31.801242z" p-id="3658" fill="#ffffff"></path></svg>', { title: '上一集' })()
})
Player.install('playNextOne', function () {
addPlayerBtn.bind(this, 'playNextOne', '<svg t="1595866110378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3946" style="width: 20px;height: 20px;margin-top: 11px;margin-left: 0px;" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M925.416149 3.180124h-190.807453a31.801242 31.801242 0 0 0-31.801243 31.801242v387.021118L121.798758 10.176398 110.668323 2.544099A31.801242 31.801242 0 0 0 98.583851 0a31.801242 31.801242 0 0 0-31.801242 31.801242v960.397516a31.801242 31.801242 0 0 0 31.801242 31.801242 31.801242 31.801242 0 0 0 12.084472-2.544099l11.130435-7.632299L702.807453 601.997516V989.018634a31.801242 31.801242 0 0 0 31.801243 31.801242h190.807453a31.801242 31.801242 0 0 0 31.801242-31.801242v-954.037268a31.801242 31.801242 0 0 0-31.801242-31.801242z" p-id="3947" fill="#ffffff"></path></svg>', { title: '下一集' })()
})
Player.install('videoStop', function () {
addPlayerBtn.bind(this, 'videoStop', '<svg t="1603093629102" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3621" style="width: 25px;height: 25px;margin-top: 8px;margin-left: 0px;"><path d="M768 768H256V256h512v512z" p-id="3622" fill="#ffffff"></path></svg>', { title: '停止播放' })()
})
Player.install('showList', function () {
addPlayerBtn.bind(this, 'showList', '<svg t="1595866128681" class="icon" viewBox="0 0 1316 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4187" style="width: 22px;height: 22px;margin-top: 9px;margin-left: 6px;" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M0 0h1316.571429v146.285714H0zM0 438.857143h1316.571429v146.285714H0zM0 877.714286h1316.571429v146.285714H0z" p-id="4188" fill="#ffffff"></path></svg>', { title: '播放列表' })()
})
Player.install('showHistory', function () {
addPlayerBtn.bind(this, 'showHistory', '<svg t="1595866015473" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3282" style="width: 22px;height: 22px;margin-top: 9px;margin-left: 6px;" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 910.222222a398.222222 398.222222 0 1 1 398.222222-398.222222 398.222222 398.222222 0 0 1-398.222222 398.222222z" p-id="3283" fill="#ffffff"></path><path d="M568.888889 227.555556h-113.777778v341.333333h227.555556v-113.777778h-113.777778V227.555556z" p-id="3284" fill="#ffffff"></path></svg>', { title: '播放历史' })()
})
const that = this
Player.install('videoTitle', function () {
let title
if (that.right.list.length > 1) {
title = `『第 ${that.video.info.index + 1} 集』${that.name}`
} else {
title = `${that.name}`
}
addPlayerView.bind(this, 'videoTitle', `<span>${title}</span>`, {})()
})
},
showShortcutEvent () {
this.right.show = !this.right.show
shortcut.all().then(res => {
this.right.type = 'shortcut'
this.right.shortcut = res
})
}
},
created () {
this.getAllhistory()
this.getChannelList()
this.mtEvent()
},
async mounted () {
const db = await setting.find()
this.playerInstall()
this.config.volume = db.volume ? db.volume : 0.6
this.xg = new HlsJsPlayer(this.config)
this.bindEvent()
this.minMaxEvent()
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
<style>
.xgplayer-skin-default .xgplayer-live {
width: 100px;
position: absolute;
top:50%;
left:50%;
transform: translate(-50%, -50%);
font-size: 18px !important;
}
.xgplayer-skin-default .xg-btn-playPrev,
.xgplayer-skin-default .xg-btn-playNextOne,
.xgplayer-skin-default .xg-btn-showList,
.xgplayer-skin-default .xg-btn-showHistory,
.xgplayer-skin-default .xg-btn-videoStop {
width: 32px;
position: relative;
-webkit-order: 0;
-moz-box-ordinal-group: 1;
order: 0;
display: block;
cursor: pointer;
margin-left: 3px;
}
.xgplayer-skin-default .xg-btn-playPrev:hover,
.xgplayer-skin-default .xg-btn-playNextOne:hover,
.xgplayer-skin-default .xg-btn-showList:hover,
.xgplayer-skin-default .xg-btn-showHistory:hover,
.xgplayer-skin-default .xg-btn-videoStop:hover {
opacity: 0.8;
}
.xgplayer-skin-default .xg-btn-playNextOne {
order: 2;
}
.xgplayer-skin-default .xgplayer-play, .xgplayer-skin-default .xgplayer-play-img {
order: 1 !important;
}
.xgplayer-skin-default .xg-btn-videoStop {
order: 2;
}
.xgplayer-skin-default .xg-btn-showList {
order: 4;
}
.xgplayer-skin-default .xg-btn-showHistory {
order: 4;
}
.xgplayer-skin-default .xg-btn-showList ul, .xgplayer-skin-default .xg-btn-showHistory ul {
display: none;
list-style: none;
min-width: 85px;
max-width: 300px;
max-height: 60vh;
overflow-y: scroll;
background: rgba(0,0,0,.54);
border-radius: 1px;
position: absolute;
bottom: 45px;
left: 50%;
transform: translateX(-50%);
text-align: left;
white-space: nowrap;
z-index: 26;
cursor: pointer;
}
.xgplayer-skin-default .xg-btn-showList ul li, .xgplayer-skin-default .xg-btn-showHistory ul li {
opacity: .7;
font-family: PingFangSC-Regular;
font-size: 13px;
color: hsla(0,0%,100%,.8);
position: relative;
padding: 5px;
text-align: center;
}
.xgplayer-skin-default .xg-btn-showList ul li:first-child, .xgplayer-skin-default .xg-btn-showHistory ul li:first-child {
position: relative;
margin-top: 12px;
}
.xgplayer-skin-default .xg-btn-showList ul li:last-child, .xgplayer-skin-default .xg-btn-showHistory ul li:last-child {
margin-bottom: 12px;
}
.xgplayer-skin-default .xg-btn-showList ul li.selected, .xgplayer-skin-default .xg-btn-showHistory ul li.selected, .xgplayer-skin-default .xg-btn-showList ul li:hover, .xgplayer-skin-default .xg-btn-showHistory ul li:hover {
color: #fff;
opacity: 1;
}
.xgplayer-skin-default .xgplayer-volume {
width: 32px !important;
}
.xgplayer-skin-default .xgplayer-playbackrate {
width: 40px !important;
}
.xgplayer-skin-default .xgplayer-playbackrate .name {
top: 10px !important;
}
.xgplayer-skin-default .xgplayer-playbackrate ul {
bottom: 25px;
}
.xgplayer-skin-default .xgplayer-playbackrate ul li {
font-size: 13px !important;
}
.xgplayer-skin-default .xgplayer-screenshot .name span {
width: 40px !important;
}
.xgplayer-skin-default .xg-view-videoTitle {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
height: 40px;
padding-left: 10px;
background-image: linear-gradient(180deg,rgba(0,0,0,.75),rgba(0,0,0,.75),rgba(0,0,0,.37),transparent);
z-index: 10;
}
.xgplayer-skin-default .xg-view-videoTitle span {
font-size: 16px;
line-height: 40px;
color: #ffffff;
}
</style>
<style lang="scss" scoped>
.play{
position: relative;
height: calc(100% - 40px);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
.box{
width: 100%;
height: 100%;
display: flex;
border-radius: 5px;
align-items: center;
justify-content: center;
flex-direction: column;
.title{
width: 100%;
height: 40px;
line-height: 40px;
padding: 0 10px;
.right {
float: right;
svg {
margin-top: 8px;
cursor: pointer;
}
}
}
.player{
width: 100%;
flex: 1;
padding: 0 10px;
overflow: hidden;
}
.more{
width: 100%;
height: 50px;
min-height: 50px;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 10px;
span{
display: flex;
margin-right: 10px;
cursor: pointer;
}
}
}
.list{
position: absolute;
top: 0;
right: 0;
width: 300px;
height: 100%;
z-index: 555;
border-radius: 3px;
padding: 6px;
display: flex;
flex-direction: column;
.el-tree{
background-color: inherit;
}
.list-top{
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
.list-top-title{
font-size: 16px;
}
.list-top-close{
display: inline-block;
cursor: pointer;
}
}
.list-body{
flex: 1;
overflow-y: auto;
ul{
margin: 0;
padding: 0;
list-style: none;
li{
position: relative;
height: 28px;
width: 100%;
line-height: 28px;
padding-left: 10px;
font-size: 14px;
cursor: pointer;
display: flex;
.title{
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 231px;
}
.btn{
display: inline-block;
}
.detail-delete{
display: none;
position: absolute;
right: 0;
height: 28px;
width: 50px;
text-align: center;
}
}
}
}
}
.slideX-enter-active, .slideX-leave-active{
transition: all .5s ease-in-out;
}
.slideX-enter, .slideX-leave-to{
transform: translateX(100%);
opacity: 0;
}
}
</style>