├── .gitignore ├── .metadata ├── README.md ├── _config.yml ├── analysis_options.yaml ├── assets └── cctv.m3u ├── c851ff47e3639af1ef9ce9a7708ed9b6.cache.dill.track.dill ├── cctv.json ├── cctv.m3u ├── docs ├── .last_build_id ├── assets │ ├── AssetManifest.bin │ ├── AssetManifest.bin.json │ ├── AssetManifest.json │ ├── FontManifest.json │ ├── NOTICES │ ├── assets │ │ └── cctv.m3u │ ├── fonts │ │ └── MaterialIcons-Regular.otf │ ├── packages │ │ └── cupertino_icons │ │ │ └── assets │ │ │ └── CupertinoIcons.ttf │ └── shaders │ │ └── ink_sparkle.frag ├── canvaskit │ ├── canvaskit.js │ ├── canvaskit.wasm │ ├── chromium │ │ ├── canvaskit.js │ │ └── canvaskit.wasm │ ├── skwasm.js │ ├── skwasm.wasm │ └── skwasm.worker.js ├── favicon.png ├── flutter.js ├── flutter_service_worker.js ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html ├── main.dart.js ├── manifest.json └── version.json ├── features ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── ff3bb3a4168bd3bd0d23a408c8929e64 ├── _composite.stamp ├── gen_dart_plugin_registrant.stamp └── gen_localizations.stamp ├── flutter_assets ├── AssetManifest.bin ├── AssetManifest.bin.json ├── AssetManifest.json ├── FontManifest.json ├── NOTICES ├── assets │ ├── cctv.m3u │ └── live.txt ├── fonts │ └── MaterialIcons-Regular.otf ├── packages │ └── cupertino_icons │ │ └── assets │ │ └── CupertinoIcons.ttf └── shaders │ └── ink_sparkle.frag ├── lib ├── bindings │ ├── bindings.dart │ └── sourceBindings.dart ├── components │ ├── searchBar.dart │ └── videoPlayer.dart ├── controller │ ├── sourceListController.dart │ └── videoPlayController.dart ├── main.dart ├── other │ └── app.dart ├── pages │ ├── channelList.dart │ ├── live.dart │ └── sourceList.dart ├── parser │ └── parseM3u.dart └── utils │ ├── fileManager.dart │ ├── historyTools.dart │ ├── sourceManager.dart │ └── timeFormat.dart ├── pubspec.lock ├── pubspec.yaml └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "9e1c857886f07d342cf106f2cd588bcd5e031bb2" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 17 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 18 | - platform: android 19 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 20 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 21 | - platform: ios 22 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 23 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 24 | - platform: linux 25 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 26 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 27 | - platform: macos 28 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 29 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 30 | - platform: web 31 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 32 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 33 | - platform: windows 34 | create_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 35 | base_revision: 9e1c857886f07d342cf106f2cd588bcd5e031bb2 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # live_tv_box 2 | 3 | 一个基于 flutter 开发的在线播放器的练手项目,旨在解决电脑上看电视的痛点 4 | 5 | ## 依赖库 6 | 7 | 主要是依赖`video_player`相关插件做的视频播放 8 | 9 | ``` 10 | video_player: ^2.8.1 11 | video_player_web_hls: ^1.1.0 12 | video_player_web: ^2.1.2 13 | ``` 14 | 15 | 1. 状态管理一开始就是直接用的 setState , 觉得太麻烦了就用上 Getx 16 | 17 | 2. web 端主要还是得依靠浏览器特性 `dart:html` 系统库就提供了许多功能 选择文件读取 以及 下载保存文件 18 | 19 | 3. json 和 model 互转 用的`dart:convert` 没有用到三方库 20 | 21 | 4. 本地存储用的`localstorage` 22 | 23 | ## 功能点 24 | 25 | - 播放/暂停/全屏切换 26 | - 添加/删除管理频道列表 27 | - 添加/移除订阅源 支持 m3u 和 txt 两种常用格式 (最好是链接带文件后缀 因为解析策略是按文件后缀来区分的) 28 | - 导入/导出频道列表 json 配置 29 | 30 | 采用 github-pages 部署 31 | 存储基于浏览器本地 localstorage,需要共享可以导出配置在其他电脑上导入配置即可添加频道列表 32 | 33 | 添加 iptv 源 不能直接添加 github 的源 加个 cdn 转换 34 | 35 | ``` 36 | [X] https://github.com/WangGuibin/live_tv_box/blob/main/cctv.m3u 37 | 38 | [√] https://cdn.jsdelivr.net/gh/WangGuibin/live_tv_box@main/cctv.m3u 39 | 40 | ``` 41 | 42 | ## 跨域问题 43 | 44 | 本地调试可以尝试绕过 45 | 46 | ```bash 47 | flutter run -d chrome --web-browser-flag "--disable-web-security" 48 | ``` 49 | 50 | 网友们整理的一些源 51 | https://github.com/iptv-org/iptv 52 | https://github.com/vodtv/iptv 或者 https://m3u.vodtv.cn/ 53 | https://github.com/hujingguang/ChinaIPTV 54 | https://github.com/TCatCloud/IPTV 55 | 56 | ~~不过好多源都播不了提示跨域,不知道咋解决,放弃了~~ 57 | (需要设置一个 webServer proxy 把 ts 的链接代理到本地进行转发 或者 本地调试执行`flutter run -d chrome --web-browser-flag "--disable-web-security"`可绕过该问题) 58 | 客户端起个本地服务器进行代理或者直接后端进行代理转发可能会简单一些,至于 web 端好像是无解的,最好就是找一些支持跨域的源进行播放 59 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | defaults: 2 | - scope: 3 | path: "" 4 | values: 5 | Access-Control-Allow-Origin: "*" 6 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /assets/cctv.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1 tvg-id="",CCTV1HD 3 | https://node1.olelive.com:6443/live/CCTV1HD/hls.m3u8 4 | #EXTINF:-1 tvg-id="",CCTV2HD 5 | https://node1.olelive.com:6443/live/CCTV2HD/hls.m3u8 6 | #EXTINF:-1 tvg-id="",CCTV5HD 7 | https://node1.olelive.com:6443/live/CCTV5HD/hls.m3u8 8 | #EXTINF:-1 tvg-id="",CCTV5+ 9 | https://node1.olelive.com:6443/live/CCTV5PHD/hls.m3u8 10 | #EXTINF:-1 tvg-id="",CCTV7HD 11 | https://node1.olelive.com:6443/live/CCTV7HD/hls.m3u8 12 | #EXTINF:-1 tvg-id="",CCTV8HD 13 | https://node1.olelive.com:6443/live/CCTV8HD/hls.m3u8 14 | #EXTINF:-1 tvg-id="",CCTV9HD 15 | https://node1.olelive.com:6443/live/CCTV9HD/hls.m3u8 16 | #EXTINF:-1 tvg-id="",CCTV10HD 17 | https://node1.olelive.com:6443/live/CCTV10HD/hls.m3u8 18 | #EXTINF:-1 tvg-id="",CCTV17HD 19 | https://node1.olelive.com:6443/live/CCTV17HD/hls.m3u8 -------------------------------------------------------------------------------- /c851ff47e3639af1ef9ce9a7708ed9b6.cache.dill.track.dill: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/c851ff47e3639af1ef9ce9a7708ed9b6.cache.dill.track.dill -------------------------------------------------------------------------------- /cctv.json: -------------------------------------------------------------------------------- 1 | [{"url":"https://node1.olelive.com:6443/live/CCTV17HD/hls.m3u8","remark":"CCTV17"},{"url":"https://node1.olelive.com:6443/live/CCTV7HD/hls.m3u8","remark":"CCTV7"},{"url":"https://node1.olelive.com:6443/live/CCTV9HD/hls.m3u8","remark":"CCTV9"},{"url":"https://node1.olelive.com:6443/live/CCTV5HD/hls.m3u8","remark":"CCTV5"},{"url":"https://node1.olelive.com:6443/live/CCTV8HD/hls.m3u8","remark":"CCTV8"},{"url":"https://node1.olelive.com:6443/live/CCTV1HD/hls.m3u8","remark":"CCTV1"},{"url":"https://node1.olelive.com:6443/live/CCTV2HD/hls.m3u8","remark":"CCTV2"},{"url":"https://node1.olelive.com:6443/live/CCTV10HD/hls.m3u8","remark":"CCTV10"}] -------------------------------------------------------------------------------- /cctv.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1 tvg-id="",CCTV1HD 3 | https://node1.olelive.com:6443/live/CCTV1HD/hls.m3u8 4 | #EXTINF:-1 tvg-id="",CCTV2HD 5 | https://node1.olelive.com:6443/live/CCTV2HD/hls.m3u8 6 | #EXTINF:-1 tvg-id="",CCTV5HD 7 | https://node1.olelive.com:6443/live/CCTV5HD/hls.m3u8 8 | #EXTINF:-1 tvg-id="",CCTV5PHD 9 | https://node1.olelive.com:6443/live/CCTV5PHD/hls.m3u8 10 | #EXTINF:-1 tvg-id="",CCTV6 11 | http://[2409:8087:1e03:21::2]:6060/cms001/ch00000090990000001188/index.m3u8 12 | #EXTINF:-1 tvg-id="",CCTV7HD 13 | https://node1.olelive.com:6443/live/CCTV7HD/hls.m3u8 14 | #EXTINF:-1 tvg-id="",CCTV8HD 15 | https://node1.olelive.com:6443/live/CCTV8HD/hls.m3u8 16 | #EXTINF:-1 tvg-id="",CCTV9HD 17 | https://node1.olelive.com:6443/live/CCTV9HD/hls.m3u8 18 | #EXTINF:-1 tvg-id="",CCTV10HD 19 | https://node1.olelive.com:6443/live/CCTV10HD/hls.m3u8 20 | #EXTINF:-1 tvg-id="",CCTV13 21 | https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8 22 | #EXTINF:-1 tvg-id="",CCTV17HD 23 | https://node1.olelive.com:6443/live/CCTV17HD/hls.m3u8 24 | #EXTINF:-1 tvg-id="",CGTN-F 25 | https://news.cgtn.com/resource/live/french/cgtn-f.m3u8 26 | #EXTINF:-1 tvg-id="",CGTN-A 27 | https://news.cgtn.com/resource/live/arabic/cgtn-a.m3u8 28 | #EXTINF:-1 tvg-id="",Bread TV面包台 (720p) 29 | https://video.bread-tv.com:8091/hls-live24/online/index.m3u8 30 | #EXTINF:-1 tvg-id="",Angel TV Chinese (720p) 31 | https://cdn3.wowza.com/5/TDJ0aWNkNXFxWWta/angeltvcloud/ngrp:angelchinese_all/playlist.m3u8 32 | #EXTINF:-1 tvg-id="",余姚新闻综合 (576p) 33 | https://l.cztvcloud.com/channels/lantian/SXyuyao1/720p.m3u8 34 | #EXTINF:-1 tvg-id="",大白熊(测试) 35 | https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8 36 | #EXTINF:-1 tvg-id="",CCTV 13 37 | https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8 38 | #EXTINF:-1 tvg-id="TVBRICSChinese.cn",TV BRICS Chinese 39 | https://brics.bonus-tv.ru/cdn/brics/chinese/playlist.m3u8 40 | #EXTINF:-1 tvg-id="",baishi live 41 | https://bp-caster.bestv.com.cn/936/3/video.m3u8 -------------------------------------------------------------------------------- /docs/.last_build_id: -------------------------------------------------------------------------------- 1 | 9ba30dd61a377d15e358c96c5724e169 -------------------------------------------------------------------------------- /docs/assets/AssetManifest.bin: -------------------------------------------------------------------------------- 1 | assets/cctv.m3u  assetassets/cctv.m3u2packages/cupertino_icons/assets/CupertinoIcons.ttf  asset2packages/cupertino_icons/assets/CupertinoIcons.ttf -------------------------------------------------------------------------------- /docs/assets/AssetManifest.bin.json: -------------------------------------------------------------------------------- 1 | "DQIHD2Fzc2V0cy9jY3R2Lm0zdQwBDQEHBWFzc2V0Bw9hc3NldHMvY2N0di5tM3UHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRmDAENAQcFYXNzZXQHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRm" -------------------------------------------------------------------------------- /docs/assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"assets/cctv.m3u":["assets/cctv.m3u"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]} -------------------------------------------------------------------------------- /docs/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}] -------------------------------------------------------------------------------- /docs/assets/assets/cctv.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1 tvg-id="",CCTV1HD 3 | https://node1.olelive.com:6443/live/CCTV1HD/hls.m3u8 4 | #EXTINF:-1 tvg-id="",CCTV2HD 5 | https://node1.olelive.com:6443/live/CCTV2HD/hls.m3u8 6 | #EXTINF:-1 tvg-id="",CCTV5HD 7 | https://node1.olelive.com:6443/live/CCTV5HD/hls.m3u8 8 | #EXTINF:-1 tvg-id="",CCTV5+ 9 | https://node1.olelive.com:6443/live/CCTV5PHD/hls.m3u8 10 | #EXTINF:-1 tvg-id="",CCTV7HD 11 | https://node1.olelive.com:6443/live/CCTV7HD/hls.m3u8 12 | #EXTINF:-1 tvg-id="",CCTV8HD 13 | https://node1.olelive.com:6443/live/CCTV8HD/hls.m3u8 14 | #EXTINF:-1 tvg-id="",CCTV9HD 15 | https://node1.olelive.com:6443/live/CCTV9HD/hls.m3u8 16 | #EXTINF:-1 tvg-id="",CCTV10HD 17 | https://node1.olelive.com:6443/live/CCTV10HD/hls.m3u8 18 | #EXTINF:-1 tvg-id="",CCTV17HD 19 | https://node1.olelive.com:6443/live/CCTV17HD/hls.m3u8 -------------------------------------------------------------------------------- /docs/assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /docs/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf -------------------------------------------------------------------------------- /docs/assets/shaders/ink_sparkle.frag: -------------------------------------------------------------------------------- 1 | { 2 | "sksl": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec4 u_color;\nuniform vec4 u_composite_1;\nuniform vec2 u_center;\nuniform float u_max_radius;\nuniform vec2 u_resolution_scale;\nuniform vec2 u_noise_scale;\nuniform float u_noise_phase;\nuniform vec2 u_circle1;\nuniform vec2 u_circle2;\nuniform vec2 u_circle3;\nuniform vec2 u_rotation1;\nuniform vec2 u_rotation2;\nuniform vec2 u_rotation3;\n\nvec4 fragColor;\n\nfloat u_alpha;\nfloat u_sparkle_alpha;\nfloat u_blur;\nfloat u_radius_scale;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nmat2 FLT_flutter_local_rotate2d(vec2 rad)\n{\n return mat2(vec2(rad.x, -rad.y), vec2(rad.y, rad.x));\n}\n\nfloat FLT_flutter_local_soft_circle(vec2 uv, vec2 xy, float radius, float blur)\n{\n float blur_half = blur * 0.5;\n float d = distance(uv, xy);\n return 1.0 - smoothstep(1.0 - blur_half, 1.0 + blur_half, d / radius);\n}\n\nfloat FLT_flutter_local_circle_grid(vec2 resolution, inout vec2 p, vec2 xy, vec2 rotation, float cell_diameter)\n{\n vec2 param = rotation;\n p = (FLT_flutter_local_rotate2d(param) * (xy - p)) + xy;\n p = mod(p, vec2(cell_diameter)) / resolution;\n float cell_uv = (cell_diameter / resolution.y) * 0.5;\n float r = 0.64999997615814208984375 * cell_uv;\n vec2 param_1 = p;\n vec2 param_2 = vec2(cell_uv);\n float param_3 = r;\n float param_4 = r * 50.0;\n return FLT_flutter_local_soft_circle(param_1, param_2, param_3, param_4);\n}\n\nfloat FLT_flutter_local_turbulence(vec2 uv)\n{\n vec2 uv_scale = uv * vec2(0.800000011920928955078125);\n vec2 param = vec2(0.800000011920928955078125);\n vec2 param_1 = uv_scale;\n vec2 param_2 = u_circle1;\n vec2 param_3 = u_rotation1;\n float param_4 = 0.17000000178813934326171875;\n float _319 = FLT_flutter_local_circle_grid(param, param_1, param_2, param_3, param_4);\n float g1 = _319;\n vec2 param_5 = vec2(0.800000011920928955078125);\n vec2 param_6 = uv_scale;\n vec2 param_7 = u_circle2;\n vec2 param_8 = u_rotation2;\n float param_9 = 0.20000000298023223876953125;\n float _331 = FLT_flutter_local_circle_grid(param_5, param_6, param_7, param_8, param_9);\n float g2 = _331;\n vec2 param_10 = vec2(0.800000011920928955078125);\n vec2 param_11 = uv_scale;\n vec2 param_12 = u_circle3;\n vec2 param_13 = u_rotation3;\n float param_14 = 0.2750000059604644775390625;\n float _344 = FLT_flutter_local_circle_grid(param_10, param_11, param_12, param_13, param_14);\n float g3 = _344;\n float v = (((g1 * g1) + g2) - g3) * 0.5;\n return clamp(0.449999988079071044921875 + (0.800000011920928955078125 * v), 0.0, 1.0);\n}\n\nfloat FLT_flutter_local_soft_ring(vec2 uv, vec2 xy, float radius, float thickness, float blur)\n{\n vec2 param = uv;\n vec2 param_1 = xy;\n float param_2 = radius + thickness;\n float param_3 = blur;\n float circle_outer = FLT_flutter_local_soft_circle(param, param_1, param_2, param_3);\n vec2 param_4 = uv;\n vec2 param_5 = xy;\n float param_6 = max(radius - thickness, 0.0);\n float param_7 = blur;\n float circle_inner = FLT_flutter_local_soft_circle(param_4, param_5, param_6, param_7);\n return clamp(circle_outer - circle_inner, 0.0, 1.0);\n}\n\nfloat FLT_flutter_local_triangle_noise(inout vec2 n)\n{\n n = fract(n * vec2(5.398700237274169921875, 5.442100048065185546875));\n n += vec2(dot(n.yx, n + vec2(21.5351009368896484375, 14.3136997222900390625)));\n float xy = n.x * n.y;\n return (fract(xy * 95.43070220947265625) + fract(xy * 75.0496063232421875)) - 1.0;\n}\n\nfloat FLT_flutter_local_threshold(float v, float l, float h)\n{\n return step(l, v) * (1.0 - step(h, v));\n}\n\nfloat FLT_flutter_local_sparkle(vec2 uv, float t)\n{\n vec2 param = uv;\n float _242 = FLT_flutter_local_triangle_noise(param);\n float n = _242;\n float param_1 = n;\n float param_2 = 0.0;\n float param_3 = 0.0500000007450580596923828125;\n float s = FLT_flutter_local_threshold(param_1, param_2, param_3);\n float param_4 = n + sin(3.1415927410125732421875 * (t + 0.3499999940395355224609375));\n float param_5 = 0.100000001490116119384765625;\n float param_6 = 0.1500000059604644775390625;\n s += FLT_flutter_local_threshold(param_4, param_5, param_6);\n float param_7 = n + sin(3.1415927410125732421875 * (t + 0.699999988079071044921875));\n float param_8 = 0.20000000298023223876953125;\n float param_9 = 0.25;\n s += FLT_flutter_local_threshold(param_7, param_8, param_9);\n float param_10 = n + sin(3.1415927410125732421875 * (t + 1.0499999523162841796875));\n float param_11 = 0.300000011920928955078125;\n float param_12 = 0.3499999940395355224609375;\n s += FLT_flutter_local_threshold(param_10, param_11, param_12);\n return clamp(s, 0.0, 1.0) * 0.550000011920928955078125;\n}\n\nvoid FLT_main()\n{\n u_alpha = u_composite_1.x;\n u_sparkle_alpha = u_composite_1.y;\n u_blur = u_composite_1.z;\n u_radius_scale = u_composite_1.w;\n vec2 p = FLT_flutter_local_FlutterFragCoord();\n vec2 uv_1 = p * u_resolution_scale;\n vec2 density_uv = uv_1 - mod(p, u_noise_scale);\n float radius = u_max_radius * u_radius_scale;\n vec2 param_13 = uv_1;\n float turbulence = FLT_flutter_local_turbulence(param_13);\n vec2 param_14 = p;\n vec2 param_15 = u_center;\n float param_16 = radius;\n float param_17 = 0.0500000007450580596923828125 * u_max_radius;\n float param_18 = u_blur;\n float ring = FLT_flutter_local_soft_ring(param_14, param_15, param_16, param_17, param_18);\n vec2 param_19 = density_uv;\n float param_20 = u_noise_phase;\n float sparkle = ((FLT_flutter_local_sparkle(param_19, param_20) * ring) * turbulence) * u_sparkle_alpha;\n vec2 param_21 = p;\n vec2 param_22 = u_center;\n float param_23 = radius;\n float param_24 = u_blur;\n float wave_alpha = (FLT_flutter_local_soft_circle(param_21, param_22, param_23, param_24) * u_alpha) * u_color.w;\n vec4 wave_color = vec4(u_color.xyz * wave_alpha, wave_alpha);\n fragColor = mix(wave_color, vec4(1.0), vec4(sparkle));\n}\n\nhalf4 main(float2 iFragCoord)\n{\n flutter_FragCoord = float4(iFragCoord, 0, 0);\n FLT_main();\n return fragColor;\n}\n", 3 | "stage": 1, 4 | "target_platform": 2, 5 | "uniforms": [ 6 | { 7 | "array_elements": 0, 8 | "bit_width": 32, 9 | "columns": 1, 10 | "location": 0, 11 | "name": "u_color", 12 | "rows": 4, 13 | "type": 10 14 | }, 15 | { 16 | "array_elements": 0, 17 | "bit_width": 32, 18 | "columns": 1, 19 | "location": 1, 20 | "name": "u_composite_1", 21 | "rows": 4, 22 | "type": 10 23 | }, 24 | { 25 | "array_elements": 0, 26 | "bit_width": 32, 27 | "columns": 1, 28 | "location": 2, 29 | "name": "u_center", 30 | "rows": 2, 31 | "type": 10 32 | }, 33 | { 34 | "array_elements": 0, 35 | "bit_width": 32, 36 | "columns": 1, 37 | "location": 3, 38 | "name": "u_max_radius", 39 | "rows": 1, 40 | "type": 10 41 | }, 42 | { 43 | "array_elements": 0, 44 | "bit_width": 32, 45 | "columns": 1, 46 | "location": 4, 47 | "name": "u_resolution_scale", 48 | "rows": 2, 49 | "type": 10 50 | }, 51 | { 52 | "array_elements": 0, 53 | "bit_width": 32, 54 | "columns": 1, 55 | "location": 5, 56 | "name": "u_noise_scale", 57 | "rows": 2, 58 | "type": 10 59 | }, 60 | { 61 | "array_elements": 0, 62 | "bit_width": 32, 63 | "columns": 1, 64 | "location": 6, 65 | "name": "u_noise_phase", 66 | "rows": 1, 67 | "type": 10 68 | }, 69 | { 70 | "array_elements": 0, 71 | "bit_width": 32, 72 | "columns": 1, 73 | "location": 7, 74 | "name": "u_circle1", 75 | "rows": 2, 76 | "type": 10 77 | }, 78 | { 79 | "array_elements": 0, 80 | "bit_width": 32, 81 | "columns": 1, 82 | "location": 8, 83 | "name": "u_circle2", 84 | "rows": 2, 85 | "type": 10 86 | }, 87 | { 88 | "array_elements": 0, 89 | "bit_width": 32, 90 | "columns": 1, 91 | "location": 9, 92 | "name": "u_circle3", 93 | "rows": 2, 94 | "type": 10 95 | }, 96 | { 97 | "array_elements": 0, 98 | "bit_width": 32, 99 | "columns": 1, 100 | "location": 10, 101 | "name": "u_rotation1", 102 | "rows": 2, 103 | "type": 10 104 | }, 105 | { 106 | "array_elements": 0, 107 | "bit_width": 32, 108 | "columns": 1, 109 | "location": 11, 110 | "name": "u_rotation2", 111 | "rows": 2, 112 | "type": 10 113 | }, 114 | { 115 | "array_elements": 0, 116 | "bit_width": 32, 117 | "columns": 1, 118 | "location": 12, 119 | "name": "u_rotation3", 120 | "rows": 2, 121 | "type": 10 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /docs/canvaskit/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/canvaskit/canvaskit.wasm -------------------------------------------------------------------------------- /docs/canvaskit/chromium/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/canvaskit/chromium/canvaskit.wasm -------------------------------------------------------------------------------- /docs/canvaskit/skwasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/canvaskit/skwasm.wasm -------------------------------------------------------------------------------- /docs/canvaskit/skwasm.worker.js: -------------------------------------------------------------------------------- 1 | "use strict";var Module={};var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",data=>onmessage({data:data}));var fs=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:f=>(0,eval)(fs.readFileSync(f,"utf8")+"//# sourceURL="+f),postMessage:msg=>parentPort.postMessage(msg),performance:global.performance||{now:Date.now}})}var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");if(ENVIRONMENT_IS_NODE){fs.writeSync(2,text+"\n");return}console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}skwasm(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0,/*canBlock=*/1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage; 2 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/docs/favicon.png -------------------------------------------------------------------------------- /docs/flutter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | if (!_flutter) { 6 | var _flutter = {}; 7 | } 8 | _flutter.loader = null; 9 | 10 | (function () { 11 | "use strict"; 12 | 13 | const baseUri = ensureTrailingSlash(getBaseURI()); 14 | 15 | function getBaseURI() { 16 | const base = document.querySelector("base"); 17 | return (base && base.getAttribute("href")) || ""; 18 | } 19 | 20 | function ensureTrailingSlash(uri) { 21 | if (uri == "") { 22 | return uri; 23 | } 24 | return uri.endsWith("/") ? uri : `${uri}/`; 25 | } 26 | 27 | /** 28 | * Wraps `promise` in a timeout of the given `duration` in ms. 29 | * 30 | * Resolves/rejects with whatever the original `promises` does, or rejects 31 | * if `promise` takes longer to complete than `duration`. In that case, 32 | * `debugName` is used to compose a legible error message. 33 | * 34 | * If `duration` is < 0, the original `promise` is returned unchanged. 35 | * @param {Promise} promise 36 | * @param {number} duration 37 | * @param {string} debugName 38 | * @returns {Promise} a wrapped promise. 39 | */ 40 | async function timeout(promise, duration, debugName) { 41 | if (duration < 0) { 42 | return promise; 43 | } 44 | let timeoutId; 45 | const _clock = new Promise((_, reject) => { 46 | timeoutId = setTimeout(() => { 47 | reject( 48 | new Error( 49 | `${debugName} took more than ${duration}ms to resolve. Moving on.`, 50 | { 51 | cause: timeout, 52 | } 53 | ) 54 | ); 55 | }, duration); 56 | }); 57 | 58 | return Promise.race([promise, _clock]).finally(() => { 59 | clearTimeout(timeoutId); 60 | }); 61 | } 62 | 63 | /** 64 | * Handles the creation of a TrustedTypes `policy` that validates URLs based 65 | * on an (optional) incoming array of RegExes. 66 | */ 67 | class FlutterTrustedTypesPolicy { 68 | /** 69 | * Constructs the policy. 70 | * @param {[RegExp]} validPatterns the patterns to test URLs 71 | * @param {String} policyName the policy name (optional) 72 | */ 73 | constructor(validPatterns, policyName = "flutter-js") { 74 | const patterns = validPatterns || [ 75 | /\.js$/, 76 | ]; 77 | if (window.trustedTypes) { 78 | this.policy = trustedTypes.createPolicy(policyName, { 79 | createScriptURL: function(url) { 80 | const parsed = new URL(url, window.location); 81 | const file = parsed.pathname.split("/").pop(); 82 | const matches = patterns.some((pattern) => pattern.test(file)); 83 | if (matches) { 84 | return parsed.toString(); 85 | } 86 | console.error( 87 | "URL rejected by TrustedTypes policy", 88 | policyName, ":", url, "(download prevented)"); 89 | } 90 | }); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Handles loading/reloading Flutter's service worker, if configured. 97 | * 98 | * @see: https://developers.google.com/web/fundamentals/primers/service-workers 99 | */ 100 | class FlutterServiceWorkerLoader { 101 | /** 102 | * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). 103 | * @param {TrustedTypesPolicy | undefined} policy 104 | */ 105 | setTrustedTypesPolicy(policy) { 106 | this._ttPolicy = policy; 107 | } 108 | 109 | /** 110 | * Returns a Promise that resolves when the latest Flutter service worker, 111 | * configured by `settings` has been loaded and activated. 112 | * 113 | * Otherwise, the promise is rejected with an error message. 114 | * @param {*} settings Service worker settings 115 | * @returns {Promise} that resolves when the latest serviceWorker is ready. 116 | */ 117 | loadServiceWorker(settings) { 118 | if (settings == null) { 119 | // In the future, settings = null -> uninstall service worker? 120 | console.debug("Null serviceWorker configuration. Skipping."); 121 | return Promise.resolve(); 122 | } 123 | if (!("serviceWorker" in navigator)) { 124 | let errorMessage = "Service Worker API unavailable."; 125 | if (!window.isSecureContext) { 126 | errorMessage += "\nThe current context is NOT secure." 127 | errorMessage += "\nRead more: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"; 128 | } 129 | return Promise.reject( 130 | new Error(errorMessage) 131 | ); 132 | } 133 | const { 134 | serviceWorkerVersion, 135 | serviceWorkerUrl = `${baseUri}flutter_service_worker.js?v=${serviceWorkerVersion}`, 136 | timeoutMillis = 4000, 137 | } = settings; 138 | 139 | // Apply the TrustedTypes policy, if present. 140 | let url = serviceWorkerUrl; 141 | if (this._ttPolicy != null) { 142 | url = this._ttPolicy.createScriptURL(url); 143 | } 144 | 145 | const serviceWorkerActivation = navigator.serviceWorker 146 | .register(url) 147 | .then((serviceWorkerRegistration) => this._getNewServiceWorker(serviceWorkerRegistration, serviceWorkerVersion)) 148 | .then(this._waitForServiceWorkerActivation); 149 | 150 | // Timeout race promise 151 | return timeout( 152 | serviceWorkerActivation, 153 | timeoutMillis, 154 | "prepareServiceWorker" 155 | ); 156 | } 157 | 158 | /** 159 | * Returns the latest service worker for the given `serviceWorkerRegistration`. 160 | * 161 | * This might return the current service worker, if there's no new service worker 162 | * awaiting to be installed/updated. 163 | * 164 | * @param {ServiceWorkerRegistration} serviceWorkerRegistration 165 | * @param {String} serviceWorkerVersion 166 | * @returns {Promise} 167 | */ 168 | async _getNewServiceWorker(serviceWorkerRegistration, serviceWorkerVersion) { 169 | if (!serviceWorkerRegistration.active && (serviceWorkerRegistration.installing || serviceWorkerRegistration.waiting)) { 170 | // No active web worker and we have installed or are installing 171 | // one for the first time. Simply wait for it to activate. 172 | console.debug("Installing/Activating first service worker."); 173 | return serviceWorkerRegistration.installing || serviceWorkerRegistration.waiting; 174 | } else if (!serviceWorkerRegistration.active.scriptURL.endsWith(serviceWorkerVersion)) { 175 | // When the app updates the serviceWorkerVersion changes, so we 176 | // need to ask the service worker to update. 177 | const newRegistration = await serviceWorkerRegistration.update(); 178 | console.debug("Updating service worker."); 179 | return newRegistration.installing || newRegistration.waiting || newRegistration.active; 180 | } else { 181 | console.debug("Loading from existing service worker."); 182 | return serviceWorkerRegistration.active; 183 | } 184 | } 185 | 186 | /** 187 | * Returns a Promise that resolves when the `serviceWorker` changes its 188 | * state to "activated". 189 | * 190 | * @param {ServiceWorker} serviceWorker 191 | * @returns {Promise} 192 | */ 193 | async _waitForServiceWorkerActivation(serviceWorker) { 194 | if (!serviceWorker || serviceWorker.state == "activated") { 195 | if (!serviceWorker) { 196 | throw new Error("Cannot activate a null service worker!"); 197 | } else { 198 | console.debug("Service worker already active."); 199 | return; 200 | } 201 | } 202 | return new Promise((resolve, _) => { 203 | serviceWorker.addEventListener("statechange", () => { 204 | if (serviceWorker.state == "activated") { 205 | console.debug("Activated new service worker."); 206 | resolve(); 207 | } 208 | }); 209 | }); 210 | } 211 | } 212 | 213 | /** 214 | * Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying 215 | * the user when Flutter is ready, through `didCreateEngineInitializer`. 216 | * 217 | * @see https://docs.flutter.dev/development/platform-integration/web/initialization 218 | */ 219 | class FlutterEntrypointLoader { 220 | /** 221 | * Creates a FlutterEntrypointLoader. 222 | */ 223 | constructor() { 224 | // Watchdog to prevent injecting the main entrypoint multiple times. 225 | this._scriptLoaded = false; 226 | } 227 | 228 | /** 229 | * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). 230 | * @param {TrustedTypesPolicy | undefined} policy 231 | */ 232 | setTrustedTypesPolicy(policy) { 233 | this._ttPolicy = policy; 234 | } 235 | 236 | /** 237 | * Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a 238 | * user-specified `onEntrypointLoaded` callback with an EngineInitializer 239 | * object when it's done. 240 | * 241 | * @param {*} options 242 | * @returns {Promise | undefined} that will eventually resolve with an 243 | * EngineInitializer, or will be rejected with the error caused by the loader. 244 | * Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`. 245 | */ 246 | async loadEntrypoint(options) { 247 | const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } = 248 | options || {}; 249 | 250 | return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded); 251 | } 252 | 253 | /** 254 | * Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded` 255 | * function supplied by the user (if needed). 256 | * 257 | * Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method, 258 | * which is bound to the correct instance of the FlutterEntrypointLoader by 259 | * the FlutterLoader object. 260 | * 261 | * @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42 262 | */ 263 | didCreateEngineInitializer(engineInitializer) { 264 | if (typeof this._didCreateEngineInitializerResolve === "function") { 265 | this._didCreateEngineInitializerResolve(engineInitializer); 266 | // Remove the resolver after the first time, so Flutter Web can hot restart. 267 | this._didCreateEngineInitializerResolve = null; 268 | // Make the engine revert to "auto" initialization on hot restart. 269 | delete _flutter.loader.didCreateEngineInitializer; 270 | } 271 | if (typeof this._onEntrypointLoaded === "function") { 272 | this._onEntrypointLoaded(engineInitializer); 273 | } 274 | } 275 | 276 | /** 277 | * Injects a script tag into the DOM, and configures this loader to be able to 278 | * handle the "entrypoint loaded" notifications received from Flutter web. 279 | * 280 | * @param {string} entrypointUrl the URL of the script that will initialize 281 | * Flutter. 282 | * @param {Function} onEntrypointLoaded a callback that will be called when 283 | * Flutter web notifies this object that the entrypoint is 284 | * loaded. 285 | * @returns {Promise | undefined} a Promise that resolves when the entrypoint 286 | * is loaded, or undefined if `onEntrypointLoaded` 287 | * is a function. 288 | */ 289 | _loadEntrypoint(entrypointUrl, onEntrypointLoaded) { 290 | const useCallback = typeof onEntrypointLoaded === "function"; 291 | 292 | if (!this._scriptLoaded) { 293 | this._scriptLoaded = true; 294 | const scriptTag = this._createScriptTag(entrypointUrl); 295 | if (useCallback) { 296 | // Just inject the script tag, and return nothing; Flutter will call 297 | // `didCreateEngineInitializer` when it's done. 298 | console.debug("Injecting 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live_tv_box", 3 | "short_name": "live_tv_box", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /docs/version.json: -------------------------------------------------------------------------------- 1 | {"app_name":"live_tv_box","version":"1.0.0","build_number":"1","package_name":"live_tv_box"} -------------------------------------------------------------------------------- /features/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/features/1.png -------------------------------------------------------------------------------- /features/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/features/2.png -------------------------------------------------------------------------------- /features/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/features/3.png -------------------------------------------------------------------------------- /features/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/features/4.png -------------------------------------------------------------------------------- /features/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/features/5.png -------------------------------------------------------------------------------- /ff3bb3a4168bd3bd0d23a408c8929e64/_composite.stamp: -------------------------------------------------------------------------------- 1 | {"inputs":[],"outputs":[]} -------------------------------------------------------------------------------- /ff3bb3a4168bd3bd0d23a408c8929e64/gen_dart_plugin_registrant.stamp: -------------------------------------------------------------------------------- 1 | {"inputs":[],"outputs":[]} -------------------------------------------------------------------------------- /ff3bb3a4168bd3bd0d23a408c8929e64/gen_localizations.stamp: -------------------------------------------------------------------------------- 1 | {"inputs":[],"outputs":[]} -------------------------------------------------------------------------------- /flutter_assets/AssetManifest.bin: -------------------------------------------------------------------------------- 1 | assets/cctv.m3u  assetassets/cctv.m3u2packages/cupertino_icons/assets/CupertinoIcons.ttf  asset2packages/cupertino_icons/assets/CupertinoIcons.ttf -------------------------------------------------------------------------------- /flutter_assets/AssetManifest.bin.json: -------------------------------------------------------------------------------- 1 | "DQIHD2Fzc2V0cy9jY3R2Lm0zdQwBDQEHBWFzc2V0Bw9hc3NldHMvY2N0di5tM3UHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRmDAENAQcFYXNzZXQHMnBhY2thZ2VzL2N1cGVydGlub19pY29ucy9hc3NldHMvQ3VwZXJ0aW5vSWNvbnMudHRm" -------------------------------------------------------------------------------- /flutter_assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"assets/cctv.m3u":["assets/cctv.m3u"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]} -------------------------------------------------------------------------------- /flutter_assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}] -------------------------------------------------------------------------------- /flutter_assets/assets/cctv.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXTINF:-1 tvg-id="",CCTV1HD 3 | https://node1.olelive.com:6443/live/CCTV1HD/hls.m3u8 4 | #EXTINF:-1 tvg-id="",CCTV2HD 5 | https://node1.olelive.com:6443/live/CCTV2HD/hls.m3u8 6 | #EXTINF:-1 tvg-id="",CCTV5HD 7 | https://node1.olelive.com:6443/live/CCTV5HD/hls.m3u8 8 | #EXTINF:-1 tvg-id="",CCTV5+ 9 | https://node1.olelive.com:6443/live/CCTV5PHD/hls.m3u8 10 | #EXTINF:-1 tvg-id="",CCTV7HD 11 | https://node1.olelive.com:6443/live/CCTV7HD/hls.m3u8 12 | #EXTINF:-1 tvg-id="",CCTV8HD 13 | https://node1.olelive.com:6443/live/CCTV8HD/hls.m3u8 14 | #EXTINF:-1 tvg-id="",CCTV9HD 15 | https://node1.olelive.com:6443/live/CCTV9HD/hls.m3u8 16 | #EXTINF:-1 tvg-id="",CCTV10HD 17 | https://node1.olelive.com:6443/live/CCTV10HD/hls.m3u8 18 | #EXTINF:-1 tvg-id="",CCTV17HD 19 | https://node1.olelive.com:6443/live/CCTV17HD/hls.m3u8 -------------------------------------------------------------------------------- /flutter_assets/assets/live.txt: -------------------------------------------------------------------------------- 1 | #EXTINF:-1,tvg-id="" tvg-name="CCTV1" tvg-logo="https://epg.112114.xyz/logo/CCTV1.png" group-title="央视IPV4",CCTV1 2 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv1hd8m/8000000 3 | #EXTINF:-1,tvg-id="" tvg-name="CCTV2" tvg-logo="https://epg.112114.xyz/logo/CCTV2.png" group-title="央视IPV4",CCTV2 4 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv2hd8m/8000000 5 | #EXTINF:-1,tvg-id="" tvg-name="CCTV3" tvg-logo="https://epg.112114.xyz/logo/CCTV3.png" group-title="央视IPV4",CCTV3 6 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv38m/8000000 7 | #EXTINF:-1,tvg-id="" tvg-name="CCTV4" tvg-logo="https://epg.112114.xyz/logo/CCTV4.png" group-title="央视IPV4",CCTV4 8 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv4hd8m/8000000 9 | #EXTINF:-1,tvg-id="" tvg-name="CCTV5" tvg-logo="https://epg.112114.xyz/logo/CCTV5.png" group-title="央视IPV4",CCTV5 10 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv58m/8000000 11 | #EXTINF:-1,tvg-id="" tvg-name="CCTV5+" tvg-logo="https://epg.112114.xyz/logo/CCTV5+.png" group-title="央视IPV4",CCTV5+ 12 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv5phd8m/8000000 13 | #EXTINF:-1,tvg-id="" tvg-name="CCTV6" tvg-logo="https://epg.112114.xyz/logo/CCTV6.png" group-title="央视IPV4",CCTV6 14 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv6hd8m/8000000 15 | #EXTINF:-1,tvg-id="" tvg-name="CCTV7" tvg-logo="https://epg.112114.xyz/logo/CCTV7.png" group-title="央视IPV4",CCTV7 16 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv7hd8m/8000000 17 | #EXTINF:-1,tvg-id="" tvg-name="CCTV8" tvg-logo="https://epg.112114.xyz/logo/CCTV8.png" group-title="央视IPV4",CCTV8 18 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv8hd8m/8000000 19 | #EXTINF:-1,tvg-id="" tvg-name="CCTV9" tvg-logo="https://epg.112114.xyz/logo/CCTV9.png" group-title="央视IPV4",CCTV9 20 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv9hd8m/8000000 21 | #EXTINF:-1,tvg-id="" tvg-name="CCTV10" tvg-logo="https://epg.112114.xyz/logo/CCTV10.png" group-title="央视IPV4",CCTV10 22 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv10hd8m/8000000 23 | #EXTINF:-1,tvg-id="" tvg-name="CCTV11" tvg-logo="https://epg.112114.xyz/logo/CCTV11.png" group-title="央视IPV4",CCTV11 24 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv11hd8m/8000000 25 | #EXTINF:-1,tvg-id="" tvg-name="CCTV12" tvg-logo="https://epg.112114.xyz/logo/CCTV12.png" group-title="央视IPV4",CCTV12 26 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv12hd8m/8000000 27 | #EXTINF:-1,tvg-id="" tvg-name="CCTV13" tvg-logo="https://epg.112114.xyz/logo/CCTV13.png" group-title="央视IPV4",CCTV13 28 | https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8 29 | #EXTINF:-1,tvg-id="" tvg-name="CCTV14" tvg-logo="https://epg.112114.xyz/logo/CCTV14.png" group-title="央视IPV4",CCTV14 30 | http://148.135.93.213:81/PLTV/bestv.php?id=cctvsehd8m/8000000 31 | #EXTINF:-1,tvg-id="" tvg-name="CCTV15" tvg-logo="https://epg.112114.xyz/logo/CCTV15.png" group-title="央视IPV4",CCTV15 32 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv15hd8m/8000000 33 | #EXTINF:-1,tvg-id="" tvg-name="CCTV16" tvg-logo="https://epg.112114.xyz/logo/CCTV16.png" group-title="央视IPV4",CCTV16 34 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv16hd8m/8000000 35 | #EXTINF:-1,tvg-id="" tvg-name="CCTV17" tvg-logo="https://epg.112114.xyz/logo/CCTV17.png" group-title="央视IPV4",CCTV17 36 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv17hd8m/8000000 37 | #EXTINF:-1,tvg-id="" tvg-name="CCTV16" tvg-logo="https://epg.112114.xyz/logo/CCTV16.png" group-title="央视IPV4",CCTV16-4K 38 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv16hd4k/15000000 39 | #EXTINF:-1,tvg-id="" tvg-name="CCTV4K" tvg-logo="https://epg.112114.xyz/logo/CCTV4K.png" group-title="央视IPV4",CCTV-4K 40 | http://148.135.93.213:81/PLTV/bestv.php?id=cctv4k/15000000 41 | #EXTINF:-1,tvg-id="" tvg-name="浙江卫视" tvg-logo="https://epg.112114.eu.org/logo/浙江卫视.png" group-title="卫视IPV4",浙江卫视 42 | http://148.135.93.213:81/PLTV/bestv.php?id=zjwshd8m/8000000 43 | #EXTINF:-1,tvg-id="" tvg-name="北京卫视" tvg-logo="https://epg.112114.eu.org/logo/北京卫视.png" group-title="卫视IPV4",北京卫视 44 | http://148.135.93.213:81/PLTV/bestv.php?id=bjwshd8m/8000000 45 | #EXTINF:-1,tvg-id="" tvg-name="辽宁卫视" tvg-logo="https://epg.112114.eu.org/logo/辽宁卫视.png" group-title="卫视IPV4",辽宁卫视 46 | http://148.135.93.213:81/PLTV/bestv.php?id=lnwshd8m/8000000 47 | #EXTINF:-1,tvg-id="" tvg-name="安徽卫视" tvg-logo="https://epg.112114.eu.org/logo/安徽卫视.png" group-title="卫视IPV4",安徽卫视 48 | http://148.135.93.213:81/PLTV/bestv.php?id=ahwshd8m/8000000 49 | #EXTINF:-1,tvg-id="" tvg-name="江苏卫视" tvg-logo="https://epg.112114.eu.org/logo/江苏卫视.png" group-title="卫视IPV4",江苏卫视 50 | http://148.135.93.213:81/PLTV/bestv.php?id=jswshd8m/8000000 51 | #EXTINF:-1,tvg-id="" tvg-name="东方卫视" tvg-logo="https://epg.112114.eu.org/logo/东方卫视.png" group-title="卫视IPV4",东方卫视 52 | http://148.135.93.213:81/PLTV/bestv.php?id=dfwshd8m/8000000 53 | #EXTINF:-1,tvg-id="" tvg-name="东南卫视" tvg-logo="https://epg.112114.eu.org/logo/东南卫视.png" group-title="卫视IPV4",东南卫视 54 | http://148.135.93.213:81/PLTV/bestv.php?id=dnwshd8m/8000000 55 | #EXTINF:-1,tvg-id="" tvg-name="湖南卫视" tvg-logo="https://epg.112114.eu.org/logo/湖南卫视.png" group-title="卫视IPV4",湖南卫视 56 | http://148.135.93.213:81/PLTV/bestv.php?id=hunanwshd8m/8000000 57 | #EXTINF:-1,tvg-id="" tvg-name="湖北卫视" tvg-logo="https://epg.112114.eu.org/logo/湖北卫视.png" group-title="卫视IPV4",湖北卫视 58 | http://39.135.138.58:18890/PLTV/88888888/224/3221225740/index.m3u8 59 | #EXTINF:-1,tvg-id="" tvg-name="广东卫视" tvg-logo="https://epg.112114.eu.org/logo/广东卫视.png" group-title="卫视IPV4",广东卫视 60 | http://148.135.93.213:81/PLTV/bestv.php?id=gdwshd8m/8000000 61 | #EXTINF:-1,tvg-id="" tvg-name="广西卫视" tvg-logo="https://epg.112114.eu.org/logo/广西卫视.png" group-title="卫视IPV4",广西卫视 62 | http://148.135.93.213:81/PLTV/bestv.php?id=gxwshd8m/8000000 63 | #EXTINF:-1,tvg-id="" tvg-name="深圳卫视" tvg-logo="https://epg.112114.eu.org/logo/深圳卫视.png" group-title="卫视IPV4",深圳卫视 64 | http://148.135.93.213:81/PLTV/bestv.php?id=szwshd8m/8000000 65 | #EXTINF:-1,tvg-id="" tvg-name="四川卫视" tvg-logo="https://epg.112114.eu.org/logo/四川卫视.png" group-title="卫视IPV4",四川卫视 66 | http://148.135.93.213:81/PLTV/bestv.php?id=scwshd/8000000 67 | #EXTINF:-1,tvg-id="" tvg-name="山东卫视" tvg-logo="https://epg.112114.eu.org/logo/山东卫视.png" group-title="卫视IPV4",山东卫视 68 | http://148.135.93.213:81/PLTV/bestv.php?id=sdws8m/8000000 69 | #EXTINF:-1,tvg-id="" tvg-name="河南卫视" tvg-logo="https://epg.112114.eu.org/logo/河南卫视.png" group-title="卫视IPV4",河南卫视 70 | http://148.135.93.213:81/PLTV/bestv.php?id=hnwshd8m/8000000 71 | #EXTINF:-1,tvg-id="" tvg-name="河北卫视" tvg-logo="https://epg.112114.eu.org/logo/河北卫视.png" group-title="卫视IPV4",河北卫视 72 | http://148.135.93.213:81/PLTV/bestv.php?id=hbwshd8m/8000000 73 | #EXTINF:-1,tvg-id="" tvg-name="江西卫视" tvg-logo="https://epg.112114.eu.org/logo/江西卫视.png" group-title="卫视IPV4",江西卫视 74 | http://148.135.93.213:81/PLTV/bestv.php?id=jxws8m/8000000 75 | #EXTINF:-1,tvg-id="" tvg-name="天津卫视" tvg-logo="https://epg.112114.eu.org/logo/天津卫视.png" group-title="卫视IPV4",天津卫视 76 | http://148.135.93.213:81/PLTV/bestv.php?id=tjwshd8m/8000000 77 | #EXTINF:-1,tvg-id="" tvg-name="重庆卫视" tvg-logo="https://epg.112114.eu.org/logo/重庆卫视.png" group-title="卫视IPV4",重庆卫视 78 | http://148.135.93.213:81/PLTV/bestv.php?id=cqws8m/8000000 79 | #EXTINF:-1,tvg-id="" tvg-name="黑龙江卫视" tvg-logo="https://epg.112114.eu.org/logo/黑龙江卫视.png" group-title="卫视IPV4",黑龙江卫视 80 | http://148.135.93.213:81/PLTV/bestv.php?id=hljwshd8m/8000000 81 | #EXTINF:-1,tvg-id="" tvg-name="吉林卫视" tvg-logo="https://epg.112114.eu.org/logo/吉林卫视.png" group-title="卫视IPV4",吉林卫视 82 | http://148.135.93.213:81/PLTV/bestv.php?id=jlwshd8m/8000000 83 | #EXTINF:-1,tvg-id="" tvg-name="海南卫视" tvg-logo="https://epg.112114.eu.org/logo/海南卫视.png" group-title="卫视IPV4",海南卫视 84 | http://148.135.93.213:81/PLTV/bestv.php?id=hainanwshd8m/8000000 85 | #EXTINF:-1,tvg-id="" tvg-name="云南卫视" tvg-logo="https://epg.112114.eu.org/logo/云南卫视.png" group-title="卫视IPV4",云南卫视 86 | http://148.135.93.213:81/PLTV/bestv.php?id=ynwshd8m/8000000 87 | #EXTINF:-1,tvg-id="" tvg-name="贵州卫视" tvg-logo="https://epg.112114.eu.org/logo/贵州卫视.png" group-title="卫视IPV4",贵州卫视 88 | http://148.135.93.213:81/PLTV/bestv.php?id=gzwshd8m/8000000 89 | #EXTINF:-1,tvg-id="" tvg-name="五星体育" tvg-logo="https://epg.112114.eu.org/logo/五星体育.png" group-title="卫视IPV4",五星体育 90 | http://148.135.93.213:81/PLTV/bestv.php?id=wxtyhd8m/8000000 91 | 92 | #EXTINF:-1,tvg-id="" tvg-name="CCTV8K" tvg-logo="https://epg.112114.eu.org/logo/CCTV8K.png" group-title="央视-4K",CCTV8K 93 | http://[2409:8087:2001:20:2800:0:df6e:eb02]:80/wh7f454c46tw2749731958_105918260/ott.mobaibox.com/PLTV/3/224/3221228165/index.m3u8?icpid=3&RTS=1681529690&from=40&popid=40&hms_devid=2039&prioritypopid=40&vqe=3 94 | #EXTINF:-1,tvg-id="" tvg-name="CCTV1" tvg-logo="https://epg.112114.eu.org/logo/CCTV1.png" group-title="央视-4K",CCTV1-4K 95 | http://[2409:8087:2001:20:2800:0:df6e:eb02]/ott.mobaibox.com/PLTV/4/224/3221227896/index.m3u8 96 | #EXTINF:-1,tvg-id="" tvg-name="CCTV1" tvg-logo="https://epg.112114.eu.org/logo/CCTV1.png" group-title="央视-4K",CCTV1-4K 97 | http://[2409:8087:2001:20:2800:0:df6e:eb03]/ott.mobaibox.com/PLTV/4/224/3221227896/index.m3u8 98 | #EXTINF:-1,tvg-id="" tvg-name="CCTV5" tvg-logo="https://epg.112114.eu.org/logo/CCTV5.png" group-title="央视-4K",CCTV5-4K 99 | http://[2409:8087:2001:20:2800:0:df6e:eb22]/ott.mobaibox.com/PLTV/4/224/3221228502/index.m3u8 100 | #EXTINF:-1,tvg-id="" tvg-name="CCTV8" tvg-logo="https://epg.112114.eu.org/logo/CCTV8.png" group-title="央视-4K",CCTV8-高码 101 | http://[2409:8087:2001:20:2800:0:df6e:eb26]/ott.mobaibox.com/PLTV/1/224/3221228578/index.m3u8 102 | 103 | #EXTINF:-1,tvg-id="CCTV1" tvg-name="CCTV1" tvg-logo="https://epg.112114.eu.org/logo/CCTV1.png" group-title="央视IPV6",CCTV1 104 | http://[2409:8087:7000:20::4]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226231/1.m3u8 105 | #EXTINF:-1,tvg-id="CCTV2" tvg-name="CCTV2" tvg-logo="https://epg.112114.eu.org/logo/CCTV2.png" group-title="央视IPV6",CCTV2 106 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226195/1.m3u8 107 | #EXTINF:-1,tvg-id="CCTV3" tvg-name="CCTV3" tvg-logo="https://epg.112114.eu.org/logo/CCTV3.png" group-title="央视IPV6",CCTV3 108 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226397/1.m3u8 109 | #EXTINF:-1,tvg-id="CCTV4" tvg-name="CCTV4" tvg-logo="https://epg.112114.eu.org/logo/CCTV4.png" group-title="央视IPV6",CCTV4 110 | http://[2409:8087:2001:20:2800:0:df6e:eb12]/wh7f454c46tw3772680253_-1555628407/ott.mobaibox.com/PLTV/3/224/3221227549/index.m3u8?icpid=3&RTS=1668594272&from=40&popid=40&hms_devid=2112&prioritypopid=40&vqe=3 111 | #EXTINF:-1,tvg-id="CCTV5" tvg-name="CCTV5" tvg-logo="https://epg.112114.eu.org/logo/CCTV5.png" group-title="央视IPV6",CCTV5 112 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226395/1.m3u8 113 | #EXTINF:-1,tvg-id="CCTV5+" tvg-name="CCTV5+" tvg-logo="https://epg.112114.eu.org/logo/CCTV5+.png" group-title="央视IPV6",CCTV5+ 114 | http://[2409:8087:2001:20:2800:0:df6e:eb26]/ott.mobaibox.com/PLTV/1/224/3221228277/index.m3u8 115 | #EXTINF:-1,tvg-id="CCTV6" tvg-name="CCTV6" tvg-logo="https://epg.112114.eu.org/logo/CCTV6.png" group-title="央视IPV6",CCTV6 116 | http://[2409:8087:2001:20:2800:0:df6e:eb22]/ott.mobaibox.com/PLTV/4/224/3221228516/index.m3u8 117 | #EXTINF:-1,tvg-id="CCTV7" tvg-name="CCTV7" tvg-logo="https://epg.112114.eu.org/logo/CCTV7.png" group-title="央视IPV6",CCTV7 118 | http://[2409:8087:2001:20:2800:0:df6e:eb26]/wh7f454c46tw3984282630_1427246842/ott.mobaibox.com/PLTV/3/224/3221228283/index.m3u8?icpid=3&RTS=1668594483&from=40&popid=40&hms_devid=2293&prioritypopid=40&vqe=3 119 | #EXTINF:-1,tvg-id="CCTV8" tvg-name="CCTV8" tvg-logo="https://epg.112114.eu.org/logo/CCTV8.png" group-title="央视IPV6",CCTV8 120 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226391/1.m3u8 121 | #EXTINF:-1,tvg-id="CCTV9" tvg-name="CCTV9" tvg-logo="https://epg.112114.eu.org/logo/CCTV9.png" group-title="央视IPV6",CCTV9 122 | http://[2409:8087:2001:20:2800:0:df6e:eb21]/wh7f454c46tw4254168827_1850088835/ott.mobaibox.com/PLTV/3/224/3221228303/index.m3u8?icpid=3&RTS=1668594753&from=40&popid=40&hms_devid=2290&prioritypopid=40&vqe=3 123 | #EXTINF:-1,tvg-id="CCTV10" tvg-name="CCTV10" tvg-logo="https://epg.112114.eu.org/logo/CCTV10.png" group-title="央视IPV6",CCTV10 124 | http://[2409:8087:2001:20:2800:0:df6e:eb21]/wh7f454c46tw30319478_-185824076/ott.mobaibox.com/PLTV/3/224/3221228286/index.m3u8?icpid=3&RTS=1668594824&from=40&popid=40&hms_devid=2290&prioritypopid=40&vqe=3 125 | #EXTINF:-1,tvg-id="CCTV11" tvg-name="CCTV11" tvg-logo="https://epg.112114.eu.org/logo/CCTV11.png" group-title="央视IPV6",CCTV11 126 | http://[2409:8087:2001:20:2800:0:df6e:eb23]/wh7f454c46tw105619488_1866436632/ott.mobaibox.com/PLTV/3/224/3221228289/index.m3u8?icpid=3&RTS=1668594900&from=40&popid=40&hms_devid=2291&prioritypopid=40&vqe=3 127 | #EXTINF:-1,tvg-id="CCTV12" tvg-name="CCTV12" tvg-logo="https://epg.112114.eu.org/logo/CCTV12.png" group-title="央视IPV6",CCTV12 128 | http://[2409:8087:2001:20:2800:0:df6e:eb23]/wh7f454c46tw185877003_-533945400/ott.mobaibox.com/PLTV/3/224/3221228401/index.m3u8?icpid=3&RTS=1668594980&from=40&popid=40&hms_devid=2291&prioritypopid=40&vqe=3 129 | #EXTINF:-1,tvg-id="CCTV13" tvg-name="CCTV13" tvg-logo="https://epg.112114.eu.org/logo/CCTV13.png" group-title="央视IPV6",CCTV13 130 | http://[2409:8087:2001:20:2800:0:df6e:eb16]/wh7f454c46tw259647455_-1559913959/ott.mobaibox.com/PLTV/3/224/3221228224/index.m3u8?icpid=3&RTS=1668595054&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 131 | #EXTINF:-1,tvg-id="CCTV14" tvg-name="CCTV14" tvg-logo="https://epg.112114.eu.org/logo/CCTV14.png" group-title="央视IPV6",CCTV14 132 | http://[2409:8087:2001:20:2800:0:df6e:eb22]/wh7f454c46tw340147088_1594094424/ott.mobaibox.com/PLTV/3/224/3221228292/index.m3u8?icpid=3&RTS=1668595134&from=40&popid=40&hms_devid=2291&prioritypopid=40&vqe=3 133 | #EXTINF:-1,tvg-id="CCTV15" tvg-name="CCTV15" tvg-logo="https://epg.112114.eu.org/logo/CCTV15.png" group-title="央视IPV6",CCTV15 134 | http://[2409:8087:2001:20:2800:0:df6e:eb22]/wh7f454c46tw434828587_188325560/ott.mobaibox.com/PLTV/3/224/3221228404/index.m3u8?icpid=3&RTS=1668595229&from=40&popid=40&hms_devid=2291&prioritypopid=40&vqe=3 135 | #EXTINF:-1,tvg-id="CCTV16" tvg-name="CCTV16" tvg-logo="https://epg.112114.eu.org/logo/CCTV16.png" group-title="央视IPV6",CCTV16 136 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226921/1.m3u8 137 | #EXTINF:-1,tvg-id="CCTV17" tvg-name="CCTV17" tvg-logo="https://epg.112114.eu.org/logo/CCTV17.png" group-title="央视IPV6",CCTV17 138 | http://[2409:8087:2001:20:2800:0:df6e:eb23]/wh7f454c46tw483903016_-67353299/ott.mobaibox.com/PLTV/3/224/3221228407/index.m3u8?icpid=3&RTS=1668595278&from=40&popid=40&hms_devid=2291&prioritypopid=40&vqe=3 139 | #EXTINF:-1,tvg-id="世界地理" tvg-name="世界地理" tvg-logo="https://epg.112114.eu.org/logo/世界地理.png" group-title="央视IPV6",世界地理 140 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226947/index.m3u8 141 | #EXTINF:-1,tvg-id="兵器科技" tvg-name="兵器科技" tvg-logo="https://epg.112114.eu.org/logo/兵器科技.png" group-title="央视IPV6",兵器科技 142 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226975/index.m3u8 143 | #EXTINF:-1,tvg-id="央视台球" tvg-name="央视台球" tvg-logo="https://epg.112114.eu.org/logo/央视台球.png" group-title="央视IPV6",央视台球 144 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226956/index.m3u8 145 | #EXTINF:-1,tvg-id="文化精品" tvg-name="文化精品" tvg-logo="https://epg.112114.eu.org/logo/文化精品.png" group-title="央视IPV6",文化精品 146 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226981/index.m3u8 147 | #EXTINF:-1,tvg-id="女性时尚" tvg-name="女性时尚" tvg-logo="https://epg.112114.eu.org/logo/女性时尚.png" group-title="央视IPV6",女性时尚 148 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226969/index.m3u8 149 | #EXTINF:-1,tvg-id="怀旧剧场" tvg-name="怀旧剧场" tvg-logo="https://epg.112114.eu.org/logo/怀旧剧场.png" group-title="央视IPV6",怀旧剧场 150 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226972/index.m3u8 151 | #EXTINF:-1,tvg-id="电视指南" tvg-name="电视指南" tvg-logo="https://epg.112114.eu.org/logo/电视指南.png" group-title="央视IPV6",电视指南 152 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226987/index.m3u8 153 | #EXTINF:-1,tvg-id="第一剧场" tvg-name="第一剧场" tvg-logo="https://epg.112114.eu.org/logo/第一剧场.png" group-title="央视IPV6",第一剧场 154 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226959/index.m3u8 155 | #EXTINF:-1,tvg-id="风云剧场" tvg-name="风云剧场" tvg-logo="https://epg.112114.eu.org/logo/风云剧场.png" group-title="央视IPV6",风云剧场 156 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226950/index.m3u8 157 | #EXTINF:-1,tvg-id="风云足球" tvg-name="风云足球" tvg-logo="https://epg.112114.eu.org/logo/风云足球.png" group-title="央视IPV6",风云足球 158 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226984/index.m3u8 159 | #EXTINF:-1,tvg-id="风云音乐" tvg-name="风云音乐" tvg-logo="https://epg.112114.eu.org/logo/风云音乐.png" group-title="央视IPV6",风云音乐 160 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226953/index.m3u8 161 | #EXTINF:-1,tvg-id="高尔夫网球" tvg-name="央视高网" tvg-logo="https://epg.112114.eu.org/logo/央视高网.png" group-title="央视IPV6",央视高网 162 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226978/index.m3u8 163 | #EXTINF:-1,tvg-id="CHC动作电影" tvg-name="CHC动作电影" tvg-logo="https://epg.112114.xyz/logo/CHC动作电影.png" group-title="央视IPV6",CHC动作电影 164 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226465/1.m3u8 165 | #EXTINF:-1,tvg-id="CHC家庭影院" tvg-name="CHC家庭影院" tvg-logo="https://epg.112114.xyz/logo/CHC家庭影院.png" group-title="央视IPV6",CHC家庭影院 166 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226462/1.m3u8 167 | #EXTINF:-1,tvg-id="CHC高清电影" tvg-name="CHC高清电影" tvg-logo="https://epg.112114.xyz/logo/CHC高清电影.png" group-title="央视IPV6",CHC高清电影 168 | http://[2409:8087:7001:20:2::3]:80/dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226463/1.m3u8 169 | 170 | #EXTINF:-1,tvg-id="北京卫视" tvg-name="北京卫视" tvg-logo="https://epg.112114.xyz/logo/北京卫视.png" group-title="卫视IPV6",北京卫视 171 | http://[2409:8087:2001:20:2800:0:df6e:eb0b]/wh7f454c46tw2687876293_-1703018199/ott.mobaibox.com/PLTV/3/224/3221227508/index.m3u8?icpid=3&RTS=1668597482&from=40&popid=40&hms_devid=2038&prioritypopid=40&vqe=3 172 | #EXTINF:-1,tvg-id="东方卫视" tvg-name="东方卫视" tvg-logo="https://epg.112114.xyz/logo/东方卫视.png" group-title="卫视IPV6",东方卫视 173 | http://[2409:8087:2001:20:2800:0:df6e:eb16]/wh7f454c46tw2542426131_1585848046/ott.mobaibox.com/PLTV/3/224/3221227511/index.m3u8?icpid=3&RTS=1668597336&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 174 | #EXTINF:-1,tvg-id="湖南卫视" tvg-name="湖南卫视" tvg-logo="https://epg.112114.xyz/logo/湖南卫视.png" group-title="卫视IPV6",湖南卫视 175 | http://[2409:8087:2001:20:2800:0:df6e:eb02]:80/PLTV/1/224/3221230620/1.m3u8 176 | #EXTINF:-1,tvg-id="浙江卫视" tvg-name="浙江卫视" tvg-logo="https://epg.112114.xyz/logo/浙江卫视.png" group-title="卫视IPV6",浙江卫视 177 | http://[2409:8087:2001:20:2800:0:df6e:eb11]/wh7f454c46tw1197826796_-265147758/ott.mobaibox.com/PLTV/3/224/3221227491/index.m3u8?icpid=3&RTS=1669699798&from=40&popid=40&hms_devid=2110&prioritypopid=40&vqe=3 178 | #EXTINF:-1,tvg-id="江苏卫视" tvg-name="江苏卫视" tvg-logo="https://epg.112114.xyz/logo/江苏卫视.png" group-title="卫视IPV6",江苏卫视 179 | http://[2409:8087:2001:20:2800:0:df6e:eb12]/wh7f454c46tw2983110475_-1591539074/ott.mobaibox.com/PLTV/3/224/3221228097/index.m3u8?icpid=3&RTS=1668597777&from=40&popid=40&hms_devid=2112&prioritypopid=40&vqe=3 180 | #EXTINF:-1,tvg-id="江西卫视" tvg-name="江西卫视" tvg-logo="https://epg.112114.xyz/logo/江西卫视.png" group-title="卫视IPV6",江西卫视 181 | http://[2409:8087:2001:20:2800:0:df6e:eb17]/wh7f454c46tw1965546073_-1364170119/ott.mobaibox.com/PLTV/3/224/3221228109/index.m3u8?icpid=3&RTS=1669700566&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 182 | #EXTINF:-1,tvg-id="河北卫视" tvg-name="河北卫视" tvg-logo="https://epg.112114.xyz/logo/河北卫视.png" group-title="卫视IPV6",河北卫视 183 | http://[2409:8087:2001:20:2800:0:df6e:eb05]/wh7f454c46tw1698524218_988816054/ott.mobaibox.com/PLTV/3/224/3221228106/index.m3u8?icpid=3&RTS=1669700299&from=40&popid=40&hms_devid=2041&prioritypopid=40&vqe=3 184 | #EXTINF:-1,tvg-id="河南卫视" tvg-name="河南卫视" tvg-logo="https://epg.112114.xyz/logo/河南卫视.png" group-title="卫视IPV6",河南卫视 185 | http://[2409:8087:2001:20:2800:0:df6e:eb17]/wh7f454c46tw1784575403_-1712002709/ott.mobaibox.com/PLTV/3/224/3221228221/index.m3u8?icpid=3&RTS=1669700385&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 186 | #EXTINF:-1,tvg-id="海南卫视" tvg-name="海南卫视" tvg-logo="https://epg.112114.xyz/logo/海南卫视.png" group-title="卫视IPV6",海南卫视 187 | http://[2409:8087:2001:20:2800:0:df6e:eb08]/wh7f454c46tw2843123663_1489055229/ott.mobaibox.com/PLTV/3/224/3221228139/index.m3u8?icpid=3&RTS=1669701444&from=40&popid=40&hms_devid=2037&prioritypopid=40&vqe=3 188 | #EXTINF:-1,tvg-id="深圳卫视" tvg-name="深圳卫视" tvg-logo="https://epg.112114.xyz/logo/深圳卫视.png" group-title="卫视IPV6",深圳卫视 189 | http://[2409:8087:2001:20:2800:0:df6e:eb11]/wh7f454c46tw3025923625_628894334/ott.mobaibox.com/PLTV/3/224/3221227555/index.m3u8?icpid=3&RTS=1668597820&from=40&popid=40&hms_devid=2110&prioritypopid=40&vqe=3 190 | #EXTINF:-1,tvg-id="湖北卫视" tvg-name="湖北卫视" tvg-logo="https://epg.112114.xyz/logo/湖北卫视.png" group-title="卫视IPV6",湖北卫视 191 | http://[2409:8087:2001:20:2800:0:df6e:eb11]/wh7f454c46tw3059146177_-1525708880/ott.mobaibox.com/PLTV/3/224/3221227479/index.m3u8?icpid=3&RTS=1668597853&from=40&popid=40&hms_devid=2110&prioritypopid=40&vqe=3 192 | #EXTINF:-1,tvg-id="四川卫视" tvg-name="四川卫视" tvg-logo="https://epg.112114.xyz/logo/四川卫视.png" group-title="卫视IPV6",四川卫视 193 | http://[2409:8087:2001:20:2800:0:df6e:eb09]/wh7f454c46tw2502717081_11504314/ott.mobaibox.com/PLTV/3/224/3221227556/index.m3u8?icpid=3&RTS=1669701103&from=40&popid=40&hms_devid=2037&prioritypopid=40&vqe=3 194 | #EXTINF:-1,tvg-id="天津卫视" tvg-name="天津卫视" tvg-logo="https://epg.112114.xyz/logo/天津卫视.png" group-title="卫视IPV6",天津卫视 195 | http://[2409:8087:2001:20:2800:0:df6e:eb11]/wh7f454c46tw2730715388_788631706/ott.mobaibox.com/PLTV/3/224/3221227488/index.m3u8?icpid=3&RTS=1668597525&from=40&popid=40&hms_devid=2110&prioritypopid=40&vqe=3 196 | #EXTINF:-1,tvg-id="安徽卫视" tvg-name="安徽卫视" tvg-logo="https://epg.112114.xyz/logo/安徽卫视.png" group-title="卫视IPV6",安徽卫视 197 | http://[2409:8087:2001:20:2800:0:df6e:eb12]/wh7f454c46tw2802330256_375747539/ott.mobaibox.com/PLTV/3/224/3221227558/index.m3u8?icpid=3&RTS=1668597596&from=40&popid=40&hms_devid=2112&prioritypopid=40&vqe=3 198 | #EXTINF:-1,tvg-id="山东卫视" tvg-name="山东卫视" tvg-logo="https://epg.112114.xyz/logo/山东卫视.png" group-title="卫视IPV6",山东卫视 199 | http://[2409:8087:2001:20:2800:0:df6e:eb10]/wh7f454c46tw2848465480_1677095697/ott.mobaibox.com/PLTV/3/224/3221227517/index.m3u8?icpid=3&RTS=1668597642&from=40&popid=40&hms_devid=2110&prioritypopid=40&vqe=3 200 | #EXTINF:-1,tvg-id="广东卫视" tvg-name="广东卫视" tvg-logo="https://epg.112114.xyz/logo/广东卫视.png" group-title="卫视IPV6",广东卫视 201 | http://[2409:8087:2001:20:2800:0:df6e:eb03]/wh7f454c46tw2917484419_-1632335828/ott.mobaibox.com/PLTV/3/224/3221227476/index.m3u8?icpid=3&RTS=1668597711&from=40&popid=40&hms_devid=2039&prioritypopid=40&vqe=3 202 | #EXTINF:-1,tvg-id="广西卫视" tvg-name="广西卫视" tvg-logo="https://epg.112114.xyz/logo/广西卫视.png" group-title="卫视IPV6",广西卫视 203 | http://[2409:8087:2001:20:2800:0:df6e:eb05]/wh7f454c46tw2325655923_1638953995/ott.mobaibox.com/PLTV/3/224/3221228183/index.m3u8?icpid=3&RTS=1669700926&from=40&popid=40&hms_devid=2041&prioritypopid=40&vqe=3 204 | #EXTINF:-1,tvg-id="贵州卫视" tvg-name="贵州卫视" tvg-logo="https://epg.112114.xyz/logo/贵州卫视.png" group-title="卫视IPV6",贵州卫视 205 | http://[2409:8087:2001:20:2800:0:df6e:eb08]/wh7f454c46tw2087544744_109645303/ott.mobaibox.com/PLTV/3/224/3221228136/index.m3u8?icpid=3&RTS=1669700688&from=40&popid=40&hms_devid=2037&prioritypopid=40&vqe=3 206 | #EXTINF:-1,tvg-id="辽宁卫视" tvg-name="辽宁卫视" tvg-logo="https://epg.112114.xyz/logo/辽宁卫视.png" group-title="卫视IPV6",辽宁卫视 207 | http://[2409:8087:2001:20:2800:0:df6e:eb16]/wh7f454c46tw3102310989_-1844874138/ott.mobaibox.com/PLTV/3/224/3221227485/index.m3u8?icpid=3&RTS=1668597896&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 208 | #EXTINF:-1,tvg-id="重庆卫视" tvg-name="重庆卫视" tvg-logo="https://epg.112114.xyz/logo/重庆卫视.png" group-title="卫视IPV6",重庆卫视 209 | http://[2409:8087:2001:20:2800:0:df6e:eb09]/wh7f454c46tw2787424866_142914197/ott.mobaibox.com/PLTV/3/224/3221228133/index.m3u8?icpid=3&RTS=1669701388&from=40&popid=40&hms_devid=2037&prioritypopid=40&vqe=3 210 | #EXTINF:-1,tvg-id="黑龙江卫视" tvg-name="黑龙江卫视" tvg-logo="https://epg.112114.xyz/logo/黑龙江卫视.png" group-title="卫视IPV6",龙江卫视 211 | http://[2409:8087:2001:20:2800:0:df6e:eb17]/wh7f454c46tw3143044227_-926148572/ott.mobaibox.com/PLTV/3/224/3221227514/index.m3u8?icpid=3&RTS=1668597937&from=40&popid=40&hms_devid=2114&prioritypopid=40&vqe=3 212 | #EXTINF:-1,tvg-id="甘肃卫视" tvg-name="甘肃卫视" tvg-logo="https://epg.112114.xyz/logo/甘肃卫视.png" group-title="卫视IPV6",甘肃卫视 213 | http://[2409:8087:2001:20:2800:0:df6e:eb1a]/wh7f454c46tw1400573680_-1979013462/ott.mobaibox.com/PLTV/3/224/3221227568/index.m3u8?icpid=3&RTS=1668600490&from=40&popid=40&hms_devid=2116&prioritypopid=40&vqe=3 214 | #EXTINF:-1,tvg-id="吉林卫视" tvg-name="吉林卫视" tvg-logo="https://epg.112114.xyz/logo/吉林卫视.png" group-title="卫视IPV6",吉林卫视 215 | http://[2409:8087:2001:20:2800:0:df6e:eb03]/wh7f454c46tw1874077489_789689702/ott.mobaibox.com/PLTV/3/224/3221228130/index.m3u8?icpid=3&RTS=1669700475&from=40&popid=40&hms_devid=2039&prioritypopid=40&vqe=3 216 | #EXTINF:-1,tvg-id="东南卫视" tvg-name="东南卫视" tvg-logo="https://epg.112114.xyz/logo/东南卫视.png" group-title="卫视IPV6",东南卫视 217 | http://[2409:8087:2001:20:2800:0:df6e:eb0a]/wh7f454c46tw2900649569_-743777193/ott.mobaibox.com/PLTV/3/224/3221227670/index.m3u8?icpid=3&RTS=1669701501&from=40&popid=40&hms_devid=2038&prioritypopid=40&vqe=3 218 | #EXTINF:-1,tvg-id="青海卫视" tvg-name="青海卫视" tvg-logo="https://epg.112114.xyz/logo/青海卫视.png" group-title="卫视IPV6",青海卫视 219 | http://[2409:8087:2001:20:2800:0:df6e:eb1b]/wh7f454c46tw1221605145_-1738716276/ott.mobaibox.com/PLTV/3/224/3221227554/index.m3u8?icpid=3&RTS=1668600311&from=40&popid=40&hms_devid=2116&prioritypopid=40&vqe=3 220 | #EXTINF:-1,tvg-id="新疆卫视" tvg-name="新疆卫视" tvg-logo="https://epg.112114.xyz/logo/新疆卫视.png" group-title="卫视IPV6",新疆卫视 221 | http://[2409:8087:2001:20:2800:0:df6e:eb0b]/wh7f454c46tw993550557_162751766/ott.mobaibox.com/PLTV/3/224/3221228290/index.m3u8?icpid=3&RTS=1668604377&from=40&popid=40&hms_devid=2038&prioritypopid=40&vqe=3 222 | #EXTINF:-1,tvg-id="内蒙古卫视" tvg-name="内蒙古卫视" tvg-logo="https://epg.112114.eu.org/logo/内蒙古卫视.png" group-title="卫视IPV6",内蒙古卫视 223 | http://[2409:8087:2001:20:2800:0:df6e:eb19]:80/wh7f454c46tw2228825807_1324573507/ott.mobaibox.com/PLTV/3/224/3221227557/index.m3u8 224 | #EXTINF:-1,tvg-id="" tvg-name="五星体育" tvg-logo="https://epg.112114.eu.org/logo/五星体育.png" group-title="卫视IPV6",五星体育 225 | http://[2409:8087:1e03:21::2]:6060/cms001/ch00000090990000001018/index.m3u8 226 | #EXTINF:-1 tvg-id="凤凰中文" tvg-name="凤凰中文" tvg-logo="https://live.fanmingming.com/tv/凤凰卫视中文台.png" group-title="卫视IPV6",凤凰中文 227 | http://[2409:8087:2001:20:2800:0:df6e:eb24]:80/wh7f454c46tw3553140416_-2021535160/ott.mobaibox.com/PLTV/3/224/3221228527/index.m3u8 228 | #EXTINF:-1 tvg-id="凤凰资讯" tvg-name="凤凰资讯" tvg-logo="https://live.fanmingming.com/tv/凤凰卫视资讯台.png" group-title="卫视IPV6",凤凰资讯 229 | http://[2409:8087:2001:20:2800:0:df6e:eb27]:80/wh7f454c46tw3352677969_1732462333/ott.mobaibox.com/PLTV/3/224/3221228524/index.m3u8 230 | #EXTINF:-1 tvg-id="凤凰香港" tvg-name="凤凰香港" tvg-logo="https://live.fanmingming.com/tv/凤凰卫视香港台.png" group-title="卫视IPV6",凤凰香港 231 | http://[2409:8087:2001:20:2800:0:df6e:eb1d]:80/ott.mobaibox.com/PLTV/1/224/3221228530/1.m3u8 232 | 233 | #EXTINF:-1,tvg-id="HBO" tvg-name="HBO" tvg-logo="https://epg.112114.eu.org/logo/HBO.png" group-title="影视",HBO家庭影院 234 | http://pull-l3-cny.douyincdn.com/third/stream-1279e1fd458348b32fd60381b1017c09.m3u8 235 | #EXTINF:-1,tvg-id="HBO" tvg-name="HBO" tvg-logo="https://epg.112114.eu.org/logo/HBO.png" group-title="影视",HBO原创电影 236 | http://pull-l3-cny.douyincdn.com/third/stream-9bfb4b08fec367ed3850357a853250d5.m3u8 237 | #EXTINF:-1,tvg-id="龙祥电影台" tvg-name="龙祥电影台" tvg-logo="https://epg.112114.eu.org/logo/龙祥电影台.png" group-title="影视",龙祥电影台 238 | http://pull-l3-cny.douyincdn.com/third/stream-c32278dece9f79334a2a878aaa730e95.m3u8 239 | #EXTINF:-1,tvg-id="CHC高清电影" tvg-name="CHC高清电影" tvg-logo="https://epg.112114.eu.org/logo/CHC高清电影.png" group-title="影视",CHC高清电影 240 | http://111.20.40.170/PLTV/88888893/224/3221226463/index.m3u8 241 | #EXTINF:-1,tvg-id="CHC家庭影院" tvg-name="CHC家庭影院" tvg-logo="https://epg.112114.eu.org/logo/CHC家庭影院.png" group-title="影视",CHC家庭影院 242 | http://111.20.40.170/PLTV/88888893/224/3221226462/index.m3u8 243 | #EXTINF:-1,tvg-id="CHC动作电影" tvg-name="CHC动作电影" tvg-logo="https://epg.112114.eu.org/logo/CHC动作电影.png" group-title="影视",CHC动作电影 244 | http://111.20.40.170/PLTV/88888893/224/3221226465/index.m3u8 245 | #EXTINF:-1,tvg-id="星城经典台" tvg-name="星城经典台" tvg-logo="https://epg.112114.eu.org/logo/星城经典台.png" group-title="影视",星城经典台 246 | https://dwz.mk/v6kgy 247 | #EXTINF:-1,tvg-id="星城功夫台" tvg-name="星城功夫台" tvg-logo="https://epg.112114.eu.org/logo/星城功夫台.png" group-title="影视",星城功夫台 248 | https://dwz.mk/mpd4e 249 | #EXTINF:-1,tvg-id="古龙影院" tvg-name="古龙影院" tvg-logo="https://epg.112114.eu.org/logo/古龙影院.png" group-title="影视",古龙影院 250 | http://maomao.kandiantv.cn/m3u8.php?/migu/639528313 251 | #EXTINF:-1,tvg-id="上视东方影视" tvg-name="上视东方影视" tvg-logo="https://epg.112114.eu.org/logo/上视东方影视.png" group-title="影视",东方影视 252 | http://maomao.kandiantv.cn/m3u8.php?/migu/617290047 253 | #EXTINF:-1,tvg-id="过瘾大片" tvg-name="过瘾大片" tvg-logo="https://epg.112114.eu.org/logo/过瘾大片.png" group-title="影视",过瘾大片 254 | http://maomao.kandiantv.cn/m3u8.php?/migu/627198610 255 | #EXTINF:-1,tvg-id="IPTV经典电影" tvg-name="IPTV经典电影" tvg-logo="https://epg.112114.eu.org/logo/IPTV经典电影.png" group-title="影视",经典电影 256 | http://maomao.kandiantv.cn/m3u8.php?/migu/625703337 257 | 258 | #EXTINF:-1,tvg-id="爱院线" tvg-name="iHOT爱院线" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱院线.png" group-title="IHOT",iHOT爱院线 259 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000030630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000030630&livemode=1&stbId=3 260 | #EXTINF:-1,tvg-id="爱科幻" tvg-name="iHOT爱科幻" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱科幻.png" group-title="IHOT",iHOT爱科幻 261 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000020630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000020630&livemode=1&stbId=3 262 | #EXTINF:-1,tvg-id="爱浪漫" tvg-name="iHOT爱浪漫" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱浪漫.png" group-title="IHOT",iHOT爱浪漫 263 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000040630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000040630&livemode=1&stbId=3 264 | #EXTINF:-1,tvg-id="爱喜剧" tvg-name="iHOT爱喜剧" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱喜剧.png" group-title="IHOT",iHOT爱喜剧 265 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000010630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000010630&livemode=1&stbId=3 266 | #EXTINF:-1,tvg-id="爱娱乐" tvg-name="iHOT爱娱乐" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱娱乐.png" group-title="IHOT",iHOT爱娱乐 267 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000130630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000130630&livemode=1&stbId=3 268 | #EXTINF:-1,tvg-id="爱悬疑" tvg-name="iHOT爱悬疑" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱悬疑.png" group-title="IHOT",iHOT爱悬疑 269 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000050630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000050630&livemode=1&stbId=3 270 | #EXTINF:-1,tvg-id="爱经典" tvg-name="iHOT爱经典" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱经典.png" group-title="IHOT",iHOT爱经典 271 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000060630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000060630&livemode=1&stbId=3 272 | #EXTINF:-1,tvg-id="爱江湖" tvg-name="iHOT爱江湖" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱江湖.png" group-title="IHOT",iHOT爱江湖 273 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000110630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000110630&livemode=1&stbId=3 274 | #EXTINF:-1,tvg-id="爱赛车" tvg-name="iHOT爱赛车" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱赛车.png" group-title="IHOT",iHOT爱赛车 275 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000240630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000240630&livemode=1&stbId=3 276 | #EXTINF:-1,tvg-id="爱体育" tvg-name="iHOT爱体育" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱体育.png" group-title="IHOT",iHOT爱体育 277 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000290630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000290630&livemode=1&stbId=3 278 | #EXTINF:-1,tvg-id="爱探索" tvg-name="iHOT爱探索" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱探索.png" group-title="IHOT",iHOT爱探索 279 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000300630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000300630&livemode=1&stbId=3 280 | #EXTINF:-1,tvg-id="爱奇谈" tvg-name="iHOT爱奇谈" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱奇谈.png" group-title="IHOT",iHOT爱奇谈 281 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000270630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000270630&livemode=1&stbId=3 282 | #EXTINF:-1,tvg-id="爱玩具" tvg-name="iHOT爱玩具" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱玩具.png" group-title="IHOT",iHOT爱玩具 283 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000220630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000220630&livemode=1&stbId=3 284 | #EXTINF:-1,tvg-id="爱世界" tvg-name="iHOT爱世界" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱世界.png" group-title="IHOT",iHOT爱世界 285 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000210630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000210630&livemode=1&stbId=3 286 | #EXTINF:-1,tvg-id="爱怀旧" tvg-name="iHOT爱怀旧" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱怀旧.png" group-title="IHOT",iHOT爱怀旧 287 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000260630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000260630&livemode=1&stbId=3 288 | #EXTINF:-1,tvg-id="爱旅行" tvg-name="iHOT爱旅行" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱旅行.png" group-title="IHOT",iHOT爱旅行 289 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000250630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000250630&livemode=1&stbId=3 290 | #EXTINF:-1,tvg-id="爱极限" tvg-name="iHOT爱极限" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱极限.png" group-title="IHOT",iHOT爱极限 291 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000170630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000170630&livemode=1&stbId=3 292 | #EXTINF:-1,tvg-id="爱幼教" tvg-name="iHOT爱幼教" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱幼教.png" group-title="IHOT",iHOT爱幼教 293 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000180630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000180630&livemode=1&stbId=3 294 | #EXTINF:-1,tvg-id="爱解密" tvg-name="iHOT爱解密" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱解密.png" group-title="IHOT",iHOT爱解密 295 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000200630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000200630&livemode=1&stbId=3 296 | #EXTINF:-1,tvg-id="爱谍战" tvg-name="iHOT爱谍战" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱谍战.png" group-title="IHOT",iHOT爱谍战 297 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000070630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000070630&livemode=1&stbId=3 298 | #EXTINF:-1,tvg-id="爱历史" tvg-name="iHOT爱历史" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱历史.png" group-title="IHOT",iHOT爱历史 299 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000150630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000150630&livemode=1&stbId=3 300 | #EXTINF:-1,tvg-id="爱猎奇" tvg-name="iHOT爱猎奇" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱猎奇.png" group-title="IHOT",iHOT爱猎奇 301 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000190630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000190630&livemode=1&stbId=3 302 | #EXTINF:-1,tvg-id="爱都市" tvg-name="iHOT爱都市" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱都市.png" group-title="IHOT",iHOT爱都市 303 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000080630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000080630&livemode=1&stbId=3 304 | #EXTINF:-1,tvg-id="爱科学" tvg-name="iHOT爱科学" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱科学.png" group-title="IHOT",iHOT爱科学 305 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000160630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000160630&livemode=1&stbId=3 306 | #EXTINF:-1,tvg-id="爱美食" tvg-name="iHOT爱美食" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱美食.png" group-title="IHOT",iHOT爱美食 307 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000120630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000120630&livemode=1&stbId=3 308 | #EXTINF:-1,tvg-id="爱时尚" tvg-name="iHOT爱时尚" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱时尚.png" group-title="IHOT",iHOT爱时尚 309 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000140630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000140630&livemode=1&stbId=3 310 | #EXTINF:-1,tvg-id="爱青春" tvg-name="iHOT爱青春" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱青春.png" group-title="IHOT",iHOT爱青春 311 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000100630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000100630&livemode=1&stbId=3 312 | #EXTINF:-1,tvg-id="爱家庭" tvg-name="iHOT爱家庭" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱家庭.png" group-title="IHOT",iHOT爱家庭 313 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000090630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000090630&livemode=1&stbId=3 314 | #EXTINF:-1,tvg-id="爱动漫" tvg-name="iHOT爱动漫" tvg-logo="https://epg.112114.eu.org/logo/IHOT爱动漫.png" group-title="IHOT",iHOT爱动漫 315 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000280630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000280630&livemode=1&stbId=3 316 | #EXTINF:-1,tvg-id="浙江精彩影视" tvg-name="浙江精彩影视" tvg-logo="https://epg.112114.eu.org/logo/浙江精彩影视.png" group-title="IHOT",精彩影视 317 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000006000320630/index.m3u8?channel-id=wasusyt&Contentid=6000000006000320630&livemode=1&stbId=3 318 | #EXTINF:-1,tvg-id="求索动物" tvg-name="求索动物" tvg-logo="https://epg.112114.eu.org/logo/求索动物.png" group-title="IHOT",求索动物 319 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000002000010046/index.m3u8?channel-id=wasusyt&Contentid=6000000002000010046&livemode=1&stbId=3 320 | #EXTINF:-1,tvg-id="求索记录" tvg-name="求索记录" tvg-logo="https://epg.112114.eu.org/logo/求索记录.png" group-title="IHOT",求索记录 321 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000002000032052/index.m3u8?channel-id=wasusyt&Contentid=6000000002000032052&livemode=1&stbId=3 322 | #EXTINF:-1,tvg-id="求索科学" tvg-name="求索科学" tvg-logo="https://epg.112114.eu.org/logo/求索科学.png" group-title="IHOT",求索科学 323 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000002000032344/index.m3u8?channel-id=wasusyt&Contentid=6000000002000032344&livemode=1&stbId=3 324 | #EXTINF:-1,tvg-id="浙江求索生活" tvg-name="浙江求索生活" tvg-logo="https://epg.112114.eu.org/logo/浙江求索生活.png" group-title="IHOT",求索生活 325 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/6000000002000003382/index.m3u8?channel-id=wasusyt&Contentid=6000000002000003382&livemode=1&stbId=3 326 | #EXTINF:-1,tvg-id="探索纪实" tvg-name="探索纪实" tvg-logo="https://epg.112114.eu.org/logo/探索纪实.png" group-title="IHOT",探索纪实 327 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/5359008697329269813/index.m3u8?channel-id=wasusyt&Contentid=5359008697329269813&livemode=1&stbId=3 328 | #EXTINF:-1,tvg-id="HD少儿动漫" tvg-name="HD少儿动漫" tvg-logo="https://epg.112114.eu.org/logo/HD少儿动漫.png" group-title="IHOT",少儿动漫 329 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/8145334647720731271/index.m3u8?channel-id=wasusyt&Contentid=8145334647720731271&livemode=1&stbId=3 330 | #EXTINF:-1,tvg-id="浙江风尚音乐" tvg-name="浙江风尚音乐" tvg-logo="https://epg.112114.eu.org/logo/浙江风尚音乐.png" group-title="IHOT",风尚音乐 331 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/5529729098703832176/index.m3u8?channel-id=wasusyt&Contentid=5529729098703832176&livemode=1&stbId=3 332 | #EXTINF:-1,tvg-id="HD精品剧场" tvg-name="HD精品剧场" tvg-logo="https://epg.112114.eu.org/logo/HD精品剧场.png" group-title="IHOT",精品剧场 333 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/8230197131234717902/index.m3u8?channel-id=wasusyt&Contentid=8230197131234717902&livemode=1&stbId=3 334 | #EXTINF:-1,tvg-id="HD欧美影院" tvg-name="HD欧美影院" tvg-logo="https://epg.112114.eu.org/logo/HD欧美影院.png" group-title="IHOT",欧美影院 335 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/7185203501769528108/index.m3u8?channel-id=wasusyt&Contentid=7185203501769528108&livemode=1&stbId=3 336 | #EXTINF:-1,tvg-id="亚洲影院" tvg-name="亚洲影院" tvg-logo="https://epg.112114.eu.org/logo/亚洲影院.png" group-title="IHOT",亚洲影院 337 | http://[2409:8087:7001:20:1000::95]:6610/000000001000/5841816227539527643/index.m3u8?channel-id=wasusyt&Contentid=5841816227539527643&livemode=1&stbId=3 -------------------------------------------------------------------------------- /flutter_assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/flutter_assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf -------------------------------------------------------------------------------- /flutter_assets/shaders/ink_sparkle.frag: -------------------------------------------------------------------------------- 1 | { 2 | "sksl": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec4 u_color;\nuniform vec4 u_composite_1;\nuniform vec2 u_center;\nuniform float u_max_radius;\nuniform vec2 u_resolution_scale;\nuniform vec2 u_noise_scale;\nuniform float u_noise_phase;\nuniform vec2 u_circle1;\nuniform vec2 u_circle2;\nuniform vec2 u_circle3;\nuniform vec2 u_rotation1;\nuniform vec2 u_rotation2;\nuniform vec2 u_rotation3;\n\nvec4 fragColor;\n\nfloat u_alpha;\nfloat u_sparkle_alpha;\nfloat u_blur;\nfloat u_radius_scale;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nmat2 FLT_flutter_local_rotate2d(vec2 rad)\n{\n return mat2(vec2(rad.x, -rad.y), vec2(rad.y, rad.x));\n}\n\nfloat FLT_flutter_local_soft_circle(vec2 uv, vec2 xy, float radius, float blur)\n{\n float blur_half = blur * 0.5;\n float d = distance(uv, xy);\n return 1.0 - smoothstep(1.0 - blur_half, 1.0 + blur_half, d / radius);\n}\n\nfloat FLT_flutter_local_circle_grid(vec2 resolution, inout vec2 p, vec2 xy, vec2 rotation, float cell_diameter)\n{\n vec2 param = rotation;\n p = (FLT_flutter_local_rotate2d(param) * (xy - p)) + xy;\n p = mod(p, vec2(cell_diameter)) / resolution;\n float cell_uv = (cell_diameter / resolution.y) * 0.5;\n float r = 0.64999997615814208984375 * cell_uv;\n vec2 param_1 = p;\n vec2 param_2 = vec2(cell_uv);\n float param_3 = r;\n float param_4 = r * 50.0;\n return FLT_flutter_local_soft_circle(param_1, param_2, param_3, param_4);\n}\n\nfloat FLT_flutter_local_turbulence(vec2 uv)\n{\n vec2 uv_scale = uv * vec2(0.800000011920928955078125);\n vec2 param = vec2(0.800000011920928955078125);\n vec2 param_1 = uv_scale;\n vec2 param_2 = u_circle1;\n vec2 param_3 = u_rotation1;\n float param_4 = 0.17000000178813934326171875;\n float _319 = FLT_flutter_local_circle_grid(param, param_1, param_2, param_3, param_4);\n float g1 = _319;\n vec2 param_5 = vec2(0.800000011920928955078125);\n vec2 param_6 = uv_scale;\n vec2 param_7 = u_circle2;\n vec2 param_8 = u_rotation2;\n float param_9 = 0.20000000298023223876953125;\n float _331 = FLT_flutter_local_circle_grid(param_5, param_6, param_7, param_8, param_9);\n float g2 = _331;\n vec2 param_10 = vec2(0.800000011920928955078125);\n vec2 param_11 = uv_scale;\n vec2 param_12 = u_circle3;\n vec2 param_13 = u_rotation3;\n float param_14 = 0.2750000059604644775390625;\n float _344 = FLT_flutter_local_circle_grid(param_10, param_11, param_12, param_13, param_14);\n float g3 = _344;\n float v = (((g1 * g1) + g2) - g3) * 0.5;\n return clamp(0.449999988079071044921875 + (0.800000011920928955078125 * v), 0.0, 1.0);\n}\n\nfloat FLT_flutter_local_soft_ring(vec2 uv, vec2 xy, float radius, float thickness, float blur)\n{\n vec2 param = uv;\n vec2 param_1 = xy;\n float param_2 = radius + thickness;\n float param_3 = blur;\n float circle_outer = FLT_flutter_local_soft_circle(param, param_1, param_2, param_3);\n vec2 param_4 = uv;\n vec2 param_5 = xy;\n float param_6 = max(radius - thickness, 0.0);\n float param_7 = blur;\n float circle_inner = FLT_flutter_local_soft_circle(param_4, param_5, param_6, param_7);\n return clamp(circle_outer - circle_inner, 0.0, 1.0);\n}\n\nfloat FLT_flutter_local_triangle_noise(inout vec2 n)\n{\n n = fract(n * vec2(5.398700237274169921875, 5.442100048065185546875));\n n += vec2(dot(n.yx, n + vec2(21.5351009368896484375, 14.3136997222900390625)));\n float xy = n.x * n.y;\n return (fract(xy * 95.43070220947265625) + fract(xy * 75.0496063232421875)) - 1.0;\n}\n\nfloat FLT_flutter_local_threshold(float v, float l, float h)\n{\n return step(l, v) * (1.0 - step(h, v));\n}\n\nfloat FLT_flutter_local_sparkle(vec2 uv, float t)\n{\n vec2 param = uv;\n float _242 = FLT_flutter_local_triangle_noise(param);\n float n = _242;\n float param_1 = n;\n float param_2 = 0.0;\n float param_3 = 0.0500000007450580596923828125;\n float s = FLT_flutter_local_threshold(param_1, param_2, param_3);\n float param_4 = n + sin(3.1415927410125732421875 * (t + 0.3499999940395355224609375));\n float param_5 = 0.100000001490116119384765625;\n float param_6 = 0.1500000059604644775390625;\n s += FLT_flutter_local_threshold(param_4, param_5, param_6);\n float param_7 = n + sin(3.1415927410125732421875 * (t + 0.699999988079071044921875));\n float param_8 = 0.20000000298023223876953125;\n float param_9 = 0.25;\n s += FLT_flutter_local_threshold(param_7, param_8, param_9);\n float param_10 = n + sin(3.1415927410125732421875 * (t + 1.0499999523162841796875));\n float param_11 = 0.300000011920928955078125;\n float param_12 = 0.3499999940395355224609375;\n s += FLT_flutter_local_threshold(param_10, param_11, param_12);\n return clamp(s, 0.0, 1.0) * 0.550000011920928955078125;\n}\n\nvoid FLT_main()\n{\n u_alpha = u_composite_1.x;\n u_sparkle_alpha = u_composite_1.y;\n u_blur = u_composite_1.z;\n u_radius_scale = u_composite_1.w;\n vec2 p = FLT_flutter_local_FlutterFragCoord();\n vec2 uv_1 = p * u_resolution_scale;\n vec2 density_uv = uv_1 - mod(p, u_noise_scale);\n float radius = u_max_radius * u_radius_scale;\n vec2 param_13 = uv_1;\n float turbulence = FLT_flutter_local_turbulence(param_13);\n vec2 param_14 = p;\n vec2 param_15 = u_center;\n float param_16 = radius;\n float param_17 = 0.0500000007450580596923828125 * u_max_radius;\n float param_18 = u_blur;\n float ring = FLT_flutter_local_soft_ring(param_14, param_15, param_16, param_17, param_18);\n vec2 param_19 = density_uv;\n float param_20 = u_noise_phase;\n float sparkle = ((FLT_flutter_local_sparkle(param_19, param_20) * ring) * turbulence) * u_sparkle_alpha;\n vec2 param_21 = p;\n vec2 param_22 = u_center;\n float param_23 = radius;\n float param_24 = u_blur;\n float wave_alpha = (FLT_flutter_local_soft_circle(param_21, param_22, param_23, param_24) * u_alpha) * u_color.w;\n vec4 wave_color = vec4(u_color.xyz * wave_alpha, wave_alpha);\n fragColor = mix(wave_color, vec4(1.0), vec4(sparkle));\n}\n\nhalf4 main(float2 iFragCoord)\n{\n flutter_FragCoord = float4(iFragCoord, 0, 0);\n FLT_main();\n return fragColor;\n}\n", 3 | "stage": 1, 4 | "target_platform": 2, 5 | "uniforms": [ 6 | { 7 | "array_elements": 0, 8 | "bit_width": 32, 9 | "columns": 1, 10 | "location": 0, 11 | "name": "u_color", 12 | "rows": 4, 13 | "type": 10 14 | }, 15 | { 16 | "array_elements": 0, 17 | "bit_width": 32, 18 | "columns": 1, 19 | "location": 1, 20 | "name": "u_composite_1", 21 | "rows": 4, 22 | "type": 10 23 | }, 24 | { 25 | "array_elements": 0, 26 | "bit_width": 32, 27 | "columns": 1, 28 | "location": 2, 29 | "name": "u_center", 30 | "rows": 2, 31 | "type": 10 32 | }, 33 | { 34 | "array_elements": 0, 35 | "bit_width": 32, 36 | "columns": 1, 37 | "location": 3, 38 | "name": "u_max_radius", 39 | "rows": 1, 40 | "type": 10 41 | }, 42 | { 43 | "array_elements": 0, 44 | "bit_width": 32, 45 | "columns": 1, 46 | "location": 4, 47 | "name": "u_resolution_scale", 48 | "rows": 2, 49 | "type": 10 50 | }, 51 | { 52 | "array_elements": 0, 53 | "bit_width": 32, 54 | "columns": 1, 55 | "location": 5, 56 | "name": "u_noise_scale", 57 | "rows": 2, 58 | "type": 10 59 | }, 60 | { 61 | "array_elements": 0, 62 | "bit_width": 32, 63 | "columns": 1, 64 | "location": 6, 65 | "name": "u_noise_phase", 66 | "rows": 1, 67 | "type": 10 68 | }, 69 | { 70 | "array_elements": 0, 71 | "bit_width": 32, 72 | "columns": 1, 73 | "location": 7, 74 | "name": "u_circle1", 75 | "rows": 2, 76 | "type": 10 77 | }, 78 | { 79 | "array_elements": 0, 80 | "bit_width": 32, 81 | "columns": 1, 82 | "location": 8, 83 | "name": "u_circle2", 84 | "rows": 2, 85 | "type": 10 86 | }, 87 | { 88 | "array_elements": 0, 89 | "bit_width": 32, 90 | "columns": 1, 91 | "location": 9, 92 | "name": "u_circle3", 93 | "rows": 2, 94 | "type": 10 95 | }, 96 | { 97 | "array_elements": 0, 98 | "bit_width": 32, 99 | "columns": 1, 100 | "location": 10, 101 | "name": "u_rotation1", 102 | "rows": 2, 103 | "type": 10 104 | }, 105 | { 106 | "array_elements": 0, 107 | "bit_width": 32, 108 | "columns": 1, 109 | "location": 11, 110 | "name": "u_rotation2", 111 | "rows": 2, 112 | "type": 10 113 | }, 114 | { 115 | "array_elements": 0, 116 | "bit_width": 32, 117 | "columns": 1, 118 | "location": 12, 119 | "name": "u_rotation3", 120 | "rows": 2, 121 | "type": 10 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /lib/bindings/bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../controller/videoPlayController.dart'; 3 | 4 | class GlobalBindings extends Bindings { 5 | @override 6 | void dependencies() { 7 | // 注入依赖 懒加载 8 | Get.lazyPut(() => VideoPlayController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/bindings/sourceBindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../controller/sourceListController.dart'; 3 | 4 | class SourceListBindings extends Bindings { 5 | @override 6 | void dependencies() { 7 | // 注入依赖 懒加载 8 | Get.put(SourceListController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/components/searchBar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSearchBar extends StatefulWidget { 4 | final Function(String query) onQueryChanged; 5 | 6 | const CustomSearchBar({super.key, required this.onQueryChanged}); 7 | 8 | @override 9 | _CustomSearchBarState createState() => _CustomSearchBarState(); 10 | } 11 | 12 | class _CustomSearchBarState extends State { 13 | String query = ''; 14 | 15 | void onQueryChanged(String newQuery) { 16 | setState(() { 17 | query = newQuery; 18 | widget.onQueryChanged(newQuery); 19 | }); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Container( 25 | padding: const EdgeInsets.all(16), 26 | child: TextField( 27 | onChanged: onQueryChanged, 28 | decoration: const InputDecoration( 29 | labelText: '请输入关键字进行搜索', 30 | border: OutlineInputBorder(), 31 | prefixIcon: Icon(Icons.search), 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/components/videoPlayer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:video_player/video_player.dart'; 6 | 7 | import '../controller/videoPlayController.dart'; 8 | import '../utils/timeFormat.dart'; 9 | 10 | class MyVideoPlayer extends GetView { 11 | const MyVideoPlayer({super.key}); 12 | @override 13 | Widget build(BuildContext context) { 14 | return Obx(() { 15 | final aspectRatio = controller.videoController.value.aspectRatio; 16 | return GestureDetector( 17 | onTap: controller.tapScreen, 18 | onDoubleTap: controller.togglePlay, 19 | child: Container( 20 | width: controller.enableFullScreen.isTrue 21 | ? MediaQuery.of(context).size.width 22 | : 480.0, 23 | height: controller.enableFullScreen.isTrue 24 | ? MediaQuery.of(context).size.height 25 | : 480.0 * aspectRatio, 26 | color: Colors.black, 27 | child: Stack(alignment: Alignment.center, children: [ 28 | AspectRatio( 29 | aspectRatio: aspectRatio, 30 | child: IgnorePointer( 31 | ignoring: true, //让事件进行冒泡 不然点击视频上不响应手势 全屏都召唤不出controls 32 | child: VideoPlayer(controller.videoController), 33 | )), 34 | controller.isDisplayPlayBtn.isTrue 35 | ? IconButton( 36 | onPressed: controller.togglePlay, 37 | icon: Icon(controller.playBtnIconData.value), 38 | iconSize: 60, 39 | color: Colors.white, 40 | ) 41 | : const Text(''), 42 | controller.showProgress.isTrue 43 | ? Positioned( 44 | bottom: 0, 45 | left: 0, 46 | right: 0, 47 | child: controller.showControls.isTrue 48 | ? Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | Slider( 52 | activeColor: Colors.lightBlue, 53 | thumbColor: Colors.grey, 54 | value: controller.currentSeconds.toDouble(), 55 | min: 0.0, 56 | max: max(controller.currentSeconds.toDouble(), 57 | controller.allSeconds.toDouble()), 58 | onChanged: (double value) { 59 | controller.videoController.seekTo( 60 | Duration(seconds: value.toInt())); 61 | }, 62 | ), 63 | Padding( 64 | padding: const EdgeInsets.only( 65 | left: 20, bottom: 20), 66 | child: Text( 67 | controller.currentSeconds.value > 68 | controller.allSeconds.value 69 | ? formatDuration( 70 | controller.currentSeconds.value) 71 | : '${formatDuration(controller.currentSeconds.value)}/${formatDuration(controller.allSeconds.value)}', 72 | style: const TextStyle( 73 | color: Colors.white, 74 | fontSize: 12, 75 | fontWeight: FontWeight.normal), 76 | )), 77 | ], 78 | ) 79 | : const SizedBox()) 80 | : const SizedBox(), 81 | controller.showControls.isTrue 82 | ? const Positioned( 83 | top: 20, 84 | left: 20, 85 | child: Text('· live', 86 | style: TextStyle( 87 | color: Colors.white, 88 | fontSize: 15, 89 | fontWeight: FontWeight.bold))) 90 | : const SizedBox(), 91 | controller.showControls.isTrue 92 | ? Positioned( 93 | bottom: 10, 94 | right: 10, 95 | child: MouseRegion( 96 | onEnter: (PointerEvent details) => 97 | {controller.isHover.value = true}, 98 | onExit: (PointerEvent details) => { 99 | Future.delayed(const Duration(seconds: 5), () { 100 | if (controller.isPlaying()) { 101 | controller.isHover.value = false; 102 | } 103 | }) 104 | }, 105 | child: SizedBox( 106 | width: 50, 107 | height: 50, 108 | child: IconButton( 109 | tooltip: controller.enableFullScreen.isTrue 110 | ? '退出全屏' 111 | : '全屏', 112 | hoverColor: Colors.white54, 113 | onPressed: controller.toggleFullScreen, 114 | icon: Icon( 115 | controller.enableFullScreen.isTrue 116 | ? Icons.fullscreen_exit 117 | : Icons.fullscreen, 118 | color: controller.isHover.isTrue 119 | ? Colors.white 120 | : Colors.white54, 121 | )), 122 | )), 123 | ) 124 | : const SizedBox(), 125 | controller.showControls.isTrue 126 | ? Positioned( 127 | top: 10, 128 | right: 10, 129 | child: IconButton( 130 | tooltip: controller.isMute ? '打开声音' : '静音', 131 | onPressed: controller.toggleMute, 132 | icon: Icon( 133 | controller.isMute 134 | ? Icons.music_off_outlined 135 | : Icons.music_note_outlined, 136 | color: Colors.white)), 137 | ) 138 | : const SizedBox(), 139 | ])), 140 | ); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/controller/sourceListController.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../utils/sourceManager.dart'; 3 | 4 | class SourceListController extends GetxController { 5 | final RxList _items = [].obs; 6 | final RxString _selectSource = ''.obs; 7 | late SourceManager manager; 8 | 9 | get selectSource { 10 | return _selectSource.value; 11 | } 12 | 13 | set selectSource(val) { 14 | _selectSource.value = val; 15 | SourceManager.setCurrentSource(val); 16 | update(); 17 | } 18 | 19 | List get items => _items.value; 20 | 21 | set items(val) { 22 | _items.value = val; 23 | update(); 24 | } 25 | 26 | @override 27 | void onInit() { 28 | selectSource = SourceManager.getCurrentSource() ?? ''; 29 | items = SourceManager.getSourceList(); 30 | update(); 31 | super.onInit(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/controller/videoPlayController.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html' as html; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:video_player/video_player.dart'; 5 | 6 | import '../utils/historyTools.dart'; 7 | import '../utils/sourceManager.dart'; 8 | import '../parser/parseM3u.dart'; 9 | 10 | const defaultConfigs = [ 11 | "https://cdn.jsdelivr.net/gh/WangGuibin/live_tv_box@main/cctv.m3u", 12 | "https://cdn.jsdelivr.net/gh/hujingguang/ChinaIPTV@main/cnTV_AutoUpdate.m3u8", 13 | "https://cdn.jsdelivr.net/gh/TCatCloud/IPTV@Files/IPTV.m3u", 14 | "https://cdn.jsdelivr.net/gh/richelsky/IPTV@main/%E5%9B%BD%E5%86%85%E7%94%B5%E8%A7%86%E5%8F%B02023.12KodiCN.txt", 15 | "https://cdn.jsdelivr.net/gh/richelsky/IPTV@main/moyulive.txt", 16 | "https://cdn.jsdelivr.net/gh/shidahuilang/shuyuan@shuyuan/iptv.txt", 17 | "https://cdn.jsdelivr.net/gh/LITUATUI/M3UPT@main/M3U/M3UPT.m3u", 18 | ]; 19 | 20 | class VideoPlayController extends GetxController { 21 | late VideoPlayerController videoController; 22 | final RxBool _isMute = false.obs; 23 | RxBool isLoading = true.obs; 24 | RxBool isFullScreen = false.obs; 25 | RxBool isDisplayPlayBtn = false.obs; 26 | RxBool isHover = false.obs; 27 | RxBool enableFullScreen = false.obs; 28 | RxInt currentSeconds = 0.obs; 29 | RxInt allSeconds = 0.obs; 30 | RxBool showControls = true.obs; 31 | RxBool showProgress = false.obs; 32 | 33 | RxString currenPlayUrl = 34 | 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'.obs; 35 | RxString currenPlayName = '大白熊(测试)'.obs; 36 | var playBtnIconData = Icons.play_arrow.obs; 37 | 38 | set isMute(bool val) { 39 | _isMute.value = val; 40 | videoController.setVolume(val ? 0.0 : 1.0); 41 | } 42 | 43 | bool get isMute => _isMute.value; 44 | 45 | void toggleMute() { 46 | isMute = !isMute; 47 | } 48 | 49 | @override 50 | void onInit() { 51 | isLoading.value = true; 52 | //提前初始化一下 53 | HistoryTools.getItems(); 54 | List sources = SourceManager.getSourceList(); 55 | if (sources.isEmpty) { 56 | //默认添加一下内置源 57 | SourceManager.addSubscriSource(defaultConfigs); 58 | } 59 | 60 | if (SourceManager.getCurrentSource() != '' && 61 | SourceManager.getCurrentSource() != null) { 62 | SourceItem ipTextItem = SourceManager.getSourceList() 63 | .where( 64 | (element) => element.iptvUrl == SourceManager.getCurrentSource()) 65 | .toList() 66 | .first; 67 | List channels = parseM3U8File(ipTextItem); 68 | HistoryTools.getSubscribeChannels(channels); 69 | } 70 | 71 | update(); 72 | try { 73 | videoController = VideoPlayerController.networkUrl( 74 | Uri.parse(currenPlayUrl.value), 75 | )..initialize().then((_) { 76 | isLoading.value = false; 77 | update(); 78 | }); 79 | videoController.play(); 80 | _addListener(); 81 | enableFullScreen.value = false; 82 | isDisplayPlayBtn.value = false; 83 | } catch (e) { 84 | print(e); 85 | } 86 | super.onInit(); 87 | } 88 | 89 | ///添加监听 90 | _addListener() { 91 | //监听播放进度 92 | videoController.addListener(() { 93 | final Duration position = videoController.value.position; 94 | List buffered = videoController.value.buffered; 95 | showProgress.value = buffered.isNotEmpty; 96 | if (buffered.isNotEmpty) { 97 | DurationRange range = buffered.first; 98 | // print('$position / ${range.end}'); 99 | currentSeconds.value = position.inSeconds; 100 | allSeconds.value = range.end.inSeconds; 101 | showProgress.value = true; 102 | } else { 103 | currentSeconds.value = 0; 104 | allSeconds.value = 0; 105 | } 106 | update(); 107 | }); 108 | } 109 | 110 | bool isPlaying() { 111 | return videoController.value.isPlaying; 112 | } 113 | 114 | //单击屏幕 115 | void tapScreen() { 116 | showControls.value = !showControls.value; 117 | update(); 118 | 119 | if (isPlaying()) return; 120 | isDisplayPlayBtn.value = true; 121 | update(); 122 | hiddenPlayBtn(); 123 | } 124 | 125 | //切换全屏 126 | void toggleFullScreen() { 127 | if (enableFullScreen.isTrue) { 128 | enableFullScreen.value = false; 129 | } else { 130 | enableFullScreen.value = true; 131 | } 132 | if (enableFullScreen.isTrue) { 133 | _goFullScreen(); 134 | } else { 135 | _exitFullScreen(); 136 | } 137 | showControls.value = true; 138 | tapScreen(); 139 | update(); 140 | } 141 | 142 | void _goFullScreen() { 143 | html.document.documentElement?.requestFullscreen(); 144 | } 145 | 146 | void _exitFullScreen() { 147 | html.document.exitFullscreen(); 148 | } 149 | 150 | // 切换播放 151 | void togglePlay() { 152 | isPlaying() ? videoController.pause() : videoController.play(); 153 | isDisplayPlayBtn.value = true; 154 | playBtnIconData.value = isPlaying() ? Icons.pause : Icons.play_arrow; 155 | update(); 156 | 157 | /// 如果2秒后是播放状态则隐藏播放按钮 158 | hiddenPlayBtn(); 159 | } 160 | 161 | void hiddenPlayBtn() { 162 | Future.delayed(const Duration(seconds: 2), () { 163 | if (isPlaying()) { 164 | isDisplayPlayBtn.value = false; 165 | isHover.value = false; 166 | update(); 167 | } 168 | }); 169 | } 170 | 171 | //播放新的url 172 | void playNewUrl(String url, {String remark = ''}) { 173 | print('正在播放 $url'); 174 | videoController.pause(); 175 | videoController.dispose(); 176 | isLoading.value = true; 177 | 178 | ///fix 179 | if (url.contains('m3u8')) { 180 | String result = url.substring(0, url.indexOf('.m3u8')); 181 | url = "$result.m3u8"; 182 | } 183 | 184 | try { 185 | videoController = VideoPlayerController.networkUrl(Uri.parse(url)) 186 | ..initialize().then((_) { 187 | isLoading.value = false; 188 | update(); 189 | }); 190 | enableFullScreen.value = false; 191 | isDisplayPlayBtn.value = false; 192 | currenPlayUrl.value = url; 193 | currenPlayName.value = remark; 194 | videoController.play(); 195 | 196 | List items = HistoryTools.getItems(); 197 | if (items.isEmpty) { 198 | items.insert(0, ChannelItem(remark: remark, url: url)); 199 | HistoryTools.saveToDB(items); 200 | return; 201 | } 202 | 203 | var newItems = items.where((element) => element.url != url).toList(); 204 | newItems.insert(0, ChannelItem(remark: remark, url: url)); 205 | HistoryTools.saveToDB(newItems); 206 | _addListener(); 207 | update(); 208 | } catch (e) { 209 | print(e); 210 | } 211 | } 212 | 213 | @override 214 | void onClose() { 215 | videoController.dispose(); 216 | super.onClose(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: library_private_types_in_public_api 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:get/get.dart'; 5 | import 'other/app.dart'; 6 | import 'bindings/bindings.dart'; 7 | 8 | void main() { 9 | runApp(const VideoApp()); 10 | } 11 | 12 | class VideoApp extends StatelessWidget { 13 | const VideoApp({super.key}); 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetMaterialApp( 17 | title: '在线播放器', 18 | debugShowCheckedModeBanner: false, 19 | theme: ThemeData( 20 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue), 21 | useMaterial3: true, 22 | ), 23 | initialRoute: App.root, 24 | defaultTransition: Transition.rightToLeftWithFade, 25 | getPages: App.routes, 26 | initialBinding: GlobalBindings(), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/other/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../pages/channelList.dart'; 3 | import '../pages/live.dart'; 4 | import '../pages/sourceList.dart'; 5 | import '../bindings/sourceBindings.dart'; 6 | 7 | class App { 8 | //路由表 9 | static String root = '/'; 10 | static String channelPage = '/channels'; 11 | static String sourceList = '/sourceList'; 12 | 13 | //路由和页面的关系 14 | static final routes = [ 15 | GetPage(name: root, page: () => LivePage()), 16 | GetPage(name: channelPage, page: () => const ChannelList()), 17 | GetPage( 18 | name: sourceList, 19 | page: () => SourceList(), 20 | binding: SourceListBindings()), 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/pages/channelList.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:flutter/services.dart'; 4 | import '../utils/historyTools.dart'; 5 | import '../utils/fileManager.dart'; 6 | import '../components/searchBar.dart'; 7 | 8 | class ChannelList extends StatefulWidget { 9 | const ChannelList({super.key}); 10 | 11 | @override 12 | State createState() => _ChannelListState(); 13 | } 14 | 15 | class _ChannelListState extends State { 16 | bool isEditMode = false; 17 | bool isAllSelect = false; 18 | List items = []; 19 | List selectItems = []; 20 | 21 | @override 22 | void initState() { 23 | setState(() { 24 | items = HistoryTools.getItems(); 25 | }); 26 | super.initState(); 27 | } 28 | 29 | //copy text 30 | void copyTextToClipboard(String text) { 31 | Clipboard.setData(ClipboardData(text: text)).then((_) { 32 | Get.showSnackbar(const GetSnackBar( 33 | duration: Duration(seconds: 2), 34 | title: '友情提示', 35 | message: '链接已复制到剪贴板', 36 | snackPosition: SnackPosition.TOP, 37 | )); 38 | }); 39 | } 40 | 41 | void _toggleCheckBox(ChannelItem item) { 42 | setState(() { 43 | if (selectItems.contains(item)) { 44 | selectItems.remove(item); 45 | } else { 46 | selectItems.add(item); 47 | } 48 | isAllSelect = items.length == selectItems.length; 49 | }); 50 | } 51 | 52 | void _showAlertDialog(String content, Function callBack) { 53 | Get.defaultDialog( 54 | title: '友情提示', 55 | content: Text(content), 56 | textCancel: '取消', 57 | textConfirm: '确定', 58 | onConfirm: () { 59 | callBack(); 60 | }); 61 | } 62 | 63 | //按钮组 64 | List _createActions() { 65 | return [ 66 | // IconButton( 67 | // tooltip: '搜索', 68 | // onPressed: () {}, 69 | // icon: const Icon(Icons.search, color: Colors.lightBlue)), 70 | isEditMode 71 | ? IconButton( 72 | tooltip: '全选', 73 | onPressed: () { 74 | setState(() { 75 | isAllSelect = !isAllSelect; 76 | List localItems = List.of( 77 | items); //不能直接引用不然会错乱 需要使用List.of或者List.from深拷贝一下啥的 78 | selectItems = isAllSelect ? localItems : []; 79 | }); 80 | }, 81 | icon: Icon( 82 | isAllSelect 83 | ? Icons.check_box_rounded 84 | : Icons.check_box_outline_blank_rounded, 85 | color: Colors.lightBlue)) 86 | : const Text(''), 87 | IconButton( 88 | tooltip: isEditMode ? '取消编辑' : '编辑', 89 | onPressed: () { 90 | setState(() { 91 | isEditMode = !isEditMode; 92 | }); 93 | }, 94 | icon: Icon(isEditMode ? Icons.cancel_rounded : Icons.edit_note)), 95 | IconButton( 96 | tooltip: '导入频道json配置', 97 | onPressed: () { 98 | pickAndReadFile(); 99 | }, 100 | icon: const Icon(Icons.insert_drive_file)), 101 | IconButton( 102 | tooltip: '导出频道json配置', 103 | onPressed: () { 104 | String jsonStr = HistoryTools.exportJsonData(); 105 | saveTextFile(jsonStr); 106 | Get.showSnackbar(const GetSnackBar( 107 | duration: Duration(seconds: 2), 108 | title: '友情提示', 109 | message: '记录已导出成功!!', 110 | snackPosition: SnackPosition.TOP, 111 | )); 112 | }, 113 | icon: const Icon(Icons.download)), 114 | const SizedBox(width: 30) 115 | ]; 116 | } 117 | 118 | //cell滑动的背景 119 | Widget _createDismissibleBg() { 120 | return Container( 121 | color: Colors.red, 122 | child: const Row( 123 | children: [ 124 | Padding( 125 | padding: EdgeInsets.all(10), 126 | child: Text( 127 | '右滑拷贝播放链接', 128 | style: TextStyle( 129 | color: Colors.white, 130 | ), 131 | ), 132 | ), 133 | Expanded(child: SizedBox.shrink()), 134 | Padding( 135 | padding: EdgeInsets.all(10), 136 | child: Text( 137 | '左滑删除', 138 | style: TextStyle(color: Colors.white), 139 | ), 140 | ) 141 | ], 142 | ), 143 | ); 144 | } 145 | 146 | //创建cell 147 | Widget _createCell(ChannelItem item, int index) { 148 | return ListTile( 149 | leading: isEditMode 150 | ? Icon( 151 | selectItems.contains(item) 152 | ? Icons.check_box_rounded 153 | : Icons.check_box_outline_blank_rounded, 154 | color: Colors.lightBlue) 155 | : Container( 156 | decoration: BoxDecoration( 157 | shape: BoxShape.circle, 158 | border: Border.all( 159 | color: Colors.black54, // 设置边框颜色 160 | width: 0.5, // 设置边框宽度 161 | ), 162 | ), 163 | padding: const EdgeInsets.all(8.0), // 设置内边距 164 | child: Text( 165 | '${index + 1}', // 要展示的数字序号 166 | style: const TextStyle( 167 | fontSize: 16.0, fontWeight: FontWeight.bold // 设置字体大小 168 | ), 169 | ), 170 | ), 171 | title: Text(item.remark), 172 | subtitle: Text(item.url), 173 | onTap: () { 174 | // Get.offAllNamed(App.root, 175 | // parameters: {'url': item.url, 'remark': item.remark}); 176 | if (!isEditMode) { 177 | Get.back(result: item); 178 | } else { 179 | _toggleCheckBox(item); 180 | } 181 | }, 182 | ); 183 | } 184 | 185 | //删除时显示底部悬浮按钮 186 | Widget? _createDeleteActionButton() { 187 | return isEditMode 188 | ? FloatingActionButton( 189 | onPressed: () { 190 | _showAlertDialog('是否删除这${selectItems.length}个频道', () { 191 | setState(() { 192 | items.removeWhere((element) => selectItems.contains(element)); 193 | HistoryTools.saveToDB(items); 194 | selectItems = []; 195 | isEditMode = !isEditMode; 196 | Get.back(); 197 | Get.showSnackbar(const GetSnackBar( 198 | duration: Duration(seconds: 1), 199 | title: '友情提示', 200 | message: '删除成功!!', 201 | snackPosition: SnackPosition.TOP, 202 | )); 203 | }); 204 | }); 205 | }, 206 | child: Container( 207 | decoration: BoxDecoration( 208 | color: Colors.red, // 设置颜色 209 | borderRadius: BorderRadius.circular(10), // 设置圆角 210 | ), 211 | width: 100, 212 | height: 100, 213 | // color: Colors.red, 214 | alignment: Alignment.center, 215 | child: Center( 216 | child: Text( 217 | '删除(${selectItems.length})', 218 | textAlign: TextAlign.center, 219 | style: const TextStyle(color: Colors.white), 220 | ), 221 | ), 222 | )) 223 | : null; 224 | } 225 | 226 | //搜索 227 | void _onChangeQuery(String query) { 228 | Future.delayed(const Duration(milliseconds: 300), () { 229 | setState(() { 230 | List tempItems = List.of(HistoryTools.getItems()); 231 | List newItems = tempItems 232 | .where((element) => element.remark.contains(query)) 233 | .toList(); 234 | items = newItems.isEmpty && query == '' ? tempItems : newItems; 235 | }); 236 | }); 237 | } 238 | 239 | @override 240 | Widget build(BuildContext context) { 241 | return Scaffold( 242 | appBar: AppBar( 243 | title: Text('频道列表(${items.length})'), 244 | centerTitle: true, 245 | actions: _createActions(), 246 | ), 247 | body: Column( 248 | children: [ 249 | CustomSearchBar(onQueryChanged: _onChangeQuery), 250 | Expanded( 251 | child: items.isEmpty 252 | ? const Center( 253 | child: Text('暂无数据'), 254 | ) 255 | : ListView.builder( 256 | itemBuilder: (context, index) { 257 | ChannelItem item = items[index]; 258 | return Dismissible( 259 | key: Key(item.url), 260 | direction: DismissDirection.horizontal, 261 | confirmDismiss: (direction) { 262 | if (direction == DismissDirection.startToEnd) { 263 | copyTextToClipboard(item.url); 264 | } else { 265 | _showAlertDialog( 266 | '你确定要删除${item.remark != '' ? item.remark : item.url}频道吗', 267 | () { 268 | setState(() { 269 | items.remove(item); 270 | HistoryTools.saveToDB(items); 271 | }); 272 | Get.back(); 273 | Get.showSnackbar(GetSnackBar( 274 | duration: const Duration(seconds: 2), 275 | title: '友情提示', 276 | message: 277 | '${item.remark != '' ? item.remark : item.url}已删除!', 278 | snackPosition: SnackPosition.TOP, 279 | )); 280 | }); 281 | } 282 | return Future.value(false); 283 | }, 284 | // Show a red background as the item is swiped away. 285 | background: _createDismissibleBg(), 286 | child: _createCell(item, index), 287 | // 系统自带的 CheckboxListTile 改不到左边去 不好用 ! 288 | ); 289 | }, 290 | itemCount: items.length, 291 | )) 292 | ], 293 | ), 294 | floatingActionButton: _createDeleteActionButton(), 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /lib/pages/live.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:flutter/services.dart' show rootBundle; 5 | import '../components/videoPlayer.dart'; 6 | import '../controller/videoPlayController.dart'; 7 | import '../other/app.dart'; 8 | 9 | Future loadM3uAssets() async { 10 | return await rootBundle.loadString('assets/cctv.m3u'); 11 | } 12 | 13 | // ignore: must_be_immutable 14 | class LivePage extends GetView { 15 | VideoPlayController playerController = Get.find(); 16 | final textController = TextEditingController(); 17 | final remarkController = TextEditingController(); 18 | 19 | LivePage({super.key}); 20 | 21 | void _callChildChangeFullScreenMethod() { 22 | playerController.toggleFullScreen(); 23 | } 24 | 25 | bool isFullScreen() { 26 | return playerController.enableFullScreen.isTrue; 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | Future.delayed(const Duration(milliseconds: 300), () { 32 | String url = Get.parameters['url'] ?? ''; 33 | String remark = Get.parameters['remark'] ?? ''; 34 | if (url != '') { 35 | playerController.playNewUrl(url, remark: remark); 36 | } 37 | }); 38 | 39 | return Obx(() { 40 | return Scaffold( 41 | appBar: isFullScreen() 42 | ? null 43 | : AppBar( 44 | title: Text(controller.currenPlayName.value), 45 | centerTitle: true, 46 | actions: [ 47 | IconButton( 48 | tooltip: '全屏', 49 | onPressed: _callChildChangeFullScreenMethod, 50 | icon: isFullScreen() 51 | ? const Icon(Icons.fullscreen_exit) 52 | : const Icon(Icons.fullscreen)), 53 | IconButton( 54 | onPressed: () async { 55 | dynamic result = await Get.toNamed(App.channelPage); 56 | if (result?.url != null && 57 | result?.url != 58 | playerController.currenPlayUrl.value) { 59 | //点击不同的url才会重新播放 60 | playerController.playNewUrl(result.url, 61 | remark: result.remark); 62 | } 63 | }, 64 | tooltip: '频道列表', 65 | icon: const Icon(Icons.playlist_play)), 66 | IconButton( 67 | tooltip: '添加播放链接', 68 | onPressed: () { 69 | Get.dialog(_buildAlertDialog(context)); 70 | }, 71 | icon: const Icon(Icons.add_box)), 72 | IconButton( 73 | tooltip: 'm3u/txt源管理', 74 | onPressed: () { 75 | Get.toNamed(App.sourceList); 76 | }, 77 | icon: const Icon(Icons.settings)), 78 | IconButton( 79 | tooltip: 'github', 80 | onPressed: () { 81 | window.open( 82 | 'https://github.com/WangGuibin/live_tv_box', 83 | 'github'); 84 | }, 85 | icon: const Icon(Icons.code)), 86 | const SizedBox(width: 30), 87 | ], 88 | ), 89 | body: controller.isLoading.isTrue 90 | ? const Center(child: CircularProgressIndicator()) 91 | : Container( 92 | padding: const EdgeInsets.all(0), 93 | alignment: Alignment.center, 94 | child: const Column( 95 | children: [Expanded(child: MyVideoPlayer())]), 96 | // 替换YourWidget为您的实际小部件 97 | )); 98 | }); 99 | } 100 | 101 | Widget _buildAlertDialog(BuildContext context) { 102 | return AlertDialog( 103 | title: const Text('输入播放地址/备注'), 104 | content: Column(mainAxisSize: MainAxisSize.min, children: [ 105 | TextField( 106 | controller: textController, 107 | decoration: const InputDecoration(hintText: '在这里输入m3u8直播地址'), 108 | ), 109 | TextField( 110 | controller: remarkController, 111 | decoration: const InputDecoration(hintText: '在这里输入备注(可选)'), 112 | ) 113 | ]), 114 | actions: [ 115 | TextButton( 116 | child: const Text('取消'), 117 | onPressed: () { 118 | Get.back(); 119 | }, 120 | ), 121 | TextButton( 122 | child: const Text('播放'), 123 | onPressed: () { 124 | String text = textController.text; 125 | // playerController.playNewUrl(text.trim(), 126 | // remark: remarkController.text.trim()); 127 | Get.back(); 128 | Get.offAllNamed(App.root, parameters: { 129 | 'url': text.trim(), 130 | 'remark': remarkController.text.trim() 131 | }); 132 | }, 133 | ), 134 | ], 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/pages/sourceList.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import '../utils/sourceManager.dart'; 5 | import '../parser/parseM3u.dart'; 6 | import '../utils/historyTools.dart'; 7 | import '../controller/sourceListController.dart'; 8 | import '../other/app.dart'; 9 | 10 | class SourceList extends GetView { 11 | SourceList({super.key}); 12 | Future initMyContext() async { 13 | await Future.delayed(const Duration(milliseconds: 200)); //模拟异步延时200ms 14 | } 15 | 16 | final _iptvSourceController = TextEditingController(); 17 | 18 | Widget _addIptvSource(BuildContext context) { 19 | return AlertDialog( 20 | title: const Text('添加IPTV源(仅支持指定格式)'), 21 | content: Column(mainAxisSize: MainAxisSize.min, children: [ 22 | TextField( 23 | controller: _iptvSourceController, 24 | decoration: 25 | const InputDecoration(hintText: '在这里输入IPTV源链接地址(仅支持指定格式)'), 26 | ) 27 | ]), 28 | actions: [ 29 | TextButton( 30 | child: const Text('取消'), 31 | onPressed: () { 32 | Get.back(); 33 | }, 34 | ), 35 | TextButton( 36 | child: const Text('添加订阅'), 37 | onPressed: () async { 38 | String url = _iptvSourceController.text; 39 | await SourceManager.addSubscriSource([url]); 40 | Get.back(); 41 | controller.items = SourceManager.getSourceList(); 42 | }, 43 | ), 44 | ], 45 | ); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return FutureBuilder( 51 | future: initMyContext(), //异步渲染UI 52 | builder: (context, snapshot) { 53 | switch (snapshot.connectionState) { 54 | case ConnectionState.waiting: 55 | return const LoadingScreen(text: '加载中...'); 56 | default: 57 | return controller.items.isEmpty 58 | ? const Text('暂无数据') 59 | : Scaffold( 60 | appBar: AppBar( 61 | title: const Text('订阅源管理'), 62 | centerTitle: true, 63 | actions: [ 64 | IconButton( 65 | tooltip: '添加m3u/txt订阅源', 66 | onPressed: () { 67 | Get.dialog(_addIptvSource(context)); 68 | }, 69 | icon: const Icon(Icons.subscriptions_rounded)), 70 | IconButton( 71 | tooltip: '拉取同步最新配置', 72 | onPressed: () { 73 | SourceManager.addSubscriSource( 74 | [controller.selectSource]); 75 | Get.showSnackbar(const GetSnackBar( 76 | duration: Duration(seconds: 2), 77 | title: '友情提示', 78 | message: '已同步最新配置', 79 | snackPosition: SnackPosition.TOP, 80 | )); 81 | }, 82 | icon: const Icon(Icons.sync_outlined)), 83 | IconButton( 84 | tooltip: '拷贝分享配置链接', 85 | onPressed: () { 86 | String shareText = controller.items 87 | .map((element) => element.iptvUrl) 88 | .toList() 89 | .join('\n'); 90 | Clipboard.setData(ClipboardData(text: shareText)); 91 | Get.showSnackbar(const GetSnackBar( 92 | duration: Duration(seconds: 2), 93 | title: '友情提示', 94 | message: '配置已成功拷贝至剪贴板!', 95 | snackPosition: SnackPosition.TOP, 96 | )); 97 | }, 98 | icon: const Icon(Icons.share)), 99 | const SizedBox(width: 20) 100 | ], 101 | ), 102 | body: Obx(() { 103 | return ListView.builder( 104 | itemCount: controller.items.length, 105 | itemBuilder: (context, index) { 106 | SourceItem item = controller.items[index]; 107 | return Dismissible( 108 | key: Key(item.iptvUrl), 109 | direction: DismissDirection.endToStart, 110 | confirmDismiss: (direction) { 111 | return Future.value( 112 | DismissDirection.endToStart == direction); 113 | }, 114 | onDismissed: (direction) { 115 | if (direction == DismissDirection.endToStart) { 116 | controller.items.remove(item); 117 | SourceManager.saveSourceList( 118 | controller.items); 119 | controller.update(); 120 | // Then show a snackbar. 121 | Get.showSnackbar(GetSnackBar( 122 | duration: const Duration(seconds: 2), 123 | title: '友情提示', 124 | message: '${item.iptvUrl} 已被删除!', 125 | snackPosition: SnackPosition.TOP, 126 | )); 127 | } 128 | }, 129 | // Show a red background as the item is swiped away. 130 | background: Container( 131 | color: Colors.red, 132 | child: const Row( 133 | children: [ 134 | Expanded(child: SizedBox.shrink()), 135 | Padding( 136 | padding: EdgeInsets.all(10), 137 | child: Text( 138 | '左滑删除', 139 | style: TextStyle(color: Colors.white), 140 | ), 141 | ) 142 | ], 143 | ), 144 | ), 145 | child: ListTile( 146 | title: Text(item.iptvUrl), 147 | trailing: Obx(() => Icon( 148 | controller.selectSource == item.iptvUrl 149 | ? Icons.check_box_rounded 150 | : Icons.check_box_outline_blank_rounded, 151 | color: Colors.lightBlue)), 152 | onTap: () { 153 | controller.selectSource = item.iptvUrl; 154 | if (SourceManager.getCurrentSource() != '' && 155 | SourceManager.getCurrentSource() != 156 | null) { 157 | SourceItem currentItem = 158 | SourceManager.getSourceList() 159 | .where((element) => 160 | element.iptvUrl == item.iptvUrl) 161 | .toList() 162 | .first; 163 | List channels = 164 | parseM3U8File(currentItem); 165 | HistoryTools.getSubscribeChannels(channels); 166 | 167 | Get.showSnackbar(GetSnackBar( 168 | duration: const Duration(seconds: 2), 169 | title: '友情提示', 170 | message: '${item.iptvUrl}的所有频道已添加至频道列表!', 171 | snackPosition: SnackPosition.TOP, 172 | )); 173 | // Get.offAndToNamed(App.channelPage); 174 | } 175 | }, 176 | ), 177 | ); 178 | }); 179 | }), 180 | ); 181 | } 182 | }, 183 | ); 184 | } 185 | } 186 | 187 | class LoadingScreen extends StatelessWidget { 188 | final String text; 189 | 190 | const LoadingScreen({super.key, required this.text}); 191 | 192 | @override 193 | Widget build(BuildContext context) { 194 | return Scaffold( 195 | appBar: AppBar( 196 | title: const Text('Loading...'), 197 | ), 198 | body: const Center( 199 | child: CircularProgressIndicator(), 200 | ), 201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/parser/parseM3u.dart: -------------------------------------------------------------------------------- 1 | import '../utils/sourceManager.dart'; 2 | 3 | class Channel { 4 | String? title; 5 | String? url; 6 | 7 | void logInfo() { 8 | print('$title --- $url'); 9 | } 10 | } 11 | 12 | List parseM3U8File(SourceItem source) { 13 | List lines = source.iptvText.split('\n'); 14 | //剔除空行 15 | lines = lines.where((element) => element.trim().isNotEmpty).toList(); 16 | 17 | if (source.iptvUrl.endsWith('.m3u') || source.iptvUrl.endsWith('.m3u8')) { 18 | return parseM3uFormat(lines); 19 | } else { 20 | return parseTxtFormat(lines); 21 | } 22 | } 23 | 24 | //解析m3u 25 | List parseM3uFormat(List lines) { 26 | List channels = []; 27 | if (lines.first.contains('#EXTM3U')) { 28 | lines.removeAt(0); 29 | } 30 | lines = lines 31 | .where((element) => 32 | element.contains('#EXTINF:') || element.startsWith('http')) 33 | .toList(); 34 | Channel currentChannel = Channel(); 35 | 36 | for (String line in lines) { 37 | if (line.contains('#EXTM3U')) { 38 | continue; 39 | } 40 | if (line.trim().isEmpty) { 41 | continue; 42 | } 43 | 44 | if (line.startsWith('#EXTINF:')) { 45 | currentChannel = Channel(); 46 | currentChannel.title = line.split(',').last; 47 | } else if (line.isNotEmpty) { 48 | if (line.contains('m3u8')) { 49 | String result = line.substring(0, line.indexOf('.m3u8')); 50 | currentChannel.url = "$result.m3u8"; 51 | channels.add(currentChannel); 52 | currentChannel.logInfo(); 53 | } 54 | } 55 | } 56 | 57 | return channels; 58 | } 59 | 60 | //解析txt 61 | List parseTxtFormat(List lines) { 62 | List channels = []; 63 | lines = lines.where((element) => element.contains('http')).toList(); 64 | for (String line in lines) { 65 | List arr = line.split(','); 66 | String title = arr.first; 67 | String url = arr.last; 68 | if (url.startsWith('http')) { 69 | Channel channel = Channel(); 70 | channel.title = title; 71 | channel.url = url; 72 | channels.add(channel); 73 | channel.logInfo(); 74 | } 75 | } 76 | return channels; 77 | } 78 | -------------------------------------------------------------------------------- /lib/utils/fileManager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html' as html; 2 | import './historyTools.dart'; 3 | 4 | //当前时间戳(毫秒级) 5 | int timeMilliseconds() { 6 | return DateTime.now().millisecondsSinceEpoch; 7 | } 8 | 9 | //浏览器模拟点击下载保存文本 10 | void saveTextFile(String text, {String filename = ''}) { 11 | if (filename == '') { 12 | filename = '${timeMilliseconds()}.json'; 13 | } 14 | html.AnchorElement( 15 | href: 'data:text/plain;charset=utf-8,${Uri.encodeComponent(text)}') 16 | ..setAttribute('download', filename) 17 | ..click(); 18 | } 19 | 20 | void pickAndReadFile() { 21 | html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); 22 | uploadInput.click(); 23 | 24 | uploadInput.onChange.listen((e) { 25 | final files = uploadInput.files; 26 | if (files?.length == 1) { 27 | final file = files?[0]; 28 | final reader = html.FileReader(); 29 | 30 | reader.onLoadEnd.listen((e) { 31 | Object? data = reader.result; 32 | try { 33 | HistoryTools.importTextSource(data as String); 34 | } catch (e) { 35 | print(e); 36 | } 37 | }); 38 | 39 | reader.onError.listen((fileEvent) { 40 | print("读取文件时发生错误"); 41 | }); 42 | 43 | reader.readAsText(file as html.Blob); 44 | } 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/utils/historyTools.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:localstorage/localstorage.dart'; 3 | import '../parser/parseM3u.dart'; 4 | 5 | const kUrlsLocalKey = 'urls.local.abc.key'; 6 | 7 | class ChannelItem { 8 | final String remark; 9 | final String url; 10 | ChannelItem({required this.remark, required this.url}); 11 | 12 | toJSONEncodable() { 13 | Map item = {}; 14 | item['url'] = url; 15 | item['remark'] = remark; 16 | return item; 17 | } 18 | 19 | static fromJsonMap(Map json) { 20 | return ChannelItem(remark: json['remark'], url: json['url']); 21 | } 22 | } 23 | 24 | class HistoryTools { 25 | static LocalStorage getStorage() { 26 | LocalStorage storage = LocalStorage('live_urls.json'); 27 | return storage; 28 | } 29 | 30 | static List getItems() { 31 | String jsonStr = getStorage().getItem(kUrlsLocalKey) ?? '[]'; 32 | List jsonMapList = json.decode(jsonStr); 33 | List? items = jsonMapList 34 | .map((item) => ChannelItem.fromJsonMap(item)) 35 | .cast() 36 | .toList(); 37 | //去重 38 | final urls = {}; 39 | items.retainWhere((item) => urls.add(item.url)); 40 | return items; 41 | } 42 | 43 | static saveToDB(List items) { 44 | LocalStorage storage = getStorage(); 45 | List? toJsonMapList = 46 | items.map((item) => item.toJSONEncodable()).cast().toList(); 47 | String jsonStr = json.encode(toJsonMapList); 48 | storage.setItem(kUrlsLocalKey, jsonStr); 49 | } 50 | 51 | //获取记录json字符串 52 | static String exportJsonData() { 53 | List? toJsonMapList = getItems() 54 | .map((item) => item.toJSONEncodable()) 55 | .cast() 56 | .toList(); 57 | String jsonStr = json.encode(toJsonMapList); 58 | return jsonStr; 59 | } 60 | 61 | ///导入源 json格式: name, remark 62 | static importTextSource(String jsonStr) { 63 | print(jsonStr); 64 | List oldItems = getItems(); 65 | List jsonMapList = json.decode(jsonStr); 66 | List? items = jsonMapList 67 | .map((item) => ChannelItem.fromJsonMap(item)) 68 | .cast() 69 | .toList(); 70 | oldItems.insertAll(0, items); 71 | saveToDB(oldItems); 72 | } 73 | 74 | //切换源之后 刷新频道列表 追加吧 没设计好 75 | static getSubscribeChannels(List channels) { 76 | List oldItems = getItems(); 77 | List items = channels.map((channel) { 78 | channel.logInfo(); 79 | return ChannelItem(remark: channel.title!, url: channel.url!); 80 | }).toList(); 81 | //url一样的剔除掉 82 | oldItems.removeWhere( 83 | (element) => items.map((e) => e.url).toList().contains(element.url)); 84 | oldItems.insertAll(0, items); 85 | saveToDB(oldItems); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/utils/sourceManager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:localstorage/localstorage.dart'; 3 | import 'package:dio/dio.dart'; 4 | 5 | const kSourceListLocalKey = 'source.list.key'; 6 | const kCurrentSourceLocalKey = 'current.source.key'; 7 | 8 | class SourceItem { 9 | final String iptvUrl; 10 | final String iptvText; 11 | 12 | SourceItem({required this.iptvUrl, required this.iptvText}); 13 | 14 | toJSONEncodable() { 15 | Map item = {}; 16 | item['iptvUrl'] = iptvUrl; 17 | item['iptvText'] = iptvText; 18 | return item; 19 | } 20 | 21 | static fromJsonMap(Map json) { 22 | return SourceItem(iptvUrl: json['iptvUrl'], iptvText: json['iptvText']); 23 | } 24 | } 25 | 26 | class SourceManager { 27 | static LocalStorage getStorage() { 28 | LocalStorage storage = LocalStorage('live_source.json'); 29 | return storage; 30 | } 31 | 32 | static setCurrentSource(String url) { 33 | getStorage().setItem(kCurrentSourceLocalKey, url); 34 | } 35 | 36 | static getCurrentSource() { 37 | return getStorage().getItem(kCurrentSourceLocalKey); 38 | } 39 | 40 | //添加源 41 | static addSource(SourceItem source) { 42 | List urls = getSourceList(); 43 | if (urls.isNotEmpty) { 44 | //覆盖旧的 45 | urls = 46 | urls.where((element) => element.iptvUrl != source.iptvUrl).toList(); 47 | urls.add(source); 48 | } else { 49 | urls.add(source); 50 | } 51 | 52 | List? toJsonMapList = 53 | urls.map((item) => item.toJSONEncodable()).cast().toList(); 54 | String jsonStr = json.encode(toJsonMapList); 55 | getStorage().setItem(kSourceListLocalKey, jsonStr); 56 | } 57 | 58 | static saveSourceList(List sources) { 59 | if (sources.isEmpty) { 60 | getStorage().setItem(kSourceListLocalKey, '[]'); 61 | return; 62 | } 63 | 64 | List? toJsonMapList = 65 | sources.map((item) => item.toJSONEncodable()).cast().toList(); 66 | String jsonStr = json.encode(toJsonMapList); 67 | getStorage().setItem(kSourceListLocalKey, jsonStr); 68 | } 69 | 70 | static List getSourceList() { 71 | String jsonStr = getStorage().getItem(kSourceListLocalKey) ?? '[]'; 72 | List jsonMapList = json.decode(jsonStr); 73 | List? items = jsonMapList 74 | .map((item) => SourceItem.fromJsonMap(item)) 75 | .cast() 76 | .toList(); 77 | return items; 78 | } 79 | 80 | //订阅源 81 | static addSubscriSource(List urls) async { 82 | Dio dio = Dio(); 83 | try { 84 | List responses = await Future.wait( 85 | urls.map((url) => dio.get(url)), 86 | ); 87 | Map resMap = responses.asMap(); 88 | for (int index in resMap.keys.toList()) { 89 | addSource( 90 | SourceItem(iptvUrl: urls[index], iptvText: responses[index].data)); 91 | } 92 | SourceManager.setCurrentSource(urls.first); 93 | } catch (e) { 94 | print(e); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/utils/timeFormat.dart: -------------------------------------------------------------------------------- 1 | // 将秒数转换为时分秒的格式化字符串 2 | String formatDuration(int sec) { 3 | // 创建一个 Duration 对象 4 | Duration duration = Duration(seconds: sec); 5 | // 获取小时、分钟和秒数 6 | int hours = duration.inHours; 7 | int minutes = duration.inMinutes.remainder(60); 8 | int seconds = duration.inSeconds.remainder(60); 9 | // 格式化字符串 10 | String formattedTime = 11 | '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; 12 | // 返回格式化后的字符串 13 | return formattedTime; 14 | } 15 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | csslib: 45 | dependency: transitive 46 | description: 47 | name: csslib 48 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.0" 52 | cupertino_icons: 53 | dependency: "direct main" 54 | description: 55 | name: cupertino_icons 56 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.0.6" 60 | dio: 61 | dependency: "direct main" 62 | description: 63 | name: dio 64 | sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "5.4.0" 68 | fake_async: 69 | dependency: transitive 70 | description: 71 | name: fake_async 72 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.3.1" 76 | ffi: 77 | dependency: transitive 78 | description: 79 | name: ffi 80 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.1.0" 84 | flutter: 85 | dependency: "direct main" 86 | description: flutter 87 | source: sdk 88 | version: "0.0.0" 89 | flutter_lints: 90 | dependency: "direct dev" 91 | description: 92 | name: flutter_lints 93 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "2.0.3" 97 | flutter_test: 98 | dependency: "direct dev" 99 | description: flutter 100 | source: sdk 101 | version: "0.0.0" 102 | flutter_web_plugins: 103 | dependency: transitive 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | get: 108 | dependency: "direct main" 109 | description: 110 | name: get 111 | sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e 112 | url: "https://pub.dev" 113 | source: hosted 114 | version: "4.6.6" 115 | html: 116 | dependency: transitive 117 | description: 118 | name: html 119 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 120 | url: "https://pub.dev" 121 | source: hosted 122 | version: "0.15.4" 123 | http: 124 | dependency: transitive 125 | description: 126 | name: http 127 | sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.1.2" 131 | http_parser: 132 | dependency: transitive 133 | description: 134 | name: http_parser 135 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "4.0.2" 139 | js: 140 | dependency: transitive 141 | description: 142 | name: js 143 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "0.6.7" 147 | lints: 148 | dependency: transitive 149 | description: 150 | name: lints 151 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.1" 155 | localstorage: 156 | dependency: "direct main" 157 | description: 158 | name: localstorage 159 | sha256: fdff4f717114e992acfd4045dc4a9ab9b987ca57f020965d63e3eb34089c60d8 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "4.0.1+4" 163 | matcher: 164 | dependency: transitive 165 | description: 166 | name: matcher 167 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "0.12.16" 171 | material_color_utilities: 172 | dependency: transitive 173 | description: 174 | name: material_color_utilities 175 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "0.5.0" 179 | meta: 180 | dependency: transitive 181 | description: 182 | name: meta 183 | sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "1.10.0" 187 | path: 188 | dependency: transitive 189 | description: 190 | name: path 191 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "1.8.3" 195 | path_provider: 196 | dependency: transitive 197 | description: 198 | name: path_provider 199 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "2.1.1" 203 | path_provider_android: 204 | dependency: transitive 205 | description: 206 | name: path_provider_android 207 | sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "2.2.2" 211 | path_provider_foundation: 212 | dependency: transitive 213 | description: 214 | name: path_provider_foundation 215 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "2.3.1" 219 | path_provider_linux: 220 | dependency: transitive 221 | description: 222 | name: path_provider_linux 223 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "2.2.1" 227 | path_provider_platform_interface: 228 | dependency: transitive 229 | description: 230 | name: path_provider_platform_interface 231 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 232 | url: "https://pub.dev" 233 | source: hosted 234 | version: "2.1.1" 235 | path_provider_windows: 236 | dependency: transitive 237 | description: 238 | name: path_provider_windows 239 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 240 | url: "https://pub.dev" 241 | source: hosted 242 | version: "2.2.1" 243 | platform: 244 | dependency: transitive 245 | description: 246 | name: platform 247 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 248 | url: "https://pub.dev" 249 | source: hosted 250 | version: "3.1.3" 251 | plugin_platform_interface: 252 | dependency: transitive 253 | description: 254 | name: plugin_platform_interface 255 | sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 256 | url: "https://pub.dev" 257 | source: hosted 258 | version: "2.1.7" 259 | sky_engine: 260 | dependency: transitive 261 | description: flutter 262 | source: sdk 263 | version: "0.0.99" 264 | source_span: 265 | dependency: transitive 266 | description: 267 | name: source_span 268 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 269 | url: "https://pub.dev" 270 | source: hosted 271 | version: "1.10.0" 272 | stack_trace: 273 | dependency: transitive 274 | description: 275 | name: stack_trace 276 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 277 | url: "https://pub.dev" 278 | source: hosted 279 | version: "1.11.1" 280 | stream_channel: 281 | dependency: transitive 282 | description: 283 | name: stream_channel 284 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 285 | url: "https://pub.dev" 286 | source: hosted 287 | version: "2.1.2" 288 | string_scanner: 289 | dependency: transitive 290 | description: 291 | name: string_scanner 292 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 293 | url: "https://pub.dev" 294 | source: hosted 295 | version: "1.2.0" 296 | term_glyph: 297 | dependency: transitive 298 | description: 299 | name: term_glyph 300 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 301 | url: "https://pub.dev" 302 | source: hosted 303 | version: "1.2.1" 304 | test_api: 305 | dependency: transitive 306 | description: 307 | name: test_api 308 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 309 | url: "https://pub.dev" 310 | source: hosted 311 | version: "0.6.1" 312 | typed_data: 313 | dependency: transitive 314 | description: 315 | name: typed_data 316 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 317 | url: "https://pub.dev" 318 | source: hosted 319 | version: "1.3.2" 320 | vector_math: 321 | dependency: transitive 322 | description: 323 | name: vector_math 324 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "2.1.4" 328 | video_player: 329 | dependency: "direct main" 330 | description: 331 | name: video_player 332 | sha256: e16f0a83601a78d165dabc17e4dac50997604eb9e4cc76e10fa219046b70cef3 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.8.1" 336 | video_player_android: 337 | dependency: transitive 338 | description: 339 | name: video_player_android 340 | sha256: "3fe89ab07fdbce786e7eb25b58532d6eaf189ceddc091cb66cba712f8d9e8e55" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "2.4.10" 344 | video_player_avfoundation: 345 | dependency: transitive 346 | description: 347 | name: video_player_avfoundation 348 | sha256: "01a57940e1dabc8769ccd457c4ae9ea50274e7d5a7617f7820dae5fe1d8436ae" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "2.5.3" 352 | video_player_platform_interface: 353 | dependency: transitive 354 | description: 355 | name: video_player_platform_interface 356 | sha256: be72301bf2c0150ab35a8c34d66e5a99de525f6de1e8d27c0672b836fe48f73a 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "6.2.1" 360 | video_player_web: 361 | dependency: "direct main" 362 | description: 363 | name: video_player_web 364 | sha256: ab7a462b07d9ca80bed579e30fb3bce372468f1b78642e0911b10600f2c5cb5b 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "2.1.2" 368 | video_player_web_hls: 369 | dependency: "direct main" 370 | description: 371 | name: video_player_web_hls 372 | sha256: "278c7841a2450d1b1ad2a2eb746840045b07e5d2ad21663c84a1486925ed338b" 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "1.1.0" 376 | web: 377 | dependency: transitive 378 | description: 379 | name: web 380 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "0.3.0" 384 | win32: 385 | dependency: transitive 386 | description: 387 | name: win32 388 | sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "3.1.4" 392 | xdg_directories: 393 | dependency: transitive 394 | description: 395 | name: xdg_directories 396 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "1.0.3" 400 | sdks: 401 | dart: ">=3.2.2 <4.0.0" 402 | flutter: ">=3.16.0" 403 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: live_tv_box 2 | description: "A new Flutter project." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.0.0+1 20 | 21 | environment: 22 | sdk: ">=3.2.2 <4.0.0" 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | video_player: ^2.8.1 38 | video_player_web_hls: ^1.1.0 39 | video_player_web: ^2.1.2 40 | 41 | localstorage: ^4.0.1+4 42 | get: ^4.6.6 43 | dio: ^5.4.0 44 | dev_dependencies: 45 | flutter_test: 46 | sdk: flutter 47 | 48 | # The "flutter_lints" package below contains a set of recommended lints to 49 | # encourage good coding practices. The lint set provided by the package is 50 | # activated in the `analysis_options.yaml` file located at the root of your 51 | # package. See that file for information about deactivating specific lint 52 | # rules and activating additional ones. 53 | flutter_lints: ^2.0.0 54 | 55 | # For information on the generic Dart part of this file, see the 56 | # following page: https://dart.dev/tools/pub/pubspec 57 | 58 | # The following section is specific to Flutter packages. 59 | flutter: 60 | # The following line ensures that the Material Icons font is 61 | # included with your application, so that you can use the icons in 62 | # the material Icons class. 63 | uses-material-design: true 64 | 65 | # To add assets to your application, add an assets section, like this: 66 | assets: 67 | - assets/cctv.m3u 68 | 69 | # An image asset can refer to one or more resolution-specific "variants", see 70 | # https://flutter.dev/assets-and-images/#resolution-aware 71 | 72 | # For details regarding adding assets from package dependencies, see 73 | # https://flutter.dev/assets-and-images/#from-packages 74 | 75 | # To add custom fonts to your application, add a fonts section here, 76 | # in this "flutter" section. Each entry in this list should have a 77 | # "family" key with the font family name, and a "fonts" key with a 78 | # list giving the asset and other descriptors for the font. For 79 | # example: 80 | # fonts: 81 | # - family: Schyler 82 | # fonts: 83 | # - asset: fonts/Schyler-Regular.ttf 84 | # - asset: fonts/Schyler-Italic.ttf 85 | # style: italic 86 | # - family: Trajan Pro 87 | # fonts: 88 | # - asset: fonts/TrajanPro.ttf 89 | # - asset: fonts/TrajanPro_Bold.ttf 90 | # weight: 700 91 | # 92 | # For details regarding fonts from package dependencies, 93 | # see https://flutter.dev/custom-fonts/#from-packages 94 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WangGuibin/live_tv_box/2fa201dc7429aaa5996c252e5aa3e406c9ad7e4a/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | live_tv_box 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live_tv_box", 3 | "short_name": "live_tv_box", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------