Compare commits

...

84 Commits

Author SHA1 Message Date
haiyangcui
b580ae4329 记录最近打开detail页面的视图,当回到该视图时,恢复detail页面 2021-02-21 22:55:31 +01:00
haiyangcui
8ae32f0f55 v2.7.5 2021-02-21 21:49:39 +01:00
haiyangcui
713ffa6b3e 从gitee上获取推荐影视列表 2021-02-21 21:39:56 +01:00
haiyangcui
a3bc8f1f31 当重置或源站列表为空时,从云端获取源站 2021-02-21 21:32:58 +01:00
haiyangcui
22318f601b 修复播放速率显示信息位置错误的问题 2021-02-21 21:15:56 +01:00
buvta
aa8b4b527d 修复历史兼容导入m3u8List 2021-02-21 23:03:05 +08:00
haiyangcui
7bee4df2f9 默认使用gitee上的源站文件 2021-02-21 12:49:57 +01:00
haiyangcui
7a72b352c0 v2.7.4 2021-02-21 09:03:34 +01:00
haiyangcui
f924c6979b Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player 2021-02-20 18:31:53 +01:00
haiyangcui
ef2740d801 降级qrcode.vue和saas模块 2021-02-20 18:31:36 +01:00
haiyangcui
0650e868eb 降级qrcode.vue 2021-02-20 18:24:56 +01:00
haiyangcui
573f026207 升级模块 2021-02-20 17:46:21 +01:00
haiyangcui
f9504439b0 使用axios get 无需使用post 2021-02-20 17:24:06 +01:00
haiyangcui
59f9772e82 支持从默认Github源或自定义在线源站文件重置源 2021-02-20 10:46:15 +01:00
buvta
2db48c9220 Merge branch 'master' into master 2021-02-09 23:55:26 +08:00
buvta
63c6ad0ec0 修复分享地址未正确生成的bug 2021-02-05 23:21:25 +08:00
buvta
730181e4bc 解析播放时隐藏部分菜单 2021-02-05 23:09:38 +08:00
buvta
7d61bb04a7 修复全新使用时不含源时产生的bug 2021-02-04 17:59:30 +08:00
buvta
613901872e v2.7.3 2021-02-04 16:40:08 +08:00
buvta
ef3264f3db 应用不再内置源 2021-02-04 16:28:07 +08:00
Hunlongyu
56d5858e0f Update README.md 2021-02-03 14:40:57 +08:00
buvta
3f4da7806a 功能重复,zy.search已包含过滤功能 2021-01-27 22:30:50 +08:00
haiyangcui
00aa3c8408 过滤搜索结果,只返回名字中包含搜索关键词的结果 2021-01-27 11:49:12 +01:00
buvta
a9d889f7d8 再次调整 2021-01-27 18:27:12 +08:00
buvta
b946423b65 修复精简模式切换播放器后停止时窗口大小异常 2021-01-26 19:25:10 +08:00
buvta
f2c87a7880 优化搜索结果过滤器,避免屏蔽福利时筛选为空 2021-01-25 19:52:32 +08:00
buvta
0ecc1367d2 优化搜索,过滤掉异常结果 2021-01-25 17:15:04 +08:00
Hunlongyu
9673303fe9 增加友链 2021-01-25 09:44:02 +08:00
Hunlongyu
88582b45b3 升级依赖 2021-01-25 09:32:01 +08:00
buvta
443cdc59fc 停止时默认生成hls播放器,使停止播放mp4时“播放”可用 2021-01-24 15:34:55 +08:00
buvta
c08ae7666c 优化音量保存策略 2021-01-24 15:25:13 +08:00
buvta
6bfeb9fcd8 修复历史清空按钮显示 2021-01-22 19:00:58 +08:00
buvta
e3bc519128 历史导入兼容老版数据m3u8List 2021-01-22 18:52:21 +08:00
buvta
504b11ceec 修复频道搜索功能 2021-01-22 18:40:38 +08:00
buvta
d281a2adab 再次调整直播自动换源 2021-01-22 17:43:52 +08:00
buvta
4e0f73a3de 再次调整黑屏操作 2021-01-22 17:05:44 +08:00
buvta
3a78425e7f 修复频道prefer源被删除导致的bug 2021-01-22 16:47:01 +08:00
buvta
a38b9aed56 优化直播卡顿自动切换 2021-01-21 23:40:26 +08:00
buvta
ec4980a4b6 黑屏有声音时尽量避免手动操作 2021-01-21 22:28:12 +08:00
buvta
2c93d755c7 Revert "修复play-request-was-interrupted报错",重新发布
This reverts commit 088cd70e41.
2021-01-21 18:16:25 +08:00
buvta
fe75084dd0 v2.7.2 2021-01-21 17:19:39 +08:00
buvta
047224ce80 修复直播时触发timeupdate导致的异常 2021-01-21 17:11:46 +08:00
buvta
31ea52e267 为应对flv切换到其它格式时可能出现的黑屏有声音情况,新增的关闭按钮常显 2021-01-21 16:11:52 +08:00
buvta
088cd70e41 修复play-request-was-interrupted报错 2021-01-21 15:47:02 +08:00
buvta
6bfd96942d 切换频道首尾部自动轮替 2021-01-21 15:44:28 +08:00
buvta
5d6579326c 频道所有源全部卡顿时自动换台 2021-01-21 15:36:33 +08:00
buvta
334933ce82 直播卡顿时自动换源 2021-01-20 23:22:23 +08:00
buvta
9fd3e5e2ed 修复flv直播停止功能 2021-01-20 22:22:46 +08:00
buvta
1e7199af3f 修复改动flv直播状态导致的bug 2021-01-20 18:23:10 +08:00
buvta
0d83b277e7 下移播放器速率栏,避免鼠标移动时误触进度条 2021-01-19 23:34:12 +08:00
buvta
08cbd1a73c 修正某些源站剧集顺序 2021-01-19 22:34:50 +08:00
buvta
01e58f458b 修复zy.download因分隔符导致的bug 2021-01-19 20:26:53 +08:00
buvta
3ac21ad1e5 更新7k源解析接口地址 2021-01-19 19:54:40 +08:00
buvta
f098198448 可通过重置更新应用默认解析接口地址 2021-01-19 19:54:07 +08:00
buvta
35e6e59b73 添加应用默认解析接口设置 2021-01-19 17:33:39 +08:00
buvta
bf9eaf09eb 调整解析接口默认打开方式 2021-01-19 17:28:55 +08:00
buvta
03ec267c1f 修改jiexiURL应对源自带解析的情况 2021-01-19 16:29:07 +08:00
buvta
97aea7b98d 修复因分隔符"$"改动引入的bug 2021-01-19 16:22:02 +08:00
buvta
f8ad00b4d0 zy.detail获取到的链接分隔符包含多个"$", 88资源站有点怪 2021-01-18 22:30:03 +08:00
buvta
607d4baae4 修复使图片无法加载时分享可用 2021-01-18 21:07:57 +08:00
buvta
51208892d0 修复zy.detail获取到的链接不包含"$"时产生的bug 2021-01-18 21:07:42 +08:00
buvta
516b791719 再次调整直播源检测跳过功能 2021-01-18 18:44:29 +08:00
buvta
8032645c25 修复调整生成调试信息时引入的bug 2021-01-18 18:33:15 +08:00
Hunlongyu
9d0536a3f8 升级依赖, 解决 GitHub 报错问题 2021-01-18 10:38:01 +08:00
Hunlongyu
85a39d67ce 修改第三方播放路径为 Gitee. 2021-01-18 10:28:01 +08:00
Hunlongyu
3184147910 升级依赖, 修复分享图片和二维码失效的问题 2021-01-18 10:09:18 +08:00
buvta
9d8a09e90d 修复直播源检测跳过功能 2021-01-17 23:27:56 +08:00
buvta
5d08e715aa 使用代理时可检测flv直播源 2021-01-17 14:55:39 +08:00
buvta
98378788fd 直播源检测支持flv格式,使用代理时暂不可用 2021-01-16 17:20:19 +08:00
buvta
af768d527a 直播支持flv格式,播放器停止功能待修复 2021-01-15 22:12:55 +08:00
buvta
4e5aab6f66 调整播放调试生成信息 2021-01-03 22:49:17 +08:00
buvta
fda305a8ae Film搜索亦过滤 2021-01-03 00:14:18 +08:00
buvta
21487e2754 Film添加列表时过滤掉无链接的项 2021-01-02 23:58:00 +08:00
buvta
0f934413d0 再次调整axios超时设置 2021-01-02 21:38:56 +08:00
buvta
96c68da8b7 有些直播源网址路径不含m3u8而是以参数带m3u8结尾 2021-01-02 21:15:17 +08:00
buvta
0e0de8f23c 修正关闭内嵌播放器bug 2021-01-02 16:01:30 +08:00
buvta
2b24ac7d0c 手动修正变量定义var 2021-01-02 15:05:24 +08:00
buvta
458144a6ea 修正清理缓存描述 2021-01-02 14:58:24 +08:00
buvta
69113c7a9a 更新下载完毕后通知 2021-01-01 22:55:14 +08:00
buvta
d247a2fa23 分享在详情页可选定集数,播放页面分享为当前集,其它页面分享默认首集 2021-01-01 22:50:33 +08:00
buvta
c14c78b68f Star添加DetailCache 2021-01-01 15:46:54 +08:00
buvta
cc4e2c5cad 历史页面禁止拖动 2021-01-01 15:24:24 +08:00
buvta
073e111af8 嵌入播放时添加关闭按钮 2021-01-01 15:19:24 +08:00
buvta
f134785696 修改请求referer 2020-12-29 16:02:50 +08:00
25 changed files with 981 additions and 3912 deletions

