Compare commits
7 Commits
989ca20d13
...
c8bb8537c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8bb8537c8 | ||
|
|
4f47258b17 | ||
|
|
7bef512b07 | ||
|
|
52fc8099ae | ||
|
|
2a565fb8da | ||
|
|
327f03c562 | ||
|
|
b7f16e1444 |
129
.gitignore
vendored
129
.gitignore
vendored
@ -1,6 +1,125 @@
|
||||
# ================================================
|
||||
# System files
|
||||
# ================================================
|
||||
.DS_Store
|
||||
dist-web
|
||||
node_modules
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
desktop/src-tauri/target
|
||||
Thumbs.db
|
||||
|
||||
# ================================================
|
||||
# IDE
|
||||
# ================================================
|
||||
.idea/
|
||||
.vscode/settings.json
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# ================================================
|
||||
# Dependencies
|
||||
# ================================================
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# ================================================
|
||||
# Build outputs
|
||||
# ================================================
|
||||
# UI
|
||||
ui/dist-web/
|
||||
ui/dist/
|
||||
|
||||
# Web
|
||||
web/public/
|
||||
web/dist/
|
||||
|
||||
# Desktop
|
||||
desktop/src-tauri/target/
|
||||
desktop/dist/
|
||||
desktop/*.log
|
||||
|
||||
# ================================================
|
||||
# Android
|
||||
# ================================================
|
||||
android/.gradle/
|
||||
android/.idea/
|
||||
android/local.properties
|
||||
android/*.iml
|
||||
android/app/build/
|
||||
android/app/release/
|
||||
android/captures/
|
||||
android/*.hprof
|
||||
android/.cxx/
|
||||
android/app/src/main/assets/www/
|
||||
!android/app/src/main/assets/error.html
|
||||
!android/app/src/main/assets/test.html
|
||||
|
||||
# ================================================
|
||||
# Android TV
|
||||
# ================================================
|
||||
android-tv/.gradle/
|
||||
android-tv/.idea/
|
||||
android-tv/local.properties
|
||||
android-tv/*.iml
|
||||
android-tv/app/build/
|
||||
android-tv/app/release/
|
||||
android-tv/captures/
|
||||
android-tv/*.hprof
|
||||
android-tv/.cxx/
|
||||
android-tv/app/src/main/assets/www/
|
||||
!android-tv/app/src/main/assets/error.html
|
||||
!android-tv/app/src/main/assets/test.html
|
||||
|
||||
# ================================================
|
||||
# HarmonyOS
|
||||
# ================================================
|
||||
harmonyos/.idea/
|
||||
harmonyos/build/
|
||||
harmonyos/entry/build/
|
||||
|
||||
# ================================================
|
||||
# Docker
|
||||
# ================================================
|
||||
docker-compose.override.yml
|
||||
.env.docker
|
||||
.env.production
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# ================================================
|
||||
# Logs
|
||||
# ================================================
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# ================================================
|
||||
# Environment
|
||||
# ================================================
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.development
|
||||
.env.test
|
||||
|
||||
# ================================================
|
||||
# Testing
|
||||
# ================================================
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# ================================================
|
||||
# Temporary files
|
||||
# ================================================
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# ================================================
|
||||
# Misc
|
||||
# ================================================
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
|
||||
71
README.md
Normal file
71
README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 📺 IPTV 跨平台应用
|
||||
|
||||
基于 Web 技术的跨平台 IPTV 播放器,一套代码支持 Windows、Mac、Android、Android TV、HarmonyOS。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
iptv-app/
|
||||
├── ui/ # 🌐 Web 核心代码 (Vue 3)
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # 播放器组件
|
||||
│ │ ├── utils/ # M3U 解析等工具
|
||||
│ │ └── App.vue # 主应用
|
||||
│ ├── public/ # 静态资源
|
||||
│ └── dist-web/ # 📦 Web 构建输出(各平台共用)
|
||||
├── web/ # 网页端,nodejs中间层+docker封装
|
||||
├── desktop/ # 🖥️ Tauri 桌面端 (Win/Mac)
|
||||
├── android/ # 📱 Android WebView 壳
|
||||
├── android-tv/ # 📺 Android TV WebView 壳
|
||||
└── harmonyos/ # 🔶 HarmonyOS WebView 壳
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 平台 | 技术 | 说明 |
|
||||
|------|------|------|
|
||||
| UI 核心 | Vue 3 + Vite + hls.js | 一套代码,所有平台共用 |
|
||||
| Web | Nodejs | 中间层代理,解决跨域问题 |
|
||||
| Desktop | Tauri (Rust) | 轻量 (~5MB),高性能 |
|
||||
| Android | WebView + ExoPlayer | 手机/平板 Web 壳 |
|
||||
| Android TV | WebView + Leanback | TV 专用,支持遥控器导航 |
|
||||
| HarmonyOS | Web 组件 + Video 组件 | Web 壳 + 原生播放器 |
|
||||
|
||||
## 快速开始
|
||||
|
||||
### Docker Web(推荐)
|
||||
|
||||
```bash
|
||||
# 1. 构建 Web UI
|
||||
cd ui && npm run build
|
||||
|
||||
# 2. 启动 Docker 服务
|
||||
cd ../web
|
||||
docker-compose up -d
|
||||
|
||||
# 访问 http://localhost:3000
|
||||
```
|
||||
|
||||
### Android 构建
|
||||
|
||||
```bash
|
||||
# 1. 构建 Web UI
|
||||
cd ui
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# 2. 构建 Android APK
|
||||
cd ../android
|
||||
./build.sh
|
||||
```
|
||||
|
||||
APK 输出位置: `android/app/build/outputs/apk/debug/app-debug.apk`
|
||||
|
||||
### Android TV 构建
|
||||
|
||||
```bash
|
||||
cd android-tv
|
||||
./build.sh
|
||||
```
|
||||
|
||||
---
|
||||
101
android-tv/README.md
Normal file
101
android-tv/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
# Android TV WebView 壳
|
||||
|
||||
为 Android TV 设备(包括智能电视、电视盒子等)提供的 WebView 套壳应用。
|
||||
|
||||
## 特性
|
||||
|
||||
- ✅ **Leanback 支持** - 专为 TV 优化的主题和启动器
|
||||
- ✅ **D-Pad 导航** - 遥控器方向键支持,自动注入 JS 导航
|
||||
- ✅ **全屏沉浸** - 无状态栏/导航栏,纯全屏体验
|
||||
- ✅ **横屏锁定** - 强制横屏显示
|
||||
- ✅ **按键处理** - 支持返回、菜单、信息键等遥控器按键
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
android-tv/
|
||||
├── app/src/main/
|
||||
│ ├── java/com/iptv/tv/
|
||||
│ │ └── MainActivity.java # TV 主活动(含 D-Pad 支持)
|
||||
│ ├── res/
|
||||
│ │ ├── layout/activity_main.xml
|
||||
│ │ ├── values/
|
||||
│ │ │ ├── strings.xml
|
||||
│ │ │ ├── styles.xml # Leanback 主题
|
||||
│ │ │ └── colors.xml
|
||||
│ │ └── drawable/
|
||||
│ │ └── ic_banner.xml # TV 启动器横幅
|
||||
│ ├── assets/
|
||||
│ │ └── www/ # 打包的 Web 资源
|
||||
│ └── AndroidManifest.xml # TV 特定配置
|
||||
├── build.sh # 一键构建脚本
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 构建步骤
|
||||
|
||||
```bash
|
||||
# 1. 构建 Web UI
|
||||
cd ../ui
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# 2. 构建 Android TV APK
|
||||
cd ../android-tv
|
||||
./build.sh
|
||||
```
|
||||
|
||||
APK 输出: `app/build/outputs/apk/debug/app-debug.apk`
|
||||
|
||||
## TV 特定配置
|
||||
|
||||
### AndroidManifest.xml
|
||||
|
||||
```xml
|
||||
<!-- TV 必需特性 -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<!-- TV 启动器 -->
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
```
|
||||
|
||||
### D-Pad 导航支持
|
||||
|
||||
应用自动注入 JavaScript 代码,使 Web 应用支持遥控器方向键导航:
|
||||
|
||||
- **方向键** - 在可聚焦元素间移动
|
||||
- **确认键** - 点击当前聚焦元素
|
||||
- **返回键** - 页面后退/退出应用
|
||||
- **菜单键** - 刷新页面
|
||||
- **信息键** - 显示版本信息
|
||||
|
||||
## 遥控器按键映射
|
||||
|
||||
| 按键 | 功能 |
|
||||
|------|------|
|
||||
| 上/下/左/右 | 导航焦点 |
|
||||
| 确认/OK | 点击 |
|
||||
| 返回 | 页面后退 |
|
||||
| 菜单 | 刷新页面 |
|
||||
| 信息/INFO | 显示版本 |
|
||||
|
||||
## 开发与调试
|
||||
|
||||
1. **ADB 连接 TV**
|
||||
```bash
|
||||
adb connect <TV_IP>:5555
|
||||
adb install app-debug.apk
|
||||
```
|
||||
|
||||
2. **查看日志**
|
||||
```bash
|
||||
adb logcat -s IPTV:D
|
||||
```
|
||||
|
||||
3. **远程调试**
|
||||
```bash
|
||||
adb shell am start -a android.intent.action.VIEW -d "http://<dev_server>:5173"
|
||||
```
|
||||
33
android-tv/app/build.gradle
Normal file
33
android-tv/app/build.gradle
Normal file
@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.iptv.tv'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.iptv.tv"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.leanback:leanback:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
}
|
||||
1
android-tv/app/proguard-rules.pro
vendored
Normal file
1
android-tv/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1 @@
|
||||
# ProGuard rules
|
||||
41
android-tv/app/src/main/AndroidManifest.xml
Normal file
41
android-tv/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.iptv.tv">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<!-- TV 特定配置 -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_banner"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:banner="@drawable/ic_banner">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
|
||||
<!-- TV Launcher -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- 普通 Launcher(用于调试) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
48
android-tv/app/src/main/java/com/iptv/tv/AssetReader.java
Normal file
48
android-tv/app/src/main/java/com/iptv/tv/AssetReader.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.iptv.tv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class AssetReader {
|
||||
private Context context;
|
||||
|
||||
public AssetReader(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String readFile(String path) {
|
||||
try {
|
||||
InputStream is = context.getAssets().open(path);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
return "ERROR: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String readChannelData() {
|
||||
return readFile("www/api/result.txt");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean fileExists(String path) {
|
||||
try {
|
||||
context.getAssets().open(path).close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
android-tv/app/src/main/java/com/iptv/tv/MainActivity.java
Normal file
148
android-tv/app/src/main/java/com/iptv/tv/MainActivity.java
Normal file
@ -0,0 +1,148 @@
|
||||
package com.iptv.tv;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
private static final String TAG = "IPTV_TV";
|
||||
private WebView webView;
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.d(TAG, "onCreate started");
|
||||
|
||||
// 全屏设置
|
||||
getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
);
|
||||
|
||||
// 设置内容视图
|
||||
webView = new WebView(this);
|
||||
webView.setBackgroundColor(Color.BLACK);
|
||||
setContentView(webView);
|
||||
|
||||
// 添加 JavaScript 接口
|
||||
webView.addJavascriptInterface(new AssetReader(this), "AndroidAsset");
|
||||
|
||||
// WebView 设置
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setDatabaseEnabled(true);
|
||||
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setAllowContentAccess(true);
|
||||
settings.setAllowFileAccessFromFileURLs(true);
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
|
||||
// TV 不需要缩放
|
||||
settings.setSupportZoom(false);
|
||||
settings.setBuiltInZoomControls(false);
|
||||
|
||||
// WebViewClient
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
Log.d(TAG, "onPageFinished: " + url);
|
||||
injectDPadSupport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
Log.e(TAG, "onReceivedError: " + errorCode + " - " + description);
|
||||
}
|
||||
});
|
||||
|
||||
// WebChromeClient
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
||||
Log.d(TAG, "Console: " + consoleMessage.message());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// 延迟加载
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
webView.loadUrl("file:///android_asset/www/index.html");
|
||||
}, 100);
|
||||
|
||||
Log.d(TAG, "onCreate finished");
|
||||
}
|
||||
|
||||
private void injectDPadSupport() {
|
||||
String js = "javascript:(function() {" +
|
||||
"document.addEventListener('keydown', function(e) {" +
|
||||
" if(e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {" +
|
||||
" e.preventDefault();" +
|
||||
" var focusable = document.querySelectorAll('button, a, input, [tabindex]:not([tabindex=\"-1\"])');" +
|
||||
" var current = document.activeElement;" +
|
||||
" var index = Array.prototype.indexOf.call(focusable, current);" +
|
||||
" if(e.key === 'ArrowRight' || e.key === 'ArrowDown') {" +
|
||||
" var next = focusable[index + 1] || focusable[0];" +
|
||||
" next.focus();" +
|
||||
" } else {" +
|
||||
" var prev = focusable[index - 1] || focusable[focusable.length - 1];" +
|
||||
" prev.focus();" +
|
||||
" }" +
|
||||
" }" +
|
||||
" if(e.key === 'Enter') {" +
|
||||
" document.activeElement.click();" +
|
||||
" }" +
|
||||
"});" +
|
||||
"})()";
|
||||
webView.evaluateJavascript(js, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (webView != null) webView.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (webView != null) webView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (webView != null) webView.destroy();
|
||||
}
|
||||
}
|
||||
33
android-tv/app/src/main/res/drawable/ic_banner.xml
Normal file
33
android-tv/app/src/main/res/drawable/ic_banner.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0a0a0a" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:left="48dp"
|
||||
android:right="48dp"
|
||||
android:top="20dp"
|
||||
android:bottom="20dp"
|
||||
android:gravity="center">
|
||||
<vector
|
||||
android:width="160dp"
|
||||
android:height="60dp"
|
||||
android:viewportWidth="160"
|
||||
android:viewportHeight="60">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M15,10 L15,50 L23,50 L23,34 L40,50 L52,50 L52,10 L40,10 L23,26 L23,10 Z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M65,20 L65,25 L70,25 L70,35 L65,35 L65,40 L85,40 L85,35 L90,35 L90,25 L85,25 L85,20 Z M75,30 L80,30 L80,32 L75,32 Z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M95,20 L95,40 L105,40 L105,35 L115,35 L115,40 L125,40 L125,20 L115,20 L115,25 L105,25 L105,20 Z M105,30 L115,30 L115,32 L105,32 Z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M130,20 L130,40 L150,40 L150,32 L140,32 L140,20 Z"/>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
17
android-tv/app/src/main/res/layout/activity_main.xml
Normal file
17
android-tv/app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0a0a0a">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0a0a0a"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
5
android-tv/app/src/main/res/values/colors.xml
Normal file
5
android-tv/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
3
android-tv/app/src/main/res/values/strings.xml
Normal file
3
android-tv/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">IPTV TV</string>
|
||||
</resources>
|
||||
12
android-tv/app/src/main/res/values/styles.xml
Normal file
12
android-tv/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.Leanback">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="AppTheme">
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
4
android-tv/build.gradle
Normal file
4
android-tv/build.gradle
Normal file
@ -0,0 +1,4 @@
|
||||
// Top-level build file
|
||||
plugins {
|
||||
id 'com.android.application' version '8.1.0' apply false
|
||||
}
|
||||
31
android-tv/build.sh
Executable file
31
android-tv/build.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Android TV WebView 壳打包脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== IPTV Android TV 构建 ==="
|
||||
|
||||
# 检查 UI 构建产物
|
||||
if [ ! -d "../ui/dist-web" ]; then
|
||||
echo "错误: 未找到 ../ui/dist-web 目录"
|
||||
echo "请先构建 Web UI: cd ../ui && npm run build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 复制 Web 资源到 Android assets
|
||||
echo "复制 Web 资源..."
|
||||
mkdir -p app/src/main/assets/www
|
||||
cp -r ../ui/dist-web/* app/src/main/assets/www/
|
||||
|
||||
# 统计文件
|
||||
echo "已复制文件数量:"
|
||||
find app/src/main/assets/www -type f | wc -l
|
||||
|
||||
# 构建 Debug APK
|
||||
echo "构建 Debug APK..."
|
||||
./gradlew assembleDebug
|
||||
|
||||
echo ""
|
||||
echo "=== 构建完成 ==="
|
||||
echo "APK 位置: app/build/outputs/apk/debug/app-debug.apk"
|
||||
5
android-tv/gradle.properties
Normal file
5
android-tv/gradle.properties
Normal file
@ -0,0 +1,5 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
BIN
android-tv/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android-tv/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
android-tv/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
android-tv/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
17
android-tv/gradlew
vendored
Executable file
17
android-tv/gradlew
vendored
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Gradle Wrapper Startup Script
|
||||
|
||||
# Find the script's directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WRAPPER_JAR="$SCRIPT_DIR/gradle/wrapper/gradle-wrapper.jar"
|
||||
|
||||
# Find Java
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
JAVA_CMD="$JAVA_HOME/bin/java"
|
||||
else
|
||||
JAVA_CMD="java"
|
||||
fi
|
||||
|
||||
# Run Gradle wrapper
|
||||
exec "$JAVA_CMD" -cp "$WRAPPER_JAR" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
16
android-tv/settings.gradle
Normal file
16
android-tv/settings.gradle
Normal file
@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "IPTV TV"
|
||||
include ':app'
|
||||
71
android/README.md
Normal file
71
android/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Android WebView 壳
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
android/
|
||||
├── app/src/main/
|
||||
│ ├── java/com/iptv/app/
|
||||
│ │ └── MainActivity.java # WebView 主活动
|
||||
│ ├── res/
|
||||
│ │ ├── layout/activity_main.xml
|
||||
│ │ ├── values/
|
||||
│ │ │ ├── strings.xml
|
||||
│ │ │ ├── styles.xml
|
||||
│ │ │ └── colors.xml
|
||||
│ │ └── mipmap-*/ # 图标资源
|
||||
│ ├── assets/
|
||||
│ │ └── www/ # 打包的 Web 资源 (从 ui/dist-web 复制)
|
||||
│ └── AndroidManifest.xml
|
||||
├── build.gradle # 项目级构建配置
|
||||
├── settings.gradle
|
||||
└── gradle.properties
|
||||
```
|
||||
|
||||
## 构建步骤
|
||||
|
||||
### 1. 构建 Web UI
|
||||
|
||||
```bash
|
||||
cd ../ui
|
||||
npm install
|
||||
npm run build:web
|
||||
```
|
||||
|
||||
### 2. 复制资源到 Android
|
||||
|
||||
```bash
|
||||
# 将构建好的 web 资源复制到 Android assets
|
||||
cp -r ../ui/dist-web/* app/src/main/assets/www/
|
||||
```
|
||||
|
||||
### 3. 构建 APK
|
||||
|
||||
```bash
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
APK 输出位置: `app/build/outputs/apk/debug/app-debug.apk`
|
||||
|
||||
## 开发模式
|
||||
|
||||
如需连接开发服务器测试,修改 `MainActivity.java`:
|
||||
|
||||
```java
|
||||
private static final String LOAD_MODE = "remote";
|
||||
private static final String REMOTE_URL = "http://你的IP:5173";
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ WebView 加载 Web UI
|
||||
- ✅ 全屏无标题栏
|
||||
- ✅ 下拉刷新
|
||||
- ✅ 返回键支持页面后退
|
||||
- ✅ 视频全屏自动横屏
|
||||
- ✅ 暗色主题
|
||||
|
||||
## 权限
|
||||
|
||||
- `INTERNET` - 网络访问
|
||||
- `ACCESS_NETWORK_STATE` - 网络状态检测
|
||||
33
android/app/build.gradle
Normal file
33
android/app/build.gradle
Normal file
@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.iptv.app'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.iptv.app"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
}
|
||||
1
android/app/proguard-rules.pro
vendored
Normal file
1
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1 @@
|
||||
# ProGuard rules
|
||||
27
android/app/src/main/AndroidManifest.xml
Normal file
27
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.iptv.app">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:screenOrientation="landscape">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
74
android/app/src/main/assets/error.html
Normal file
74
android/app/src/main/assets/error.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>加载错误</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 12px;
|
||||
color: #ff6b6b;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.tips {
|
||||
margin-top: 30px;
|
||||
padding: 16px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 8px;
|
||||
max-width: 400px;
|
||||
}
|
||||
.tips h2 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
.tips ul {
|
||||
text-align: left;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.tips li {
|
||||
font-size: 13px;
|
||||
color: #aaa;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="icon">⚠️</div>
|
||||
<h1>页面加载失败</h1>
|
||||
<p>无法加载应用资源,可能是以下原因:</p>
|
||||
|
||||
<div class="tips">
|
||||
<h2>可能的解决方案:</h2>
|
||||
<ul>
|
||||
<li>检查网络连接是否正常</li>
|
||||
<li>清除应用数据后重试</li>
|
||||
<li>重新安装应用</li>
|
||||
<li>如果是开发模式,请检查远程服务器地址</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
49
android/app/src/main/java/com/iptv/app/AssetReader.java
Normal file
49
android/app/src/main/java/com/iptv/app/AssetReader.java
Normal file
@ -0,0 +1,49 @@
|
||||
package com.iptv.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class AssetReader {
|
||||
private Context context;
|
||||
|
||||
public AssetReader(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String readFile(String path) {
|
||||
try {
|
||||
// path 如: "www/api/result.txt"
|
||||
InputStream is = context.getAssets().open(path);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
return "ERROR: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String readChannelData() {
|
||||
return readFile("www/api/result.txt");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean fileExists(String path) {
|
||||
try {
|
||||
context.getAssets().open(path).close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
231
android/app/src/main/java/com/iptv/app/MainActivity.java
Normal file
231
android/app/src/main/java/com/iptv/app/MainActivity.java
Normal file
@ -0,0 +1,231 @@
|
||||
package com.iptv.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "IPTV";
|
||||
private WebView webView;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
|
||||
// 加载模式:"local" 使用本地打包的web资源,"remote" 使用远程服务器,"test" 使用测试页面
|
||||
private static final String LOAD_MODE = "local";
|
||||
|
||||
// 远程服务器地址(开发测试用)
|
||||
private static final String REMOTE_URL = "http://192.168.1.100:5173";
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.d(TAG, "onCreate started");
|
||||
|
||||
// 必须在 setContentView 之前调用
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
webView = findViewById(R.id.webview);
|
||||
swipeRefresh = findViewById(R.id.swipe_refresh);
|
||||
|
||||
if (webView == null) {
|
||||
Log.e(TAG, "WebView is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 全屏设置(在 setContentView 之后)
|
||||
setFullscreen();
|
||||
|
||||
// 添加 JavaScript 接口用于读取本地文件
|
||||
webView.addJavascriptInterface(new AssetReader(this), "AndroidAsset");
|
||||
|
||||
// WebView 设置
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setDatabaseEnabled(true);
|
||||
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setAllowContentAccess(true);
|
||||
settings.setAllowFileAccessFromFileURLs(true);
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
|
||||
// 支持缩放
|
||||
settings.setSupportZoom(true);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setDisplayZoomControls(false);
|
||||
|
||||
// 启用硬件加速
|
||||
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
|
||||
// WebViewClient 处理页面跳转
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
Log.d(TAG, "shouldOverrideUrlLoading: " + request.getUrl());
|
||||
return false; // 在 WebView 内打开链接
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) {
|
||||
Log.d(TAG, "onPageStarted: " + url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
Log.d(TAG, "onPageFinished: " + url);
|
||||
if (swipeRefresh != null) {
|
||||
swipeRefresh.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
Log.e(TAG, "onReceivedError: " + errorCode + " - " + description + " - " + failingUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// WebChromeClient 处理全屏视频和 JS 控制台
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
private View customView;
|
||||
private CustomViewCallback customViewCallback;
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
||||
Log.d(TAG, "WebView Console: " + consoleMessage.message() + " at " + consoleMessage.sourceId() + ":" + consoleMessage.lineNumber());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
Log.d(TAG, "Loading progress: " + newProgress + "%");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
customView = view;
|
||||
customViewCallback = callback;
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
if (customViewCallback != null) {
|
||||
customViewCallback.onCustomViewHidden();
|
||||
}
|
||||
customView = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 下拉刷新
|
||||
if (swipeRefresh != null) {
|
||||
swipeRefresh.setOnRefreshListener(() -> webView.reload());
|
||||
swipeRefresh.setColorSchemeResources(android.R.color.holo_blue_bright);
|
||||
}
|
||||
|
||||
// 延迟加载页面,确保 WebView 完全初始化
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
loadContent();
|
||||
}, 100);
|
||||
|
||||
Log.d(TAG, "onCreate finished");
|
||||
}
|
||||
|
||||
private void loadContent() {
|
||||
Log.d(TAG, "loadContent, mode: " + LOAD_MODE);
|
||||
String url;
|
||||
if ("test".equals(LOAD_MODE)) {
|
||||
// 加载测试页面(红色背景)
|
||||
url = "file:///android_asset/test.html";
|
||||
} else if ("local".equals(LOAD_MODE)) {
|
||||
// 加载本地打包的web资源
|
||||
url = "file:///android_asset/www/index.html";
|
||||
} else {
|
||||
// 加载远程服务器(开发测试)
|
||||
url = REMOTE_URL;
|
||||
}
|
||||
Log.d(TAG, "Loading URL: " + url);
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
private void setFullscreen() {
|
||||
// 设置状态栏和导航栏颜色
|
||||
getWindow().setStatusBarColor(Color.BLACK);
|
||||
getWindow().setNavigationBarColor(Color.BLACK);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getWindow().setDecorFitsSystemWindows(false);
|
||||
WindowInsetsController controller = getWindow().getInsetsController();
|
||||
if (controller != null) {
|
||||
controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
|
||||
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
} else {
|
||||
getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
);
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView != null && webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (webView != null) {
|
||||
webView.onResume();
|
||||
}
|
||||
setFullscreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (webView != null) {
|
||||
webView.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (webView != null) {
|
||||
webView.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
10
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M38,32 L38,76 L46,76 L46,58 L62,76 L74,76 L74,32 L62,32 L46,50 L46,32 Z"/>
|
||||
</vector>
|
||||
15
android/app/src/main/res/layout/activity_main.xml
Normal file
15
android/app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0a0a0a">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0a0a0a" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
android/app/src/main/res/values/colors.xml
Normal file
5
android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0a0a0a</color>
|
||||
</resources>
|
||||
3
android/app/src/main/res/values/strings.xml
Normal file
3
android/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">IPTV</string>
|
||||
</resources>
|
||||
9
android/app/src/main/res/values/styles.xml
Normal file
9
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
</resources>
|
||||
4
android/build.gradle
Normal file
4
android/build.gradle
Normal file
@ -0,0 +1,4 @@
|
||||
// Top-level build file
|
||||
plugins {
|
||||
id 'com.android.application' version '8.1.0' apply false
|
||||
}
|
||||
31
android/build.sh
Executable file
31
android/build.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Android WebView 壳打包脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== IPTV Android 构建 ==="
|
||||
|
||||
# 检查 UI 构建产物
|
||||
if [ ! -d "../ui/dist-web" ]; then
|
||||
echo "错误: 未找到 ../ui/dist-web 目录"
|
||||
echo "请先构建 Web UI: cd ../ui && npm run build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 复制 Web 资源到 Android assets
|
||||
echo "复制 Web 资源..."
|
||||
mkdir -p app/src/main/assets/www
|
||||
cp -r ../ui/dist-web/* app/src/main/assets/www/
|
||||
|
||||
# 统计文件
|
||||
echo "已复制文件数量:"
|
||||
find app/src/main/assets/www -type f | wc -l
|
||||
|
||||
# 构建 Debug APK
|
||||
echo "构建 Debug APK..."
|
||||
./gradlew assembleDebug
|
||||
|
||||
echo ""
|
||||
echo "=== 构建完成 ==="
|
||||
echo "APK 位置: app/build/outputs/apk/debug/app-debug.apk"
|
||||
4
android/gradle.properties
Normal file
4
android/gradle.properties
Normal file
@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
17
android/gradlew
vendored
Executable file
17
android/gradlew
vendored
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Gradle Wrapper Startup Script
|
||||
|
||||
# Find the script's directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WRAPPER_JAR="$SCRIPT_DIR/gradle/wrapper/gradle-wrapper.jar"
|
||||
|
||||
# Find Java
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
JAVA_CMD="$JAVA_HOME/bin/java"
|
||||
else
|
||||
JAVA_CMD="java"
|
||||
fi
|
||||
|
||||
# Run Gradle wrapper
|
||||
exec "$JAVA_CMD" -cp "$WRAPPER_JAR" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
16
android/settings.gradle
Normal file
16
android/settings.gradle
Normal file
@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "IPTV App"
|
||||
include ':app'
|
||||
@ -38,7 +38,6 @@ if (Test-Path "dist-web/api") {
|
||||
Remove-Item "dist-web/api" -Recurse -Force
|
||||
}
|
||||
Copy-Item -Path "public/api" -Destination "dist-web/api" -Recurse -Force
|
||||
Set-Location ..
|
||||
|
||||
# 步骤2: 安装依赖
|
||||
Write-Host "📦 步骤 2/4: 安装 Tauri 依赖..." -ForegroundColor Cyan
|
||||
|
||||
315
desktop/package-lock.json
generated
Normal file
315
desktop/package-lock.json
generated
Normal file
@ -0,0 +1,315 @@
|
||||
{
|
||||
"name": "iptv-desktop",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "iptv-desktop",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli/-/cli-1.6.3.tgz",
|
||||
"integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": ">=7.5.2"
|
||||
},
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "1.6.3",
|
||||
"@tauri-apps/cli-darwin-x64": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "1.6.3",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "1.6.3",
|
||||
"@tauri-apps/cli-linux-x64-musl": "1.6.3",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "1.6.3",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "1.6.3",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz",
|
||||
"integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz",
|
||||
"integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz",
|
||||
"integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz",
|
||||
"integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz",
|
||||
"integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz",
|
||||
"integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz",
|
||||
"integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/cli": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli/-/cli-1.6.3.tgz",
|
||||
"integrity": "sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@tauri-apps/cli-darwin-arm64": "1.6.3",
|
||||
"@tauri-apps/cli-darwin-x64": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "1.6.3",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "1.6.3",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "1.6.3",
|
||||
"@tauri-apps/cli-linux-x64-musl": "1.6.3",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "1.6.3",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "1.6.3",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "1.6.3",
|
||||
"semver": ">=7.5.2"
|
||||
}
|
||||
},
|
||||
"@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.3.tgz",
|
||||
"integrity": "sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-darwin-x64": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.3.tgz",
|
||||
"integrity": "sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.3.tgz",
|
||||
"integrity": "sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.3.tgz",
|
||||
"integrity": "sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.3.tgz",
|
||||
"integrity": "sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.3.tgz",
|
||||
"integrity": "sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.6.3.tgz",
|
||||
"integrity": "sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.3.tgz",
|
||||
"integrity": "sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,8 @@
|
||||
"build": {
|
||||
"beforeBuildCommand": "",
|
||||
"beforeDevCommand": "",
|
||||
"devPath": "../dist-web",
|
||||
"distDir": "../dist-web",
|
||||
"devPath": "../../ui/dist-web",
|
||||
"distDir": "../../ui/dist-web",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"tauri": {
|
||||
|
||||
982
ui/package-lock.json
generated
Normal file
982
ui/package-lock.json
generated
Normal file
@ -0,0 +1,982 @@
|
||||
{
|
||||
"name": "iptv-web-core",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "iptv-web-core",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^14.2.0",
|
||||
"hls.js": "^1.5.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.57.1",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.0.0 || ^6.0.0",
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/shared": "3.5.27",
|
||||
"entities": "^7.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-ssr": "3.5.27",
|
||||
"@vue/shared": "3.5.27",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.6",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/devtools-kit": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
|
||||
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-shared": "^7.7.9",
|
||||
"birpc": "^2.3.0",
|
||||
"hookable": "^5.5.3",
|
||||
"mitt": "^3.0.1",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"speakingurl": "^14.0.1",
|
||||
"superjson": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-shared": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
|
||||
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
||||
"dependencies": {
|
||||
"rfdc": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.27",
|
||||
"@vue/runtime-core": "3.5.27",
|
||||
"@vue/shared": "3.5.27",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.2.0.tgz",
|
||||
"integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "14.2.0",
|
||||
"@vueuse/shared": "14.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.2.0.tgz",
|
||||
"integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.2.0.tgz",
|
||||
"integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
|
||||
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-anything": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz",
|
||||
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
||||
"dependencies": {
|
||||
"is-what": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.6.15",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/hookable": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
||||
},
|
||||
"node_modules/is-what": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz",
|
||||
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
|
||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^7.7.7"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.5.0",
|
||||
"vue": "^3.5.11"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia/node_modules/@vue/devtools-api": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
|
||||
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-kit": "^7.7.9"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.57.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.57.1",
|
||||
"@rollup/rollup-android-arm64": "4.57.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.57.1",
|
||||
"@rollup/rollup-darwin-x64": "4.57.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.57.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.57.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.57.1",
|
||||
"@rollup/rollup-openbsd-x64": "4.57.1",
|
||||
"@rollup/rollup-openharmony-arm64": "4.57.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.57.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.57.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/speakingurl": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superjson": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz",
|
||||
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
||||
"dependencies": {
|
||||
"copy-anything": "^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-sfc": "3.5.27",
|
||||
"@vue/runtime-dom": "3.5.27",
|
||||
"@vue/server-renderer": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.27.1"
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5"
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.29.0",
|
||||
"requires": {
|
||||
"@babel/types": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5"
|
||||
},
|
||||
"@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.57.1",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"dev": true
|
||||
},
|
||||
"@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="
|
||||
},
|
||||
"@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/shared": "3.5.27",
|
||||
"entities": "^7.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-dom": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-ssr": "3.5.27",
|
||||
"@vue/shared": "3.5.27",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.6",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-ssr": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"@vue/devtools-api": {
|
||||
"version": "6.6.4"
|
||||
},
|
||||
"@vue/devtools-kit": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
|
||||
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
||||
"requires": {
|
||||
"@vue/devtools-shared": "^7.7.9",
|
||||
"birpc": "^2.3.0",
|
||||
"hookable": "^5.5.3",
|
||||
"mitt": "^3.0.1",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"speakingurl": "^14.0.1",
|
||||
"superjson": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"@vue/devtools-shared": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
|
||||
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
||||
"requires": {
|
||||
"rfdc": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-core": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-dom": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.5.27",
|
||||
"@vue/runtime-core": "3.5.27",
|
||||
"@vue/shared": "3.5.27",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"@vue/server-renderer": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/compiler-ssr": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.5.27"
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.2.0.tgz",
|
||||
"integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==",
|
||||
"requires": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "14.2.0",
|
||||
"@vueuse/shared": "14.2.0"
|
||||
}
|
||||
},
|
||||
"@vueuse/metadata": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.2.0.tgz",
|
||||
"integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ=="
|
||||
},
|
||||
"@vueuse/shared": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.2.0.tgz",
|
||||
"integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==",
|
||||
"requires": {}
|
||||
},
|
||||
"birpc": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
|
||||
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="
|
||||
},
|
||||
"copy-anything": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz",
|
||||
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
||||
"requires": {
|
||||
"is-what": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.2.3"
|
||||
},
|
||||
"entities": {
|
||||
"version": "7.0.1"
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.21.5",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "2.0.2"
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.3",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"hls.js": {
|
||||
"version": "1.6.15"
|
||||
},
|
||||
"hookable": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
||||
},
|
||||
"is-what": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz",
|
||||
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.21",
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.11"
|
||||
},
|
||||
"perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.1.1"
|
||||
},
|
||||
"pinia": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
|
||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^7.7.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": {
|
||||
"version": "7.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
|
||||
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
||||
"requires": {
|
||||
"@vue/devtools-kit": "^7.7.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.5.6",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
|
||||
},
|
||||
"rollup": {
|
||||
"version": "4.57.1",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.57.1",
|
||||
"@rollup/rollup-android-arm64": "4.57.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.57.1",
|
||||
"@rollup/rollup-darwin-x64": "4.57.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.57.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.57.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.57.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.57.1",
|
||||
"@rollup/rollup-openbsd-x64": "4.57.1",
|
||||
"@rollup/rollup-openharmony-arm64": "4.57.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.57.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.57.1",
|
||||
"@types/estree": "1.0.8",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.2.1"
|
||||
},
|
||||
"speakingurl": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="
|
||||
},
|
||||
"superjson": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz",
|
||||
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
||||
"requires": {
|
||||
"copy-anything": "^4"
|
||||
}
|
||||
},
|
||||
"vite": {
|
||||
"version": "5.4.21",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.21.3",
|
||||
"fsevents": "~2.3.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "3.5.27",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-sfc": "3.5.27",
|
||||
"@vue/runtime-dom": "3.5.27",
|
||||
"@vue/server-renderer": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "4.6.4",
|
||||
"requires": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
786
ui/pnpm-lock.yaml
generated
Normal file
786
ui/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,786 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
hls.js:
|
||||
specifier: ^1.5.0
|
||||
version: 1.6.15
|
||||
vue:
|
||||
specifier: ^3.4.0
|
||||
version: 3.5.27
|
||||
vue-router:
|
||||
specifier: ^4.2.0
|
||||
version: 4.6.4(vue@3.5.27)
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.0.0
|
||||
version: 5.2.4(vite@5.4.21)(vue@3.5.27)
|
||||
vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.4.21
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/helper-string-parser@7.27.1':
|
||||
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.29.0':
|
||||
resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/types@7.29.0':
|
||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.57.1':
|
||||
resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.57.1':
|
||||
resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.57.1':
|
||||
resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.57.1':
|
||||
resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.57.1':
|
||||
resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.57.1':
|
||||
resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.57.1':
|
||||
resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.57.1':
|
||||
resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.57.1':
|
||||
resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.57.1':
|
||||
resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.57.1':
|
||||
resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.57.1':
|
||||
resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.57.1':
|
||||
resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@vitejs/plugin-vue@5.2.4':
|
||||
resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@vue/compiler-core@3.5.27':
|
||||
resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==}
|
||||
|
||||
'@vue/compiler-dom@3.5.27':
|
||||
resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.27':
|
||||
resolution: {integrity: sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.27':
|
||||
resolution: {integrity: sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==}
|
||||
|
||||
'@vue/devtools-api@6.6.4':
|
||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||
|
||||
'@vue/reactivity@3.5.27':
|
||||
resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==}
|
||||
|
||||
'@vue/runtime-core@3.5.27':
|
||||
resolution: {integrity: sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==}
|
||||
|
||||
'@vue/runtime-dom@3.5.27':
|
||||
resolution: {integrity: sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==}
|
||||
|
||||
'@vue/server-renderer@3.5.27':
|
||||
resolution: {integrity: sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==}
|
||||
peerDependencies:
|
||||
vue: 3.5.27
|
||||
|
||||
'@vue/shared@3.5.27':
|
||||
resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
entities@7.0.1:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
esbuild@0.21.5:
|
||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
hls.js@1.6.15:
|
||||
resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
rollup@4.57.1:
|
||||
resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
vite@5.4.21:
|
||||
resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
sass-embedded: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
vue-router@4.6.4:
|
||||
resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
vue@3.5.27:
|
||||
resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/parser@7.29.0':
|
||||
dependencies:
|
||||
'@babel/types': 7.29.0
|
||||
|
||||
'@babel/types@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@vitejs/plugin-vue@5.2.4(vite@5.4.21)(vue@3.5.27)':
|
||||
dependencies:
|
||||
vite: 5.4.21
|
||||
vue: 3.5.27
|
||||
|
||||
'@vue/compiler-core@3.5.27':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.0
|
||||
'@vue/shared': 3.5.27
|
||||
entities: 7.0.1
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-dom@3.5.27':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/compiler-sfc@3.5.27':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.0
|
||||
'@vue/compiler-core': 3.5.27
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/compiler-ssr': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-ssr@3.5.27':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/devtools-api@6.6.4': {}
|
||||
|
||||
'@vue/reactivity@3.5.27':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/runtime-core@3.5.27':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/runtime-dom@3.5.27':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.27
|
||||
'@vue/runtime-core': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.27(vue@3.5.27)':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
vue: 3.5.27
|
||||
|
||||
'@vue/shared@3.5.27': {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
esbuild@0.21.5:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.21.5
|
||||
'@esbuild/android-arm': 0.21.5
|
||||
'@esbuild/android-arm64': 0.21.5
|
||||
'@esbuild/android-x64': 0.21.5
|
||||
'@esbuild/darwin-arm64': 0.21.5
|
||||
'@esbuild/darwin-x64': 0.21.5
|
||||
'@esbuild/freebsd-arm64': 0.21.5
|
||||
'@esbuild/freebsd-x64': 0.21.5
|
||||
'@esbuild/linux-arm': 0.21.5
|
||||
'@esbuild/linux-arm64': 0.21.5
|
||||
'@esbuild/linux-ia32': 0.21.5
|
||||
'@esbuild/linux-loong64': 0.21.5
|
||||
'@esbuild/linux-mips64el': 0.21.5
|
||||
'@esbuild/linux-ppc64': 0.21.5
|
||||
'@esbuild/linux-riscv64': 0.21.5
|
||||
'@esbuild/linux-s390x': 0.21.5
|
||||
'@esbuild/linux-x64': 0.21.5
|
||||
'@esbuild/netbsd-x64': 0.21.5
|
||||
'@esbuild/openbsd-x64': 0.21.5
|
||||
'@esbuild/sunos-x64': 0.21.5
|
||||
'@esbuild/win32-arm64': 0.21.5
|
||||
'@esbuild/win32-ia32': 0.21.5
|
||||
'@esbuild/win32-x64': 0.21.5
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hls.js@1.6.15: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
rollup@4.57.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.57.1
|
||||
'@rollup/rollup-android-arm64': 4.57.1
|
||||
'@rollup/rollup-darwin-arm64': 4.57.1
|
||||
'@rollup/rollup-darwin-x64': 4.57.1
|
||||
'@rollup/rollup-freebsd-arm64': 4.57.1
|
||||
'@rollup/rollup-freebsd-x64': 4.57.1
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.57.1
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.57.1
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-arm64-musl': 4.57.1
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-loong64-musl': 4.57.1
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.57.1
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.57.1
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-x64-gnu': 4.57.1
|
||||
'@rollup/rollup-linux-x64-musl': 4.57.1
|
||||
'@rollup/rollup-openbsd-x64': 4.57.1
|
||||
'@rollup/rollup-openharmony-arm64': 4.57.1
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.57.1
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.57.1
|
||||
'@rollup/rollup-win32-x64-gnu': 4.57.1
|
||||
'@rollup/rollup-win32-x64-msvc': 4.57.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
vite@5.4.21:
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.5.6
|
||||
rollup: 4.57.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
vue-router@4.6.4(vue@3.5.27):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.27
|
||||
|
||||
vue@3.5.27:
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/compiler-sfc': 3.5.27
|
||||
'@vue/runtime-dom': 3.5.27
|
||||
'@vue/server-renderer': 3.5.27(vue@3.5.27)
|
||||
'@vue/shared': 3.5.27
|
||||
616
ui/src/App.vue
616
ui/src/App.vue
@ -1,171 +1,210 @@
|
||||
<template>
|
||||
<div class="iptv-app">
|
||||
<!-- 三栏主布局 -->
|
||||
<div class="main-container">
|
||||
<!-- 左侧:频道分组 -->
|
||||
<aside class="sidebar-left">
|
||||
<div class="sidebar-header">
|
||||
<span class="icon">📡</span>
|
||||
<span>频道分组</span>
|
||||
</div>
|
||||
<div class="group-list">
|
||||
<div
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
class="group-item"
|
||||
:class="{ active: selectedGroup === group.id }"
|
||||
@click="selectGroup(group.id)"
|
||||
>
|
||||
<span class="group-icon">{{ group.icon }}</span>
|
||||
<span class="group-name">{{ group.name }}</span>
|
||||
<span class="group-count">{{ group.count }}</span>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 中间:频道列表 -->
|
||||
<section class="channel-panel">
|
||||
<div class="panel-header">
|
||||
<span class="icon">⏱</span>
|
||||
<span>{{ currentGroupName }}</span>
|
||||
</div>
|
||||
<div class="channel-list">
|
||||
<div
|
||||
v-for="channel in filteredChannels"
|
||||
:key="channel.id"
|
||||
class="channel-item"
|
||||
:class="{ active: currentChannel?.id === channel.id }"
|
||||
@click="playChannel(channel)"
|
||||
>
|
||||
<div class="channel-icon">CC</div>
|
||||
<div class="channel-info">
|
||||
<div class="channel-name">{{ channel.name }}</div>
|
||||
<div class="channel-meta">{{ channel.sources?.length || 1 }}线路</div>
|
||||
</div>
|
||||
<span
|
||||
class="star-icon"
|
||||
:class="{ favorited: isFavorite(channel.id) }"
|
||||
@click.stop="toggleFavorite(channel.id)"
|
||||
>★</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:节目表/播放器 -->
|
||||
<section class="epg-panel">
|
||||
<div class="panel-header">
|
||||
<span class="icon">📋</span>
|
||||
<span>节目表</span>
|
||||
</div>
|
||||
|
||||
<!-- 节目列表 -->
|
||||
<div v-if="currentChannel && epgList.length > 0" class="epg-list">
|
||||
<div
|
||||
v-for="(item, index) in epgList"
|
||||
:key="index"
|
||||
class="epg-item"
|
||||
:class="{ current: isCurrentProgram(item) }"
|
||||
>
|
||||
<div class="epg-time">{{ item.time }}</div>
|
||||
<div class="epg-title">
|
||||
{{ item.title }}
|
||||
<span v-if="isCurrentProgram(item)" class="live-badge">直播</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无节目信息 -->
|
||||
<div v-else-if="currentChannel" class="epg-empty">
|
||||
<p>暂无节目信息</p>
|
||||
</div>
|
||||
|
||||
<!-- 未选择频道 -->
|
||||
<div v-else class="player-placeholder">
|
||||
<div class="placeholder-icon">📹</div>
|
||||
<p>请选择频道</p>
|
||||
<p class="hint">按菜单键打开频道列表</p>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 调试信息面板 -->
|
||||
<div v-if="showDebug" class="debug-panel">
|
||||
<div class="debug-header">
|
||||
<span>调试信息</span>
|
||||
<button @click="showDebug = false">×</button>
|
||||
</div>
|
||||
<div class="debug-content">
|
||||
<p><strong>URL:</strong> {{ debugInfo.url }}</p>
|
||||
<p><strong>协议:</strong> {{ debugInfo.protocol }}</p>
|
||||
<p><strong>路径:</strong> {{ debugInfo.loadPath }}</p>
|
||||
<p><strong>状态:</strong> {{ debugInfo.status }}</p>
|
||||
<p><strong>错误:</strong> {{ debugInfo.error || '无' }}</p>
|
||||
<hr>
|
||||
<button @click="retryLoad">重新加载</button>
|
||||
<button @click="copyDebug">复制日志</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 调试按钮 -->
|
||||
<button v-if="!showDebug" class="debug-toggle" @click="showDebug = true">🐛</button>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<div v-if="loading" class="loading-screen">
|
||||
<div class="spinner"></div>
|
||||
<p>{{ loadingText }}</p>
|
||||
<p class="debug-hint">长按屏幕显示调试信息</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div v-else-if="error" class="error-screen">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h3>加载失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="loadChannels">重试</button>
|
||||
</div>
|
||||
|
||||
<!-- 主界面 -->
|
||||
<template v-else>
|
||||
<!-- 三栏主布局 -->
|
||||
<div class="main-container">
|
||||
<!-- 左侧:频道分组 -->
|
||||
<aside class="sidebar-left">
|
||||
<div class="sidebar-header">
|
||||
<span class="icon">📡</span>
|
||||
<span>频道分组</span>
|
||||
</div>
|
||||
<div class="group-list">
|
||||
<div
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
class="group-item"
|
||||
:class="{ active: selectedGroup === group.id }"
|
||||
@click="selectGroup(group.id)"
|
||||
>
|
||||
<span class="group-icon">{{ group.icon }}</span>
|
||||
<span class="group-name">{{ group.name }}</span>
|
||||
<span class="group-count">{{ group.count }}</span>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 底部播放控制栏 -->
|
||||
<footer v-if="currentChannel" class="player-bar">
|
||||
<div class="bar-left">
|
||||
<div class="channel-logo">CC</div>
|
||||
<div class="program-info">
|
||||
<div class="channel-line">
|
||||
<span class="channel-name">{{ currentChannel.name }}</span>
|
||||
<span class="line-tag">线路 {{ currentLine }}/{{ currentChannel.sources?.length || 1 }}</span>
|
||||
<!-- 中间:频道列表 -->
|
||||
<section class="channel-panel">
|
||||
<div class="panel-header">
|
||||
<span class="icon">⏱</span>
|
||||
<span>{{ currentGroupName }}</span>
|
||||
</div>
|
||||
<div class="program-title">
|
||||
<span class="live-dot">●</span>
|
||||
{{ currentProgramTitle }}
|
||||
<div class="channel-list">
|
||||
<div
|
||||
v-for="channel in filteredChannels"
|
||||
:key="channel.id"
|
||||
class="channel-item"
|
||||
:class="{ active: currentChannel?.id === channel.id }"
|
||||
@click="playChannel(channel)"
|
||||
>
|
||||
<div class="channel-icon">CC</div>
|
||||
<div class="channel-info">
|
||||
<div class="channel-name">{{ channel.name }}</div>
|
||||
<div class="channel-meta">{{ channel.sources?.length || 1 }}线路</div>
|
||||
</div>
|
||||
<span
|
||||
class="star-icon"
|
||||
:class="{ favorited: isFavorite(channel.id) }"
|
||||
@click.stop="toggleFavorite(channel.id)"
|
||||
>★</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: programProgress + '%' }"></div>
|
||||
<span class="time-label">{{ currentTime }} / {{ totalTime }}</span>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:节目表/播放器 -->
|
||||
<section class="epg-panel">
|
||||
<div class="panel-header">
|
||||
<span class="icon">📋</span>
|
||||
<span>节目表</span>
|
||||
</div>
|
||||
|
||||
<!-- 节目列表 -->
|
||||
<div v-if="currentChannel && epgList.length > 0" class="epg-list">
|
||||
<div
|
||||
v-for="(item, index) in epgList"
|
||||
:key="index"
|
||||
class="epg-item"
|
||||
:class="{ current: isCurrentProgram(item) }"
|
||||
>
|
||||
<div class="epg-time">{{ item.time }}</div>
|
||||
<div class="epg-title">
|
||||
{{ item.title }}
|
||||
<span v-if="isCurrentProgram(item)" class="live-badge">直播</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无节目信息 -->
|
||||
<div v-else-if="currentChannel" class="epg-empty">
|
||||
<p>暂无节目信息</p>
|
||||
</div>
|
||||
|
||||
<!-- 未选择频道 -->
|
||||
<div v-else class="player-placeholder">
|
||||
<div class="placeholder-icon">📹</div>
|
||||
<p>请选择频道</p>
|
||||
<p class="hint">按菜单键打开频道列表</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 底部播放控制栏 -->
|
||||
<footer v-if="currentChannel" class="player-bar">
|
||||
<div class="bar-left">
|
||||
<div class="channel-logo">CC</div>
|
||||
<div class="program-info">
|
||||
<div class="channel-line">
|
||||
<span class="channel-name">{{ currentChannel.name }}</span>
|
||||
<span class="line-tag">线路 {{ currentLine }}/{{ currentChannel.sources?.length || 1 }}</span>
|
||||
</div>
|
||||
<div class="program-title">
|
||||
<span class="live-dot">●</span>
|
||||
{{ currentProgramTitle }}
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: programProgress + '%' }"></div>
|
||||
<span class="time-label">{{ currentTime }} / {{ totalTime }}</span>
|
||||
</div>
|
||||
<div class="next-program">下一节目: {{ nextProgramTitle }}</div>
|
||||
</div>
|
||||
<div class="next-program">下一节目: {{ nextProgramTitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bar-right">
|
||||
<button
|
||||
class="bar-btn"
|
||||
:class="{ active: isFavorite(currentChannel.id) }"
|
||||
@click="toggleFavorite(currentChannel.id)"
|
||||
>
|
||||
<span class="icon">★</span>
|
||||
<span>{{ isFavorite(currentChannel.id) ? '已收藏' : '收藏' }}</span>
|
||||
</button>
|
||||
<button class="bar-btn" @click="showSourceSelector = true">
|
||||
<span class="icon">↻</span>
|
||||
<span>切换线路</span>
|
||||
</button>
|
||||
<button class="bar-btn" @click="showConfig = true">
|
||||
<span class="icon">⚙</span>
|
||||
<span>设置</span>
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="bar-right">
|
||||
<button
|
||||
class="bar-btn"
|
||||
:class="{ active: isFavorite(currentChannel.id) }"
|
||||
@click="toggleFavorite(currentChannel.id)"
|
||||
>
|
||||
<span class="icon">★</span>
|
||||
<span>{{ isFavorite(currentChannel.id) ? '已收藏' : '收藏' }}</span>
|
||||
</button>
|
||||
<button class="bar-btn" @click="showSourceSelector = true">
|
||||
<span class="icon">↻</span>
|
||||
<span>切换线路</span>
|
||||
</button>
|
||||
<button class="bar-btn" @click="showConfig = true">
|
||||
<span class="icon">⚙</span>
|
||||
<span>设置</span>
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- 播放器区域(全屏或嵌入) -->
|
||||
<div v-if="currentChannel" class="video-container">
|
||||
<VideoPlayer
|
||||
ref="playerRef"
|
||||
:url="currentUrl"
|
||||
:title="currentChannel.name"
|
||||
<!-- 播放器区域(全屏或嵌入) -->
|
||||
<div v-if="currentChannel" class="video-container">
|
||||
<VideoPlayer
|
||||
ref="playerRef"
|
||||
:url="currentUrl"
|
||||
:title="currentChannel.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 设置弹窗 -->
|
||||
<ConfigPanel
|
||||
:show="showConfig"
|
||||
@close="showConfig = false"
|
||||
@reload="loadChannels"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 设置弹窗 -->
|
||||
<ConfigPanel
|
||||
:show="showConfig"
|
||||
@close="showConfig = false"
|
||||
@reload="loadChannels"
|
||||
/>
|
||||
|
||||
<!-- 线路选择弹窗 -->
|
||||
<div v-if="showSourceSelector" class="modal-overlay" @click="showSourceSelector = false">
|
||||
<div class="modal-content source-modal" @click.stop>
|
||||
<h3>切换线路</h3>
|
||||
<div class="source-list">
|
||||
<div
|
||||
v-for="(src, index) in currentChannel?.sources"
|
||||
:key="index"
|
||||
class="source-item"
|
||||
:class="{ active: currentUrl === src.url, online: src.status === 'online' }"
|
||||
@click="switchSource(src)"
|
||||
>
|
||||
<span class="source-name">线路 {{ index + 1 }}</span>
|
||||
<span v-if="src.status === 'online'" class="source-status online">● {{ src.delay }}ms</span>
|
||||
<span v-else-if="src.status === 'offline'" class="source-status offline">● 离线</span>
|
||||
<span v-else class="source-status">● 未检测</span>
|
||||
<!-- 线路选择弹窗 -->
|
||||
<div v-if="showSourceSelector" class="modal-overlay" @click="showSourceSelector = false">
|
||||
<div class="modal-content source-modal" @click.stop>
|
||||
<h3>切换线路</h3>
|
||||
<div class="source-list">
|
||||
<div
|
||||
v-for="(src, index) in currentChannel?.sources"
|
||||
:key="index"
|
||||
class="source-item"
|
||||
:class="{ active: currentUrl === src.url, online: src.status === 'online' }"
|
||||
@click="switchSource(src)"
|
||||
>
|
||||
<span class="source-name">线路 {{ index + 1 }}</span>
|
||||
<span v-if="src.status === 'online'" class="source-status online">● {{ src.delay }}ms</span>
|
||||
<span v-else-if="src.status === 'offline'" class="source-status offline">● 离线</span>
|
||||
<span v-else class="source-status">● 未检测</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -185,6 +224,17 @@ const { toggleFavorite, isFavorite, addToHistory, settings } = store
|
||||
const { isMobile } = useMobile()
|
||||
|
||||
// ============ 状态 ============
|
||||
const loading = ref(true)
|
||||
const loadingText = ref('加载中...')
|
||||
const error = ref(null)
|
||||
const showDebug = ref(false)
|
||||
const debugInfo = ref({
|
||||
url: '',
|
||||
protocol: '',
|
||||
loadPath: '',
|
||||
status: '',
|
||||
error: ''
|
||||
})
|
||||
const channels = ref([])
|
||||
const currentChannel = ref(null)
|
||||
const currentUrl = ref('')
|
||||
@ -347,9 +397,72 @@ function generateMockEPG(channelId) {
|
||||
|
||||
// ============ 加载频道 ============
|
||||
async function loadChannels() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
loadingText.value = '加载中...'
|
||||
|
||||
// 更新调试信息
|
||||
debugInfo.value.url = window.location.href
|
||||
debugInfo.value.protocol = window.location.protocol
|
||||
debugInfo.value.status = '开始加载...'
|
||||
debugInfo.value.error = ''
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/result.txt')
|
||||
const text = await response.text()
|
||||
let text = null
|
||||
|
||||
// 检测是否在 Android WebView 本地环境
|
||||
const isAndroidLocal = window.location.protocol === 'file:' ||
|
||||
window.location.href.includes('android_asset')
|
||||
|
||||
// 检测是否有 Android 接口
|
||||
const hasAndroidInterface = typeof window.AndroidAsset !== 'undefined'
|
||||
|
||||
if (isAndroidLocal && hasAndroidInterface) {
|
||||
// Android 本地环境:使用 JS 接口
|
||||
debugInfo.value.loadPath = 'AndroidAsset.readChannelData()'
|
||||
try {
|
||||
const result = window.AndroidAsset.readChannelData()
|
||||
if (result && !result.startsWith('ERROR:')) {
|
||||
text = result
|
||||
debugInfo.value.status = 'Android 接口加载成功'
|
||||
console.log('[IPTV] 从 Android 接口加载数据成功')
|
||||
} else {
|
||||
throw new Error(result)
|
||||
}
|
||||
} catch (e) {
|
||||
debugInfo.value.error = `接口加载失败: ${e.message}`
|
||||
console.log('[IPTV] Android 接口加载失败:', e.message)
|
||||
}
|
||||
} else if (isAndroidLocal) {
|
||||
// Android 但没有接口:尝试 fetch(会失败)
|
||||
debugInfo.value.loadPath = 'api/result.txt (fetch)'
|
||||
debugInfo.value.error = 'Android 接口未找到'
|
||||
} else {
|
||||
// 正常网络环境
|
||||
debugInfo.value.loadPath = '/api/result.txt (网络)'
|
||||
try {
|
||||
const response = await fetch('/api/result.txt')
|
||||
debugInfo.value.status = `响应状态: ${response.status}`
|
||||
if (response.ok) {
|
||||
text = await response.text()
|
||||
debugInfo.value.status = '网络加载成功'
|
||||
console.log('[IPTV] 从网络加载数据成功')
|
||||
} else {
|
||||
throw new Error(`HTTP ${response.status}`)
|
||||
}
|
||||
} catch (e) {
|
||||
debugInfo.value.error = `网络加载失败: ${e.message}`
|
||||
console.log('[IPTV] 网络加载失败:', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都失败,使用模拟数据
|
||||
if (!text) {
|
||||
debugInfo.value.status = '使用模拟数据'
|
||||
console.log('[IPTV] 使用模拟数据')
|
||||
text = generateMockData()
|
||||
}
|
||||
|
||||
const rawChannels = parseTXT(text)
|
||||
channels.value = mergeChannels(rawChannels)
|
||||
console.log(`[IPTV] 加载了 ${channels.value.length} 个频道`)
|
||||
@ -363,11 +476,56 @@ async function loadChannels() {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载频道失败:', e)
|
||||
error.value = e.message || '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生成模拟数据(用于测试或离线模式)
|
||||
function generateMockData() {
|
||||
return `
|
||||
央视频道,#genre#
|
||||
CCTV1,http://example.com/cctv1
|
||||
CCTV2,http://example.com/cctv2
|
||||
CCTV3,http://example.com/cctv3
|
||||
|
||||
卫视频道,#genre#
|
||||
湖南卫视,http://example.com/hunan
|
||||
浙江卫视,http://example.com/zhejiang
|
||||
东方卫视,http://example.com/dongfang
|
||||
`.trim()
|
||||
}
|
||||
|
||||
function retryLoad() {
|
||||
loadChannels()
|
||||
}
|
||||
|
||||
function copyDebug() {
|
||||
const info = JSON.stringify(debugInfo.value, null, 2)
|
||||
navigator.clipboard?.writeText(info)
|
||||
alert('调试信息已复制到剪贴板')
|
||||
}
|
||||
|
||||
// 长按显示调试
|
||||
let pressTimer = null
|
||||
function startPress() {
|
||||
pressTimer = setTimeout(() => {
|
||||
showDebug.value = true
|
||||
}, 1000)
|
||||
}
|
||||
function endPress() {
|
||||
clearTimeout(pressTimer)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadChannels()
|
||||
|
||||
// 添加长按事件
|
||||
document.addEventListener('touchstart', startPress)
|
||||
document.addEventListener('touchend', endPress)
|
||||
document.addEventListener('mousedown', startPress)
|
||||
document.addEventListener('mouseup', endPress)
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -381,6 +539,152 @@ onMounted(() => {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* 调试面板 */
|
||||
.debug-panel {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #1a1a1a;
|
||||
border: 2px solid #ff6b6b;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
z-index: 9999;
|
||||
min-width: 300px;
|
||||
max-width: 90vw;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
.debug-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.debug-header span {
|
||||
font-weight: bold;
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.debug-header button {
|
||||
background: #333;
|
||||
border: none;
|
||||
color: #fff;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.debug-content {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.debug-content p {
|
||||
margin-bottom: 6px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.debug-content strong {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.debug-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid #333;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.debug-content button {
|
||||
margin-right: 8px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
background: #333;
|
||||
border: 1px solid #555;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.debug-content button:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.debug-toggle {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: #333;
|
||||
border: 1px solid #555;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 9998;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.debug-hint {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 加载中 */
|
||||
.loading-screen, .error-screen {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0a0a0a;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #333;
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-screen p, .error-screen p {
|
||||
color: #888;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-screen .error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-screen h3 {
|
||||
color: #fff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.error-screen button {
|
||||
padding: 10px 24px;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ========== 三栏布局 ========== */
|
||||
.main-container {
|
||||
flex: 1;
|
||||
|
||||
@ -5,6 +5,6 @@ export default defineConfig({
|
||||
plugins: [vue()],
|
||||
base: './',
|
||||
build: {
|
||||
outDir: '../desktop/dist-web'
|
||||
outDir: 'dist-web'
|
||||
}
|
||||
})
|
||||
|
||||
7
web/.dockerignore
Normal file
7
web/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
32
web/Dockerfile
Normal file
32
web/Dockerfile
Normal file
@ -0,0 +1,32 @@
|
||||
# 多阶段构建
|
||||
|
||||
# 阶段1:构建前端
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制前端代码
|
||||
COPY ../ui ./ui
|
||||
WORKDIR /app/ui
|
||||
RUN npm install && npm run build
|
||||
|
||||
# 阶段2:运行服务端
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装依赖
|
||||
COPY package*.json ./
|
||||
RUN npm install --production
|
||||
|
||||
# 复制服务端代码
|
||||
COPY src ./src
|
||||
|
||||
# 复制构建好的前端
|
||||
COPY --from=builder /app/ui/dist-web ./public
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 3000
|
||||
|
||||
# 启动
|
||||
CMD ["node", "src/index.js"]
|
||||
84
web/README.md
Normal file
84
web/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
# IPTV Web Docker 版
|
||||
|
||||
基于 Node.js + Express 的 IPTV Web 服务端,提供:
|
||||
|
||||
- 📺 频道列表 API
|
||||
- 🔀 M3U8/HLS 流代理(解决跨域)
|
||||
- 📦 Docker 一键部署
|
||||
- 🚀 可选 Nginx 反向代理
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 构建并启动
|
||||
|
||||
```bash
|
||||
# 基础版本
|
||||
docker-compose up -d
|
||||
|
||||
# 带 Nginx 的版本
|
||||
docker-compose --profile nginx up -d
|
||||
```
|
||||
|
||||
### 2. 访问
|
||||
|
||||
```
|
||||
http://localhost:3000 # 直接访问 Node 服务
|
||||
http://localhost # 通过 Nginx(如果启用)
|
||||
```
|
||||
|
||||
## API 接口
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `GET /health` | 健康检查 |
|
||||
| `GET /api/channels` | 获取频道列表 |
|
||||
| `GET /proxy/m3u8?url=xxx` | 代理 M3U8 播放列表 |
|
||||
| `GET /proxy/ts?url=xxx` | 代理 TS 视频片段 |
|
||||
| `GET /proxy/stream?url=xxx` | 通用流代理 |
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
web/
|
||||
├── src/
|
||||
│ └── index.js # Express 服务端
|
||||
├── nginx/
|
||||
│ └── nginx.conf # Nginx 配置
|
||||
├── public/ # 前端静态文件(构建时复制)
|
||||
├── Dockerfile # Docker 构建
|
||||
├── docker-compose.yml # 编排配置
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 更新频道数据
|
||||
|
||||
频道数据来自 `ui/dist-web/api/result.txt`,更新方式:
|
||||
|
||||
```bash
|
||||
# 方法1:重新构建
|
||||
docker-compose up -d --build
|
||||
|
||||
# 方法2:挂载数据卷(已配置)
|
||||
# 修改 ui/dist-web/api/result.txt 后自动生效
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `PORT` | 3000 | 服务端口号 |
|
||||
| `NODE_ENV` | production | 运行环境 |
|
||||
|
||||
## 单独运行(不依赖 Docker)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **跨域问题** - 所有流媒体通过 `/proxy/*` 接口代理,解决浏览器 CORS 限制
|
||||
2. **性能优化** - 静态资源使用 Nginx 缓存,API 请求直连 Node
|
||||
3. **HTTPS** - 生产环境建议启用 HTTPS(配置 `nginx/ssl/` 目录)
|
||||
36
web/docker-compose.yml
Normal file
36
web/docker-compose.yml
Normal file
@ -0,0 +1,36 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
iptv-web:
|
||||
build: .
|
||||
container_name: iptv-web
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- PORT=3000
|
||||
- NODE_ENV=production
|
||||
volumes:
|
||||
# 挂载频道数据(可选,用于更新)
|
||||
- ../ui/dist-web/api:/app/public/api:ro
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# 可选:使用 Nginx 作为反向代理
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: iptv-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
||||
depends_on:
|
||||
- iptv-web
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- nginx
|
||||
87
web/nginx/nginx.conf
Normal file
87
web/nginx/nginx.conf
Normal file
@ -0,0 +1,87 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# 日志
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
# Gzip
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml;
|
||||
|
||||
# 上游服务
|
||||
upstream iptv_backend {
|
||||
server iptv-web:3000;
|
||||
}
|
||||
|
||||
# HTTP 服务器
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# 静态文件缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
proxy_pass http://iptv_backend;
|
||||
expires 1d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# API 和代理
|
||||
location / {
|
||||
proxy_pass http://iptv_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# 流媒体代理(长连接)
|
||||
location /proxy/ {
|
||||
proxy_pass http://iptv_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
# 长超时(流媒体需要)
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS 服务器(需要证书)
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name localhost;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://iptv_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
web/package.json
Normal file
20
web/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "iptv-web-server",
|
||||
"version": "1.0.0",
|
||||
"description": "IPTV Web 服务端 - 代理 M3U 和流媒体",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"node-fetch": "^2.7.0",
|
||||
"m3u8-parser": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
1004
web/pnpm-lock.yaml
generated
Normal file
1004
web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
155
web/src/index.js
Normal file
155
web/src/index.js
Normal file
@ -0,0 +1,155 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
const fetch = require('node-fetch');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 静态文件服务
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
|
||||
// 健康检查
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', time: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// 获取频道列表
|
||||
app.get('/api/channels', async (req, res) => {
|
||||
try {
|
||||
const dataDir = path.join(__dirname, '../../ui/dist-web/api');
|
||||
const filePath = path.join(dataDir, 'result.txt');
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.send(content);
|
||||
} else {
|
||||
res.status(404).json({ error: 'Channel data not found' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading channels:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 代理 M3U8 流(解决跨域)
|
||||
app.get('/proxy/m3u8', async (req, res) => {
|
||||
const { url } = req.query;
|
||||
|
||||
if (!url) {
|
||||
return res.status(400).json({ error: 'Missing url parameter' });
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[Proxy] M3U8: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0',
|
||||
'Referer': new URL(url).origin
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
|
||||
// 转换相对路径为绝对路径
|
||||
const baseUrl = url.substring(0, url.lastIndexOf('/') + 1);
|
||||
const modifiedContent = content.replace(
|
||||
/^(?!#)([^\s].*\.ts.*)$/gm,
|
||||
(match) => {
|
||||
if (match.startsWith('http')) return match;
|
||||
return `/proxy/ts?url=${encodeURIComponent(baseUrl + match)}`;
|
||||
}
|
||||
);
|
||||
|
||||
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.send(modifiedContent);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Proxy] Error:', error.message);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 代理 TS 流
|
||||
app.get('/proxy/ts', async (req, res) => {
|
||||
const { url } = req.query;
|
||||
|
||||
if (!url) {
|
||||
return res.status(400).json({ error: 'Missing url parameter' });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
response.headers.forEach((value, name) => {
|
||||
res.setHeader(name, value);
|
||||
});
|
||||
|
||||
response.body.pipe(res);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Proxy] TS Error:', error.message);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 通用代理(用于其他类型的流)
|
||||
app.get('/proxy/stream', async (req, res) => {
|
||||
const { url } = req.query;
|
||||
|
||||
if (!url) {
|
||||
return res.status(400).json({ error: 'Missing url parameter' });
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[Proxy] Stream: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
response.headers.forEach((value, name) => {
|
||||
res.setHeader(name, value);
|
||||
});
|
||||
|
||||
response.body.pipe(res);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Proxy] Stream Error:', error.message);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 IPTV Web Server running on port ${PORT}`);
|
||||
console.log(`📺 Access: http://localhost:${PORT}`);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user