├── src-tauri ├── build.rs ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square310x310Logo.png │ └── icons.md ├── .gitignore ├── src │ ├── tvbox │ │ ├── source │ │ │ ├── rule.rs │ │ │ ├── ijk.rs │ │ │ ├── parse.rs │ │ │ ├── mod.rs │ │ │ ├── live.rs │ │ │ └── vod.rs │ │ ├── check.rs │ │ ├── mod.rs │ │ └── playlist.rs │ ├── main.rs │ ├── server.rs │ ├── desktop.rs │ └── utils.rs ├── Cargo.toml ├── tauri.conf.json └── tests │ └── lib.rs ├── images ├── a.png ├── b.png ├── c.png └── d.png ├── postcss.config.js ├── src ├── components │ ├── ui │ │ ├── list.vue │ │ ├── link-text.vue │ │ ├── height-auto.vue │ │ ├── width-auto.vue │ │ ├── list-item.vue │ │ ├── copy-text.vue │ │ ├── play-text.vue │ │ └── video-player.vue │ ├── playlist │ │ ├── m3u8.vue │ │ ├── txt-preview.vue │ │ ├── txt-group.vue │ │ └── txt.vue │ ├── playlist.vue │ ├── tvbox │ │ ├── flags.vue │ │ ├── ijk.vue │ │ ├── warning.vue │ │ ├── rules.vue │ │ ├── mergin.vue │ │ ├── ads.vue │ │ ├── spider.vue │ │ ├── wallpaper.vue │ │ ├── preview.vue │ │ ├── parses.vue │ │ ├── vod.vue │ │ └── live.vue │ └── tvbox.vue ├── store │ ├── localstorage.ts │ ├── index.ts │ ├── txt.ts │ └── tvbox.ts ├── vite-env.d.ts ├── utils.ts ├── assets │ ├── vue.svg │ └── addon.scss ├── styles.css ├── main.ts ├── App.vue └── interfaces │ └── index.ts ├── .vscode └── extensions.json ├── tailwind.config.js ├── tsconfig.node.json ├── .gitignore ├── index.html ├── key └── readme.md ├── tsconfig.json ├── vite.config.ts ├── package.json ├── public ├── vite.svg └── tauri.svg ├── readme.md └── data ├── list.md └── test.m3u8 /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /images/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/images/a.png -------------------------------------------------------------------------------- /images/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/images/b.png -------------------------------------------------------------------------------- /images/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/images/c.png -------------------------------------------------------------------------------- /images/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/images/d.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmynet/tvboxsp/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src/components/ui/list.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/store/localstorage.ts: -------------------------------------------------------------------------------- 1 | import { Store } from "tauri-plugin-store-api"; 2 | const datastore = new Store(".settings.dat"); 3 | 4 | export default datastore; 5 | -------------------------------------------------------------------------------- /src/components/ui/link-text.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { useTxtPlaylistStore } from "./txt"; 2 | import { useTvBoxStore } from "./tvbox"; 3 | import localstorage from "./localstorage"; 4 | export { useTxtPlaylistStore, useTvBoxStore, localstorage }; 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/ui/height-auto.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/ui/width-auto.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.vue" { 4 | import type { DefineComponent } from "vue"; 5 | const component: DefineComponent<{}, {}, any>; 6 | export default component; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/ui/list-item.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/rule.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Serialize, Deserialize)] 2 | pub struct Rule { 3 | // 针对多个主机 4 | pub hosts: Option>, 5 | pub name: Option, 6 | pub regex: Option>, 7 | // 只针对1格主机 8 | pub host: Option, 9 | pub rule: Option>, 10 | } 11 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Modal } from "@arco-design/web-vue"; 2 | function confirm(content: string) { 3 | return new Promise((ok, fail) => { 4 | Modal.confirm({ 5 | content, 6 | onOk(e) { 7 | ok(e); 8 | }, 9 | onCancel(e) { 10 | fail(e); 11 | }, 12 | }); 13 | }); 14 | } 15 | 16 | export { confirm }; 17 | -------------------------------------------------------------------------------- /src/components/playlist/m3u8.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | key/*.key 26 | key/*.pub -------------------------------------------------------------------------------- /src/components/playlist.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tauri + Vue + TS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /key/readme.md: -------------------------------------------------------------------------------- 1 | # 帮助 2 | 3 | ## 生成密钥 4 | 5 | ```bash 6 | yarn tauri signer generate -w ./key/app.key 7 | 8 | ``` 9 | 10 | ## build release 11 | 12 | Windows 13 | 14 | ```powershell 15 | $env:TAURI_PRIVATE_KEY="" 16 | $env:TAURI_KEY_PASSWORD="" 17 | 18 | yarn tauri build 19 | 20 | ``` 21 | 22 | linux: 23 | 24 | ```bash 25 | export TAURI_PRIVATE_KEY="" 26 | export TAURI_KEY_PASSWORD="" 27 | 28 | yarn tauri build 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /src/components/tvbox/flags.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | {{ item }} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/ijk.rs: -------------------------------------------------------------------------------- 1 | use serde_aux::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize)] 4 | pub struct Ijk { 5 | pub group: String, 6 | pub options: Vec, 7 | } 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | pub struct Opt { 11 | #[serde(deserialize_with = "deserialize_number_from_string")] 12 | pub category: i32, 13 | pub name: String, 14 | pub value: String, 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color: #0f0f0f; 8 | background-color: #f6f6f6; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | @tailwind base; 17 | @tailwind components; 18 | @tailwind utilities; 19 | -------------------------------------------------------------------------------- /src/components/ui/copy-text.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /src-tauri/icons/icons.md: -------------------------------------------------------------------------------- 1 | ```icons 2 | icons 3 | ├─ 128x128.png 4 | ├─ 128x128@2x.png 5 | ├─ 32x32.png 6 | ├─ icon.icns 7 | ├─ icon.ico 8 | ├─ icon.png 9 | ├─ icons.md 10 | ├─ Square107x107Logo.png 11 | ├─ Square142x142Logo.png 12 | ├─ Square150x150Logo.png 13 | ├─ Square284x284Logo.png 14 | ├─ Square30x30Logo.png 15 | ├─ Square310x310Logo.png 16 | ├─ Square44x44Logo.png 17 | ├─ Square71x71Logo.png 18 | ├─ Square89x89Logo.png 19 | └─ StoreLogo.png 20 | 21 | ``` 22 | --- -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from "pinia"; 2 | import ArcoVue from "@arco-design/web-vue"; 3 | import ArcoVueIcon from "@arco-design/web-vue/es/icon"; 4 | import { Message, Notification } from "@arco-design/web-vue"; 5 | import { createApp } from "vue"; 6 | import "@arco-design/web-vue/dist/arco.css"; 7 | import "./styles.css"; 8 | import "./assets/addon.scss"; 9 | import "./interfaces/index"; 10 | import App from "./App.vue"; 11 | const pinia = createPinia(); 12 | const app = createApp(App); 13 | Message._context = app._context; 14 | Notification._context = app._context; 15 | app.use(ArcoVue); 16 | app.use(pinia); 17 | app.use(ArcoVueIcon); 18 | app.mount("#app"); 19 | -------------------------------------------------------------------------------- /src/assets/addon.scss: -------------------------------------------------------------------------------- 1 | .flex { 2 | &.flex-row { 3 | .flex-x-auto { 4 | flex: 1 1 auto; 5 | width: 0; 6 | } 7 | } 8 | &.flex-col { 9 | .flex-y-auto { 10 | flex: 1 1 auto; 11 | height: 0; 12 | } 13 | } 14 | } 15 | 16 | .flex.arco-tabs { 17 | display: flex; 18 | flex-direction: column; 19 | width: 100%; 20 | height: 100%; 21 | flex: 1; 22 | .arco-tabs-content { 23 | flex: 1 1 0%; 24 | .arco-tabs-content-list { 25 | height: 100%; 26 | // flex-direction: column; 27 | } 28 | } 29 | .arco-tabs-content-item { 30 | .arco-tabs-pane { 31 | display: flex; 32 | flex-direction: column; 33 | height: 100%; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ui/play-text.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 21 | ▶️ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(async () => ({ 6 | plugins: [ 7 | vue({ 8 | template: { 9 | compilerOptions: { 10 | isCustomElement: (tag) => /fluent/i.test(tag), 11 | }, 12 | }, 13 | }), 14 | ], 15 | 16 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 17 | // 18 | // 1. prevent vite from obscuring rust errors 19 | clearScreen: false, 20 | // 2. tauri expects a fixed port, fail if that port is not available 21 | server: { 22 | port: 1420, 23 | strictPort: true, 24 | }, 25 | // 3. to make use of `TAURI_DEBUG` and other env variables 26 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand 27 | envPrefix: ["VITE_", "TAURI_"], 28 | })); 29 | -------------------------------------------------------------------------------- /src/components/tvbox/ijk.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | {{ item.group }} 15 | 配置参数: 16 | 17 | 18 | 19 | 20 | category:{{ opt.category }}, {{ opt.name }} : 21 | {{ opt.value }} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/tvbox/warning.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 29 | 保存 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tvbox-provider", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc --noEmit && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "@tauri-apps/api": "^1.4.0", 14 | "@videojs-player/vue": "^1.0.0", 15 | "add": "^2.0.6", 16 | "axios": "^1.4.0", 17 | "lodash-es": "^4.17.21", 18 | "pinia": "^2.1.7", 19 | "qrcode-vue3": "^1.6.8", 20 | "tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store.git#v1", 21 | "video.js": "^8.6.1", 22 | "vue": "^3.3.4" 23 | }, 24 | "devDependencies": { 25 | "@arco-design/web-vue": "^2.53.3", 26 | "@tauri-apps/cli": "^1.4.0", 27 | "@types/node": "^20.10.5", 28 | "@vitejs/plugin-vue": "^4.2.3", 29 | "autoprefixer": "^10.4.16", 30 | "postcss": "^8.4.32", 31 | "sass": "^1.69.5", 32 | "sass-loader": "^13.3.2", 33 | "scss": "^0.2.4", 34 | "tailwindcss": "^3.4.0", 35 | "typescript": "^5.0.2", 36 | "vite": "^4.4.4", 37 | "vue-tsc": "^1.8.5" 38 | } 39 | } -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/ui/video-player.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 22 | 23 | 31 | 32 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/components/tvbox/rules.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 14 | 规则名称:{{ item.name }} 15 | 16 | 生效站点:{{ item.hosts?.join(",") }} 17 | 18 | 19 | 20 | 正则匹配: 21 | 24 | 25 | 生效站点:{{ item.host }} 26 | 27 | 匹配规则: 28 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/tvbox/mergin.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 24 | 25 | 每个URL点播源地址一行,或者使用 26 | ,; 27 | 将URL点播源地址 28 | 29 | 30 | 31 | 32 | 33 | 合并项:点播源,直播源,解析器,vip标识,广告过滤,规则 34 | 35 | 请注意:检测仅保证连接可访问,无法保证其内容有效 36 | 在合并点播源时需要注意各个tvbox之间的版本差异,部分版本可能不支持python脚本 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/parse.rs: -------------------------------------------------------------------------------- 1 | use super::super::Connection; 2 | use crate::utils; 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | use serde_aux::prelude::*; 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | pub struct Parse { 8 | pub name: String, 9 | #[serde( 10 | rename = "type", 11 | default, 12 | deserialize_with = "deserialize_number_from_string" 13 | )] 14 | pub src_type: i32, 15 | pub url: String, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub ext: Option, 18 | } 19 | 20 | #[async_trait] 21 | impl Connection for Parse { 22 | async fn check(&mut self, quick_mode: bool, skip_ipv6: bool) -> Result { 23 | if utils::is_http_url(&self.url) { 24 | if skip_ipv6 && self.url.contains("://[") { 25 | return Ok(false); 26 | } else { 27 | let connectable = if quick_mode { 28 | utils::url_connectivity(&self.url).await.unwrap_or_default() 29 | } else { 30 | utils::url_accessibility(&self.url) 31 | .await 32 | .unwrap_or_default() 33 | }; 34 | return Ok(connectable); 35 | } 36 | } 37 | Ok(true) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/tvbox/ads.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 屏蔽一下站点广告: 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 删除 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/tvbox/spider.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 下载 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | #[macro_use] 5 | extern crate anyhow; 6 | #[macro_use] 7 | extern crate serde; 8 | #[macro_use] 9 | extern crate log; 10 | 11 | mod desktop; 12 | mod server; 13 | mod tvbox; 14 | mod utils; 15 | fn main() { 16 | std::env::set_var("RUST_LOG", "info"); 17 | std::thread::spawn(|| { 18 | let rt = tokio::runtime::Runtime::new().unwrap(); 19 | rt.block_on(async { 20 | server::run().await; 21 | }); 22 | rt.shutdown_background(); 23 | }); 24 | tauri::Builder::default() 25 | .plugin(tauri_plugin_store::Builder::default().build()) 26 | .invoke_handler(tauri::generate_handler![ 27 | desktop::parse_playlist, 28 | desktop::parse_tvbox, 29 | desktop::get_content, 30 | desktop::urls_accessibility, 31 | desktop::exec, 32 | desktop::vods_connectivity, 33 | desktop::live_connectivity, 34 | desktop::parses_connectivity, 35 | desktop::save, 36 | desktop::cache, 37 | desktop::lan_ip, 38 | desktop::is_install, 39 | desktop::download, 40 | desktop::hash, 41 | ]) 42 | .run(tauri::generate_context!()) 43 | .expect("error while running tauri application"); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/tvbox/wallpaper.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | {{ url }} 32 | 33 | 图片按16/9比例方式模拟电视屏幕最终的显示效果 34 | 35 | 36 | 37 | 38 | 45 | 修改 46 | 47 | 48 | 49 | 55 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tvbox-provider" 3 | version = "0.0.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "mit" 7 | repository = "" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [build-dependencies] 13 | tauri-build = { version = "1.4", features = [] } 14 | 15 | [dependencies] 16 | tauri = { version = "1.4", features = ["api-all", "windows7-compat"] } 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | 20 | axum = "0.7.3" 21 | 22 | anyhow = "1" 23 | tokio = { version = "1", features = ["full", "time"] } 24 | reqwest = { version = "0.11", features = ["json"] } 25 | url = "2.3" 26 | 27 | serde_qs = "0.12" 28 | serde-aux = "4" 29 | regex = "1.10" 30 | json5 = "0.4.1" 31 | base64 = "0.21" 32 | 33 | indicatif = "0.17.3" 34 | log = "0.4" 35 | env_logger = "0.10" 36 | m3u8-rs = "5.0.5" 37 | # strum = { version = "0.25.0", features = ["derive"] } 38 | cached = { version = "0.47.0", features = [ 39 | "async", 40 | "async_tokio_rt_multi_thread", 41 | ] } 42 | once_cell = "1" 43 | default-net = "0.21.0" 44 | num_cpus = "1.16.0" 45 | async-trait = "0.1.75" 46 | tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace.git", branch = "v1" } 47 | xxhash-rust = { version = "0.8.6", features = ["xxh3"] } 48 | [features] 49 | # this feature is used for production builds or when `devPath` points to the filesystem 50 | # DO NOT REMOVE!! 51 | custom-protocol = ["tauri/custom-protocol"] 52 | 53 | [dev-dependencies] 54 | image = "0.24" 55 | icns = "0.3.1" 56 | bytes = "1.5.0" 57 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "yarn dev", 4 | "beforeBuildCommand": "yarn build", 5 | "devPath": "http://localhost:1420", 6 | "distDir": "../dist", 7 | "withGlobalTauri": false 8 | }, 9 | "package": { 10 | "productName": "tvbox-provider", 11 | "version": "0.0.5" 12 | }, 13 | "tauri": { 14 | "allowlist": { 15 | "all": true, 16 | "http": { 17 | "all": true, 18 | "scope": [ 19 | "http://*", 20 | "https://*" 21 | ] 22 | }, 23 | "shell": { 24 | "all": true, 25 | "scope": [ 26 | { 27 | "name": "ffplay", 28 | "cmd": "ffplay" 29 | }, 30 | { 31 | "name": "cmd", 32 | "cmd": "cmd" 33 | } 34 | ], 35 | "execute": true, 36 | "sidecar": true, 37 | "open": true 38 | }, 39 | "dialog": { 40 | "all": true 41 | } 42 | }, 43 | "bundle": { 44 | "active": true, 45 | "targets": "all", 46 | "identifier": "com.iomessage.tvboxsp", 47 | "icon": [ 48 | "icons/32x32.png", 49 | "icons/128x128.png", 50 | "icons/128x128@2x.png", 51 | "icons/icon.icns", 52 | "icons/icon.ico" 53 | ] 54 | }, 55 | "security": { 56 | "csp": "*" 57 | }, 58 | "windows": [ 59 | { 60 | "fullscreen": false, 61 | "resizable": true, 62 | "title": "Tvbox Provider", 63 | "width": 960, 64 | "height": 720, 65 | "minWidth": 960, 66 | "minHeight": 720 67 | } 68 | ] 69 | } 70 | } -------------------------------------------------------------------------------- /src/components/tvbox/preview.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 47 | 48 | 49 | 61 | 62 | 63 | 64 | 65 | 下载 66 | 67 | 打开 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/components/playlist/txt-preview.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 49 | 50 | 62 | 63 | 64 | 65 | 66 | 下载 67 | 68 | 打开 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Tvbox Source Provider 2 | 3 | 4 | 5 | tvbox 视频源检测与合并工具 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | > 点击【预览】实现导出与测试 15 | 16 | ## 构建 17 | 18 | Windows 19 | 20 | ```powershell 21 | # release 构建 22 | # $env:TAURI_PRIVATE_KEY="" 23 | # $env:TAURI_KEY_PASSWORD="" 24 | 25 | npm install 26 | npm tauri build 27 | # or 28 | yarn install 29 | yarn tauri build 30 | 31 | ``` 32 | 33 | ## 注意 34 | 35 | 1. IPv6的视频源或直播源在IPv4网络下是无法连接的。 36 | 2. 视频播放当前依赖于mpv播放器[https://github.com/mpv-player/mpv](https://github.com/mpv-player/mpv) 37 | 3. 直播源测试用的mpv播放器目前仅在Windows下可用 38 | 39 | ## 跨平台编译Linux版本 40 | 41 | 详情参考: [https://tauri.app/v1/guides/building/](https://tauri.app/v1/guides/building/) 42 | 以下为基于docker构建debain/Ubuntu版本 43 | 44 | ```bash 45 | docker run --rm -it -v "${pwd}:/home/rust/src" -v "${env:userprofile}/.cargo/registry:/usr/local/cargo/registry" -v "${env:userprofile}/.cargo/config:/usr/local/cargo/config" rust:1.70-bullseye 46 | 47 | 48 | # 修改软件源镜像 49 | sed -i 's#http://deb.debian.org#https://mirrors.ustc.edu.cn#g' /etc/apt/sources.list 50 | sed -i 's#http://security.debian.org#http://mirrors.ustc.edu.cn#g' /etc/apt/sources.list 51 | 52 | # 安装必要的软件 53 | apt update -y && apt-get install libssl-dev libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf -y 54 | # 安装 nodejs 55 | # https://nodejs.org/en/download/package-manager#debian-and-ubuntu-based-linux-distributions 56 | 57 | curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&\ 58 | apt-get install -y nodejs 59 | 60 | cd /home/rust/src 61 | # 安装yarn 62 | # npm config set registry https://registry.npm.taobao.org/ 63 | # npm config set registry https://registry.npmjs.org/ 64 | 65 | npm -g i yarn 66 | # 安装依赖 67 | # yarn config set registry https://registry.npm.taobao.org/ 68 | # yarn config set registry https://registry.npmjs.org/ 69 | yarn 70 | # 构建 71 | # proxy for wget 72 | # export http_proxy=http://127.0.0.1:10811/ 73 | # export https_proxy=http://127.0.0.1:10811/ 74 | yarn tauri build 75 | 76 | # AppImage软件包的构建依赖于github相关脚本,如需构建AppImage版本请务必保证可以正常访问github 77 | 78 | 79 | ``` 80 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | interface TvBoxSource { 2 | sites: TvBoxVod[]; 3 | lives: TvBoxLive[]; 4 | parses?: TvBoxParse[]; 5 | flags?: string[]; 6 | ijk: TvBoxIjk[]; 7 | rules?: TvBoxRule[]; 8 | ads?: string[]; 9 | wallpaper?: string; 10 | spider?: string; 11 | warningText?: string; 12 | } 13 | interface TvBoxVod { 14 | key: string; 15 | name: string; 16 | type: number; 17 | api: string; 18 | searchable: number; 19 | quickSearch: number; 20 | filterable?: number; 21 | ext?: any; 22 | // 自定义JAR刮削库 - 引用第三方 23 | jar?: string; 24 | /// 播放器类型 25 | /// 0 system 1 ikj 2 exo 10 mxplayer -1 以参数设置页面的为准 26 | player_type?: number; 27 | /// 分类&排序 28 | categories?: string[]; 29 | /// 需要点击播放的嗅探站点selector ddrk.me;#id 30 | click?: string; 31 | hide?: number; 32 | 33 | features?: string; 34 | // 0:To be tested 1: valid, -1: invalid 35 | status?: number; 36 | } 37 | 38 | interface TvBoxLive { 39 | name?: string; 40 | group?: string; 41 | channels?: { 42 | name: string; 43 | urls: string[]; 44 | }[]; 45 | epg?: string; 46 | type?: number; 47 | url?: string; 48 | full_url?: string; 49 | // 0:To be tested 1: valid, -1: invalid 50 | status?: number; 51 | } 52 | 53 | interface TvBoxParse { 54 | name: string; 55 | type: number; 56 | url: string; 57 | ext?: any; 58 | // 0:To be tested 1: valid, -1: invalid 59 | status?: number; 60 | } 61 | 62 | interface TvBoxIjk { 63 | group: string; 64 | options: { 65 | category: number; 66 | name: string; 67 | value: string; 68 | }[]; 69 | } 70 | interface TvBoxRule { 71 | hosts?: string[]; 72 | name?: string; 73 | regex?: string[]; 74 | host?: string; 75 | rule?: string[]; 76 | } 77 | 78 | interface Playlist { 79 | loss: number; 80 | count: number; 81 | content: string; 82 | } 83 | 84 | interface TxtPlaylist { 85 | name: string; 86 | url: string; 87 | // 0: uncheck, -1, loss, 1: online 88 | online?: number; 89 | http?: boolean; 90 | group?: boolean; 91 | raw: string; 92 | hash: string; 93 | } 94 | 95 | interface ITxtPlaylistGroup { 96 | group: string; 97 | raw: TxtPlaylist, 98 | items: TxtPlaylist[]; 99 | } 100 | 101 | interface ConnectionStatus { 102 | connectable: boolean; 103 | extra: any; 104 | } 105 | -------------------------------------------------------------------------------- /public/tauri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src-tauri/tests/lib.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut}; 2 | use image::GenericImageView; 3 | fn icns(icon: &image::DynamicImage, icns: &str) -> anyhow::Result<()> { 4 | // Load an icon family from an ICNS file. 5 | let file = std::fs::read(icns)?; 6 | 7 | let file = file.reader(); 8 | let (width, height) = icns::IconFamily::read(file)? 9 | .elements 10 | .iter() 11 | .filter_map(|i| i.decode_image().ok()) 12 | .map(|i| (i.width(), i.height())) 13 | .max_by(|a, b| a.0.cmp(&b.0)) 14 | .unwrap_or((512, 512)); 15 | let target = icon.resize(width, height, image::imageops::Lanczos3); 16 | let new = icns::Image::from_data( 17 | icns::PixelFormat::RGBA, 18 | width, 19 | height, 20 | target.to_rgba8().to_vec(), 21 | )?; 22 | 23 | let mut icon_family = icns::IconFamily::new(); 24 | icon_family.add_icon(&new)?; 25 | 26 | let mut file = vec![].writer(); 27 | icon_family 28 | .write(&mut file) 29 | .map_err(|e| { 30 | println!("写入失败: {:?}", e); 31 | e 32 | }) 33 | .unwrap(); 34 | std::fs::write(icns, file.get_ref())?; 35 | Ok(()) 36 | } 37 | 38 | #[test] 39 | fn icon_generate() { 40 | let files = r#" 41 | 128x128.png 42 | 128x128@2x.png 43 | 32x32.png 44 | icon.icns 45 | icon.ico 46 | Square107x107Logo.png 47 | Square142x142Logo.png 48 | Square150x150Logo.png 49 | Square284x284Logo.png 50 | Square30x30Logo.png 51 | Square310x310Logo.png 52 | Square44x44Logo.png 53 | Square71x71Logo.png 54 | Square89x89Logo.png 55 | StoreLogo.png 56 | "#; 57 | let icon = "./icons/icon.png"; 58 | let exist = std::path::Path::new(icon).exists(); 59 | assert!(exist); 60 | let icon = image::open(icon).unwrap(); 61 | files.lines().map(|l| l.trim()).for_each(|f| { 62 | let f = format!("./icons/{}", f); 63 | let file = std::path::Path::new(&f); 64 | if !file.exists() { 65 | return; 66 | } 67 | if f.ends_with(".icns") { 68 | icns(&icon, &f).ok(); 69 | return; 70 | } 71 | if let Ok(i) = image::open(&f) { 72 | let (width, height) = i.dimensions(); 73 | let target = icon 74 | .resize(width, height, image::imageops::Lanczos3) 75 | .to_rgba8(); 76 | target.save(f).ok(); 77 | } 78 | }); 79 | assert_eq!(1, 1) 80 | } 81 | -------------------------------------------------------------------------------- /src-tauri/src/server.rs: -------------------------------------------------------------------------------- 1 | use axum::{response::IntoResponse, routing::get, Router}; 2 | use once_cell::sync::Lazy; 3 | use tokio::sync::Mutex; 4 | 5 | pub struct Cache { 6 | pub tvbox: String, 7 | pub playlist: String, 8 | } 9 | 10 | impl Cache { 11 | pub fn update(&mut self, key: &str, value: String) { 12 | let key = key.to_lowercase(); 13 | if key == "tvbox" { 14 | self.tvbox = value 15 | } else if key == "playlist" { 16 | self.playlist = value; 17 | } 18 | } 19 | } 20 | 21 | pub async fn updata_cache(key: &str, value: String) { 22 | let mut m = CACHE.lock().await; 23 | m.update(key, value); 24 | } 25 | 26 | static CACHE: Lazy> = Lazy::new(|| { 27 | Mutex::new(Cache { 28 | tvbox: String::default(), 29 | playlist: String::default(), 30 | }) 31 | }); 32 | 33 | pub async fn run() { 34 | let app = Router::new() 35 | .route("/", get(root)) 36 | .route("/playlist.txt", get(playlist_txt)) 37 | .route("/playlist.m3u", get(playlist_m3u)) 38 | .route("/playlist.m3u8", get(playlist_m3u)) 39 | .route("/tvbox.json", get(tvbox_json)); 40 | 41 | // run our app with hyper, listening globally on port 3000 42 | let listener = tokio::net::TcpListener::bind("0.0.0.0:8090").await.unwrap(); 43 | axum::serve(listener, app).await.unwrap(); 44 | } 45 | async fn root() -> impl IntoResponse { 46 | let content = r#" 47 | 48 | playlist.txt 49 | playlist.m3u 50 | playlist.m3u8 51 | tvbox.json 52 | 53 | "# 54 | .to_string(); 55 | let mut resp = axum::response::Response::new(content); 56 | resp.headers_mut() 57 | .insert("content-type", "text/html".parse().unwrap()); 58 | resp 59 | } 60 | 61 | /// 文本格式的直播源 62 | async fn playlist_txt() -> impl IntoResponse { 63 | CACHE.lock().await.playlist.clone() 64 | } 65 | /// m3u格式的直播源 66 | async fn playlist_m3u() -> impl IntoResponse { 67 | let content = CACHE.lock().await.playlist.clone(); 68 | let mut resp = axum::response::Response::new(content); 69 | resp.headers_mut() 70 | .insert("content-type", "application/x-mpegURL".parse().unwrap()); 71 | resp 72 | } 73 | /// tvbox 配置信息 74 | async fn tvbox_json() -> impl IntoResponse { 75 | let content = CACHE.lock().await.tvbox.clone(); 76 | let mut resp = axum::response::Response::new(content); 77 | resp.headers_mut() 78 | .insert("content-type", "application/json".parse().unwrap()); 79 | resp 80 | } 81 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/check.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use super::{spawn, Connection}; 4 | use tauri::{Runtime, Window}; 5 | use tokio::sync::Mutex; 6 | 7 | #[derive(Clone, Serialize)] 8 | pub struct ProgressPayload { 9 | pub progress: u64, 10 | pub total: u64, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize)] 14 | pub struct ConnectionStatus { 15 | pub connectable: bool, 16 | pub extra: T, 17 | } 18 | 19 | pub async fn check_connections( 20 | window: Window, 21 | links: Vec, 22 | quick_mode: bool, 23 | skip_ipv6: Option, 24 | ) -> Vec> 25 | where 26 | T: for<'se> Connection + Clone + Send + Sync + 'static, 27 | R: Runtime, 28 | { 29 | if links.is_empty() { 30 | return vec![]; 31 | } 32 | let skip_ipv6 = skip_ipv6.unwrap_or_default(); 33 | let threads = { 34 | let tasks = links.len().max(1); 35 | let threads = (num_cpus::get() / 2).max(1); 36 | if tasks > threads * threads { 37 | threads 38 | } else { 39 | (tasks / threads).max(1) 40 | } 41 | }; 42 | let mut size = links.len() / threads; 43 | if size == 0 { 44 | size = links.len(); 45 | } 46 | 47 | let total = links.len() as u64; 48 | let count = Arc::new(Mutex::new(0)); 49 | let chunck = links.chunks(size).map(|ch| ch.to_vec()); 50 | let mut tasks = vec![]; 51 | window 52 | .emit( 53 | "check_connections://progress", 54 | ProgressPayload { progress: 0, total }, 55 | ) 56 | .ok(); 57 | for c in chunck.into_iter() { 58 | let w = window.clone(); 59 | let cnt = count.clone(); 60 | let t = spawn(async move { 61 | let mut items = vec![]; 62 | for mut i in c { 63 | let ok = i 64 | .check(quick_mode, skip_ipv6) 65 | .await 66 | .ok() 67 | .unwrap_or_default(); 68 | items.push(ConnectionStatus { 69 | connectable: ok, 70 | extra: i, 71 | }); 72 | let mut c = cnt.lock().await; 73 | *c += 1; 74 | w.emit( 75 | "check_connections://progress", 76 | ProgressPayload { 77 | progress: *c as u64, 78 | total, 79 | }, 80 | ) 81 | .ok(); 82 | } 83 | items 84 | }); 85 | tasks.push(t); 86 | } 87 | let mut items = vec![]; 88 | for t in tasks { 89 | if let Ok(mut v) = t.await { 90 | items.append(&mut v); 91 | } 92 | } 93 | items 94 | } 95 | -------------------------------------------------------------------------------- /data/list.md: -------------------------------------------------------------------------------- 1 | 2 | 视频源提供方: 3 | https://agit.ai/Yoursmile7/TVBox 4 | https://github.com/Cyril0563/lanjing_live 5 | https://github.com/mengzehe/TVBox 6 | 7 | 8 | 视频源 9 | 10 | https://pastebin.com/raw/gtbKvnE1 11 | 12 | https://leezn.eu.org/TVBox/py.json 13 | 14 | https://ghproxy.com/raw.githubusercontent.com/lm317379829/PyramidStore/pyramid/py.json 15 | 16 | https://agit.ai/Yoursmile7/TVBox/raw/branch/master/XC.json 17 | 18 | https://raw.fastgit.org/FongMi/CatVodSpider/main/json/config.json 19 | 20 | https://ghproxy.com/https://raw.githubusercontent.com/mengzehe/tvbox/main/%E8%87%AA%E7%94%A8%E5%8D%95%E4%BB%93 21 | 22 | https://ghproxy.com/https://raw.githubusercontent.com/mengzehe/tvbox/main/%E8%87%AA%E7%94%A8%E6%BA%90 23 | 24 | https://leezn.eu.org/TVBox/py.json 25 | 26 | https://dxawi.github.io/0/0.json 27 | 28 | https://pastebin.com/raw/gtbKvnE1 29 | https://freed.yuanhsing.cf/TVBox/meowcf.json 30 | https://pastebin.com/raw/sbPpDm9G 31 | 32 | https://raw.liucn.cc/box/sub/MeowXB/%E5%BD%B1%E8%A7%86%E5%B7%A5%E5%8E%82.json 33 | 34 | https://cdn.jsdelivr.net/gh/Cyril0563/lanjing_live@main/TVbox_Free/tv.txt 35 | 36 | 37 | https://raw.liucn.cc/box/m.json 38 | http://byyds.top/w.txt 39 | https://dxawi.github.io/0/0.json 40 | https://download.kstore.space/download/2863/01.txt 41 | https://liu673cn.github.io/box/m.json 42 | https://raw.liucn.cc/box/xiaopingguo.json 43 | http://52bsj.vip:81/api/v3/file/get/29899/box2.json?sign=3cVyKZQr3lFAwdB3HK-A7h33e0MnmG6lLB9oWlvSNnM%3D%3A0 44 | https://ghproxy.com/https://raw.githubusercontent.com/chengxueli818913/maoTV/main/44.txt 45 | https://cdn.jsdelivr.net/gh/chengxueli818913/maoTV@main/44.txt 46 | https://freed.yuanhsing.cf/TVBox/meowcf.json 47 | https://notabug.org/imbig66/tv-spider-man/raw/master/配置/0801.json 48 | http://js.134584.xyz/json/pp87.json 49 | http://52bsj.vip:98/wuai 50 | https://pastebin.com/raw/gtbKvnE1 51 | https://cdn.jsdelivr.net/gh/GaiVmao/dianshiyuan@main/yuan2.txt 52 | https://raw.iqiq.io/liu673cn/box/main/m.json 53 | https://raw.iqiq.io/zhanghong1983/TVBOXZY/main/TVBOX/iqiqgr.json 54 | https://ghproxy.com/https://raw.githubusercontent.com/tv-player/tvbox-line/main/tv/ptest.json 55 | https://ghproxy.com/https://raw.githubusercontent.com/tv-player/tvbox-line/main/tv/ikbb.json 56 | https://wds.ecsxs.com/223843.txt 57 | https://pastebin.com/raw/gtbKvnE1 58 | https://pastebin.com/raw/sbPpDm9G 59 | https://agit.ai/hu/hcr/raw/branch/master/MMM.txt 60 | https://freed.yuanhsing.cf/TVBox/meowcf.json 61 | https://github.com/YuanHsing/freed/raw/master/TVBox/meow.json 62 | https://dxawi.github.io/0/0.json 63 | https://raw.githubusercontent.com/UndCover/PyramidStore/main/py.json 64 | https://raw.iqiq.io/lm317379829/PyramidStore/pyramid/py.json 65 | https://leezn.github.io/TVBox/py.json 66 | https://leezn.github.io/TVBox/js.json 67 | https://maoyingshi.cc/tvbox/云星日记/1.m3u8 68 | http://饭太硬.ga/x/o.json 69 | http://pandown.pro/tvbox/tvbox.json 70 | http://drpy.site/js1 71 | https://神器每日推送.tk/pz.json -------------------------------------------------------------------------------- /src/components/tvbox/parses.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 总计:{{ stats.count }}, 已检查: {{ stats.checked }}, 有效:{{ 47 | stats.vaild 48 | }} 49 | 50 | 55 | 删除失效 56 | 57 | 检测 58 | 59 | 60 | 61 | 65 | 66 | 67 | 解析器名称:{{ item.name }}, 类型:{{ item.type }} 68 | 69 | 解析器地址:{{ item.url }} 70 | 71 | 72 | 附加参数: 73 | 74 | 77 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/components/tvbox/vod.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 总计:{{ stats.count }}, 已检查: {{ stats.checked }}, 有效:{{ 53 | stats.vaild 54 | }} 55 | 56 | 57 | 58 | 仅显示失效: 59 | 64 | 65 | 66 | 检测 67 | 68 | 69 | 删除失效 70 | 71 | 72 | 73 | 74 | 75 | 80 | 81 | 接口名称:{{ item.name }} 82 | 接口类型:{{ item.type }} 83 | 点播接口:{{ item.api }} 84 | 85 | 点播功能:{{ item.features }} 86 | 87 | 解析引擎:{{ item.jar }} 88 | 89 | 90 | 附加参数: 91 | 92 | 95 | 96 | 97 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod check; 2 | pub mod playlist; 3 | pub mod source; 4 | use crate::utils; 5 | 6 | use async_trait::async_trait; 7 | use core::future::Future; 8 | use core::pin::Pin; 9 | use core::task::{Context, Poll}; 10 | use std::sync::Arc; 11 | use tauri::{Runtime, Window}; 12 | use tokio::sync::Mutex; 13 | 14 | #[async_trait] 15 | pub trait Connection { 16 | /// 检测是否可以连通 17 | async fn check(&mut self, quick_mode: bool, skip_ipv6: bool) -> anyhow::Result; 18 | } 19 | 20 | /// 地址可访问性, 21 | /// quick_mode开启时检测url服务器是否可连接, 22 | /// quick_mode关闭时检测url地址是否可以访问 23 | /// skip_ipv6:跳过ipv6可以加快检测速度 24 | pub async fn urls_accessibility( 25 | window: Window, 26 | urls: Vec, 27 | quick_mode: bool, 28 | skip_ipv6: Option, 29 | check_m3u8: Option, 30 | ) -> Vec { 31 | if urls.is_empty() { 32 | return vec![]; 33 | } 34 | let skip_ipv6 = skip_ipv6.unwrap_or_default(); 35 | let check_m3u8 = check_m3u8.unwrap_or_default(); 36 | // 生成一个合理线程数 37 | let threads = { 38 | let tasks = urls.len(); 39 | if tasks == 0 { 40 | 1 41 | } else { 42 | let threads = num_cpus::get(); 43 | if tasks > threads * threads { 44 | threads 45 | } else { 46 | let threads = tasks / threads; 47 | if threads == 0 { 48 | 1 49 | } else { 50 | threads 51 | } 52 | } 53 | } 54 | }; 55 | let mut size = urls.len() / threads; 56 | if size == 0 { 57 | size = urls.len(); 58 | } 59 | let total = urls.len() as u64; 60 | let count = Arc::new(Mutex::new(0)); 61 | // 每50份为一个处理单元 62 | let chunck = urls.chunks(size); 63 | // 使用多线程处理数据 64 | let mut tasks = vec![]; 65 | window 66 | .emit( 67 | "urls_accessibility://progress", 68 | check::ProgressPayload { progress: 0, total }, 69 | ) 70 | .ok(); 71 | for c in chunck { 72 | let c = c.to_vec(); 73 | let w = window.clone(); 74 | let cnt = count.clone(); 75 | let t = spawn(async move { 76 | let mut items = vec![]; 77 | for i in c { 78 | if url::Url::parse(&i).is_ok() { 79 | if skip_ipv6 && i.contains("://[") { 80 | // http://[ipv6]:port/path?query or https://[ipv6]:port/path?query 81 | continue; 82 | } 83 | let ok = if quick_mode { 84 | utils::url_connectivity(&i).await.unwrap_or_default() 85 | } else { 86 | if check_m3u8 { 87 | utils::url_m3u8_accessibility(&i).await.unwrap_or_default() 88 | }else{ 89 | utils::url_accessibility(&i).await.unwrap_or_default() 90 | } 91 | }; 92 | if ok { 93 | items.push(i); 94 | } 95 | let mut c = cnt.lock().await; 96 | *c += 1; 97 | w.emit( 98 | "urls_accessibility://progress", 99 | check::ProgressPayload { 100 | progress: *c as u64, 101 | total, 102 | }, 103 | ) 104 | .ok(); 105 | } 106 | } 107 | items 108 | }); 109 | tasks.push(t); 110 | } 111 | let mut items = vec![]; 112 | for t in tasks { 113 | if let Ok(mut v) = t.await { 114 | items.append(&mut v); 115 | } 116 | } 117 | items 118 | } 119 | 120 | /// Spawn a new tokio Task and cancel it on drop. 121 | pub fn spawn(future: T) -> Wrapper 122 | where 123 | T: Future + Send + 'static, 124 | T::Output: Send + 'static, 125 | { 126 | Wrapper(tokio::task::spawn(future)) 127 | } 128 | 129 | /// Cancels the wrapped tokio Task on Drop. 130 | pub struct Wrapper(tokio::task::JoinHandle); 131 | 132 | impl Future for Wrapper { 133 | type Output = Result; 134 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 135 | unsafe { Pin::new_unchecked(&mut self.0) }.poll(cx) 136 | } 137 | } 138 | 139 | impl Drop for Wrapper { 140 | fn drop(&mut self) { 141 | // do `let _ = self.0.cancel()` for `async_std::task::Task` 142 | self.0.abort(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use indicatif::ProgressBar; 5 | pub mod ijk; 6 | pub mod live; 7 | pub mod parse; 8 | pub mod rule; 9 | pub mod vod; 10 | use ijk::Ijk; 11 | use live::Live; 12 | use parse::Parse; 13 | use rule::Rule; 14 | use vod::Vod; 15 | /// 视频源结构 16 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 17 | pub struct Source { 18 | pub sites: Vec, 19 | pub lives: Vec, 20 | /// 解析地址 21 | pub parses: Option>, 22 | /// 需要使用vip解析的flag 23 | pub flags: Option>, 24 | pub ijk: Option>, 25 | pub rules: Option>, 26 | pub ads: Option>, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub wallpaper: Option, 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub spider: Option, 31 | #[serde(rename = "warningText")] 32 | pub warning_text: Option, 33 | } 34 | 35 | impl Source { 36 | pub fn base(&mut self, base: &str) -> Result<()> { 37 | self.sites.iter_mut().for_each(|item| item.base(base)); 38 | self.lives.iter_mut().for_each(|item| item.base(base)); 39 | if let Some(spider) = self.spider.as_mut() { 40 | let mut s = spider.split(";").collect::>(); 41 | let p = s.first_mut().unwrap(); 42 | let n = base_url(base, p); 43 | *p = n.as_str(); 44 | *spider = s.join(";"); 45 | } 46 | Ok(()) 47 | } 48 | pub fn parse(i: &str, illegal_comment: char) -> Result { 49 | // 过滤[#] 50 | let r = regex::Regex::new(&format!("^{}.*", illegal_comment))?; 51 | let i = r.replace_all(&i, "").to_string(); 52 | let r = regex::Regex::new(&format!("\n{}.*", illegal_comment)).unwrap(); 53 | let i = r.replace_all(&i, "").to_string(); 54 | let doc = json5::from_str::(&i); 55 | if doc.is_ok() { 56 | // debug!("json5 解析成功!"); 57 | return Ok(doc.unwrap()); 58 | } 59 | // 过滤[/] 60 | let r = regex::Regex::new("^//.*")?; 61 | let i = r.replace_all(&i, "").to_string(); 62 | let r = regex::Regex::new("\\s+//.*").unwrap(); 63 | let i = r.replace_all(&i, "").to_string(); 64 | let doc = serde_json::from_str::(&i).map_err(|e| { 65 | println!("json5.parse.error: {:?}", e); 66 | anyhow!("解析失败, 不是有效的 json/json5 文件.") 67 | })?; 68 | // debug!("json 解析成功!"); 69 | Ok(doc) 70 | } 71 | } 72 | 73 | /// 74 | /// let pb = progress_bar(self.sites.len() as u64);+ 75 | /// pb.inc(1); 76 | /// pb.finish(); 77 | /// 78 | pub fn progress_bar(count: u64) -> Arc { 79 | let len = format!("{}", count).len(); 80 | let template = format!( 81 | "[{{elapsed_precise}}] {{wide_bar:.white/white}} {{pos:>{}}}/{{len:{}}}", 82 | len, len 83 | ); 84 | let pb = ProgressBar::new(count as u64); 85 | let style = indicatif::ProgressStyle::with_template(&template) 86 | .unwrap() 87 | .progress_chars("█░"); 88 | pb.enable_steady_tick(std::time::Duration::from_secs(1)); 89 | pb.set_style(style); 90 | Arc::new(pb) 91 | } 92 | 93 | fn base_url(base: &str, path: &str) -> String { 94 | if path.starts_with(".") || path.starts_with("/") { 95 | if let Ok(base) = url::Url::parse(base) { 96 | return base 97 | .join(path) 98 | .and_then(|new| Ok(new.to_string())) 99 | .unwrap_or(path.to_string()); 100 | } 101 | } 102 | path.to_string() 103 | } 104 | 105 | #[tokio::test] 106 | async fn test_d() { 107 | let _i = "https://jihulab.com/z-blog/xh2/-/raw/main/t.json"; 108 | let i = "https://jihulab.com/z-blog/vip/-/raw/main/ysc/t.json"; 109 | let content = crate::utils::read_content(i).await; 110 | assert!(content.is_ok()); 111 | let content = content.unwrap(); 112 | let src = Source::parse(&content, '#'); 113 | println!("{:?}", src); 114 | assert!(src.is_ok()); 115 | let src = src.unwrap(); 116 | println!("live: {}", src.lives.len()); 117 | println!("sites: {}", src.sites.len()); 118 | println!("spider: {:?}", src.spider); 119 | println!("parses: {}", src.parses.is_some()); 120 | println!("ads: {:?}", src.ads.and_then(|s| Some(s.len()))); 121 | println!("flags: {:?}", src.flags.and_then(|s| Some(s.len()))); 122 | println!("rules: {:?}", src.rules.and_then(|r| Some(r.len()))); 123 | println!("wallpaper: {:?}", src.wallpaper); 124 | println!("warning_text: {:?}", src.warning_text); 125 | src.sites.iter().for_each(|i| { 126 | if i.key == "csp_xBPQ_奇优" { 127 | println!("{:?}", i) 128 | } 129 | }); 130 | assert_eq!(1, 1) 131 | } 132 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/live.rs: -------------------------------------------------------------------------------- 1 | use crate::utils; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use base64::{engine::general_purpose, Engine}; 5 | 6 | use super::{super::Connection, base_url}; 7 | 8 | #[derive(Debug, Clone, Serialize, Deserialize)] 9 | pub struct Live { 10 | pub name: Option, 11 | pub group: Option, 12 | pub channels: Option>, 13 | pub epg: Option, 14 | #[serde(rename = "type")] 15 | pub src_type: Option, 16 | pub url: Option, 17 | } 18 | 19 | impl Live { 20 | pub fn base(&mut self, base: &str) { 21 | if let Some(url) = self.url.as_mut() { 22 | *url = base_url(base, &url); 23 | } 24 | if let Some(chns) = self.channels.as_mut() { 25 | chns.iter_mut().for_each(|c| c.base(base)) 26 | } 27 | } 28 | } 29 | 30 | #[async_trait] 31 | impl Connection for Live { 32 | async fn check(&mut self, quick_mode: bool, skip_ipv6: bool) -> Result { 33 | let mut ok = false; 34 | // url 35 | if let Some(url) = self.url.as_ref() { 36 | if skip_ipv6 && url.contains("://[") { 37 | self.url = None; 38 | } else { 39 | let connectable = if quick_mode { 40 | utils::url_connectivity(url).await.unwrap_or_default() 41 | } else { 42 | utils::url_txt_playlist_accessibility(url).await.unwrap_or_default() 43 | }; 44 | // if !connectable { 45 | // self.url = None; 46 | // } 47 | ok = connectable; 48 | } 49 | } 50 | // channel 51 | if let Some(channels) = self.channels.as_mut() { 52 | let mut items = vec![]; 53 | for i in channels { 54 | i.check(quick_mode, skip_ipv6).await.ok(); 55 | if !i.urls.is_empty() { 56 | items.push(i.clone()); 57 | }; 58 | } 59 | if !ok { 60 | ok = !items.is_empty(); 61 | } 62 | self.channels = Some(items); 63 | } 64 | Ok(ok) 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone, Serialize, Deserialize)] 69 | pub struct Channel { 70 | pub name: String, 71 | pub urls: Vec, 72 | } 73 | impl Channel { 74 | pub fn base(&mut self, base: &str) { 75 | for i in &mut self.urls { 76 | *i = base_url(base, i); 77 | } 78 | } 79 | } 80 | 81 | #[async_trait] 82 | impl Connection for Channel { 83 | async fn check(&mut self, quick_mode: bool, skip_ipv6: bool) -> Result { 84 | let mut connectable = vec![]; 85 | for i in &self.urls { 86 | if utils::is_http_url(&i) { 87 | if skip_ipv6 && i.contains("://[") { 88 | continue; 89 | } 90 | let ok = if quick_mode { 91 | utils::url_connectivity(&i).await.unwrap_or_default() 92 | } else { 93 | utils::url_accessibility(&i).await.unwrap_or_default() 94 | }; 95 | if ok { 96 | connectable.push(i.to_string()) 97 | } 98 | } else if i.starts_with("proxy://") { 99 | let proxy = i.trim_start_matches("proxy://"); 100 | let proxy = Proxy::parse(proxy).ok(); 101 | if let Some(proxy) = proxy.and_then(|p| p.url()) { 102 | if skip_ipv6 && proxy.contains("://[") { 103 | continue; 104 | } 105 | let ok = if quick_mode { 106 | utils::url_connectivity(&proxy).await.unwrap_or_default() 107 | } else { 108 | utils::url_accessibility(&proxy).await.unwrap_or_default() 109 | }; 110 | if ok { 111 | connectable.push(i.to_string()) 112 | } 113 | } 114 | } 115 | } 116 | self.urls = connectable; 117 | Ok(true) 118 | } 119 | } 120 | 121 | #[derive(Debug, Clone, Serialize, Deserialize)] 122 | pub struct Proxy { 123 | #[serde(rename = "do")] 124 | pub action: String, 125 | #[serde(rename = "type")] 126 | pub kind: String, 127 | pub ext: String, 128 | } 129 | 130 | impl Proxy { 131 | pub fn parse(input: &str) -> Result { 132 | let doc = serde_qs::from_str(input)?; 133 | Ok(doc) 134 | } 135 | pub fn url(&self) -> Option { 136 | if utils::is_http_url(&self.ext) { 137 | return Some(self.ext.to_string()); 138 | } 139 | let decode = general_purpose::URL_SAFE 140 | .decode(&self.ext) 141 | .ok() 142 | .and_then(|buff| String::from_utf8(buff).ok()); 143 | decode 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/source/vod.rs: -------------------------------------------------------------------------------- 1 | use super::{super::Connection, base_url}; 2 | use crate::utils; 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | use serde_aux::prelude::*; 6 | 7 | // 参考: https://github.com/takagen99/Box/blob/main/app/src/main/java/com/github/tvbox/osc/bean/SourceBean.java 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | pub struct Vod { 11 | pub key: String, 12 | pub name: String, 13 | /// 0 xml 1 json 3 Spider 14 | #[serde(rename = "type", deserialize_with = "deserialize_number_from_string")] 15 | pub src_type: i32, 16 | // 接口地址 17 | pub api: String, 18 | /// 是否可搜索 19 | #[serde(default, deserialize_with = "deserialize_number_from_string")] 20 | pub searchable: i32, 21 | /// 是否可以快速搜索 22 | #[serde( 23 | rename = "quickSearch", 24 | default, 25 | deserialize_with = "deserialize_number_from_string" 26 | )] 27 | pub quick_search: i32, 28 | /// 可筛选? 29 | #[serde(default, deserialize_with = "deserialize_option_number_from_string")] 30 | pub filterable: Option, 31 | // 站点解析Url 32 | #[serde(rename = "playerUrl")] 33 | pub player_url: Option, 34 | /// 扩展数据 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub ext: Option, 37 | // 自定义JAR刮削库 - 引用第三方 38 | pub jar: Option, 39 | /// 播放器类型 40 | /// 0 system 1 ikj 2 exo 10 mxplayer -1 以参数设置页面的为准 41 | #[serde( 42 | rename = "playerType", 43 | default = "Vod::player_type_default", 44 | deserialize_with = "deserialize_option_number_from_string" 45 | )] 46 | pub player_type: Option, 47 | /// 分类&排序 48 | pub categories: Option>, 49 | /// 需要点击播放的嗅探站点selector ddrk.me;#id 50 | pub click: Option, 51 | #[serde( 52 | default = "Vod::player_type_default", 53 | deserialize_with = "deserialize_option_number_from_string" 54 | )] 55 | pub hide: Option, 56 | } 57 | impl Vod { 58 | pub fn player_type_default() -> Option { 59 | Some(-1) 60 | } 61 | pub fn base(&mut self, base: &str) { 62 | self.api = base_url(base, &self.api); 63 | if let Some(ext) = self.ext.as_mut() { 64 | match ext { 65 | serde_json::Value::String(x) => { 66 | *x = base_url(base, x); 67 | } 68 | _ => {} 69 | } 70 | } 71 | } 72 | } 73 | 74 | #[async_trait] 75 | impl Connection for Vod { 76 | async fn check(&mut self, quick_mode: bool, skip_ipv6: bool) -> Result { 77 | // 当前测试仅针对,api和ext两个字段 78 | if utils::is_http_url(&self.api) { 79 | let ok = if quick_mode { 80 | utils::url_connectivity(&self.api).await? 81 | } else { 82 | utils::url_accessibility(&self.api).await? 83 | }; 84 | if !ok { 85 | return Err(anyhow!("连接失败!")); 86 | } 87 | if let Some(ext) = self.ext.as_ref() { 88 | match ext { 89 | serde_json::Value::String(x) => { 90 | // 跳过本地ip 91 | if x.starts_with("http://127.0.0.1") || x.starts_with("http://localhost") { 92 | return Ok(true); 93 | } 94 | // 测试js-spider 95 | if utils::is_http_url(&x) { 96 | if skip_ipv6 && x.contains("://[") { 97 | return Ok(false); 98 | } 99 | // 检查配置文件在的点播站点是否还有效 100 | // js通常会配合drpy.js一起使用 101 | if x.ends_with(".js") || x.ends_with(".py") { 102 | let content = reqwest::get(x).await?.text().await?; 103 | let r = regex::Regex::new("https?://[0-9A-Za-z.%-]+")?; 104 | let host = r 105 | .find(&content) 106 | .and_then(|m| Some(m.as_str().to_string())) 107 | .ok_or(anyhow!("找不到点播站点"))?; 108 | let ok = utils::url_connectivity(&host).await.unwrap_or_default(); 109 | if !ok { 110 | return Err(anyhow!("点播站点无法连接!")); 111 | } 112 | } else { 113 | // 其他地址只要能保证可以访问就行 114 | let ok = if quick_mode { 115 | utils::url_connectivity(&x).await? 116 | } else { 117 | utils::url_accessibility(&x).await? 118 | }; 119 | if !ok { 120 | return Err(anyhow!("连接失败!")); 121 | } 122 | } 123 | } 124 | } 125 | _ => {} 126 | } 127 | } 128 | } 129 | Ok(true) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/components/tvbox/live.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 总计:{{ stats.count }}, 已检查: {{ stats.checked }}, 有效:{{ 70 | stats.vaild 71 | }} 72 | 73 | 78 | 添加 79 | 80 | 检测 81 | 82 | 83 | 84 | 88 | 89 | 名称:{{ item.name }} 90 | 分组:{{ item.group }} 91 | 92 | 类型:{{ item.type }} 93 | 94 | 95 | 地址: 96 | 97 | 98 | EPG:{{ item.epg }} 99 | 100 | 101 | 频道 102 | 103 | 104 | 105 | 106 | 名称: 107 | {{ chn.name }} 108 | 109 | 110 | 地址: 111 | 112 | 115 | {{ url }} 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src-tauri/src/desktop.rs: -------------------------------------------------------------------------------- 1 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command 2 | 3 | use crate::{ 4 | tvbox::{ 5 | self, 6 | check::ConnectionStatus, 7 | source::{live::Live, parse::Parse, vod::Vod}, 8 | }, 9 | utils, 10 | }; 11 | use tauri::{Result, Runtime, Window}; 12 | 13 | #[tauri::command] 14 | pub async fn parse_playlist( 15 | uri: String, 16 | threads: Option, 17 | skip_ipv6: Option, 18 | ) -> Result { 19 | let content = utils::read_content(&uri).await.map_err(|e| { 20 | println!("utils::read_content:{:?}", e); 21 | tauri::Error::AssetNotFound(e.to_string()) 22 | })?; 23 | let source = tvbox::playlist::PlaylistSource { 24 | threads, 25 | skip_ipv6, 26 | content, 27 | }; 28 | let res = source 29 | .check() 30 | .await 31 | .map_err(|e| tauri::Error::ApiNotAllowlisted(e.to_string()))?; 32 | Ok(res) 33 | } 34 | 35 | #[tauri::command] 36 | pub async fn parse_tvbox(uri: String, base: Option) -> Result { 37 | let content = utils::read_content(&uri).await.map_err(|e| { 38 | println!("err:{:?}", e); 39 | tauri::Error::AssetNotFound(e.to_string()) 40 | })?; 41 | let mut source = tvbox::source::Source::parse(&content, '#').map_err(|e| { 42 | println!("err:{:?}", e); 43 | tauri::Error::ApiNotAllowlisted(e.to_string()) 44 | })?; 45 | if uri.starts_with("http://") || uri.starts_with("https://") { 46 | source.base(&uri).ok(); 47 | } else if let Some(base) = base { 48 | source.base(&base).ok(); 49 | } 50 | Ok(source) 51 | } 52 | 53 | #[tauri::command] 54 | pub async fn get_content(uri: String) -> String { 55 | utils::read_content(&uri).await.unwrap_or_default() 56 | } 57 | 58 | #[tauri::command] 59 | pub async fn urls_accessibility( 60 | window: Window, 61 | urls: Vec, 62 | quick_mode: Option, 63 | skip_ipv6: Option, 64 | check_m3u8: Option, 65 | ) -> Vec { 66 | tvbox::urls_accessibility(window, urls, quick_mode.unwrap_or_default(), skip_ipv6,check_m3u8).await 67 | } 68 | 69 | /// 执行 70 | #[tauri::command] 71 | pub async fn exec(args: String) -> String { 72 | inline_exec(args).await 73 | } 74 | 75 | #[tauri::command] 76 | pub async fn vods_connectivity( 77 | window: Window, 78 | items: Vec, 79 | quick_mode: Option, 80 | skip_ipv6: Option, 81 | ) -> Vec> 82 | where 83 | { 84 | let items = 85 | tvbox::check::check_connections(window, items, quick_mode.unwrap_or_default(), skip_ipv6) 86 | .await; 87 | items 88 | } 89 | #[tauri::command] 90 | pub async fn live_connectivity( 91 | window: Window, 92 | items: Vec, 93 | quick_mode: Option, 94 | skip_ipv6: Option, 95 | ) -> Vec> 96 | where 97 | { 98 | let items = 99 | tvbox::check::check_connections(window, items, quick_mode.unwrap_or_default(), skip_ipv6) 100 | .await; 101 | items 102 | } 103 | 104 | #[tauri::command] 105 | pub async fn parses_connectivity( 106 | window: Window, 107 | items: Vec, 108 | quick_mode: Option, 109 | skip_ipv6: Option, 110 | ) -> Vec> 111 | where 112 | { 113 | let items = 114 | tvbox::check::check_connections(window, items, quick_mode.unwrap_or_default(), skip_ipv6) 115 | .await; 116 | items 117 | } 118 | 119 | #[tauri::command] 120 | pub async fn save(path: String, content: String) -> bool { 121 | std::fs::write(path, content).is_ok() 122 | } 123 | 124 | #[tauri::command] 125 | pub async fn cache(key: String, value: String) { 126 | crate::server::updata_cache(&key, value).await; 127 | } 128 | 129 | #[tauri::command] 130 | pub async fn lan_ip() -> Option> { 131 | crate::utils::lan_ip() 132 | } 133 | 134 | #[tauri::command] 135 | pub async fn is_install(application: String) -> bool { 136 | crate::utils::is_installed(&application) 137 | } 138 | 139 | pub async fn inline_exec(args: String) -> String { 140 | if args.is_empty() { 141 | return args; 142 | } 143 | let (shell, first) = if cfg!(windows) { 144 | ("cmd", "/c") 145 | } else { 146 | ("sh", "-c") 147 | }; 148 | println!("args: {}", args); 149 | let child = tokio::process::Command::new(shell) 150 | .args([first]) 151 | .args(&[args]) 152 | .stdout(std::process::Stdio::piped()) 153 | .stderr(std::process::Stdio::piped()) 154 | .kill_on_drop(true) 155 | .spawn(); 156 | if let Ok(child) = child { 157 | let one_minute = std::time::Duration::from_secs(60); 158 | tokio::time::timeout(one_minute, child.wait_with_output()) 159 | .await 160 | .ok() 161 | .and_then(|out| out.map_err(|e| println!("shell.error: {:?}", e)).ok()) 162 | .and_then(|out| { 163 | println!("out: {:?}", out); 164 | String::from_utf8(out.stdout).ok() 165 | }) 166 | .unwrap_or_default() 167 | } else { 168 | println!("shell.err2: {:?}", child); 169 | String::default() 170 | } 171 | } 172 | 173 | #[tauri::command] 174 | pub async fn download(url: String, path: String) -> bool { 175 | if let Ok(resp) = reqwest::get(url).await { 176 | if let Ok(buff) = resp.bytes().await { 177 | return std::fs::write(path, buff).is_ok(); 178 | } 179 | } 180 | false 181 | } 182 | 183 | #[tauri::command] 184 | pub async fn hash(content: String) -> String { 185 | let value = xxhash_rust::xxh3::xxh3_64_with_seed(content.as_bytes(), 42); 186 | format!("{:0>16X}", value) 187 | } 188 | 189 | 190 | #[tokio::test] 191 | async fn test_exec() { 192 | let args = r#"start /d D:\"Program Files"\mpv mpv http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226395/1.m3u8"#; 193 | inline_exec(args.to_string()).await; 194 | assert_eq!(1, 1) 195 | } 196 | -------------------------------------------------------------------------------- /src/components/tvbox.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 71 | 76 | 77 | 78 | 83 | 84 | 85 | 86 | 87 | 88 | 当前进度: {{ tvbox.tips }} 89 | 90 | 91 | 92 | 93 | 94 | 100 | 101 | 加载 102 | 103 | 104 | 105 | 106 | 107 | 112 | 预览 113 | 114 | 115 | 合并 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 167 | 168 | 169 | 175 | 176 | 177 | 178 | 179 | 180 | 189 | -------------------------------------------------------------------------------- /src-tauri/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use cached::proc_macro::cached; 3 | use reqwest::header::CONTENT_TYPE; 4 | use std::{net::ToSocketAddrs, time::Duration}; 5 | use url::Url; 6 | 7 | /// 检测url的服务器网络可连接性,并不检测实际url的内容 8 | #[cached(key = "String", result = true, convert = r#"{ format!("{}", uri) }"#)] 9 | pub async fn url_connectivity(uri: &str) -> Result { 10 | let uri = url::Url::parse(&uri)?; 11 | let host = uri.host().ok_or(anyhow!("无效主机"))?.to_string(); 12 | let port = uri.port().unwrap_or(80); 13 | let origin = format!("{}:{}", host, port); 14 | let addr = origin 15 | .to_socket_addrs()? 16 | .next() 17 | .ok_or(anyhow!("无效主机"))?; 18 | tokio::time::timeout( 19 | tokio::time::Duration::from_secs_f32(0.5), 20 | tokio::net::TcpStream::connect(addr), 21 | ) 22 | .await??; 23 | Ok(true) 24 | } 25 | 26 | /// 检测url的可访问性 27 | /// 超时时间被设置为1.5秒 28 | pub async fn url_accessibility(uri: &str) -> Result { 29 | let ok = url_connectivity(uri).await?; 30 | if !ok { 31 | return Ok(false); 32 | } 33 | let client = reqwest::ClientBuilder::new() 34 | .user_agent( 35 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0", 36 | ) 37 | .connect_timeout(Duration::from_secs_f32(6.0)) 38 | .timeout(Duration::from_secs_f32(10.0)) 39 | .build()?; 40 | let resp = client.get(uri).send().await?; 41 | Ok(resp.status().is_success()) 42 | } 43 | 44 | /// 检测tvbox中直播源的url的可访问性 45 | pub async fn url_txt_playlist_accessibility(uri: &str) -> Result { 46 | let ok = url_connectivity(uri).await?; 47 | if !ok { 48 | return Ok(false); 49 | } 50 | let client = reqwest::ClientBuilder::new() 51 | .user_agent( 52 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0", 53 | ) 54 | .connect_timeout(Duration::from_secs_f32(6.0)) 55 | .timeout(Duration::from_secs_f32(10.0)) 56 | .build()?; 57 | let resp = client.get(uri).send().await?; 58 | if resp.status().is_success() { 59 | let text_plain = resp 60 | .headers() 61 | .get(CONTENT_TYPE) 62 | .and_then(|contnet_type| contnet_type.to_str().ok()) 63 | .and_then(|contnet_type| Some(contnet_type.contains("text/plain"))) 64 | .unwrap_or_default(); 65 | if text_plain { 66 | let content = resp.text().await?; 67 | let checked = content 68 | .lines() 69 | .any(|line| line.contains("http://") || line.contains("https://")); 70 | return Ok(checked); 71 | } 72 | } 73 | Ok(false) 74 | } 75 | 76 | /// 检测m3u8直播地址url的可访问性 77 | pub async fn url_m3u8_accessibility(uri: &str) -> Result { 78 | let ok = url_connectivity(uri).await?; 79 | if !ok { 80 | return Ok(false); 81 | } 82 | let client = reqwest::ClientBuilder::new() 83 | .user_agent( 84 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0", 85 | ) 86 | .connect_timeout(Duration::from_secs_f32(6.0)) 87 | .timeout(Duration::from_secs_f32(10.0)) 88 | .build()?; 89 | let resp = client.get(uri).send().await?; 90 | let uri = Url::parse(uri)?; 91 | if resp.status().is_success() { 92 | // 假定所有请求的url都必须是m3u8 93 | let path = uri.path(); 94 | let not_m3u8 = path.ends_with(".mp4") 95 | || path.ends_with(".flv") 96 | || path.ends_with(".mkv") 97 | || path.ends_with(".webm") 98 | || path.ends_with(".av1"); 99 | if not_m3u8 { 100 | return Ok(true) 101 | } else { 102 | let is_m3u8 = resp 103 | .headers() 104 | .get(CONTENT_TYPE) 105 | .and_then(|contnet_type| contnet_type.to_str().ok()) 106 | .and_then(|contnet_type| Some(contnet_type.contains("mpegURL"))) 107 | .unwrap_or_default(); 108 | if is_m3u8 { 109 | let content = resp.text().await?; 110 | let checked = m3u8_rs::parse_playlist(content.as_bytes()).is_ok(); 111 | return Ok(checked); 112 | } 113 | } 114 | } 115 | Ok(false) 116 | } 117 | 118 | /// ipv6下待测试 119 | pub async fn ipv6_connectable() -> bool { 120 | // Ali DNS 121 | let url = "tcp://[2400:3200::1]:53"; 122 | url_connectivity(url).await.unwrap_or_default() 123 | } 124 | 125 | pub fn is_http_url(i: &str) -> bool { 126 | return i.to_lowercase().starts_with("http://") || i.to_lowercase().starts_with("https://"); 127 | } 128 | 129 | /// 读取uri中的内容 130 | pub async fn read_content(uri: &str) -> anyhow::Result { 131 | let href = url::Url::parse(uri) 132 | .ok() 133 | .and_then(|uri| if uri.has_host() { Some(uri) } else { None }); 134 | if let Some(uri) = href { 135 | let content = reqwest::get(uri).await?.text().await?; 136 | Ok(content) 137 | } else if std::path::Path::new(&uri).exists() { 138 | let content = std::fs::read_to_string(&uri)?; 139 | 140 | Ok(content) 141 | } else { 142 | Err(anyhow!("无效资源!")) 143 | } 144 | } 145 | 146 | pub fn lan_ip() -> Option> { 147 | default_net::get_default_interface().ok().and_then(|i| { 148 | Some( 149 | i.ipv4 150 | .into_iter() 151 | .map(|ip| ip.addr.to_string()) 152 | .collect::>(), 153 | ) 154 | }) 155 | } 156 | 157 | pub fn is_installed(app: &str) -> bool { 158 | if let Some(os_path) = std::env::var_os("PATH") { 159 | let s = if cfg!(windows) { ";" } else { ":" }; 160 | os_path 161 | .into_string() 162 | .unwrap_or_default() 163 | .split(s) 164 | .map(|p| { 165 | if cfg!(windows) { 166 | std::path::Path::new(p) 167 | .join(format!("{}.exe", app)) 168 | .exists() 169 | } else { 170 | std::path::Path::new(p).join(app).exists() 171 | } 172 | }) 173 | .any(|exist| exist) 174 | } else { 175 | false 176 | } 177 | } 178 | 179 | #[tokio::test] 180 | async fn test_get() { 181 | let x = "https://www.baidu.com/asd"; 182 | let n = url_connectivity(x).await.unwrap_or_default(); 183 | assert!(n) 184 | } 185 | #[tokio::test] 186 | async fn test_rtsp() { 187 | let x = "rtsp://admin:mm4123456@192.168.0.220:554/Streaming/Channels/101"; 188 | let n = url_connectivity(x).await.unwrap_or_default(); 189 | assert!(n) 190 | } 191 | 192 | #[test] 193 | fn test_mpv_installed() { 194 | let app = "mpv"; 195 | let exist = is_installed(app); 196 | assert!(exist) 197 | } 198 | -------------------------------------------------------------------------------- /src/components/playlist/txt-group.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 86 | 87 | 88 | 94 | 95 | {{ item.group }}({{ item.items.length }}) 98 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 131 | 132 | 138 | 139 | 144 | 145 | 146 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 167 | 168 | 169 | 175 | 176 | 177 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 195 | 删除选中 196 | 197 | 198 | 转移选中 199 | 200 | 201 | 频道排序 202 | 203 | 204 | 205 | 206 | 207 | 213 | 214 | 220 | 221 | ({{ item.items.length }}) 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /src/store/txt.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref, computed } from "vue"; 3 | import { invoke } from "@tauri-apps/api/tauri"; 4 | import { open } from "@tauri-apps/api/dialog"; 5 | import { dirname, basename, sep } from "@tauri-apps/api/path"; 6 | import { confirm } from "../utils"; 7 | async function loadResource(uri: string) { 8 | uri = uri.trim(); 9 | if (!uri) { 10 | return ""; 11 | } 12 | const content = await invoke("get_content", { 13 | uri, 14 | }).catch((e) => { 15 | console.log(e); 16 | return ""; 17 | }); 18 | return content; 19 | } 20 | async function checkResource(items: TxtPlaylist[]) { 21 | if (!items.length) { 22 | return []; 23 | } 24 | console.time("check"); 25 | const value = await invoke("urls_accessibility", { 26 | urls: items.map((item) => item.url), 27 | check_m3u8: true, 28 | }).catch((e) => { 29 | console.log(e); 30 | return []; 31 | }); 32 | console.timeEnd("check"); 33 | return value; 34 | } 35 | async function try_play(url: string) { 36 | const exist = await invoke("is_install", { application: "mpv" }); 37 | if (exist) { 38 | await invoke("exec", { args: `start mpv ${url}` }); 39 | return; 40 | } 41 | let exec = window.localStorage.getItem("native_player"); 42 | if (!exec) { 43 | await confirm("请配置mpv播放器"); 44 | const selected = await open({ 45 | multiple: true, 46 | filters: [ 47 | { 48 | name: "mpv", 49 | extensions: ["exe", ""], 50 | }, 51 | ], 52 | }); 53 | if (Array.isArray(selected)) { 54 | exec = selected[0]; 55 | } else if (typeof selected == "string") { 56 | exec = selected; 57 | } 58 | window.localStorage.setItem("native_player", exec || ""); 59 | } 60 | if (!exec) { 61 | return; 62 | } 63 | const dir = await dirname(exec); 64 | const dir_fixed = dir 65 | .split(sep) 66 | .map((name) => { 67 | if (/\s/.test(name)) { 68 | return `"${name}"`; 69 | } 70 | return name; 71 | }) 72 | .join(sep); 73 | const name = await basename(exec); 74 | const args = `start /d ${dir_fixed} ${name.split(".")[0]} ${url}`; 75 | // TODO 仅对windows做了处理 76 | const info = await invoke("exec", { args }).catch((e) => { 77 | console.log("exec.error", e); 78 | }); 79 | console.log("exec.info", info); 80 | } 81 | async function to_playlist(content: string) { 82 | const items = content 83 | .split("\n") 84 | .map((line) => { 85 | const item = line.split(",", 2); 86 | return { 87 | name: item[0], 88 | url: item[1] || "", 89 | online: 0, 90 | http: maybe_http(item[1]), 91 | group: !item[1] || item[1].startsWith("#"), 92 | raw: line, 93 | hash: "" 94 | } as TxtPlaylist; 95 | }) 96 | .filter((item) => item.url); 97 | for (let i = 0; i < items.length; i++) { 98 | items[i].hash = await invoke("hash", { 99 | content: items[i].raw, 100 | }) 101 | } 102 | 103 | return items; 104 | } 105 | 106 | function maybe_http(str: string) { 107 | if (!str) { 108 | return false; 109 | } 110 | return /^https?:\/\//i.test(str); 111 | } 112 | 113 | function dedup_by_url(items: TxtPlaylist[]) { 114 | const hashs = [] as string[]; 115 | let list = [] as TxtPlaylist[]; 116 | items.forEach((item) => { 117 | if (!hashs.includes(item.hash)) { 118 | hashs.push(item.hash); 119 | list.push(item) 120 | } 121 | }) 122 | return list; 123 | } 124 | 125 | const useTxtPlaylistStore = defineStore("txt_playlist", () => { 126 | const content = ref([] as TxtPlaylist[]); 127 | const content_text = computed(() => { 128 | return to_string(); 129 | }); 130 | const group = computed({ 131 | get() { 132 | let i = 0; 133 | let items = [] as ITxtPlaylistGroup[]; 134 | content.value.forEach((item: TxtPlaylist) => { 135 | if (item.group) { 136 | i = items.length; 137 | if (!items[i]) { 138 | items[i] = { group: item.name, raw: item, items: [] } as ITxtPlaylistGroup; 139 | } 140 | } else { 141 | items[i].items.push(item); 142 | } 143 | }); 144 | return items; 145 | }, 146 | set(v: ITxtPlaylistGroup[]) { 147 | const items = v.map(i => { 148 | return [i.raw, ...i.items] 149 | }).flat(); 150 | content.value = items; 151 | } 152 | }) 153 | const load = async (uri: string) => { 154 | const text = await loadResource(uri); 155 | const list = await to_playlist(text); 156 | const items = dedup_by_url(list); 157 | content.value = items; 158 | }; 159 | const push = async (uri: string) => { 160 | const text = await loadResource(uri); 161 | const items = await to_playlist(text); 162 | const new_items = content.value.concat(items); 163 | const deduped = dedup_by_url(new_items); 164 | // 除去空白分组 165 | let index = 0; 166 | let list = deduped.filter((item, i) => { 167 | if (!item.url) { 168 | const current = index + 1; 169 | index = i; 170 | if (current == i) { 171 | return false; 172 | } 173 | } 174 | return true 175 | }) 176 | content.value = list; 177 | }; 178 | const to_string = () => { 179 | return content.value 180 | .map((item) => { 181 | return `${item.name},${item.url}`; 182 | }) 183 | .join("\n"); 184 | }; 185 | const check = async () => { 186 | const urls = await checkResource(content.value); 187 | content.value.forEach((item) => { 188 | if (urls.includes(item.url)) { 189 | item.online = 1; 190 | } else if (item.http) { 191 | item.online = -1; 192 | } 193 | }); 194 | }; 195 | const check_by = async (items: TxtPlaylist[]) => { 196 | const keys = items.map((item) => item.url); 197 | const urls = await checkResource(items); 198 | content.value.forEach((item) => { 199 | if (keys.includes(item.url)) { 200 | item.online = urls.includes(item.url) ? 1 : -1; 201 | } 202 | }); 203 | }; 204 | const remove_by = (items: TxtPlaylist[]) => { 205 | const keys = items.map((item) => item.name + "-" + item.url); 206 | content.value = content.value.filter((item) => { 207 | const key = item.name + "-" + item.url; 208 | return !keys.includes(key); 209 | }); 210 | }; 211 | const play = async (item: TxtPlaylist) => { 212 | if (item.http) { 213 | try_play(item.url); 214 | } 215 | }; 216 | const update = async (text: string) => { 217 | const list = await to_playlist(text); 218 | const items = dedup_by_url(list); 219 | content.value = items; 220 | }; 221 | const cache = async () => { 222 | await invoke("cache", { key: "playlist", value: content_text.value }); 223 | }; 224 | const group_move = async (hashs: string[], from: string, to: string) => { 225 | console.log('hash', hashs); 226 | 227 | let retain = [] as TxtPlaylist[]; 228 | const items = group.value.map(item => { 229 | if (item.raw.hash == from) { 230 | retain = item.items.filter(i => hashs.includes(i.hash)); 231 | console.log(retain.length); 232 | 233 | item.items = item.items.filter(i => !hashs.includes(i.hash)) 234 | return item 235 | } else { 236 | return item 237 | } 238 | }) 239 | console.log('retain', retain.length); 240 | 241 | items.forEach(item => { 242 | if (item.raw.hash == to) { 243 | console.log('retain', retain.length, to); 244 | item.items.push(...retain); 245 | } 246 | }) 247 | group.value = items; 248 | } 249 | return { 250 | content, 251 | content_text, 252 | group, 253 | load, 254 | push, 255 | to_string, 256 | check, 257 | check_by, 258 | remove_by, 259 | play, 260 | update, 261 | cache, 262 | group_move, 263 | }; 264 | }); 265 | 266 | export { useTxtPlaylistStore }; 267 | -------------------------------------------------------------------------------- /src-tauri/src/tvbox/playlist.rs: -------------------------------------------------------------------------------- 1 | use crate::utils; 2 | use anyhow::Result; 3 | use m3u8_rs::{AlternativeMedia, MediaSegment, Playlist, VariantStream}; 4 | 5 | #[derive(Debug, Default, Serialize)] 6 | pub struct PlaylistCheckResult { 7 | pub loss: usize, 8 | pub count: usize, 9 | pub content: String, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | pub struct PlaylistSource { 14 | /// 指定线程数量:默认16 15 | pub threads: Option, 16 | /// 播放列表内容 17 | pub content: String, 18 | /// 跳过IPv6的URL地址检查 19 | pub skip_ipv6: Option, 20 | } 21 | /// TODO 待实现进度功能 22 | impl PlaylistSource { 23 | pub fn threads(&self, tasks: usize) -> usize { 24 | let tasks = tasks.max(1); 25 | let threads = (num_cpus::get() / 2).max(1); 26 | if tasks > threads * threads { 27 | threads 28 | } else { 29 | (tasks / threads).max(1) 30 | } 31 | } 32 | pub fn skip_ipv6(&self) -> bool { 33 | let b = self.skip_ipv6.as_ref().unwrap_or(&true); 34 | *b 35 | } 36 | 37 | pub async fn check(&self) -> Result { 38 | let content = self.content.as_bytes(); 39 | match m3u8_rs::parse_playlist_res(&content) { 40 | Ok(Playlist::MasterPlaylist(mut pl)) => { 41 | println!("{:#?}", pl); 42 | let count = pl.alternatives.len(); 43 | pl.alternatives = self.check_master_playlist(pl.alternatives).await; 44 | let loss = count - pl.alternatives.len(); 45 | 46 | pl.variants = self.check_variant_stream(pl.variants).await; 47 | 48 | let mut content = vec![]; 49 | pl.write_to(&mut content)?; 50 | Ok(PlaylistCheckResult { 51 | loss, 52 | count, 53 | content: String::from_utf8(content)?, 54 | }) 55 | } 56 | Ok(Playlist::MediaPlaylist(mut pl)) => { 57 | let count = pl.segments.len(); 58 | pl.segments = self.check_media_playlist(pl.segments).await; 59 | let loss = count - pl.segments.len(); 60 | let mut content = vec![]; 61 | pl.write_to(&mut content)?; 62 | Ok(PlaylistCheckResult { 63 | loss, 64 | count, 65 | content: String::from_utf8(content)?, 66 | }) 67 | } 68 | Err(e) => { 69 | // 对错误进行简单的处理 70 | let err = e.map(|e| format!("Error.code: {:?}", e.code)).to_string(); 71 | Err(anyhow!(err)) 72 | } 73 | } 74 | } 75 | 76 | async fn check_master_playlist( 77 | &self, 78 | playlist: Vec, 79 | ) -> Vec { 80 | if playlist.is_empty() { 81 | return vec![]; 82 | } 83 | // 指定线程数量 84 | let skip_ipv6 = self.skip_ipv6(); 85 | let threads = self.threads(playlist.len()); 86 | let mut size = playlist.len() / threads; 87 | if size == 0 { 88 | size = playlist.len(); 89 | } 90 | // 每50份为一个处理单元 91 | let chunck = playlist.chunks(size); 92 | // 使用多线程处理数据 93 | let mut tasks = vec![]; 94 | for c in chunck { 95 | let c = c.to_vec(); 96 | let t = tokio::spawn(async move { 97 | let mut items = vec![]; 98 | for i in c { 99 | if let Some(uri) = i.uri.as_ref() { 100 | if skip_ipv6 && uri.contains("://[") { 101 | // http://[ipv6]:port/path?query or https://[ipv6]:port/path?query 102 | continue; 103 | } 104 | if url::Url::parse(uri).is_ok() { 105 | let ok = utils::url_accessibility(uri).await.unwrap_or_default(); 106 | if ok { 107 | items.push(i); 108 | } 109 | } else { 110 | // TODO 暂时默认它是Path,不做不处理 111 | items.push(i); 112 | } 113 | } 114 | } 115 | items 116 | }); 117 | tasks.push(t); 118 | } 119 | let mut items = vec![]; 120 | for t in tasks { 121 | if let Ok(mut v) = t.await { 122 | items.append(&mut v); 123 | } 124 | } 125 | items 126 | } 127 | 128 | async fn check_variant_stream(&self, playlist: Vec) -> Vec { 129 | if playlist.is_empty() { 130 | return vec![]; 131 | } 132 | // 指定线程数量 133 | let skip_ipv6 = self.skip_ipv6(); 134 | let threads = self.threads(playlist.len()); 135 | let mut size = playlist.len() / threads; 136 | if size == 0 { 137 | size = playlist.len(); 138 | } 139 | // 每50份为一个处理单元 140 | let chunck = playlist.chunks(size); 141 | // 使用多线程处理数据 142 | let mut tasks = vec![]; 143 | for c in chunck { 144 | let c = c.to_vec(); 145 | let t = tokio::spawn(async move { 146 | let mut items = vec![]; 147 | for i in c { 148 | if url::Url::parse(&i.uri).is_ok() { 149 | if skip_ipv6 && i.uri.contains("://[") { 150 | // http://[ipv6]:port/path?query or https://[ipv6]:port/path?query 151 | continue; 152 | } 153 | let ok = utils::url_accessibility(&i.uri).await.unwrap_or_default(); 154 | if ok { 155 | items.push(i); 156 | } 157 | } else { 158 | // TODO 暂时默认它是Path,不做不处理 159 | items.push(i); 160 | } 161 | } 162 | items 163 | }); 164 | tasks.push(t); 165 | } 166 | let mut items = vec![]; 167 | for t in tasks { 168 | if let Ok(mut v) = t.await { 169 | items.append(&mut v); 170 | } 171 | } 172 | items 173 | } 174 | 175 | async fn check_media_playlist(&self, playlist: Vec) -> Vec { 176 | if playlist.is_empty() { 177 | return vec![]; 178 | } 179 | let skip_ipv6 = self.skip_ipv6(); 180 | // 指定线程数量 181 | let threads = self.threads(playlist.len()); 182 | let mut size = playlist.len() / threads; 183 | if size == 0 { 184 | size = playlist.len(); 185 | } 186 | // 每50份为一个处理单元 187 | let chunck = playlist.chunks(size); 188 | // 使用多线程处理数据 189 | let mut tasks = vec![]; 190 | for c in chunck { 191 | let c = c.to_vec(); 192 | let t = tokio::spawn(async move { 193 | let mut items = vec![]; 194 | for i in c { 195 | if url::Url::parse(&i.uri).is_ok() { 196 | if skip_ipv6 && i.uri.contains("://[") { 197 | // http://[ipv6]:port/path?query or https://[ipv6]:port/path?query 198 | continue; 199 | } 200 | let ok = utils::url_accessibility(&i.uri).await.unwrap_or_default(); 201 | if ok { 202 | items.push(i); 203 | } 204 | } else { 205 | // TODO 暂时默认它是Path,不做不处理 206 | items.push(i); 207 | } 208 | } 209 | items 210 | }); 211 | tasks.push(t); 212 | } 213 | let mut items = vec![]; 214 | for t in tasks { 215 | if let Ok(mut v) = t.await { 216 | items.append(&mut v); 217 | } 218 | } 219 | items 220 | } 221 | } 222 | 223 | #[tokio::test] 224 | async fn test_m3u8() { 225 | let file = "../data/test.m3u8"; 226 | let content = std::fs::read_to_string(file).unwrap(); 227 | let pl = PlaylistSource { 228 | threads: None, 229 | skip_ipv6: None, 230 | content, 231 | }; 232 | let res = pl.check().await; 233 | assert!(res.is_ok()); 234 | let res = res.unwrap(); 235 | println!("loss: {}, count: {}", res.loss, res.count); 236 | std::fs::write("../data/test_checked.m3u8", &res.content).unwrap(); 237 | assert_ne!(res.content.len(), 0); 238 | } 239 | -------------------------------------------------------------------------------- /src/store/tvbox.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref, computed } from "vue"; 3 | import { invoke } from "@tauri-apps/api/tauri"; 4 | import localstorage from "./localstorage"; 5 | async function loadResource(uri: string) { 6 | uri = uri.trim(); 7 | if (!uri) { 8 | return undefined; 9 | } 10 | const content = await invoke("parse_tvbox", { 11 | uri, 12 | }).catch((e) => { 13 | console.log(e); 14 | return undefined; 15 | }); 16 | content?.sites.forEach((item) => { 17 | item.features = features(item); 18 | }); 19 | return content; 20 | } 21 | async function inlineCheckResource(items: any[], method: string) { 22 | if (!items.length) { 23 | return []; 24 | } 25 | console.time("check"); 26 | const value = await invoke(method, { 27 | items, 28 | }).catch((e) => { 29 | console.log(e); 30 | return []; 31 | }); 32 | console.timeEnd("check"); 33 | return value; 34 | } 35 | async function checkVods(items: TvBoxVod[]) { 36 | return inlineCheckResource(items, "vods_connectivity"); 37 | } 38 | async function checkLive(items: TvBoxLive[]) { 39 | return inlineCheckResource(items, "live_connectivity"); 40 | } 41 | async function checkParses(items: TvBoxParse[]) { 42 | return inlineCheckResource(items, "parses_connectivity"); 43 | } 44 | 45 | function features(item: TvBoxVod) { 46 | const feature = []; 47 | if (item.searchable) { 48 | feature.push("点播搜索"); 49 | } 50 | if (item.searchable) { 51 | feature.push("快速搜索"); 52 | } 53 | if (item.searchable) { 54 | feature.push("列表筛选"); 55 | } 56 | return feature.join(", "); 57 | } 58 | 59 | const useTvBoxStore = defineStore("tvbox", () => { 60 | const SOURCE = "TVBOX_SOURCE"; 61 | const MERGIN_SOURCE = "TVBOX_MERGIN_SOURCE"; 62 | 63 | const source = ref(undefined as undefined | TvBoxSource); 64 | const source_text = computed(() => { 65 | return JSON.stringify(source.value, undefined, " "); 66 | }); 67 | const merginSource = ref(""); 68 | const loading = ref(false); 69 | const init = async () => { 70 | const ms = await localstorage.get(MERGIN_SOURCE).catch((_) => ""); 71 | if (ms) { 72 | merginSource.value = ms; 73 | } 74 | const src = await localstorage.get(SOURCE).catch((_) => ""); 75 | if (src) { 76 | try { 77 | source.value = JSON.parse(src); 78 | } catch (_) {} 79 | } 80 | }; 81 | const done = async () => { 82 | await localstorage 83 | .set(SOURCE, JSON.stringify(source.value)) 84 | .catch((_) => null); 85 | await localstorage.save().catch((_) => null); 86 | loading.value = false; 87 | }; 88 | const load = async (uri: string) => { 89 | loading.value = true; 90 | const value = await loadResource(uri); 91 | source.value = value; 92 | await done(); 93 | }; 94 | const push = async (uri: string) => { 95 | loading.value = true; 96 | const value = await loadResource(uri); 97 | if (!source.value) { 98 | source.value = value; 99 | loading.value = false; 100 | return; 101 | } 102 | if (value) { 103 | value.sites.forEach((item) => { 104 | if (!item.jar) { 105 | item.jar = value.spider; 106 | } 107 | }); 108 | // 合并点播 109 | const sites = source.value.sites.concat(value.sites); 110 | const sitesMap = {} as Record; 111 | sites.forEach((item) => { 112 | const key = item.api + item.ext || ""; 113 | if (!sitesMap[key]) { 114 | sitesMap[key] = item; 115 | } 116 | }); 117 | source.value.sites = Object.values(sitesMap); 118 | // 合并直播 119 | const lives = source.value.lives.concat(value.lives); 120 | const livesMap = {} as Record; 121 | lives.forEach((item) => { 122 | const key = 123 | item.url + 124 | (item.channels || []).map((chn) => chn.urls.join(",")).join(","); 125 | if (!livesMap[key]) { 126 | livesMap[key] = item; 127 | } 128 | }); 129 | source.value.lives = Object.values(livesMap); 130 | // 合并解析器 131 | const parses = source.value.parses 132 | ? source.value.parses.concat(value.parses || []) 133 | : value.parses || []; 134 | const parsesMap = {} as Record; 135 | parses.forEach((item) => { 136 | const key = item.url; 137 | if (!parsesMap[key]) { 138 | parsesMap[key] = item; 139 | } 140 | }); 141 | source.value.parses = Object.values(parsesMap); 142 | // 合并vip标识 143 | if (value.flags) { 144 | if (source.value.flags) { 145 | const items = source.value.flags.concat(value.flags); 146 | source.value.flags = Array.from(new Set(items)); 147 | } else { 148 | source.value.flags = value.flags; 149 | } 150 | } 151 | // 合并广告过滤规则 152 | if (value.ads) { 153 | if (source.value.ads) { 154 | const items = source.value.ads.concat(value.ads); 155 | source.value.ads = Array.from(new Set(items)); 156 | } else { 157 | source.value.ads = value.ads; 158 | } 159 | } 160 | // 合并提取规则 161 | if (value.rules) { 162 | if (source.value.rules) { 163 | const items = source.value.rules.concat(value.rules); 164 | const rulesMap = {} as Record; 165 | items.forEach((item) => { 166 | const key = 167 | item.host || 168 | "" + 169 | (item.hosts || []).join(",") + 170 | (item.regex || []).join(",") + 171 | (item.rule || []).join(","); 172 | if (!rulesMap[key]) { 173 | rulesMap[key] = item; 174 | } 175 | }); 176 | source.value.rules = Object.values(rulesMap); 177 | } else { 178 | source.value.rules = value.rules; 179 | } 180 | } 181 | } 182 | await done(); 183 | }; 184 | const check = async () => { 185 | if (!source.value) { 186 | return; 187 | } 188 | loading.value = true; 189 | const items = await checkVods(source.value.sites); 190 | const onlines = items.map((i) => { 191 | const item = i.extra as TvBoxVod; 192 | item.status = i.connectable ? 1 : -1; 193 | return item; 194 | }); 195 | source.value.sites = onlines; 196 | await done(); 197 | }; 198 | const check_live = async (value?: TvBoxLive[]) => { 199 | if (!source.value) { 200 | return; 201 | } 202 | loading.value = true; 203 | const items = await checkLive(value || source.value.lives); 204 | const onlines = items.map((i) => { 205 | const item = i.extra as TvBoxLive; 206 | item.status = i.connectable ? 1 : -1; 207 | return item; 208 | }); 209 | source.value.lives = onlines; 210 | await done(); 211 | }; 212 | const check_parses = async (value?: TvBoxParse[]) => { 213 | if (!source.value) { 214 | return; 215 | } 216 | loading.value = true; 217 | const items = await checkParses(value || source.value.parses || []); 218 | const onlines = items.map((i) => { 219 | const item = i.extra as TvBoxParse; 220 | item.status = i.connectable ? 1 : -1; 221 | return item; 222 | }); 223 | source.value.parses = onlines; 224 | await done(); 225 | }; 226 | const check_by = async (list: TvBoxVod[]) => { 227 | if (!source.value) { 228 | return; 229 | } 230 | loading.value = true; 231 | const items = await checkVods(list); 232 | const onlines = {} as Record; 233 | items.forEach((i) => { 234 | const item = i.extra as TvBoxVod; 235 | onlines[item.key] = i.connectable ? 1 : -1; 236 | }); 237 | source.value.sites.forEach((i) => { 238 | if (onlines[i.key]) { 239 | i.status = onlines[i.key]; 240 | } 241 | }); 242 | await done(); 243 | }; 244 | const remove_by = async (items: TvBoxVod[]) => { 245 | if (!source.value) { 246 | return; 247 | } 248 | const keys = items.map((item) => item.name + "-" + item.key); 249 | source.value.sites = source.value?.sites.filter((item) => { 250 | const key = item.name + "-" + item.key; 251 | return !keys.includes(key); 252 | }); 253 | await done(); 254 | }; 255 | const remove_live = async (i: number) => { 256 | if (!source.value) { 257 | return; 258 | } 259 | source.value.lives.splice(i, 1); 260 | await done(); 261 | }; 262 | const remove_parses = async (i: number | TvBoxParse[]) => { 263 | if (!source.value) { 264 | return; 265 | } 266 | if (typeof i == "number") { 267 | source.value.parses?.splice(i, 1); 268 | } else if (Array.isArray(i)) { 269 | const keys = i.map((item) => item.name + "-" + item.url); 270 | source.value.parses = source.value?.parses?.filter((item) => { 271 | const key = item.name + "-" + item.url; 272 | return !keys.includes(key); 273 | }); 274 | } 275 | await done(); 276 | }; 277 | const remove_ads = async (i: number) => { 278 | if (!source.value || !source.value.ads) { 279 | return; 280 | } 281 | source.value.ads.splice(i, 1); 282 | await done(); 283 | }; 284 | const add_live = async (item: TvBoxLive) => { 285 | if (!source.value || !source.value.ads) { 286 | return; 287 | } 288 | const extist = source.value.lives.some((i) => i.url == item.url); 289 | if (!extist) { 290 | source.value.lives.push(item); 291 | } 292 | await done(); 293 | }; 294 | const update_wallpaper = async (wallpaper: string) => { 295 | if (!source.value) { 296 | return; 297 | } 298 | source.value.wallpaper = wallpaper; 299 | await done(); 300 | }; 301 | const update_warningText = async (warningText: string) => { 302 | if (!source.value) { 303 | return; 304 | } 305 | source.value.warningText = warningText; 306 | await done(); 307 | }; 308 | const update_loading = (b?: boolean) => { 309 | loading.value = !!b; 310 | }; 311 | const update_merginSource = async (t: string) => { 312 | merginSource.value = t; 313 | await localstorage.set(MERGIN_SOURCE, t); 314 | }; 315 | const mergin = async () => { 316 | const src = merginSource.value.trim(); 317 | if (!src) { 318 | return; 319 | } 320 | const lines = src 321 | .split(/[,;,;\n]/) 322 | .map((line) => line.trim()) 323 | .filter((line) => { 324 | return line && /https:\/\//.test(line); 325 | }); 326 | for (let i = 0; i < lines.length; i++) { 327 | const item = lines[i]; 328 | await push(item).catch((_) => null); 329 | } 330 | }; 331 | const cache = async () => { 332 | if (!source.value) { 333 | return; 334 | } 335 | const src = JSON.parse(JSON.stringify(source.value)) as TvBoxSource; 336 | src.sites.forEach((i) => { 337 | if (!i.jar) { 338 | i.jar = src.spider; 339 | } 340 | }); 341 | await invoke("cache", { 342 | key: "tvbox", 343 | value: JSON.stringify(src, undefined, " "), 344 | }); 345 | }; 346 | return { 347 | source, 348 | source_text, 349 | loading, 350 | merginSource, 351 | init, 352 | load, 353 | push, 354 | check, 355 | check_by, 356 | check_live, 357 | check_parses, 358 | remove_by, 359 | remove_live, 360 | remove_parses, 361 | remove_ads, 362 | add_live, 363 | update_wallpaper, 364 | update_warningText, 365 | update_loading, 366 | update_merginSource, 367 | mergin, 368 | cache, 369 | }; 370 | }); 371 | 372 | export { useTvBoxStore }; 373 | -------------------------------------------------------------------------------- /src/components/playlist/txt.vue: -------------------------------------------------------------------------------- 1 | 184 | 185 | 186 | 191 | 192 | 193 | 198 | 199 | 200 | 201 | 202 | 203 | 当前进度: {{ playlist.tips }} 204 | 205 | 206 | 207 | 208 | 209 | 210 | 218 | 219 | 220 | 221 | 文件 222 | 223 | 224 | 加载 225 | 226 | 231 | 232 | 测试 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 246 | 预览 247 | 248 | 253 | 重测无效源 254 | 255 | 260 | 删除无效源 261 | 262 | 266 | 合并直播源 267 | 268 | 269 | 270 | 271 | 290 | 291 | 301 | 308 | 310 | 311 | setFilterValue([value])" /> 317 | 318 | 323 | 重置 324 | 325 | 329 | 确定 330 | 331 | 332 | 333 | 334 | 335 | 336 | 340 | 341 | 342 | 352 | 359 | 361 | 362 | setFilterValue([value])" /> 368 | 369 | 374 | 重置 375 | 376 | 380 | 确定 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 395 | 396 | 405 | 414 | 415 | 416 | 417 | 421 | 422 | 429 | 430 | 431 | 436 | 437 | 438 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 454 | 删除选中 455 | 456 | 457 | 删除IPv6源 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 483 | 484 | 485 | 490 | 491 | 492 | 493 | 文件 494 | 495 | 496 | 497 | 新的直播源将被合并到当前打开的直播源当中 498 | 499 | 500 | 501 | 506 | 507 | 508 | 509 | 510 | 511 | 529 | 538 | -------------------------------------------------------------------------------- /data/test.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U x-tvg-url=http://epg.51zmt.top:8000/cc.xml,http://epg.51zmt.top:8000/difang.xml 2 | #EXTINF:-1,纯享4K 3 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225786/index.m3u8 4 | #EXTINF:-1,纯享4K.备用 5 | http://[2409:8087:5c00:10:5::8a]/cdnrrs.gx.chinamobile.com/PLTV/3/224/3221225726/index.m3u8 6 | #EXTINF:-1,直播中国 7 | https://gcalic.v.myalicdn.com/gc/wgw05_1/index.m3u8?contentid=2820180516001 8 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV4k" tvg-id="CCTV4k" tvg-name="CCTV4k",CCTV4K 9 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221226998/index.m3u8?servicetype=1&IASHttpSessionId=RR423820220409134714119178&type=IPv6 10 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV4k" tvg-id="CCTV4k" tvg-name="CCTV4k",CCTV4K.备用1080P 11 | http://liveop.cctv.cn/hls/4KHD/playlist.m3u8 12 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-4K 13 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227162/index.m3u8?servicetype=1&c=福建三明移动 14 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-4K.备用 15 | http://[2409:8087:1070:301:2028::23]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221226301/index.m3u8?fmt=ts2hls 16 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-4K 25M2160 17 | http://ott.mobaibox.com/PLTV/3/224/3221228168/index.m3u8 18 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-4K 34M2160HDR 19 | http://ott.mobaibox.com/PLTV/3/224/3221228127/index.m3u8 20 | #EXTINF:-1,欢笑剧场4K[3840*2160] 21 | http://[2409:8087:7000:20:1000::22]:6060/yinhe/2/ch00000090990000002156/index.m3u8?virtualDomain=yinhe.live_hls.zte.com 22 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/欢笑剧场" tvg-id="欢笑剧场" tvg-name="欢笑剧场",SiTV欢笑剧场 12M2160 23 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226582/index.m3u8 24 | #EXTINF:-1,4K修复 7M2160HEVC 25 | http://ott.mobaibox.com/PLTV/3/224/3221228141/index.m3u8 26 | #EXTINF:-1,爱上4K 27 | http://[2409:8087:1070:301:2026::12]:80/wh7f454c46tw4179594525_1038075197/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225858/10000100000000060000000000262129_0.smil/01.m3u8?fmt=ts2hls 28 | #EXTINF:-1,爱上4K.备用 29 | http://[2409:8087:5e01:34::23]:6610/ZTE_CMS/00000001000000060000000000000459/index.m3u8?IAS 30 | #EXTINF:-1,河南卫视4K测试 31 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8 32 | #EXTINF:-1,苏州4K频道 33 | http://liveshowbak2.kan0512.com/ksz-norecord/csztv4k_4k.m3u8 34 | #EXTINF:-1,芒果娱乐4K 35 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221226887/index.m3u8?servicetype=1&c=福建三明移动 36 | #EXTINF:-1,赣州综合 37 | http://pl1.cloud.dayang.com.cn/live/10062_nfeG7l.m3u8 38 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/江西卫视" tvg-id="江西卫视" tvg-name="江西卫视",江西卫视 39 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225492/index.m3u8?fmt=ts2hls 40 | #EXTINF:-1,江西卫视.备用 41 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226428/index.m3u8 42 | #EXTINF:-1,江西新闻 43 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225615/index.m3u8?fmt=ts2hls 44 | #EXTINF:-1,江西都市576 45 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225584/index.m3u8?fmt=ts2hls 46 | #EXTINF:-1,江西经济生活 47 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226206/index.m3u8?fmt=ts2hls 48 | #EXTINF:-1,江西影视旅游576 49 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225579/2/index.m3u8?fmt=ts2hls 50 | #EXTINF:-1,江西少儿 51 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226194/index.m3u8?fmt=ts2hls 52 | #EXTINF:-1,陶瓷频道 53 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226247/index.m3u8?fmt=ts2hls 54 | #EXTINF:-1,江西教育576 55 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226197/index.m3u8?fmt=ts2hls 56 | #EXTINF:-1,凤凰卫视HD 57 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227222/index.m3u8?servicetype=1&IASHttpSessionId=RR423820220409134714119178 58 | #EXTINF:-1,凤凰资讯HD 59 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227226/index.m3u8?servicetype=1&IASHttpSessionId=RR423820220409134714119178 60 | #EXTINF:-1,凤凰中文备用2 61 | https://playtv-live.ifeng.com/live/06OLEGEGM4G.m3u8 62 | #EXTINF:-1,凤凰资讯备用2 63 | https://playtv-live.ifeng.com/live/06OLEEWQKN4.m3u8 64 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV13新闻" tvg-id="CCTV13新闻" tvg-name="CCTV13新闻",CCTV-13新闻 65 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225638/index.m3u8 66 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV13" tvg-id="CCTV13" tvg-name="CCTV13",CCTV-13 67 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226543/index.m3u8 68 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV综合" tvg-id="CCTV综合" tvg-name="CCTV综合",CCTV综合 69 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225642/index.m3u8 70 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV综合" tvg-id="CCTV综合" tvg-name="CCTV综合",CCTV综合.备用 71 | http://[2409:8087:1070:301:2028::23]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225920/index.m3u8?fmt=ts2hls 72 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV财经" tvg-id="CCTV财经" tvg-name="CCTV财经",CCTV财经 73 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225643/index.m3u8 74 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV财经" tvg-id="CCTV财经" tvg-name="CCTV财经",CCTV财经.备用 75 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225918/index.m3u8?fmt=ts2hls 76 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV综艺" tvg-id="CCTV综艺" tvg-name="CCTV综艺",CCTV综艺 77 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225634/index.m3u8 78 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV综艺" tvg-id="CCTV综艺" tvg-name="CCTV综艺",CCTV综艺.备用 79 | http://[2409:8087:1070:301:2028::1e]:80/wh7f454c46tw4200914459_582749386/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225963/10000100000000060000000000869061_0.smil/01.m3u8?fmt=ts2hls 80 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV国际" tvg-id="CCTV国际" tvg-name="CCTV国际",CCTV国际 81 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225621/index.m3u8 82 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV国际" tvg-id="CCTV国际" tvg-name="CCTV国际",CCTV国际.备用 83 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225898/index.m3u8?fmt=ts2hls 84 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV体育" tvg-id="CCTV体育" tvg-name="CCTV体育",CCTV体育 85 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226259/index.m3u8?fmt=ts2hls 86 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV体育" tvg-id="CCTV体育" tvg-name="CCTV体育",CCTV体育.备用 87 | http://[2409:8087:1070:301:2028::1e]:80/wh7f454c46tw4201204269_1958960221/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225964/10000100000000060000000000869060_0.smil/01.m3u8?fmt=ts2hls 88 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV赛事" tvg-id="CCTV赛事" tvg-name="CCTV赛事",CCTV赛事 89 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225706/index.m3u8 90 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV赛事" tvg-id="CCTV赛事" tvg-name="CCTV赛事",CCTV赛事.备用 91 | http://[2409:8087:1070:301:2028::1a]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225663/index.m3u8?fmt=ts2hls 92 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV电影" tvg-id="CCTV电影" tvg-name="CCTV电影",CCTV电影 93 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225632/index.m3u8 94 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV电影" tvg-id="CCTV电影" tvg-name="CCTV电影",CCTV电影.备用 95 | http://[2409:8087:1070:301:2028::14]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225965/index.m3u8?fmt=ts2hls 96 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV军事" tvg-id="CCTV军事" tvg-name="CCTV军事",CCTV军事 97 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225644/index.m3u8 98 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV军事" tvg-id="CCTV军事" tvg-name="CCTV军事",CCTV军事.备用 99 | http://[2409:8087:1070:301:2028::1a]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225897/index.m3u8?fmt=ts2hls 100 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV剧集" tvg-id="CCTV剧集" tvg-name="CCTV剧集",CCTV剧集 101 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225631/index.m3u8 102 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV剧集" tvg-id="CCTV剧集" tvg-name="CCTV剧集",CCTV剧集.备用 103 | http://[2409:8087:1070:301:2028::1f]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225966/index.m3u8?fmt=ts2hls 104 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV纪录" tvg-id="CCTV纪录" tvg-name="CCTV纪录",CCTV纪录 105 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225646/index.m3u8 106 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV纪录" tvg-id="CCTV纪录" tvg-name="CCTV纪录",CCTV纪录.备用 107 | http://[2409:8087:1070:301:2028::1f]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225919/index.m3u8?fmt=ts2hls 108 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV科教" tvg-id="CCTV科教" tvg-name="CCTV科教",CCTV科教 109 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225636/index.m3u8 110 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV科教" tvg-id="CCTV科教" tvg-name="CCTV科教",CCTV科教.备用 111 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225917/index.m3u8?fmt=ts2hls 112 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV戏曲" tvg-id="CCTV戏曲" tvg-name="CCTV戏曲",CCTV戏曲 113 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225628/index.m3u8 114 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV戏曲" tvg-id="CCTV戏曲" tvg-name="CCTV戏曲",CCTV戏曲.备用 115 | http://[2409:8087:1070:301:2028::1a]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225867/index.m3u8?fmt=ts2hls 116 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV法制" tvg-id="CCTV法制" tvg-name="CCTV法制",CCTV法制 117 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225637/index.m3u8 118 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV法制" tvg-id="CCTV法制" tvg-name="CCTV法制",CCTV法制.备用 119 | http://[2409:8087:1070:301:2028::14]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225915/index.m3u8?fmt=ts2hls 120 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV少儿" tvg-id="CCTV少儿" tvg-name="CCTV少儿",CCTV少儿 121 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225640/index.m3u8 122 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV少儿" tvg-id="CCTV少儿" tvg-name="CCTV少儿",CCTV少儿.备用 123 | http://[2409:8087:1070:301:2028::18]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225916/index.m3u8?fmt=ts2hls 124 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV音乐" tvg-id="CCTV音乐" tvg-name="CCTV音乐",CCTV音乐 125 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225641/index.m3u8 126 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV音乐" tvg-id="CCTV音乐" tvg-name="CCTV音乐",CCTV音乐.备用 127 | http://[2409:8087:1070:301:2028::22]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225866/index.m3u8?fmt=ts2hls 128 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-HD 129 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226233/index.m3u8 130 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV16" tvg-id="CCTV16" tvg-name="CCTV16",CCTV16-HD.备用 131 | http://[2409:8087:1070:301:2028::18]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221226300/index.m3u8?fmt=ts2hls 132 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/CCTV17" tvg-id="CCTV17" tvg-name="CCTV17",CCTV-17 HD 133 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225909/index.m3u8 134 | #EXTINF:-1,CGTN 135 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000000002000000028019/main.m3u8?IASHttpSessionId=OTT 136 | #EXTINF:-1,CGTN.576 137 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225747/index.m3u8 138 | #EXTINF:-1,CGTN记录 139 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000000002000000028012/main.m3u8?IASHttpSessionId=OTT 140 | #EXTINF:-1,CGTN记录.576 141 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225645/index.m3u8 142 | #EXTINF:-1,CETV1 143 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225917/index.m3u8?fmt=ts2hls 144 | #EXTINF:-1,CETV1.576 145 | http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/index.m3u8 146 | #EXTINF:-1,CETV2.576 147 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226242/index.m3u8 148 | #EXTINF:-1,CETV4.576 149 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225937/index.m3u8 150 | #EXTINF:-1,CNC.720 151 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225693/index.m3u8 152 | #EXTINF:-1,CNC中文.720 153 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225694/index.m3u8 154 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/浙江卫视" tvg-id="浙江卫视" tvg-name="浙江卫视",浙江卫视 155 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225703/index.m3u8 156 | #EXTINF:-1,浙江卫视.备用 157 | http://[2409:8087:1070:301:2028::1f]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225906/index.m3u8?fmt=ts2hls 158 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/湖南卫视" tvg-id="湖南卫视" tvg-name="湖南卫视",湖南卫视 159 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225704/index.m3u8 160 | #EXTINF:-1,湖南卫视.备用 161 | http://[2409:8087:1070:301:2028::1c]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225912/index.m3u8?fmt=ts2hls 162 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/金鹰纪实" tvg-id="金鹰纪实" tvg-name="金鹰纪实",金鹰纪实 163 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225595/index.m3u8?fmt=ts2hls 164 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/金鹰卡通" tvg-id="金鹰卡通" tvg-name="金鹰卡通",金鹰卡通 165 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226246/index.m3u8?fmt=ts2hls 166 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/东方卫视" tvg-id="东方卫视" tvg-name="东方卫视",东方卫视 167 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225659/index.m3u8 168 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/北京卫视" tvg-id="北京卫视" tvg-name="北京卫视",北京卫视 169 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225674/index.m3u8 170 | #EXTINF:-1,北京卫视.备用 171 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225913/index.m3u8?fmt=ts2hls 172 | #EXTINF:-1,北京东奥纪实 173 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225676/index.m3u8 174 | #EXTINF:-1,北京卡酷少儿.576 175 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225677/index.m3u8 176 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/山东卫视" tvg-id="山东卫视" tvg-name="山东卫视",山东卫视 177 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225738/index.m3u8 178 | #EXTINF:-1,山东教育.576 179 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225719/index.m3u8 180 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/安徽卫视" tvg-id="安徽卫视" tvg-name="安徽卫视",安徽卫视 181 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225737/index.m3u8 182 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/重庆卫视" tvg-id="重庆卫视" tvg-name="重庆卫视",重庆卫视 183 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225734/index.m3u8 184 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/广东卫视" tvg-id="广东卫视" tvg-name="广东卫视",广东卫视 185 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225742/index.m3u8 186 | #EXTINF:-1,广东卫视.备用 187 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226525/index.m3u8 188 | #EXTINF:-1,嘉佳卡通.576 189 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226193/index.m3u8 190 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/深圳卫视" tvg-id="深圳卫视" tvg-name="深圳卫视",深圳卫视 191 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225741/index.m3u8 192 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/江苏卫视" tvg-id="江苏卫视" tvg-name="江苏卫视",江苏卫视 193 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225743/index.m3u8 194 | #EXTINF:-1,江苏卫视.备用 195 | http://[2409:8087:1070:301:2028::18]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225909/index.m3u8?fmt=ts2hls 196 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/天津卫视" tvg-id="天津卫视" tvg-name="天津卫视",天津卫视 197 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225739/index.m3u8 198 | #EXTINF:-1,天津卫视.备用 199 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225907/index.m3u8?fmt=ts2hls 200 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/湖北卫视" tvg-id="湖北卫视" tvg-name="湖北卫视",湖北卫视 201 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225740/index.m3u8 202 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/黑龙江卫视" tvg-id="黑龙江卫视" tvg-name="黑龙江卫视",黑龙江卫视 203 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225736/index.m3u8 204 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/辽宁卫视" tvg-id="辽宁卫视" tvg-name="辽宁卫视",辽宁卫视 205 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225735/index.m3u8 206 | #EXTINF:-1,辽宁卫视.备用 207 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225902/index.m3u8?fmt=ts2hls 208 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/四川卫视" tvg-id="四川卫视" tvg-name="四川卫视",四川卫视 209 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225487/index.m3u8?fmt=ts2hls 210 | #EXTINF:-1,四川卫视.备用 211 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226573/index.m3u8 212 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/东南卫视" tvg-id="东南卫视" tvg-name="东南卫视",东南卫视 213 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225657/index.m3u8 214 | #EXTINF:-1,东南卫视.备用 215 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226720/index.m3u8 216 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/厦门卫视" tvg-id="厦门卫视" tvg-name="厦门卫视",厦门卫视 217 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221226781/index.m3u8?servicetype=1&IASHttpSessionId=OTT 218 | #EXTINF:-1,厦门卫视.576 219 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226199/index.m3u8 220 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/海南卫视" tvg-id="海南卫视" tvg-name="海南卫视",海南卫视 221 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226212/index.m3u8?fmt=ts2hls 222 | #EXTINF:-1,海南卫视.备用 223 | http://[2409:8087:1070:301:2028::1e]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225930/index.m3u8?fmt=ts2hls 224 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/三沙卫视" tvg-id="三沙卫视" tvg-name="三沙卫视",三沙卫视 225 | http://[2409:8087:5e01:34::20]:6610/ZTE_CMS/08984400000000060000000000000319/index.m3u8?IAS 226 | #EXTINF:-1,三沙卫视.576 227 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227172/index.m3u8?servicetype=1&c=福建三明移动 228 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/广西卫视" tvg-id="广西卫视" tvg-name="广西卫视",广西卫视 229 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226211/index.m3u8?fmt=ts2hls 230 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/河北卫视" tvg-id="河北卫视" tvg-name="河北卫视",河北卫视 231 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225732/index.m3u8 232 | #EXTINF:-1,河北卫视.备用 233 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226462/index.m3u8 234 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/河南卫视" tvg-id="河南卫视" tvg-name="河南卫视",河南卫视 235 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225611/index.m3u8?fmt=ts2hls 236 | #EXTINF:-1,河南卫视.备用 237 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226463/index.m3u8 238 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/云南卫视" tvg-id="云南卫视" tvg-name="云南卫视",云南卫视 239 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227181/index.m3u8?servicetype=1&IASHttpSessionId=RR423820220409134714119178&type=IPv6 240 | #EXTINF:-1,云南卫视.576 241 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225664/index.m3u8 242 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/贵州卫视" tvg-id="贵州卫视" tvg-name="贵州卫视",贵州卫视 243 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225728/index.m3u8 244 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/吉林卫视" tvg-id="吉林卫视" tvg-name="吉林卫视",吉林卫视 245 | http://[2409:8087:1070:301:2028::11]:80/wh7f454c46tw4200346826_-520081866/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225959/10000100000000060000000000777681_0.smil/01.m3u8?fmt=ts2hls 246 | #EXTINF:-1,吉林卫视.576 247 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225680/index.m3u8 248 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/甘肃卫视" tvg-id="甘肃卫视" tvg-name="甘肃卫视",甘肃卫视 249 | http://[2409:8087:1070:301:2028::22]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225958/index.m3u8?fmt=ts2hls 250 | #EXTINF:-1,甘肃卫视.576 251 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225724/index.m3u8 252 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/内蒙古卫视" tvg-id="内蒙古卫视" tvg-name="内蒙古卫视",内蒙古卫视 253 | http://[2409:8087:1070:301:2028::1c]/gslbserv.iptv.nm.chinamobile.com/PLTV/88888890/224/3221225676/index.m3u8?fmt=ts2hls 254 | #EXTINF:-1,内蒙古卫视.576 255 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225667/index.m3u8 256 | #EXTINF:-1,山西卫视.720 257 | http://liveflash.sxrtv.com/live/sxwshd.flv 258 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/山西卫视" tvg-id="山西卫视" tvg-name="山西卫视",山西卫视576 259 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225730/index.m3u8 260 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/宁夏卫视" tvg-id="宁夏卫视" tvg-name="宁夏卫视",宁夏卫视720 261 | http://livepgc.cmc.ningxiahuangheyun.com/pgc/deb40eb3174c0c2d62c30810f0087d64.m3u8?txSecret=27098501b42eae2199df753a323534df&txTime=6326BD7B 262 | #EXTINF:-1,宁夏卫视.576 263 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225726/index.m3u8 264 | #EXTINF:-1,陕西卫视.576 265 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225729/index.m3u8 266 | #EXTINF:-1,西藏卫视.576 267 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225723/index.m3u8 268 | #EXTINF:-1,新疆卫视.576 269 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225725/index.m3u8 270 | #EXTINF:-1,兵团卫视.576 271 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226200/index.m3u8 272 | #EXTINF:-1,青海卫视.576 273 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225727/index.m3u8 274 | #EXTINF:-1,梨园频道 275 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225581/index.m3u8?fmt=ts2hls 276 | #EXTINF:-1,大湾区卫视.576 277 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226203/index.m3u8 278 | #EXTINF:-1,农林卫视.576 279 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226204/index.m3u8 280 | #EXTINF:-1,康巴卫视.576 281 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226207/index.m3u8 282 | #EXTINF:-1,安多卫视.576 283 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226195/index.m3u8 284 | #EXTINF:-1,延边卫视.720 285 | http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8 286 | #EXTINF:-1,延边卫视.576 287 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226201/index.m3u8 288 | #EXTINF:-1,汽摩频道.576 289 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225506/index.m3u8?fmt=ts2hls 290 | #EXTINF:-1,靓妆.576 291 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225509/index.m3u8?fmt=ts2hls 292 | #EXTINF:-1,哈哈炫动.576 293 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225720/index.m3u8 294 | #EXTINF:-1,湖南经视 295 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000000002000000027983/main.m3u8?IASHttpSessionId=OTT 296 | #EXTINF:-1,湖南都市 297 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000041/main.m3u8?IASHttpSessionId=OTT 298 | #EXTINF:-1,湖南电视剧 299 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000037/main.m3u8?IASHttpSessionId=OTT 300 | #EXTINF:-1,湖南电影 301 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000038/main.m3u8?IASHttpSessionId=OTT 302 | #EXTINF:-1,湖南娱乐 303 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000040/main.m3u8?IASHttpSessionId=OTT 304 | #EXTINF:-1,湖南公共 305 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000044/main.m3u8?IASHttpSessionId=OTT 306 | #EXTINF:-1,湖南国际 307 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000045/main.m3u8?IASHttpSessionId=OTT 308 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/金鹰卡通" tvg-id="金鹰卡通" tvg-name="金鹰卡通",金鹰卡通 309 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000039/main.m3u8?IASHttpSessionId=OTT 310 | #EXTINF:-1,湖南教育 311 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001016000000368716/main.m3u8?IASHttpSessionId=OTT 312 | #EXTINF:-1,长沙影视 313 | http://[2409:8087:5011:1020::1f]/180000001001/00000001000000001001000000000030/main.m3u8?IASHttpSessionId=OTT 314 | #EXTINF:-1,芒果剧场 315 | http://zterr.hvs.fj.chinamobile.com:6060/PLTV/88888888/224/3221226692/index.m3u8?servicetype=1&c=福建三明移动 316 | #EXTINF:-1,第一财经.备用 317 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226966/index.m3u8 318 | #EXTINF:-1,东方财经 319 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8 320 | #EXTINF:-1,M1905电影网 321 | http://mmitv.top/other/1905.php?id=1905 322 | #EXTINF:-1,广东珠江频道.720 323 | http://cdnrrs.gx.chinamobile.com/PLTV/77777777/224/3221225717/index.m3u8 324 | #EXTINF:-1,广东新闻.720 325 | http://mmitv.top/pltv/itouchtv.php?id=gdxw 326 | #EXTINF:-1,广州综合.720 327 | http://player.521fanli.cn/1691/gd/gztvm3u8.php?id=gzzh 328 | #EXTINF:-1,广州南国都市.1080P 329 | http://player.521fanli.cn/1691/gd/gztvm3u8.php?id=gzngds 330 | #EXTINF:-1,广州影视.720 331 | http://player.521fanli.cn/1691/gd/gztvm3u8.php?id=gzys 332 | #EXTINF:-1,宁夏公共 333 | http://livepgc.cmc.ningxiahuangheyun.com/pgc/041867befe3dde090202f620ade5b87c.m3u8?txSecret=39373ebcf0d57da2477968efb258e2b7&txTime=6235C9B6 334 | #EXTINF:-1,宁夏经济 335 | http://livepgc.cmc.ningxiahuangheyun.com/pgc/10717759047cafd37fd87caa5883e9c2.m3u8?txSecret=6a3918f766d0ff625282376ffee72687&txTime=6235C9C1 336 | #EXTINF:-1,黑莓电影 337 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225718/index.m3u8 338 | #EXTINF:-1,黑莓动画 339 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225672/index.m3u8 340 | #EXTINF:-1,CHC高清电影 341 | http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226463/index.m3u8 342 | #EXTINF:-1,CHC动作电影 8M1080 343 | http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/index.m3u8 344 | #EXTINF:-1,CHC家庭影院 8M1080 345 | http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226462/index.m3u8 346 | #EXTINF:-1,悦美生活[1920*1080] 347 | http://[2409:8087:7000:20:1000::22]:6060/yinhe/2/ch00000090990000002325/index.m3u8?virtualDomain=yinhe.live_hls.zte.com 348 | #EXTINF:-1,都市剧场 349 | http://[2409:8087:3428:20:500::100f]:6610/PLTV/88888888/224/3221227204/index.m3u8?servicetype=1 350 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/全纪实" tvg-id="全纪实" tvg-name="全纪实",全纪实 351 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226335/index.m3u8 352 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/游戏风云" tvg-id="游戏风云" tvg-name="游戏风云",游戏风云 353 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226328/index.m3u8 354 | #EXTINF:-1,极速汽车 355 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226337/index.m3u8 356 | #EXTINF:-1,快乐垂钓 357 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225780/index.m3u8?fmt=ts2hls 358 | #EXTINF:-1,茶频道 359 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225781/index.m3u8?fmt=ts2hls 360 | #EXTINF:-1,超级综艺 361 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225620/index.m3u8 362 | #EXTINF:-1,超级体育 363 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225622/index.m3u8 364 | #EXTINF:-1,超级电影 365 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225623/index.m3u8 366 | #EXTINF:-1,超级电视剧 367 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225625/index.m3u8 368 | #EXTINF:-1,哒啵赛事 369 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225653/index.m3u8 370 | #EXTINF:-1,哒啵电竞 371 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226265/index.m3u8 372 | #EXTINF:-1,武博世界 373 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225656/index.m3u8 374 | #EXTINF:-1,中国功夫 375 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225660/index.m3u8 376 | #EXTINF:-1,怡伴健康 377 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225666/index.m3u8 378 | #EXTINF:-1,军旅剧场 379 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225669/index.m3u8 380 | #EXTINF:-1,农业致富 381 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225670/index.m3u8 382 | #EXTINF:-1,古装剧场 383 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225678/index.m3u8 384 | #EXTINF:-1,炫舞未来 385 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225707/index.m3u8 386 | #EXTINF:-1,潮妈辣婆 387 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225708/index.m3u8 388 | #EXTINF:-1,精品体育 389 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225709/index.m3u8 390 | #EXTINF:-1,精品记录 391 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225710/index.m3u8 392 | #EXTINF:-1,金牌综艺 393 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225711/index.m3u8 394 | #EXTINF:-1,家庭剧场 395 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225713/index.m3u8 396 | #EXTINF:-1,精品大剧 397 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225714/index.m3u8 398 | #EXTINF:-1,军事评论 399 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225757/index.m3u8 400 | #EXTINF:-1,东北热剧 401 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226202/index.m3u8 402 | #EXTINF:-1,欢乐剧场 403 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226210/index.m3u8 404 | #EXTINF:-1,精品萌宠 405 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226245/index.m3u8 406 | #EXTINF:-1,IPTV1 407 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225712/index.m3u8 408 | #EXTINF:-1,IPTV2 409 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225716/index.m3u8 410 | #EXTINF:-1,NewTV爱情喜剧 411 | http://zterr.hvs.fj.chinamobile.com:6060/PLTV/88888888/224/3221225877/index.m3u8?servicetype=1 412 | #EXTINF:-1,NewTV动作电影 413 | http://zterr.hvs.fj.chinamobile.com:6060/PLTV/88888888/224/3221225879/index.m3u8?servicetype=1 414 | #EXTINF:-1,NewTV惊悚悬疑 415 | http://zterr.hvs.fj.chinamobile.com:6060/PLTV/88888888/224/3221225885/index.m3u8?servicetype=1 416 | #EXTINF:-1,NewTV海外剧场 417 | http://zterr.hvs.fj.chinamobile.com:6060/PLTV/88888888/224/3221225881/index.m3u8?servicetype=1 418 | #EXTINF:-1,咪咕视频 419 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226235/index.m3u8 420 | #EXTINF:-1,咪咕视频 421 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226238/index.m3u8 422 | #EXTINF:-1,咪咕视频.体育 423 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226240/index.m3u8 424 | #EXTINF:-1,咪咕视频 425 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226241/index.m3u8 426 | #EXTINF:-1,咪咕视频.体育2 427 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221226243/index.m3u8 428 | #EXTINF:-1,求索记录 429 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226114/index.m3u8 430 | #EXTINF:-1,功夫 431 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226440/index.m3u8 432 | #EXTINF:-1,动漫秀场 433 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226325/index.m3u8 434 | #EXTINF:-1 tvg-logo="https://wtv.tools.bianbingdang.com/prod/api/v1/tvg/logo/生活时尚" tvg-id="生活时尚" tvg-name="生活时尚",生活时尚 435 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226330/index.m3u8 436 | #EXTINF:-1,中华特产 437 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226345/index.m3u8 438 | #EXTINF:-1,体育赛事 439 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226357/index.m3u8 440 | #EXTINF:-1,精彩综艺 441 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226009/index.m3u8 442 | #EXTINF:-1,兵器科技高清 443 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8 444 | #EXTINF:-1,电视指南高清 445 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8 446 | #EXTINF:-1,央视台球高清 447 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226956/index.m3u8 448 | #EXTINF:-1,高尔夫网球高清 449 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8 450 | #EXTINF:-1,世界地理高清 451 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226947/index.m3u8 452 | #EXTINF:-1,怀旧剧场高清 453 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226972/index.m3u8 454 | #EXTINF:-1,央视文化精品高清 455 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8 456 | #EXTINF:-1,风云剧场高清 457 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8 458 | #EXTINF:-1,第一剧场高清 459 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226959/index.m3u8 460 | #EXTINF:-1,风云音乐高清 461 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226953/index.m3u8 462 | #EXTINF:-1,风云足球高清 463 | http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8 464 | #EXTINF:-1,中国交通 465 | http://[2409:8087:5e01:34::20]:6610/ZTE_CMS/08984400000000060000000000000438/index.m3u8?IAS 466 | #EXTINF:-1,香港卫视.576 467 | http://zhibo.hkstv.tv/livestream/mutfysrq/playlist.m3u8 468 | #EXTINF:-1,经典功夫台.720 469 | https://cn-hbyc2-dx-live-01.bilivideo.com/live-bvc/545768/live_179302204_68184269.m3u8 470 | #EXTINF:-1,国学 471 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226629/index.m3u8 472 | #EXTINF:-1,老故事.576 473 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226654/index.m3u8 474 | #EXTINF:-1,青岛中华美食.576 475 | http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225924/index.m3u8 476 | #EXTINF:-1,家庭理财.576 477 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226333/index.m3u8 478 | #EXTINF:-1,财富天下.576 479 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226334/index.m3u8 480 | #EXTINF:-1,环球旅游.576 481 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226336/index.m3u8 482 | #EXTINF:-1,摄影频道.576 483 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226339/index.m3u8 484 | #EXTINF:-1,天元围棋.576 485 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8 486 | #EXTINF:-1,书画频道.576 487 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226343/index.m3u8 488 | #EXTINF:-1,湖南先锋乒羽.576 489 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226331/index.m3u8 490 | #EXTINF:-1,车迷频道.576 491 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226347/index.m3u8 492 | #EXTINF:-1,江苏优漫卡通.576 493 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8 494 | #EXTINF:-1,棋牌汇.576 495 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226449/index.m3u8 496 | #EXTINF:-1,美食汇.576 497 | http://iptv.cdn.ha.chinamobile.com/PLTV/88888888/224/3221226702/index.m3u8 498 | --------------------------------------------------------------------------------
497 | 新的直播源将被合并到当前打开的直播源当中 498 |