View File

@@ -42,9 +42,8 @@
### 🌴 下载
- 🍓 [Github -- 官方下载](https://github.com/Hunlongyu/ZY-Player/releases)
- 🍉 [蓝奏云 -- 快速下载](https://www.lanzoux.com/b04s6a3re) 密码:95px
- 🍒 适用于32位操作系统的x86软件,在蓝奏云网盘里, 后缀名: ZY Player * 32位.exe
- 🎃 软件暂时关闭下载通道. 请大家支持正版.
- 🎭 所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.
### 🎠 平台

View File

@@ -236,6 +236,7 @@
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="#">Home</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://www.ghpym.com/zyplayer.html">果核剥壳</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://www.iplaysoft.com/zy-player.html">异次元软件世界</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://xydh.fun/">炫辕</a></li>
</ul>
</div>

View File

@@ -1,6 +1,6 @@
{
"name": "zy",
"version": "2.7.1-HappyNewYear",
"version": "2.7.5",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -19,54 +19,54 @@
"dependencies": {
"axios": "^0.21.1",
"cheerio": "^1.0.0-rc.5",
"core-js": "^3.8.1",
"core-js": "^3.9.0",
"dexie": "^3.0.3",
"electron-localshortcut": "^3.2.1",
"electron-proxy-agent": "^1.2.0",
"electron-updater": "^4.3.5",
"element-ui": "^2.14.1",
"fast-xml-parser": "^3.17.5",
"element-ui": "^2.15.0",
"fast-xml-parser": "^3.18.0",
"html2canvas": "^1.0.0-rc.7",
"iptv-playlist-parser": "^0.5.1",
"iptv-playlist-parser": "^0.6.0",
"m3u": "0.0.2",
"m3u8-parser": "^4.5.0",
"m3u8-parser": "^4.5.2",
"memcached": "^2.2.2",
"modern-normalize": "^1.0.0",
"mousetrap": "^1.6.5",
"pinyin-match": "^1.1.7",
"pinyin-match": "^1.2.0",
"qrcode.vue": "^1.7.0",
"randomstring": "^1.1.5",
"session": "^0.1.0",
"sortablejs": "^1.12.0",
"sortablejs": "^1.13.0",
"v-fit-columns": "^0.2.0",
"vue": "^2.6.12",
"vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.1.0",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.0",
"xgplayer": "^2.16.0",
"xgplayer-flv.js": "^2.1.2",
"xgplayer-hls.js": "^2.3.0"
"vuex": "^3.6.2",
"xgplayer": "^2.18.0",
"xgplayer-flv.js": "^2.2.0",
"xgplayer-hls.js": "^2.4.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.9",
"@vue/cli-plugin-eslint": "~4.5.9",
"@vue/cli-plugin-vuex": "~4.5.9",
"@vue/cli-service": "~4.5.9",
"@vue/cli-plugin-babel": "~4.5.11",
"@vue/cli-plugin-eslint": "~4.5.11",
"@vue/cli-plugin-vuex": "~4.5.11",
"@vue/cli-service": "~4.5.11",
"@vue/eslint-config-standard": "^6.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"electron": "^11.1.1",
"electron": "^11.3.0",
"electron-devtools-installer": "^3.1.1",
"eslint": "^7.16.0",
"eslint": "^7.20.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^7.3.0",
"eslint-plugin-vue": "^7.6.0",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"vue-cli-plugin-electron-builder": "2.0.0-rc.5",
"vue-cli-plugin-electron-builder": "2.0.0-rc.6",
"vue-template-compiler": "^2.6.12"
}
}

View File

@@ -167,6 +167,21 @@
}
}
.box{
.title{
.right{
color: var(--d-fc-2);
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
}
}
.more{
span{
svg{

View File

@@ -161,6 +161,21 @@
}
}
.box{
.title{
.right{
color: var(--g-fc-2);
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
}
}
.more{
span{
svg{

View File

@@ -161,6 +161,21 @@
}
}
.box{
.title{
.right{
color: var(--l-fc-2);
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
}
}
.more{
span{
svg{

View File

@@ -161,6 +161,21 @@
}
}
.box{
.title{
.right{
color: var(--p-fc-2);
svg{
stroke: var(--p-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--p-c-8);
stroke-width: 1.5;
fill: var(--p-c-2);
}
}
}
}
.more{
span{
svg{

View File

@@ -39,7 +39,7 @@ function createWindow () {
// 修改request headers
// Sec-Fetch下禁止修改浏览器自动加上请求头 https://www.cnblogs.com/fulu/p/13879080.html 暂时先用index.html的meta referer policy替代
const filter = {
urls: ['http://*/*', 'http://*/*']
urls: ['http://*/*', 'http://*/*']
}
win.webContents.session.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
const url = new URL(details.url)
@@ -47,7 +47,7 @@ function createWindow () {
if (!details.url.includes('//localhost') && details.requestHeaders.Referer && details.requestHeaders.Referer.includes('//localhost')) {
details.requestHeaders.Referer = url.origin
}
callback({
callback({ // https://github.com/electron/electron/issues/23988 回调似乎无法修改headers暂时先用index.html的meta referer policy替代
cancel: false,
requestHeaders: details.requestHeaders
})

View File

@@ -53,6 +53,12 @@
import { mapMutations } from 'vuex'
export default {
name: 'Aside',
data () {
return {
lastViewOpenDetail: '',
savedDetail: {}
}
},
computed: {
view: {
get () {
@@ -74,9 +80,19 @@ export default {
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL']),
changeView (e) {
// 记录打开detail的view
if (this.detail.show === true) {
this.lastViewOpenDetail = this.view
this.savedDetail = this.detail
}
this.view = e
this.detail = {
show: false
// 如果回到上一次打开detail的试图页面,恢复detail页面
if (e === this.lastViewOpenDetail) {
this.detail = this.savedDetail
} else {
this.detail = {
show: false
}
}
}
}

View File

@@ -36,7 +36,7 @@
</div>
</div>
<div class="operate">
<span @click="playEvent(0)">播放</span>
<span @click="playEvent(selectedEpisode)">播放</span>
<span @click="starEvent">收藏</span>
<span @click="downloadEvent">下载</span>
<span @click="shareEvent">分享</span>
@@ -61,7 +61,7 @@
</div>
<div class="m3u8">
<div class="box">
<span v-for="(i, j) in videoList" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
<span v-bind:class="{ selected: j === selectedEpisode }" v-for="(i, j) in videoList" :key="j" @click="playEvent(j)" @mouseenter="() => { selectedEpisode = j }">{{ i | ftName(j) }}</span>
</div>
</div>
</div>
@@ -87,14 +87,19 @@ export default {
videoFullList: [],
info: {},
playOnline: false,
selectedEpisode: 0, // 选定集数
selectedOnlineSite: '哔嘀',
onlineSites: ['哔嘀', '素白白', '简影', '极品', '喜欢看', '1080影视']
}
},
filters: {
ftName (e) {
const name = e.split('$')[0]
return name
ftName (e, n) {
const num = e.split('$')
if (num.length > 1) {
return e.split('$')[0]
} else {
return `${(n + 1)}`
}
}
},
computed: {
@@ -256,7 +261,8 @@ export default {
this.share = {
show: true,
key: this.detail.key,
info: this.info
info: this.info,
index: this.selectedEpisode
}
},
doubanLinkEvent () {
@@ -276,7 +282,10 @@ export default {
const id = this.detail.info.ids || this.detail.info.id
const cacheKey = this.detail.key + '@' + id
const db = await history.find({ site: this.detail.key, ids: id })
if (db) this.videoFlag = db.videoFlag
if (db) {
this.videoFlag = db.videoFlag
this.selectedEpisode = db.index
}
if (!this.DetailCache[cacheKey]) {
this.DetailCache[cacheKey] = await zy.detail(this.detail.key, id)
}

View File

@@ -95,7 +95,7 @@
<el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/>
</el-form-item>
<el-form-item label="解析接口" prop='jiexiUrl'>
<el-input v-model="siteInfo.jiexiUrl" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址可以空着"/>
<el-input v-model="siteInfo.jiexiUrl" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址默认源自带解析,若要调用应用默认解析接口请输入默认或default"/>
</el-form-item>
<el-form-item label="分组" prop='group'>
<el-select v-model="siteInfo.group" allow-create filterable default-first-option placeholder="请输入分组">
@@ -119,7 +119,6 @@ import { mapMutations } from 'vuex'
import { sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs'
import Sortable from 'sortablejs'
@@ -173,9 +172,9 @@ export default {
},
getFilters () {
const groups = [...new Set(this.sites.map(site => site.group))]
var filters = []
const filters = []
groups.forEach(g => {
var doc = {
const doc = {
text: g,
value: g
}
@@ -327,8 +326,8 @@ export default {
if (!this.checkSiteKey()) {
return false
}
var randomstring = require('randomstring')
var doc = {
const randomstring = require('randomstring')
const doc = {
key: this.dialogType === 'edit' ? this.siteInfo.key : this.siteInfo.key ? this.siteInfo.key : randomstring.generate(6),
id: this.dialogType === 'edit' ? this.siteInfo.id : this.sites.length ? this.sites[this.sites.length - 1].id + 1 : 1,
name: this.siteInfo.name,
@@ -387,7 +386,7 @@ export default {
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(ele => {
if (ele.api && this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.api === ele.api).length === 0) {
@@ -410,13 +409,23 @@ export default {
})
},
resetSitesEvent () {
this.stopFlag = true
if (this.checkAllSitesLoading) {
this.$message.info('部分检测还未完全终止, 请稍等...')
return
let url = this.setting.sitesDataURL
if (!url) {
// 如果没有设置源站文件链接,使用默认的gitee源
url = 'https://gitee.com/cuiocean/ZY-Player-Resources/raw/main/Sites/Sites.json'
}
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
this.$message.success('重置源成功')
const axios = require('axios')
axios.get(url).then(res => {
if (res.status === 200) {
if (res.data.length > 0) {
sites.clear().then(sites.bulkAdd(res.data))
this.$message.success('重置源成功')
this.getSites()
}
}
}).catch(error => {
this.$message.error('导入云端源站失败. ' + error)
})
},
moveToTopEvent (i) {
if (this.checkAllSitesLoading) {
@@ -436,7 +445,7 @@ export default {
sites.add(row)
},
resetId (inArray) {
var id = 1
let id = 1
inArray.forEach(ele => {
ele.id = id
id += 1
@@ -446,7 +455,7 @@ export default {
// 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据
this.syncTableData()
sites.clear().then(res => {
var id = 1
let id = 1
this.sites.forEach(ele => {
ele.id = id
id += 1
@@ -467,7 +476,7 @@ export default {
return false
}
const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody')
var _this = this
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.sites.splice(oldIndex, 1)[0]

View File

@@ -457,6 +457,7 @@ export default {
},
searchContents: {
handler (list) {
list = list.filter(res => !this.setting.excludeR18Films || !this.containsR18Keywords(res.type))
this.areas = [...new Set(list.map(ele => ele.area))].filter(x => x)
this.searchClassList = [...new Set(list.map(ele => ele.type))].filter(x => x)
this.refreshFilteredList()
@@ -544,7 +545,7 @@ export default {
return a.localeCompare(b, 'zh')
},
dateFormat (row, column) {
var date = row[column.property]
const date = row[column.property]
if (date === undefined) {
return ''
}
@@ -620,7 +621,7 @@ export default {
// 屏蔽主分类
const classToHide = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫']
zy.class(key).then(res => {
var allClass = [{ name: '最新', tid: 0 }]
const allClass = [{ name: '最新', tid: 0 }]
res.class.forEach(element => {
if (!this.setting.excludeRootClasses || !classToHide.includes(element.name)) {
if (this.setting.excludeR18Films) {
@@ -640,7 +641,7 @@ export default {
})
},
containsR18Keywords (name) {
var containKeyWord = false
const containKeyWord = false
if (!name) {
return containKeyWord
}
@@ -656,12 +657,12 @@ export default {
infiniteHandler ($state) {
const key = this.site.key
const typeTid = this.type.tid
var page = this.pagecount
let page = this.pagecount
if (this.toFlipPagecount()) {
page = this.totalpagecount - this.pagecount + 1
}
this.statusText = ' '
if (key === undefined || page < 1 || typeTid === undefined) {
if (key === undefined || page < 1 || page > this.totalpagecount || typeTid === undefined) {
$state.complete()
this.statusText = '暂无数据'
return false
@@ -675,10 +676,9 @@ export default {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Undefined]') {
$state.complete()
}
if (type === '[object Array]') {
// 过滤掉无链接的项
res = res.filter(e => e.dl.dd && (e.dl.dd._t || (Object.prototype.toString.call(e.dl.dd) === '[object Array]' && e.dl.dd.some(i => i._t))))
if (!this.toFlipPagecount()) {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
@@ -686,9 +686,10 @@ export default {
// 如果是需要解析的视频网站zy.list已经是按从新到旧排列
this.list.push(...res)
}
}
if (type === '[object Object]') {
this.list.push(res)
} else if (type === '[object Object]') {
if (res.dl.dd && (res.dl.dd._t || (Object.prototype.toString.call(res.dl.dd) === '[object Array]' && res.dl.dd.some(e => e._t)))) {
this.list.push(res)
}
}
$state.loaded()
// 更新缓存数据
@@ -759,8 +760,8 @@ export default {
})
},
querySearch (queryString, cb) {
var searchList = this.searchList.slice(0, -1)
var results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
const searchList = this.searchList.slice(0, -1)
const results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
// 调用 callback 返回建议列表的数据
cb(results)
},
@@ -818,20 +819,24 @@ export default {
zy.detail(site.key, element.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
}
}).finally(() => { count++; if (count === res.length) { this.siteSearchCount++; this.statusText = '暂无数据' } })
})
} else if (type === '[object Object]') {
zy.detail(site.key, res.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
}
}).finally(() => { this.siteSearchCount++; this.statusText = '暂无数据' })
} else if (res === undefined) {
this.siteSearchCount++
@@ -841,6 +846,10 @@ export default {
}).catch(() => { this.siteSearchCount++; if (this.searchGroup === '站内') this.$message.error('本次查询状态异常,未获取到数据!') })
})
},
isValidSearchResult (detailRes) {
return detailRes.dl.dd && (detailRes.dl.dd._t || (Object.prototype.toString.call(detailRes.dl.dd) === '[object Array]' &&
detailRes.dl.dd.some(i => i._t)))
},
searchAndRecord () {
this.addSearchRecord()
this.searchEvent()

View File

@@ -111,7 +111,6 @@
import { mapMutations } from 'vuex'
import { history, sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import Sortable from 'sortablejs'
import { remote } from 'electron'
import fs from 'fs'
import Waterfall from 'vue-waterfall-plugin'
@@ -212,6 +211,7 @@ export default {
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.history
this.multipleSelection.forEach(e => history.remove(e.id))
this.multipleSelection = []
this.getAllhistory()
this.updateDatabase()
},
@@ -278,8 +278,14 @@ export default {
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(record => {
if (record.detail && record.detail.m3u8List) {
record.detail.fullList = [].concat({ flag: 'm3u8', list: record.detail.m3u8List })
delete record.detail.m3u8List
}
})
history.bulkAdd(json).then(res => {
this.$message.success('导入成功')
this.getAllhistory()
@@ -299,7 +305,7 @@ export default {
})
},
getSiteName (key) {
var site = this.sites.find(e => e.key === key)
const site = this.sites.find(e => e.key === key)
if (site) {
return site.name
}
@@ -313,7 +319,7 @@ export default {
},
updateDatabase () {
history.clear().then(res => {
var id = length
let id = length
this.history.forEach(ele => {
ele.id = id
id -= 1
@@ -321,21 +327,8 @@ export default {
})
})
},
rowDrop () {
if (!document.getElementById('history-table')) return
const tbody = document.getElementById('history-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.history.splice(oldIndex, 1)[0]
_this.history.splice(newIndex, 0, currRow)
_this.updateDatabase()
}
})
},
updateViewMode () {
if (this.setting.historyViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.refresh() }, 700)
@@ -358,7 +351,6 @@ export default {
}
},
mounted () {
if (this.setting.historyViewMode === 'table') setTimeout(() => { this.rowDrop() }, 100)
addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
addEventListener('resize', () => {

View File

@@ -168,9 +168,9 @@ export default {
},
getFilters () {
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
var filters = []
const filters = []
groups.forEach(g => {
var doc = {
const doc = {
text: g,
value: g
}
@@ -257,7 +257,7 @@ export default {
},
mergeChannel () {
if (this.inputContent && this.multipleSelection.length) {
var channels = []
let channels = []
const id = this.multipleSelection[0].id
this.multipleSelection.forEach(ele => {
channels = channels.concat(ele.channels)
@@ -274,8 +274,13 @@ export default {
if (e.url) {
this.video = { iptv: e }
} else {
const prefer = e.prefer ? e.channels.find(c => c.id === e.prefer) : e.channels.filter(c => c.isActive)[0]
if (!prefer) return
let prefer
if (e.prefer) prefer = e.channels.find(c => c.id === e.prefer)
if (!prefer) prefer = e.channels.filter(c => c.isActive)[0]
if (!prefer) {
this.$message.error('当前频道所有源已全部停用,不可播放!')
return
}
this.video = { iptv: prefer }
}
this.view = 'Play'
@@ -298,6 +303,7 @@ export default {
ele.channels.splice(ele.channels.findIndex(e => e.id === row.id), 1)
channelList.remove(row.channelID)
if (ele.channels.length) {
if (ele.prefer === row.id) delete ele.prefer
if (ele.channels.length === 1) ele.hasChildren = false
channelList.add(ele)
this.$set(this.$refs.iptvTable.store.states.lazyTreeNodeMap, ele.id, ele.channels)
@@ -320,7 +326,7 @@ export default {
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (result.filePath.endsWith('m3u')) {
var writer = require('m3u').extendedWriter()
const writer = require('m3u').extendedWriter()
this.iptvList.forEach(e => {
writer.file(e.url, -1, e.name)
})
@@ -360,11 +366,12 @@ export default {
const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist)
const supportFormats = /\.(m3u8|flv)$/
result.items.forEach(ele => {
const urls = ele.url.split('#').filter(e => e.startsWith('http')) // 网址带#时自动分割
urls.forEach(url => {
if (ele.name && url && new URL.URL(url).pathname.endsWith('.m3u8')) { // 网址可能带参数
var doc = {
if (ele.name && url && (supportFormats.test(url) || supportFormats.test(new URL.URL(url).pathname))) { // 网址可能带参数
const doc = {
id: id,
name: ele.name,
url: url,
@@ -438,12 +445,12 @@ export default {
res = res.filter(o => !this.iptvList.find(e => o.url === e.url))
const resClone = JSON.parse(JSON.stringify(res))
const uniqueChannelName = {}
for (var i = 0; i < resClone.length; i++) {
var channelName = resClone[i].name.trim().replace(/[- ]?(1080p|蓝光|超清|高清|标清|hd|cq|4k)(\d{1,2})?$/i, '')
for (let i = 0; i < resClone.length; i++) {
let channelName = resClone[i].name.trim().replace(/[- ]?(1080p|蓝光|超清|高清|标清|hd|cq|4k)(\d{1,2})?$/i, '')
if (channelName.match(/cctv/i)) channelName = channelName.replace('-', '')
if (Object.keys(uniqueChannelName).some(name => channelName.match(new RegExp(`${name}(1080p|4k|(?!\\d))`, 'i')))) continue // 避免重复
const matchRule = new RegExp(`${channelName}(1080p|4k|(?!\\d))`, 'i')
for (var j = i; j < resClone.length; j++) {
for (let j = i; j < resClone.length; j++) {
if (resClone[j].name.match(/cctv/i)) {
resClone[j].name = resClone[j].name.replace('-', '')
}
@@ -513,7 +520,7 @@ export default {
})
},
resetId (channelList) {
var id = 1
let id = 1
channelList.forEach(ele => {
ele.id = id
id += 1
@@ -575,7 +582,7 @@ export default {
})
},
async checkChannelsBySite (channels) {
var siteList = {}
const siteList = {}
channels.forEach(channel => {
const site = channel.url.split('/')[2]
if (siteList[site]) {
@@ -592,44 +599,50 @@ export default {
await this.checkSingleChannel(c)
}
},
async checkSingleChannel (channel) {
if (this.setting.allowPassWhenIptvCheck && !channel.isActive) {
async checkSingleChannel (channel, force = false) {
if (this.stopFlag) {
this.checkProgress += 1
return
}
channel.status = ' '
const ele = this.channelList.find(e => e.id === channel.channelID)
if (this.stopFlag) {
this.checkProgress += 1
return channel.status
}
const flag = await zy.checkChannel(channel.url)
this.checkProgress += 1
ele.hasCheckedNum++
if (flag) {
channel.status = '可用'
if (!force && this.setting.allowPassWhenIptvCheck && (!channel.isActive || !ele.isActive)) {
if (!ele.isActive) {
ele.status = '跳过'
} else if (!channel.isActive) {
channel.status = '跳过'
}
} else {
channel.status = '失效'
channel.isActive = false
if (this.setting.autocleanWhenIptvCheck) {
ele.channels.splice(ele.channels.findIndex(e => e.id === channel.id), 1)
ele.hasCheckedNum--
channel.status = ' '
const flag = await zy.checkChannel(channel.url)
if (flag) {
channel.status = '可用'
} else {
channel.status = '失效'
channel.isActive = false
if (this.setting.autocleanWhenIptvCheck) {
if (ele.prefer === channel.id) delete ele.prefer
ele.channels.splice(ele.channels.findIndex(e => e.id === channel.id), 1)
ele.hasCheckedNum--
}
}
}
this.checkProgress += 1
ele.hasCheckedNum++
if (ele.hasCheckedNum === ele.channels.length) {
ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效'
if (ele.status === '失效') ele.isActive = false
if (ele.status === ' ') {
ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效'
if (ele.status === '失效') ele.isActive = false
}
channelList.remove(channel.channelID)
if (ele.channels.length === 1) ele.hasChildren = false
if (ele.channels.length) channelList.add(ele)
}
return channel.status
},
async checkChannel (row) {
if (row.channels) {
row.status = ' '
row.hasCheckedNum = 0
row.channels.forEach(e => this.checkSingleChannel(e))
row.channels.forEach(e => this.checkSingleChannel(e, true))
} else {
this.checkSingleChannel(row)
}

View File

@@ -3,6 +3,12 @@
<div class="box">
<div class="title">
<span v-if="this.right.list.length > 1"> {{(video.info.index + 1)}} </span>{{name}}
<span class="right" @click="() => { onlineUrl = ''; videoStop(); }">
<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="player" v-show="!onlineUrl">
<div id="xgplayer"></div>
@@ -59,13 +65,13 @@
<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">
<span class="zy-svg" @click="miniEvent" v-show="!onlineUrl && 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">
<span class="zy-svg" @click="playWithExternalPalyerEvent" v-show="!onlineUrl && 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>
@@ -89,7 +95,7 @@
<rect x="17" y="6" width="1" height="1"></rect>
</svg>
</span>
<span class="zy-svg" @click="showShortcutEvent" :class="right.type === 'shortcut' ? 'active' : ''" v-show="right.list.length > 0">
<span class="zy-svg" @click="showShortcutEvent" :class="right.type === 'shortcut' ? 'active' : ''" v-show="!onlineUrl && 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>
@@ -251,11 +257,11 @@ import FlvJsPlayer from 'xgplayer-flv.js'
import mt from 'mousetrap'
import Clickoutside from 'element-ui/src/utils/clickoutside'
import { exec, execFile } from 'child_process'
import PinyinMatch from 'pinyin-match'
const { remote, clipboard } = require('electron')
const win = remote.getCurrentWindow()
const PinyinMatch = require('pinyin-match')
const URL = require('url')
const VIDEO_DETAIL_CACHE = {}
const addPlayerBtn = function (event, svg, attrs) {
@@ -366,7 +372,8 @@ export default {
currentShortcutList: [],
onlineUrl: '',
playerType: 'hls',
exportablePlaylist: false
exportablePlaylist: false,
changingIPTV: false
}
},
filters: {
@@ -453,6 +460,7 @@ export default {
},
video: {
handler () {
if (this.changingIPTV) return
this.getUrls()
},
deep: true
@@ -538,7 +546,7 @@ export default {
const index = this.video.info.index || 0
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
const key = this.video.key + '@' + this.video.info.id
var time = this.video.info.time
let time = this.video.info.time
this.xg.removeAllProgressDot()
this.startPosition = { min: '00', sec: '00' }
this.endPosition = { min: '00', sec: '00' }
@@ -572,10 +580,12 @@ export default {
},
playChannel (channel) {
this.isLive = true
this.getPlayer('hls')
if (channel.channels) {
let prefer
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]
if (channel.prefer) prefer = channel.channels.find(e => e.id === channel.prefer)
if (!prefer) prefer = channel.channels.filter(e => e.isActive)[0]
channel = prefer
} else {
const ele = this.channelList.find(e => e.id === channel.channelID)
ele.prefer = channel.id
@@ -583,18 +593,34 @@ export default {
channelList.add(ele)
this.right.sources = ele.channels.filter(e => e.isActive)
}
this.changingIPTV = true // 避免二次执行playChannel
this.video.iptv = channel
this.name = channel.name
const supportFormats = /\.(m3u8|flv)$/
const extRE = channel.url.match(supportFormats) || new URL.URL(channel.url).pathname.match(supportFormats)
this.getPlayer(extRE[1])
if (extRE[1] === 'flv') this.xg.config.isLive = true
this.xg.src = channel.url
this.xg.play()
document.querySelector('xg-btn-showhistory').style.display = 'none'
document.querySelector('.xgplayer-playbackrate').style.display = 'none'
this.changingIPTV = false
setTimeout(() => {
if (!document.getElementById('xgplayer').querySelector('video')) {
this.getPlayer(this.playerType, true)
this.playChannel(channel)
}
}, 1000)
if (document.querySelector('xg-btn-showhistory')) document.querySelector('xg-btn-showhistory').style.display = 'none'
if (document.querySelector('.xgplayer-playbackrate')) document.querySelector('.xgplayer-playbackrate').style.display = 'none'
},
getPlayer (playerType, force = false) {
async getPlayer (playerType, force = false) {
if (!force && this.playerType === playerType) return
this.xg.src = ''
this.config.url = ''
this.xg.destroy()
if (this.playerType !== 'flv') {
this.xg.src = '' // https://developers.google.com/web/updates/2017/06/play-request-was-interrupted#danger-zone
this.config.url = ''
}
try {
if (this.xg) this.xg.destroy()
} catch (err) { }
this.xg = null
switch (playerType) {
case 'mp4':
@@ -609,27 +635,28 @@ export default {
this.playerInstall()
this.bindEvent()
this.playerType = playerType
if (this.miniMode) { await this.saveMiniWindowState(); this.miniEvent() }
},
playVideo (index = 0, time = 0) {
this.isLive = false
this.exportablePlaylist = false
this.fetchPlaylist().then(async (fullList) => {
var playlist = fullList[0].list // ZY支持的已移到首位
let playlist = fullList[0].list // ZY支持的已移到首位
// 如果设定了特定的video flag, 获取该flag下的视频列表
const videoFlag = this.video.info.videoFlag
if (videoFlag) {
playlist = fullList.find(x => x.flag === videoFlag).list
}
this.right.list = playlist
var url = playlist[index].split('$')[1]
if (playlist.every(e => e.split('$')[1].endsWith('.m3u8'))) this.exportablePlaylist = true
const url = playlist[index].includes('$') ? playlist[index].split('$')[1] : playlist[index]
if (playlist.every(e => e.includes('$') ? e.split('$')[1].endsWith('.m3u8') : e.endsWith('.m3u8'))) this.exportablePlaylist = true
if (!url.endsWith('.m3u8') && !url.endsWith('.mp4')) {
const currentSite = await sites.find({ key: this.video.key })
this.$message.info('即将调用解析接口播放,请等待...')
if (currentSite.jiexiUrl) {
this.onlineUrl = currentSite.jiexiUrl + url
this.onlineUrl = /^\s*(default|默认)\s*$/i.test(currentSite.jiexiUrl) ? this.setting.defaultParseURL + url : currentSite.jiexiUrl + url
} else {
this.onlineUrl = 'https://jx.7kjx.com/?url=' + url
this.onlineUrl = url
}
this.videoPlaying('online')
return
@@ -641,6 +668,12 @@ export default {
const key = this.video.key + '@' + this.video.info.id
const startTime = VIDEO_DETAIL_CACHE[key].startPosition || 0
this.xg.play()
setTimeout(() => {
if (!document.getElementById('xgplayer').querySelector('video')) {
this.getPlayer(this.playerType, true)
this.getUrls()
}
}, 1000)
if (document.querySelector('xg-btn-showhistory')) document.querySelector('xg-btn-showhistory').style.display = 'block'
if (document.querySelector('.xgplayer-playbackrate')) document.querySelector('.xgplayer-playbackrate').style.display = 'inline-block'
this.xg.once('playing', () => {
@@ -770,13 +803,10 @@ export default {
},
prevEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
if (index >= 1) {
var channel = this.channelList[index - 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是第一个频道了。')
}
let index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
index = index === 0 ? this.channelList.length - 1 : index - 1
const channel = this.channelList[index]
this.playChannel(channel)
} else {
if (this.video.info.index >= 1) {
this.video.info.index--
@@ -788,13 +818,10 @@ export default {
},
nextEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
if (index < (this.channelList.length - 1)) {
var channel = this.channelList[index + 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是最后一个频道了。')
}
let index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
index = index === this.channelList.length - 1 ? 0 : index + 1
const channel = this.channelList[index]
this.playChannel(channel)
} else {
if (this.video.info.index < (this.right.list.length - 1)) {
this.video.info.index++
@@ -873,7 +900,7 @@ export default {
}
},
async miniEvent () {
this.mainWindowBounds = JSON.parse(JSON.stringify(win.getBounds()))
if (!this.miniMode) 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 }
@@ -882,7 +909,7 @@ export default {
document.querySelector('xg-btn-quitMiniMode').style.display = 'block'
this.miniMode = true
},
async exitMiniEvent () {
async saveMiniWindowState () {
await mini.find().then(res => {
let doc = {}
doc = {
@@ -895,6 +922,9 @@ export default {
mini.add(doc)
}
})
},
async exitMiniEvent () {
await this.saveMiniWindowState()
win.setBounds(this.mainWindowBounds)
this.xg.exitCssFullscreen()
document.querySelector('xg-btn-quitMiniMode').style.display = 'none'
@@ -904,14 +934,16 @@ export default {
this.share = {
show: true,
key: this.video.key,
info: this.DetailCache[this.video.key + '@' + this.video.info.id]
info: this.DetailCache[this.video.key + '@' + this.video.info.id],
index: this.video.info.index
}
},
issueEvent () {
async issueEvent () {
const currentSite = await sites.find({ key: this.video.key })
const info = {
video: this.video,
list: this.right.list,
playlist: VIDEO_DETAIL_CACHE[this.video.key + '@' + this.video.info.id] || [],
video: Object.assign({ site: currentSite, detail: this.DetailCache[this.video.key + '@' + this.video.info.id] }, this.video.info),
playlist: this.right.list.map(e => e.split('$')[1]),
playerType: this.onlineUrl ? '在线解析' : this.playerType,
playerError: this.xg.error || '',
playerState: this.xg.readyState || '',
networkState: this.xg.networkState || ''
@@ -938,7 +970,7 @@ export default {
this.$message.error('请设置第三方播放器路径')
// 在线播放该视频
if (playlistUrls[this.video.info.index].endsWith('.m3u8')) {
var link = 'https://www.m3u8play.com/?play=' + playlistUrls[this.video.info.index]
const link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + playlistUrls[this.video.info.index] + '&name=' + this.video.info.name
const open = require('open')
open(link)
}
@@ -961,11 +993,11 @@ export default {
const path = require('path')
const os = require('os')
const fs = require('fs')
var filePath = path.join(os.tmpdir(), fileName + '.m3u')
const filePath = path.join(os.tmpdir(), fileName + '.m3u')
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
}
var str = '#EXTM3U' + os.EOL
let str = '#EXTM3U' + os.EOL
for (let ind = startIndex; ind < m3u8Arr.length; ind++) {
str += `#EXTINF: -1, 第${ind + 1}` + os.EOL
str += m3u8Arr[ind] + os.EOL
@@ -1030,7 +1062,7 @@ export default {
},
listItemEvent (n) {
if (this.video.iptv) {
var channel = this.channelList[n]
const channel = this.channelList[n]
// 是直播源,直接播放
this.playChannel(channel)
} else {
@@ -1392,7 +1424,7 @@ export default {
this.channelTree = []
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
groups.forEach(g => {
var doc = {
const doc = {
label: g,
children: this.channelList.filter(x => x.group === g).map(i => { return { label: i.name, channel: i } })
}
@@ -1401,16 +1433,45 @@ export default {
})
},
bindEvent () {
// 直播卡顿时换源换台
let stallIptvTimeout
let stallCount = 0
this.xg.on('waiting', () => {
if (this.isLive && this.setting.autoChangeSourceWhenIptvStalling) {
stallIptvTimeout = setTimeout(() => {
let index = this.right.sources.indexOf(this.video.iptv) + 1
if (index === this.right.sources.length) index = 0
stallCount++
if (stallCount >= this.right.sources.length) {
stallCount = 0
this.nextEvent()
} else {
this.playChannel(this.right.sources[index])
}
}, this.setting.waitingTimeInSec * 1000)
}
})
this.xg.on('canplay', () => {
stallCount = 0
clearTimeout(stallIptvTimeout)
})
this.xg.on('destroy', () => {
stallCount = 0
clearTimeout(stallIptvTimeout)
})
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) })
const volume = this.config.volume
setTimeout(() => { if (volume === this.config.volume) setting.find().then(res => { res.volume = this.config.volume; setting.update(res) }) }, 500)
})
this.xg.on('timeupdate', () => {
if (this.isLive) return
const key = this.video.key + '@' + this.video.info.id
if (VIDEO_DETAIL_CACHE[key] && VIDEO_DETAIL_CACHE[key].endPosition) {
const time = this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition - this.xg.currentTime
@@ -1439,8 +1500,8 @@ export default {
this.toggleHistory()
})
this.xg.on('videoStop', () => {
if (this.miniMode) this.exitMiniEvent()
this.xg.on('videoStop', async () => {
if (this.miniMode) await this.exitMiniEvent()
this.videoStop()
})
@@ -1471,6 +1532,7 @@ export default {
})
this.xg.on('play', () => {
clearTimeout(stallIptvTimeout)
if (!this.video.key) {
if (!this.video.iptv && !this.video.info.ids) {
// 如果当前播放页面的播放信息没有被赋值,播放历史记录
@@ -1479,7 +1541,7 @@ export default {
this.videoStop()
return
}
var historyItem = this.right.history[0]
const historyItem = this.right.history[0]
this.video = { key: historyItem.site, info: { id: historyItem.ids, name: historyItem.name, index: historyItem.index } }
} else if (this.video.iptv && !this.isLive) {
this.playChannel(this.video.iptv)
@@ -1500,7 +1562,8 @@ export default {
this.state.showTimespanSetting = false
this.right.list = []
this.getAllhistory()
this.getPlayer(this.playerType, true)
if (this.playerType === 'flv') this.xg.destroy()
this.getPlayer('hls', true)
},
minMaxEvent () {
win.on('minimize', () => {
@@ -1677,12 +1740,6 @@ export default {
.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;
}
@@ -1731,6 +1788,7 @@ export default {
.right {
float: right;
svg {
display: inline-block;
margin-top: 8px;
cursor: pointer;
}

View File

@@ -190,7 +190,7 @@ export default {
}
},
filteredRecommendations () {
var filteredData = this.recommendations.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
let filteredData = this.recommendations.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
return filteredData
}
@@ -236,7 +236,7 @@ export default {
}
},
updateEvent () {
const url = 'https://raw.githubusercontent.com/cuiocean/ZY-Player-Resources/main/Recommendations/Recommendations.json'
const url = 'https://gitee.com/cuiocean/ZY-Player-Resources/raw/main/Recommendations/Recommendations.json'
this.loading = true
const axios = require('axios')
axios.get(url).then(res => {

View File

@@ -33,11 +33,11 @@
</div>
</div>
</div>
<div class="shortcut">
<div class="shortcut" title="清理缓存后图片资源需重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小">
<div class="title">缓存</div>
<div class="shortcut-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="clearCache">清理视频缓存</div>
<div class="vs-placeholder vs-noAfter" @click="clearCache">清理缓存</div>
</div>
</div>
</div>
@@ -75,6 +75,10 @@
<div class="zy-input">
<input type="checkbox" v-model = "d.autocleanWhenIptvCheck" @change="updateSettingEvent"> 检测时自动清理无效源
</div>
<div class="zy-input">
<input type="checkbox" v-model = "d.autoChangeSourceWhenIptvStalling" @change="updateSettingEvent">
卡顿时自动换源换台:<input style="width:50px" type="number" min=0 v-model.number = "d.waitingTimeInSec" @change="updateSettingEvent">
</div>
</div>
</div>
<div class="site">
@@ -83,6 +87,12 @@
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="show.configDefaultParseUrlDialog = true">设置默认解析接口</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="show.configSitesDataUrlDialog = true">设置源站接口文件</div>
</div>
<div class="zy-input" @click="toggleExcludeRootClasses">
<input type="checkbox" v-model = "d.excludeRootClasses" @change="updateSettingEvent"> 屏蔽主分类
</div>
@@ -148,6 +158,34 @@
<span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span>
</div>
</div>
<div> <!-- 设置默认解析接口 -->
<el-dialog :visible.sync="show.configDefaultParseUrlDialog" v-if='show.configDefaultParseUrlDialog' title="设置默认解析接口" :append-to-body="true" @close="closeDialog">
<el-form label-width="45px" label-position="left">
<el-form-item label="URL:">
<el-input v-model="setting.defaultParseURL" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址,为空时会自动设置,重置时会自动更新默认接口地址"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="danger" @click="get7kParseURL">重置</el-button>
<el-button type="primary" @click="configDefaultParseURL">确定</el-button>
</span>
</el-dialog>
</div>
<div> <!-- 设置源站接口文件 -->
<el-dialog :visible="show.configSitesDataUrlDialog" v-if='show.configSitesDataUrlDialog' title="设置源站接口文件" :append-to-body="true" @close="closeDialog">
<el-form label-width="45px" label-position="left">
<el-form-item label="URL:">
<el-input v-model="setting.sitesDataURL" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址,为空时会自动设置,重置时会自动更新默认接口地址"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="danger" @click="getDefaultdeSitesDataURL">重置</el-button>
<el-button type="primary" @click="configSitesDataURL">确定</el-button>
</span>
</el-dialog>
</div>
<div> <!-- 输入密码页面 -->
<el-dialog :visible.sync="show.checkPasswordDialog" v-if='show.checkPasswordDialog' :append-to-body="true" @close="closeDialog" width="300px">
<el-form label-width="75px" label-position="left">
@@ -220,7 +258,7 @@
import { mapMutations } from 'vuex'
import pkg from '../../package.json'
import { setting, sites, shortcut } from '../lib/dexie'
import { sites as defaultSites, localKey as defaultShortcuts } from '../lib/dexie/initData'
import { localKey as defaultShortcuts } from '../lib/dexie/initData'
import { shell, clipboard, remote, ipcRenderer } from 'electron'
import db from '../lib/dexie/dexie'
import zy from '../lib/site/tools'
@@ -238,7 +276,9 @@ export default {
checkPasswordDialog: false,
changePasswordDialog: false,
proxy: false,
proxyDialog: false
proxyDialog: false,
configDefaultParseUrlDialog: false,
configSitesDataUrlDialog: false
},
d: { },
latestVersion: pkg.version,
@@ -287,13 +327,28 @@ export default {
setting.find().then(res => {
this.d = res
this.setting = this.d
if (!this.setting.defaultParseURL) this.configDefaultParseURL()
if (!this.setting.sitesDataURL) this.getDefaultdeSitesDataURL()
})
},
getDefaultSites () {
this.getDefaultdeSitesDataURL()
const axios = require('axios')
axios.get(this.setting.sitesDataURL).then(res => {
if (res.status === 200) {
if (res.data.length > 0) {
sites.clear().then(sites.bulkAdd(res.data))
}
}
}).catch(error => {
this.$message.error('获取云端源站失败. ' + error)
})
},
getSites () {
sites.all().then(res => {
if (res.length <= 0) {
this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
this.getDefaultSites()
}
})
},
@@ -323,6 +378,29 @@ export default {
this.d.excludeRootClasses = !this.d.excludeRootClasses
this.updateSettingEvent()
},
async get7kParseURL () {
this.$message.info('正在获取7K源解析地址...')
const parseURL = await zy.get7kParseURL()
if (parseURL.startsWith('http')) {
this.$message.success('获取成功,更新应用默认解析接口地址...')
this.setting.defaultParseURL = parseURL
}
},
async configDefaultParseURL () {
if (!this.setting.defaultParseURL) await this.get7kParseURL()
this.d.defaultParseURL = this.setting.defaultParseURL.trim()
this.show.configDefaultParseUrlDialog = false
this.updateSettingEvent()
},
getDefaultdeSitesDataURL () {
this.setting.sitesDataURL = 'https://gitee.com/cuiocean/ZY-Player-Resources/raw/main/Sites/Sites.json'
},
configSitesDataURL () {
if (!this.setting.sitesDataURL) this.getDefaultdeSitesDataURL()
this.d.sitesDataURL = this.setting.sitesDataURL
this.show.configSitesDataUrlDialog = false
this.updateSettingEvent()
},
selectLocalPlayer () {
const options = {
filters: [
@@ -333,7 +411,7 @@ export default {
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var playerPath = result.filePaths[0].replace(/\\/g, '/')
const playerPath = result.filePaths[0].replace(/\\/g, '/')
this.$message.success('设定第三方播放器路径为:' + result.filePaths[0])
this.d.externalPlayer = playerPath
this.updateSettingEvent()
@@ -365,6 +443,8 @@ export default {
async closeDialog () {
this.show.checkPasswordDialog = false
this.show.changePasswordDialog = false
this.show.configDefaultParseUrlDialog = false
this.show.configSitesDataUrlDialog = false
if (this.show.proxyDialog) {
this.show.proxyDialog = false
this.setting.proxy.type = 'none'
@@ -502,6 +582,7 @@ export default {
ipcRenderer.send('downloadUpdate')
ipcRenderer.on('update-downloaded', () => {
this.update.downloaded = true
this.$message.success('更新已下载完成Mac用户须手动点击“安装”其它系统会在退出后自动安装')
})
},
installUpdate () {

View File

@@ -1,7 +1,7 @@
<template>
<div class="share" id="share" @click="shareClickEvent" v-clickoutside="shareClickEvent">
<div class="left">
<img :src="pic" alt="" @load="picLoadEvent">
<img :src="share.info.pic" alt="">
</div>
<div class="right" id="right">
<div class="title">{{ share.info.name }}</div>
@@ -76,37 +76,33 @@ export default {
info: {}
}
},
async getUrl (dl) {
const t = dl.dd._t
if (t) {
return t.split('#')[0].split('$')[1]
} else {
const id = this.share.info.ids || this.share.info.id
const cacheKey = this.share.key + '@' + id
let res = this.DetailCache[cacheKey]
if (!this.DetailCache[cacheKey]) {
res = await zy.detail(this.share.key, id)
this.DetailCache[cacheKey] = res
}
if (res) {
return res.fullList[0].list[0]
}
async getUrl (index) {
const id = this.share.info.ids || this.share.info.id
const cacheKey = this.share.key + '@' + id
let res = this.DetailCache[cacheKey]
if (!this.DetailCache[cacheKey]) {
res = await zy.detail(this.share.key, id)
this.DetailCache[cacheKey] = res
}
if (res) {
const url = res.fullList[0].list[index]
return url.includes('$') ? url.split('$')[1] : url
}
},
async getDetail () {
this.loading = true
this.pic = this.share.info.pic
const url = await this.getUrl(this.share.info.dl)
const index = this.share.index || 0
const url = await this.getUrl(index)
this.link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + url + '&name=' + this.share.info.name
this.loading = false
},
picLoadEvent () {
const dom = document.getElementById('share')
html2canvas(dom).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$message.success('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
this.$nextTick(() => {
const dom = document.getElementById('share')
html2canvas(dom, { useCORS: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$message.success('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
})
})
}
},

View File

@@ -201,6 +201,14 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
}
},
watch: {
@@ -269,9 +277,9 @@ export default {
},
async playEvent (e) {
if (e.index) {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: e.index }, detail: e.detail }
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: e.index } }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 }, detail: e.detail }
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 } }
}
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
@@ -306,21 +314,24 @@ export default {
this.getFavorites()
}
},
updateEvent (e) {
zy.detail(e.key, e.ids).then(detailRes => {
var doc = {
async updateEvent (e) {
try {
if (!this.DetailCache[e.key + '@' + e.ids]) {
this.DetailCache[e.key + '@' + e.ids] = await zy.detail(e.key, e.ids)
}
const doc = {
id: e.id,
key: e.key,
ids: e.ids,
site: e.site,
name: e.name,
detail: detailRes,
detail: this.DetailCache[e.key + '@' + e.ids],
index: e.index
}
star.get(e.id).then(resStar => {
if (!e.hasUpdate && e.detail.last !== detailRes.last) {
if (!e.hasUpdate && e.detail.last !== doc.detail.last) {
doc.hasUpdate = true
var msg = `同步"${e.name}"成功, 检查到更新。`
const msg = `同步"${e.name}"成功, 检查到更新。`
this.$message.success(msg)
} else {
this.numNoUpdate += 1
@@ -328,10 +339,10 @@ export default {
star.update(e.id, doc)
this.getFavorites()
})
}).catch(err => {
var msg = `同步"${e.name}"失败, 请重试`
} catch (err) {
const msg = `同步"${e.name}"失败, 请重试`
this.$message.warning(msg, err)
})
}
},
updateAllEvent () {
this.numNoUpdate = 0
@@ -354,7 +365,7 @@ export default {
if (row.site) {
return row.site.name
} else {
var site = this.sites.find(e => e.key === row.key)
const site = this.sites.find(e => e.key === row.key)
if (site) {
return site.name
}
@@ -406,10 +417,10 @@ export default {
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var starList = Array.from(this.list)
var id = this.list.length + 1
const starList = Array.from(this.list)
let id = this.list.length + 1
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.reverse().forEach(ele => {
const starExists = starList.some(x => x.key === ele.key && x.ids === ele.ids)
@@ -424,7 +435,7 @@ export default {
last: ele.last,
note: ele.note
}
var doc = {
const doc = {
id: id,
key: ele.key,
ids: ele.ids,
@@ -457,7 +468,7 @@ export default {
updateDatabase () {
this.syncTableData()
star.clear().then(res => {
var id = this.list.length
let id = this.list.length
this.list.forEach(ele => {
ele.id = id
id -= 1

View File

@@ -48,6 +48,17 @@ db.version(7).stores({
})
})
db.version(8).stores({
}).upgrade(trans => {
trans.sites.toCollection().modify(site => {
if (site.api.includes('7kjx.com')) site.jiexiUrl = 'default'
})
trans.setting.toCollection().modify(setting => {
setting.waitingTimeInSec = 15
setting.autoChangeSourceWhenIptvStalling = true
})
})
db.on('populate', () => {
db.setting.bulkAdd(setting)
db.sites.bulkAdd(sites)

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +1 @@
[
{
"id": 1,
"key": "mahuazy",
"name": "麻花资源",
"api": "http://www.mhapi123.com/inc/ldg_api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 2,
"key": "1886zy",
"name": "1886 资源",
"api": "http://cj.1886zy.co/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 3,
"key": "123ku",
"name": "123 资源",
"api": "http://cj.123ku2.com:12315/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"key": "7K资源",
"id": 4,
"name": "7K资源",
"api": "https://zy.7kjx.com/cjapi.php",
"download": "",
"jiexiUrl": "https://jx.7kjx.com/?url=",
"group": "默认",
"isActive": true
},
{
"key": "1717云资源网",
"id": 5,
"name": "1717云资源网",
"api": "http://zy.itono.cn/inc/api.php",
"download": "",
"jiexiUrl": "https://www.1717yun.com/jiexi/?url=",
"group": "默认",
"isActive": true
},
{
"id": 6,
"key": "subo988",
"name": "速播资源站",
"api": "https://www.subo988.com/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 7,
"key": "88zyw",
"name": "88 影视资源站",
"api": "http://www.88zyw.net/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"key": "zuidazy",
"id": 8,
"name": "最大资源网",
"api": "http://www.zdziyuan.com/inc/ldg_sea.php",
"download": "http://www.zdziyuan.com/inc/apidown.php",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"key": "mbo",
"id": 9,
"name": "秒播资源",
"api": "http://caiji.mb77.vip/inc/seacmsapi.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 10,
"key": "apibdzy",
"name": "百度云资源",
"api": "https://api.apibdzy.com/api.php/provide/vod/at/xml",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 11,
"key": "okzy",
"name": "OK 资源网",
"api": "http://cj.okzy.tv/inc/api.php",
"download": "http://cj.okzy.tv/inc/apidown.php",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 12,
"key": "kuyunzy",
"name": "酷云资源",
"api": "http://caiji.kuyun98.com/inc/ldg_api.php",
"download": "http://caiji.kuyun98.com/inc/apidown.php",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 13,
"key": "kubozy",
"name": "酷播资源",
"api": "http://api.kbzyapi.com/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 14,
"key": "yongjiuzy",
"name": "永久资源",
"api": "http://cj.yongjiuzyw.com/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 15,
"key": "rrzy",
"name": "人人资源",
"api": "https://www.rrzyw.cc/api.php/provide/vod/from/rrm3u8/at/xml/",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 16,
"key": "bbkdj",
"name": "步步高顶尖资源网",
"api": "http://api.bbkdj.com/api",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 17,
"key": "zuixinzy",
"name": "最新资源",
"api": "http://api.zuixinapi.com/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
},
{
"id": 18,
"key": "209zy",
"name": "209 资源",
"api": "http://cj.1156zy.com/inc/api.php",
"download": "",
"jiexiUrl": "",
"group": "默认",
"isActive": true
}
]
[]

View File

@@ -9,6 +9,7 @@ const setting = [
excludeRootClasses: true,
excludeR18Films: true,
forwardTimeInSec: 5,
waitingTimeInSec: 15,
starViewMode: 'picture',
recommendationViewMode: 'picture',
historyViewMode: 'picture',
@@ -21,7 +22,8 @@ const setting = [
port: ''
},
allowPassWhenIptvCheck: true,
autocleanWhenIptvCheck: false
autocleanWhenIptvCheck: false,
autoChangeSourceWhenIptvStalling: true
}
]

View File

@@ -3,23 +3,32 @@ import axios from 'axios'
import parser from 'fast-xml-parser'
import cheerio from 'cheerio'
import { Parser as M3u8Parser } from 'm3u8-parser'
// import FLVDemuxer from 'xgplayer-flv.js/src/flv/demux/flv-demuxer.js'
import SocksProxyAgent from 'socks-proxy-agent'
// axios使用系统代理 https://evandontje.com/2020/04/02/automatic-system-proxy-configuration-for-electron-applications/
// xgplayer使用chromium代理设置浏览器又默认使用系统代理 https://www.chromium.org/developers/design-documents/network-settings
// 要在设置中添加代理设置可参考https://stackoverflow.com/questions/37393248/how-connect-to-proxy-in-electron-webview
var http = require('http')
var https = require('http')
const http = require('http')
const https = require('http')
const { remote } = require('electron')
var win = remote.getCurrentWindow()
var session = win.webContents.session
var ElectronProxyAgent = require('electron-proxy-agent')
const win = remote.getCurrentWindow()
const session = win.webContents.session
const ElectronProxyAgent = require('electron-proxy-agent')
const URL = require('url')
const request = require('request')
let proxyURL
// 取消axios请求 浅析cancelToken https://juejin.cn/post/6844904168277147661 https://masteringjs.io/tutorials/axios/cancel
// const source = axios.CancelToken.source()
// const cancelToken = source.token
// 请求超时时限
// axios.defaults.timeout = 10000 // 可能使用代理,增长超时
const TIMEOUT = 6000
const TIMEOUT = 20000
// 重试次数,共请求3
axios.defaults.retry = 2
// 重试次数,共请求2
axios.defaults.retry = 1
// 请求的间隙
axios.defaults.retryDelay = 1000
@@ -42,7 +51,7 @@ axios.interceptors.response.use(function (response) {
}, function (err) { // 请求错误时做些事
// 请求超时的之后,抛出 err.code = ECONNABORTED的错误..错误信息是 timeout of xxx ms exceeded
if (err.code === 'ECONNABORTED' && err.message.indexOf('timeout') !== -1) {
var config = err.config
const config = err.config
config.__retryCount = config.__retryCount || 0
if (config.__retryCount >= config.retry) {
@@ -52,7 +61,7 @@ axios.interceptors.response.use(function (response) {
config.__retryCount += 1
var backoff = new Promise(function (resolve) {
const backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, config.retryDelay || 1)
@@ -99,7 +108,7 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = res.api
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
@@ -144,7 +153,7 @@ const zy = {
} else {
url = `${site.api}?ac=videolist&pg=${pg}`
}
axios.post(url).then(async res => {
axios.get(url).then(async res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
@@ -176,7 +185,7 @@ const zy = {
} else {
url = `${site.api}?ac=videolist`
}
axios.post(url).then(async res => {
axios.get(url).then(async res => {
const data = res.data.match(/<list [^>]*>/)[0] + '</list>' // 某些源站不含页码时获取到的数据parser无法解析
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
@@ -203,15 +212,20 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
wd = encodeURI(wd)
var url = `${site.api}?wd=${wd}`
axios.post(url, { timeout: 3000 }).then(res => {
const url = `${site.api}?wd=${encodeURI(wd)}`
axios.get(url, { timeout: 3000 }).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
if (json && jsondata && jsondata.list) {
const videoList = jsondata.list.video
resolve(videoList)
let videoList = jsondata.list.video
if (Object.prototype.toString.call(videoList) === '[object Object]') videoList = [].concat(videoList)
videoList = videoList.filter(e => e.name.toLowerCase().includes(wd.toLowerCase()))
if (videoList.length) {
resolve(videoList)
} else {
resolve()
}
}
}).catch(err => {
reject(err)
@@ -231,20 +245,21 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = `${res.api}?ac=videolist&ids=${id}`
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const videoList = jsondata.list.video
// Parse video lists
var fullList = []
let fullList = []
let index = 0
const supportedFormats = ['m3u8', 'mp4']
const dd = videoList.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
const ext = Array.from(new Set(...i._t.split('#').map(e => e.split('$')[1].match(/\.\w+?$/)))).map(e => e.slice(1))
i._t = i._t.replace(/\$+/g, '$')
const ext = Array.from(new Set(...i._t.split('#').map(e => e.includes('$') ? e.split('$')[1].match(/\.\w+?$/) : e.match(/\.\w+?$/)))).map(e => e.slice(1))
if (ext.length && ext.length <= supportedFormats.length && ext.every(e => supportedFormats.includes(e))) {
if (ext.length === 1) {
i._flag = ext[0]
@@ -256,7 +271,7 @@ const zy = {
fullList.push(
{
flag: i._flag,
list: i._t.split('#').filter(e => e && e.split('$')[1] && e.split('$')[1].startsWith('http'))
list: i._t.split('#').filter(e => e && (e.startsWith('http') || (e.split('$')[1] && e.split('$')[1].startsWith('http'))))
}
)
}
@@ -264,10 +279,13 @@ const zy = {
fullList.push(
{
flag: dd._flag,
list: dd._t.split('#').filter(e => e && e.split('$')[1] && e.split('$')[1].startsWith('http'))
list: dd._t.replace(/\$+/g, '$').split('#').filter(e => e && (e.startsWith('http') || (e.split('$')[1] && e.split('$')[1].startsWith('http'))))
}
)
}
fullList.forEach(item => {
if (item.list.every(e => e.includes('$') && /^\s*\d+\s*$/.test(e.split('$')[0]))) item.list.sort((a, b) => { return a.split('$')[0] - b.split('$')[0] })
})
if (fullList.length > 1) { // 将ZY支持的播放列表前置
index = fullList.findIndex(e => supportedFormats.includes(e.flag) || e.flag.startsWith('ZY支持'))
if (index !== -1) {
@@ -299,7 +317,7 @@ const zy = {
const site = res
if (site.download) {
const url = `${site.download}?ac=videolist&ids=${id}&ct=1`
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
@@ -308,10 +326,10 @@ const zy = {
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
downloadUrls = i._t.split('#').map(e => encodeURI(e.split('$')[1])).join('\n')
downloadUrls = i._t.replace(/\$+/g, '$').split('#').map(e => encodeURI(e.includes('$') ? e.split('$')[1] : e)).join('\n')
}
} else {
downloadUrls = dd._t.split('#').map(e => encodeURI(e.split('$')[1])).join('\n')
downloadUrls = dd._t.replace(/\$+/g, '$').split('#').map(e => encodeURI(e.includes('$') ? e.split('$')[1] : e)).join('\n')
}
if (downloadUrls) {
info = '调用下载接口获取到的链接已复制, 快去下载吧!'
@@ -327,7 +345,7 @@ const zy = {
zy.detail(key, id).then(res => {
const dl = res.fullList.find(e => e.flag === videoFlag) || res.fullList[0]
for (const i of dl.list) {
const url = encodeURI(i.split('$')[1])
const url = encodeURI(i.includes('$') ? i.split('$')[1] : i)
downloadUrls += (url + '\n')
}
if (downloadUrls) {
@@ -366,22 +384,47 @@ const zy = {
* @param {*} channel 直播频道 url
* @returns boolean
*/
async checkChannel (channel) {
checkChannel (url) {
return new Promise((resolve, reject) => {
axios.get(channel).then(res => {
const manifest = res.data
var parser = new M3u8Parser()
parser.push(manifest)
parser.end()
var parsedManifest = parser.manifest
if (parsedManifest.segments.length) {
resolve(true)
} else {
resolve(false)
const supportFormats = /\.(m3u8|flv)$/
const extRE = url.match(supportFormats) || new URL.URL(url).pathname.match(supportFormats)
if (extRE[1] === 'flv') {
const MAX_CONTENT_LENGTH = 2000 // axios配置maxContentLength不生效先用request凑合
let receivedLength = 0
let options = { uri: url, gzip: true, timeout: 10000 }
if (proxyURL) {
if (proxyURL.startsWith('http')) options = Object.assign({ proxy: proxyURL }, options)
if (proxyURL.startsWith('socks5')) options = Object.assign({ agent: new SocksProxyAgent(proxyURL) }, options)
}
}).catch(e => {
resolve(false)
})
const req = request.get(options)
.on('data', (str) => {
receivedLength += str.length
if (receivedLength > MAX_CONTENT_LENGTH) {
resolve(true) // 应该用FLVDemuxer.probe来检测先凑合
req.abort()
}
})
.on('error', function (err) {
resolve(false)
console.log(err)
})
.on('end', () => { resolve(false) })
} else if (extRE[1] === 'm3u8') {
axios.get(url).then(res => {
const manifest = res.data
const parser = new M3u8Parser()
parser.push(manifest)
parser.end()
const parsedManifest = parser.manifest
if (parsedManifest.segments.length) {
resolve(true)
} else {
resolve(false)
}
}).catch(e => {
resolve(false)
})
}
})
},
/**
@@ -393,16 +436,16 @@ const zy = {
doubanLink (name, year) {
return new Promise((resolve, reject) => {
// 豆瓣搜索链接
var nameToSearch = name.replace(/\s/g, '')
var doubanSearchLink = 'https://www.douban.com/search?q=' + nameToSearch
const nameToSearch = name.replace(/\s/g, '')
const doubanSearchLink = 'https://www.douban.com/search?q=' + nameToSearch
axios.get(doubanSearchLink).then(res => {
const $ = cheerio.load(res.data)
// 查询所有搜索结果, 看名字和年代是否相符
var link = ''
let link = ''
$('div.result').each(function () {
var linkInDouban = $(this).find('div>div>h3>a').first()
var nameInDouban = linkInDouban.text().replace(/\s/g, '')
var subjectCast = $(this).find('span.subject-cast').text()
const linkInDouban = $(this).find('div>div>h3>a').first()
const nameInDouban = linkInDouban.text().replace(/\s/g, '')
const subjectCast = $(this).find('span.subject-cast').text()
if (nameToSearch === nameInDouban && subjectCast && subjectCast.includes(year)) {
link = linkInDouban.attr('href')
}
@@ -426,14 +469,14 @@ const zy = {
*/
doubanRate (name, year) {
return new Promise((resolve, reject) => {
var nameToSearch = name.replace(/\s/g, '')
const nameToSearch = name.replace(/\s/g, '')
this.doubanLink(nameToSearch, year).then(link => {
if (link.includes('https://www.douban.com/search')) {
resolve('暂无评分')
} else {
axios.get(link).then(response => {
const parsedHtml = cheerio.load(response.data)
var rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first()
const rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first()
if (rating.text()) {
resolve(rating.text().replace(/\s/g, ''))
} else {
@@ -448,16 +491,26 @@ const zy = {
})
})
},
async proxy () {
get7kParseURL () {
return new Promise((resolve, reject) => {
axios.get('https://zy.7kjx.com/').then(res => {
const $ = cheerio.load(res.data)
const parseURL = $('body > div.container > div > div.stui-pannel > div.col-pd > p:contains("解析接口:")').first().find('a').text()
resolve(parseURL)
}).catch(err => { reject(err) })
})
},
proxy () {
return new Promise((resolve, reject) => {
setting.find().then(db => {
if (db && db.proxy && db.proxy.type === 'manual') {
if (db.proxy.scheme && db.proxy.url && db.proxy.port) {
const proxyURL = db.proxy.scheme + '://' + db.proxy.url.trim() + ':' + db.proxy.port.trim()
proxyURL = db.proxy.scheme + '://' + db.proxy.url.trim() + ':' + db.proxy.port.trim()
session.setProxy({ proxyRules: proxyURL })
http.globalAgent = https.globalAgent = new ElectronProxyAgent(session)
}
} else {
proxyURL = ''
session.setProxy({ proxyRules: 'direct://' })
http.globalAgent = https.globalAgent = new ElectronProxyAgent(session)
}

663
yarn.lock

File diff suppressed because it is too large Load Diff