v1.1
This commit is contained in:
@@ -0,0 +1,232 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# 根据命令行参数决定打包中文版还是英文版
|
||||||
|
# 用法: python patch_android.py --lang zh 或 --lang en
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--lang", choices=["zh", "en"], default="zh", help="打包语言版本")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
LANG = args.lang
|
||||||
|
PKG_NAME = "juneix.embyx"
|
||||||
|
PKG_PATH = PKG_NAME.replace(".", "/") # juneix/embyx
|
||||||
|
APP_NAME = "EmbyX"
|
||||||
|
ICON_SRC = f"{LANG}/icon.png" # zh/icon.png 或 en/icon.png
|
||||||
|
|
||||||
|
# ── 0. 从 HTML 徽章提取版本号 ─────────────────────────────────────────────────
|
||||||
|
# HTML 中的版本徽章格式为 ">v1.1<",两个版本的徽章内容保持一致,统一读 zh/index.html
|
||||||
|
# 修改规格:如需从其他文件读取版本,修改下方 VERSION_SRC 路径即可
|
||||||
|
VERSION_SRC = "zh/index.html"
|
||||||
|
version_name = "1.0" # 默认回退值
|
||||||
|
version_code = 100 # 对应 v1.0
|
||||||
|
|
||||||
|
if os.path.exists(VERSION_SRC):
|
||||||
|
with open(VERSION_SRC, "r", encoding="utf-8") as f:
|
||||||
|
html = f.read()
|
||||||
|
# 匹配徽章文本,例如 ">v1.1<" 或 ">v2.0<"(非贪婪,只取第一个)
|
||||||
|
m = re.search(r">v(\d+)\.(\d+)(?:\.(\d+))?<", html)
|
||||||
|
if m:
|
||||||
|
major = int(m.group(1))
|
||||||
|
minor = int(m.group(2))
|
||||||
|
patch = int(m.group(3)) if m.group(3) else 0
|
||||||
|
version_name = f"{major}.{minor}" if patch == 0 else f"{major}.{minor}.{patch}"
|
||||||
|
# versionCode 规则:major×10000 + minor×100 + patch
|
||||||
|
# 示例:v1.0→10000, v1.1→10100, v1.9→10900, v2.0→20000, v1.1.2→10102
|
||||||
|
# 这样三段版本号也能正确递增,且不会与两段版本号冲突
|
||||||
|
version_code = major * 10000 + minor * 100 + patch
|
||||||
|
print(f" Detected version: v{version_name} → versionCode={version_code}")
|
||||||
|
else:
|
||||||
|
print(f" WARNING: Version badge not found in {VERSION_SRC}, using default {version_name}")
|
||||||
|
else:
|
||||||
|
print(f" WARNING: {VERSION_SRC} not found, using default version {version_name}")
|
||||||
|
|
||||||
|
print(f"Patching Android Project for lang={LANG}, pkg={PKG_NAME}, version={version_name}...")
|
||||||
|
|
||||||
|
# ── 1. 图标文件 ──────────────────────────────────────────────────────────────
|
||||||
|
# 将对应语言版本的 icon.png 复制到 Android drawable 资源目录
|
||||||
|
os.makedirs("android/app/src/main/res/drawable", exist_ok=True)
|
||||||
|
if os.path.exists(ICON_SRC):
|
||||||
|
shutil.copy(ICON_SRC, "android/app/src/main/res/drawable/icon.png")
|
||||||
|
print(f" Copied {ICON_SRC} → drawable/icon.png")
|
||||||
|
else:
|
||||||
|
print(f" WARNING: {ICON_SRC} not found, skipping icon copy")
|
||||||
|
|
||||||
|
# ── 2. AndroidManifest.xml 补丁 ──────────────────────────────────────────────
|
||||||
|
manifest_path = "android/app/src/main/AndroidManifest.xml"
|
||||||
|
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||||
|
manifest = f.read()
|
||||||
|
|
||||||
|
# 替换默认图标引用为我们的 drawable/icon
|
||||||
|
manifest = manifest.replace("@mipmap/ic_launcher_round", "@drawable/icon")
|
||||||
|
manifest = manifest.replace("@mipmap/ic_launcher", "@drawable/icon")
|
||||||
|
manifest = manifest.replace("@drawable/icon_round", "@drawable/icon")
|
||||||
|
|
||||||
|
# 添加 WAKE_LOCK 权限(屏保/常亮需要)
|
||||||
|
if "android.permission.WAKE_LOCK" not in manifest:
|
||||||
|
manifest = manifest.replace(
|
||||||
|
"</manifest>",
|
||||||
|
' <uses-permission android:name="android.permission.WAKE_LOCK" />\n</manifest>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册 EmbyXDreamService(Android 系统屏保服务)
|
||||||
|
# 修改规格:如需换 label 或 icon,修改下方 android:label / android:permission 即可
|
||||||
|
service_block = """
|
||||||
|
<service
|
||||||
|
android:name=".EmbyXDreamService"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:permission="android.permission.BIND_DREAM_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.dreams.DreamService" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.dream"
|
||||||
|
android:resource="@xml/dream_info" />
|
||||||
|
</service>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if "EmbyXDreamService" not in manifest:
|
||||||
|
manifest = manifest.replace("</application>", f"{service_block}\n </application>")
|
||||||
|
|
||||||
|
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(manifest)
|
||||||
|
print(" Patched AndroidManifest.xml")
|
||||||
|
|
||||||
|
# ── 3. MainActivity.java 补丁 ────────────────────────────────────────────────
|
||||||
|
# 添加全屏沉浸式模式 + FLAG_KEEP_SCREEN_ON(视频播放常亮)
|
||||||
|
# 修改规格:如需改变沉浸式行为,修改下方 SYSTEM_UI_FLAG_* 标志位组合
|
||||||
|
main_activity_path = f"android/app/src/main/java/{PKG_PATH}/MainActivity.java"
|
||||||
|
with open(main_activity_path, "r", encoding="utf-8") as f:
|
||||||
|
main_activity = f.read()
|
||||||
|
|
||||||
|
immersive_code = """
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
|
super.onWindowFocusChanged(hasFocus);
|
||||||
|
if (hasFocus) {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
| android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
| android.view.View.SYSTEM_UI_FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 添加 FLAG_KEEP_SCREEN_ON(视频播放常亮,防止熄屏打断视频)
|
||||||
|
if "FLAG_KEEP_SCREEN_ON" not in main_activity:
|
||||||
|
main_activity = main_activity.replace(
|
||||||
|
"super.onCreate(savedInstanceState);",
|
||||||
|
"super.onCreate(savedInstanceState);\n getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加全屏沉浸式覆盖
|
||||||
|
if "onWindowFocusChanged" not in main_activity:
|
||||||
|
main_activity = main_activity.replace("}", immersive_code + "\n}", 1)
|
||||||
|
|
||||||
|
with open(main_activity_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(main_activity)
|
||||||
|
print(" Patched MainActivity.java (fullscreen + keep screen on)")
|
||||||
|
|
||||||
|
# ── 4. EmbyXDreamService.java ───────────────────────────────────────────────
|
||||||
|
# 屏保实现:使用 Android DreamService,内嵌 WebView 加载 EmbyX 本地页面
|
||||||
|
# Capacitor 打包后的 webDir 内容位于 file:///android_asset/public/index.html
|
||||||
|
# EmbyX 会读取 localStorage 中已保存的 Emby 服务器配置,自动连接并播放
|
||||||
|
# 修改规格:如需改变屏保交互性,修改 setInteractive(true/false)
|
||||||
|
dream_service_code = f"""package {PKG_NAME};
|
||||||
|
|
||||||
|
import android.service.dreams.DreamService;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
public class EmbyXDreamService extends DreamService {{
|
||||||
|
private WebView webView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToWindow() {{
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
|
// 允许用户与屏保交互(点击/滑动视频)
|
||||||
|
setInteractive(true);
|
||||||
|
setFullscreen(true);
|
||||||
|
|
||||||
|
webView = new WebView(this);
|
||||||
|
WebSettings settings = webView.getSettings();
|
||||||
|
settings.setJavaScriptEnabled(true);
|
||||||
|
settings.setDomStorageEnabled(true); // localStorage 需要,用于读取 Emby token
|
||||||
|
settings.setDatabaseEnabled(true);
|
||||||
|
settings.setMediaPlaybackRequiresUserGesture(false); // 屏保自动播放视频无需手势
|
||||||
|
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // 允许 HTTP 视频流
|
||||||
|
|
||||||
|
webView.setWebViewClient(new WebViewClient());
|
||||||
|
// 加载 Capacitor 打包的本地 index.html(EmbyX 会自动读取已保存的 Emby 配置)
|
||||||
|
webView.loadUrl("file:///android_asset/public/index.html");
|
||||||
|
|
||||||
|
setContentView(webView);
|
||||||
|
}}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDreamingStarted() {{
|
||||||
|
super.onDreamingStarted();
|
||||||
|
// 屏保激活时保持屏幕常亮(播放视频需要)
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
}}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDreamingStopped() {{
|
||||||
|
super.onDreamingStopped();
|
||||||
|
if (webView != null) {{
|
||||||
|
webView.loadUrl("about:blank");
|
||||||
|
webView.destroy();
|
||||||
|
webView = null;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
dream_service_path = f"android/app/src/main/java/{PKG_PATH}/EmbyXDreamService.java"
|
||||||
|
with open(dream_service_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(dream_service_code)
|
||||||
|
print(f" Created EmbyXDreamService.java at {dream_service_path}")
|
||||||
|
|
||||||
|
# ── 5. dream_info.xml ────────────────────────────────────────────────────────
|
||||||
|
# Android 需要此 XML 文件来注册屏保,previewImage 显示在系统屏保选择列表中
|
||||||
|
os.makedirs("android/app/src/main/res/xml", exist_ok=True)
|
||||||
|
xml_code = f"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<dream android:settingsActivity="{PKG_NAME}.MainActivity"
|
||||||
|
android:previewImage="@drawable/icon"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||||
|
"""
|
||||||
|
with open("android/app/src/main/res/xml/dream_info.xml", "w", encoding="utf-8") as f:
|
||||||
|
f.write(xml_code)
|
||||||
|
print(" Created dream_info.xml")
|
||||||
|
|
||||||
|
# ── 6. 注入 APK 版本号到 build.gradle ───────────────────────────────────────
|
||||||
|
# Android 覆盖安装依赖 versionCode(整数)必须递增,versionName 是人类可读标签
|
||||||
|
# Capacitor 生成的默认值是 versionCode=1, versionName="1.0"
|
||||||
|
# 这里用从 HTML 读取的版本替换,保证每次发布 APK 能覆盖旧版本
|
||||||
|
# 修改规格:versionCode 算法在脚本顶部第 0 步,修改 major/minor/patch 的乘数即可
|
||||||
|
gradle_path = "android/app/build.gradle"
|
||||||
|
if os.path.exists(gradle_path):
|
||||||
|
with open(gradle_path, "r", encoding="utf-8") as f:
|
||||||
|
gradle = f.read()
|
||||||
|
|
||||||
|
# 替换 versionCode(形如 "versionCode 1" 或 "versionCode 10000")
|
||||||
|
gradle = re.sub(r"versionCode\s+\d+", f"versionCode {version_code}", gradle)
|
||||||
|
# 替换 versionName(形如 versionName "1.0")
|
||||||
|
gradle = re.sub(r'versionName\s+"[^"]+"', f'versionName "{version_name}"', gradle)
|
||||||
|
|
||||||
|
with open(gradle_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(gradle)
|
||||||
|
print(f" Patched build.gradle → versionCode={version_code}, versionName=\"{version_name}\"")
|
||||||
|
else:
|
||||||
|
print(f" WARNING: {gradle_path} not found, skipping version injection")
|
||||||
|
|
||||||
|
print(f"\nPatch complete! ({LANG} version, pkg={PKG_NAME}, v{version_name})")
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
name: Android APK 构建
|
||||||
|
|
||||||
|
# 触发条件:
|
||||||
|
# 1. 每次推送代码到 main 分支:自动构建并更新名为 "latest" 的 Release (Rolling Release)
|
||||||
|
# 2. 推送 v* 格式的 tag(如 v1.0):自动构建并发布正式 Release
|
||||||
|
# 3. 手动触发(workflow_dispatch):手动指定 tag 发布
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
tags: [ 'v*' ]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
description: '上传到已有标签 (如 v1.0)'
|
||||||
|
required: true
|
||||||
|
default: 'v1.0'
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── 中文版 APK 构建 ──────────────────────────────────────────────────────────
|
||||||
|
build-android-zh:
|
||||||
|
name: Build EmbyX-zh APK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
# 动态生成中文版 capacitor.config.json(webDir 指向 zh/ 目录)
|
||||||
|
- name: Create capacitor.config.json (zh)
|
||||||
|
run: |
|
||||||
|
cat > capacitor.config.json << 'EOF'
|
||||||
|
{
|
||||||
|
"appId": "juneix.embyx",
|
||||||
|
"appName": "EmbyX",
|
||||||
|
"webDir": "zh",
|
||||||
|
"server": {
|
||||||
|
"androidScheme": "http",
|
||||||
|
"cleartext": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Install Capacitor dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Add & Sync Android Platform
|
||||||
|
run: |
|
||||||
|
npx cap add android
|
||||||
|
npx cap sync android
|
||||||
|
|
||||||
|
# 运行补丁脚本:替换图标、注册屏保、添加全屏模式
|
||||||
|
- name: Patch Android Project (zh)
|
||||||
|
run: python .github/patch_android.py --lang zh
|
||||||
|
|
||||||
|
- name: Build Debug APK
|
||||||
|
run: |
|
||||||
|
cd android
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
- name: Rename to EmbyX-zh.apk
|
||||||
|
run: mv android/app/build/outputs/apk/debug/app-debug.apk EmbyX-zh.apk
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: embyx-zh
|
||||||
|
path: EmbyX-zh.apk
|
||||||
|
|
||||||
|
# ── 英文版 APK 构建 ──────────────────────────────────────────────────────────
|
||||||
|
build-android-en:
|
||||||
|
name: Build EmbyX-en APK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
# 动态生成英文版 capacitor.config.json(webDir 指向 en/ 目录)
|
||||||
|
- name: Create capacitor.config.json (en)
|
||||||
|
run: |
|
||||||
|
cat > capacitor.config.json << 'EOF'
|
||||||
|
{
|
||||||
|
"appId": "juneix.embyx",
|
||||||
|
"appName": "EmbyX",
|
||||||
|
"webDir": "en",
|
||||||
|
"server": {
|
||||||
|
"androidScheme": "http",
|
||||||
|
"cleartext": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Install Capacitor dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Add & Sync Android Platform
|
||||||
|
run: |
|
||||||
|
npx cap add android
|
||||||
|
npx cap sync android
|
||||||
|
|
||||||
|
# 运行补丁脚本:替换图标、注册屏保、添加全屏模式
|
||||||
|
- name: Patch Android Project (en)
|
||||||
|
run: python .github/patch_android.py --lang en
|
||||||
|
|
||||||
|
- name: Build Debug APK
|
||||||
|
run: |
|
||||||
|
cd android
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
- name: Rename to EmbyX-en.apk
|
||||||
|
run: mv android/app/build/outputs/apk/debug/app-debug.apk EmbyX-en.apk
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: embyx-en
|
||||||
|
path: EmbyX-en.apk
|
||||||
|
|
||||||
|
# ── 发布 Release ─────────────────────────────────────────────────────────────
|
||||||
|
# 当推送到 main 分支、打 v* 标签或手动触发时执行
|
||||||
|
release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
needs: [build-android-zh, build-android-en]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main'
|
||||||
|
steps:
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- name: Verify APK files exist
|
||||||
|
run: |
|
||||||
|
ls -R
|
||||||
|
test -f "embyx-zh/EmbyX-zh.apk"
|
||||||
|
test -f "embyx-en/EmbyX-en.apk"
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
# 如果是 main 分支,固定使用 latest 标签以便滚动更新
|
||||||
|
tag_name: ${{ github.ref_name == 'main' && 'latest' || (github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name) }}
|
||||||
|
name: ${{ github.ref_name == 'main' && 'Latest Build (Rolling)' || (github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name) }}
|
||||||
|
prerelease: ${{ github.ref_name == 'main' }}
|
||||||
|
draft: false
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
files: |
|
||||||
|
embyx-zh/EmbyX-zh.apk
|
||||||
|
embyx-en/EmbyX-en.apk
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1 +1,4 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
/android/
|
||||||
|
__pycache__/
|
||||||
+31
-7
@@ -1762,28 +1762,52 @@
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
executeDelete() {
|
async executeDelete() {
|
||||||
const item = this.state.videos[this.state.currentIndex];
|
const item = this.state.videos[this.state.currentIndex];
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
fetch(`${this.config.server}/emby/Items/${item.Id}?api_key=${this.config.token}`, {
|
try {
|
||||||
method: 'DELETE'
|
// Note: Emby DELETE API requires admin account; regular users get 403
|
||||||
}).then(() => {
|
// Must check res.ok, otherwise 403/404 are silently treated as success
|
||||||
|
const res = await fetch(`${this.config.server}/emby/Items/${item.Id}?api_key=${this.config.token}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 403) {
|
||||||
|
this.showToast('❌ No permission. Use an admin account');
|
||||||
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.status !== 404) {
|
||||||
|
this.showToast(`❌ Delete failed (${res.status})`);
|
||||||
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 404 means already deleted — still remove from list
|
||||||
|
}
|
||||||
|
|
||||||
this.showToast('✅ Deleted');
|
this.showToast('✅ Deleted');
|
||||||
this.state.videos.splice(this.state.currentIndex, 1);
|
this.state.videos.splice(this.state.currentIndex, 1);
|
||||||
this.toggleModal('deleteConfirmModal', false);
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
|
||||||
if (this.state.viewMode === 'grid') {
|
if (this.state.viewMode === 'grid') {
|
||||||
this.renderGridView();
|
this.renderGridView();
|
||||||
} else {
|
} else {
|
||||||
if (this.state.currentIndex >= this.state.videos.length) {
|
if (this.state.currentIndex >= this.state.videos.length) {
|
||||||
this.state.currentIndex = Math.max(0, this.state.videos.length - 1);
|
this.state.currentIndex = Math.max(0, this.state.videos.length - 1);
|
||||||
}
|
}
|
||||||
|
// Fix: renderSlides only rebuilds DOM; must call loadVideo+playVideo to resume playback
|
||||||
this.renderSlides();
|
this.renderSlides();
|
||||||
|
if (this.state.videos.length > 0) {
|
||||||
|
this.loadVideo(this.state.currentIndex);
|
||||||
|
this.playVideo(this.state.currentIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
} catch (e) {
|
||||||
this.showToast('❌ Delete failed');
|
this.showToast('❌ Delete failed: network error');
|
||||||
this.toggleModal('deleteConfirmModal', false);
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showInterfaceTemp() {
|
showInterfaceTemp() {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "embyx",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Emby 短视频播放器",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build:android": "npx cap sync android"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/android": "^5.0.0",
|
||||||
|
"@capacitor/core": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@capacitor/cli": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
-7
@@ -1899,16 +1899,36 @@
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
executeDelete() {
|
async executeDelete() {
|
||||||
const item = this.state.videos[this.state.currentIndex];
|
const item = this.state.videos[this.state.currentIndex];
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
fetch(`${this.config.server}/emby/Items/${item.Id}?api_key=${this.config.token}`, {
|
try {
|
||||||
method: 'DELETE'
|
// 注意:Emby 删除 API 需要管理员账号,普通用户会返回 403
|
||||||
}).then(() => {
|
// 必须检查 res.ok,否则 403/404 也会被当成成功处理,导致前端假删除
|
||||||
|
const res = await fetch(`${this.config.server}/emby/Items/${item.Id}?api_key=${this.config.token}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
// 403:账号无管理权限;404:视频已不存在(也从列表移除)
|
||||||
|
if (res.status === 403) {
|
||||||
|
this.showToast('❌ 权限不足,请使用管理员账号');
|
||||||
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.status !== 404) {
|
||||||
|
this.showToast(`❌ 删除失败 (${res.status})`);
|
||||||
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 404 视为已删除,继续从列表移除
|
||||||
|
}
|
||||||
|
|
||||||
this.showToast('✅ 已删除');
|
this.showToast('✅ 已删除');
|
||||||
this.state.videos.splice(this.state.currentIndex, 1);
|
this.state.videos.splice(this.state.currentIndex, 1);
|
||||||
this.toggleModal('deleteConfirmModal', false);
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
|
|
||||||
if (this.state.viewMode === 'grid') {
|
if (this.state.viewMode === 'grid') {
|
||||||
this.renderGridView();
|
this.renderGridView();
|
||||||
} else {
|
} else {
|
||||||
@@ -1916,12 +1936,17 @@
|
|||||||
if (this.state.currentIndex >= this.state.videos.length) {
|
if (this.state.currentIndex >= this.state.videos.length) {
|
||||||
this.state.currentIndex = Math.max(0, this.state.videos.length - 1);
|
this.state.currentIndex = Math.max(0, this.state.videos.length - 1);
|
||||||
}
|
}
|
||||||
|
// 修复:renderSlides 只重建 DOM,必须再调用 loadVideo+playVideo 才能自动播放
|
||||||
this.renderSlides();
|
this.renderSlides();
|
||||||
|
if (this.state.videos.length > 0) {
|
||||||
|
this.loadVideo(this.state.currentIndex);
|
||||||
|
this.playVideo(this.state.currentIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
} catch (e) {
|
||||||
this.showToast('❌ 删除失败');
|
this.showToast('❌ 删除失败:网络错误');
|
||||||
this.toggleModal('deleteConfirmModal', false);
|
this.toggleModal('deleteConfirmModal', false);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showInterfaceTemp() {
|
showInterfaceTemp() {
|
||||||
|
|||||||
Reference in New Issue
Block a user