tv增加geckoview内核

This commit is contained in:
mtvpls
2026-06-07 22:10:44 +08:00
parent e8ea89726b
commit 0e7e2b6629
7 changed files with 173 additions and 20 deletions
+36 -14
View File
@@ -40,12 +40,26 @@ jobs:
fail-fast: false
matrix:
include:
- variant: standard
- variant: standard-webview
flavor: webview
flavor_task: Webview
min_sdk: '23'
label: android6plus
- variant: compat
label: webview-android6plus
- variant: compat-webview
flavor: webview
flavor_task: Webview
min_sdk: '21'
label: android5plus
label: webview-android5plus
- variant: standard-gecko
flavor: gecko
flavor_task: Gecko
min_sdk: '23'
label: geckoview-android6plus
- variant: compat-gecko
flavor: gecko
flavor_task: Gecko
min_sdk: '21'
label: geckoview-android5plus
steps:
- name: Checkout source code
@@ -74,13 +88,25 @@ jobs:
cp public/logo.png apps/android-tv/app/src/main/res/drawable/logo.png
- name: Decode Android signing keystore
id: signing
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
if [ -n "$ANDROID_KEYSTORE_BASE64" ] && [ -n "$ANDROID_KEYSTORE_PASSWORD" ] && [ -n "$ANDROID_KEY_ALIAS" ] && [ -n "$ANDROID_KEY_PASSWORD" ]; then
echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > /tmp/android-tv-release.keystore
echo "build_type=release" >> "$GITHUB_OUTPUT"
echo "build_type_task=Release" >> "$GITHUB_OUTPUT"
echo "apk_suffix=release" >> "$GITHUB_OUTPUT"
echo "artifact_suffix=release-signed" >> "$GITHUB_OUTPUT"
else
echo "ANDROID_KEYSTORE_BASE64 is not set, release APK will be unsigned."
echo "Android signing secrets are not fully set, building debug APK only."
echo "build_type=debug" >> "$GITHUB_OUTPUT"
echo "build_type_task=Debug" >> "$GITHUB_OUTPUT"
echo "apk_suffix=debug" >> "$GITHUB_OUTPUT"
echo "artifact_suffix=debug" >> "$GITHUB_OUTPUT"
fi
- name: Build APK
@@ -96,7 +122,7 @@ jobs:
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
gradle assembleDebug assembleRelease \
gradle assemble${{ matrix.flavor_task }}${{ steps.signing.outputs.build_type_task }} \
-PBASE_URL="$BASE_URL" \
-PAPP_NAME="$APP_NAME" \
-PVERSION_NAME="$VERSION_NAME" \
@@ -106,13 +132,9 @@ jobs:
- name: Collect APK artifacts
run: |
mkdir -p /tmp/android-tv-apks
cp apps/android-tv/app/build/outputs/apk/debug/app-debug.apk "/tmp/android-tv-apks/moontvplus-tv-${{ inputs.version_name }}-${{ matrix.label }}-debug.apk"
if [ -f apps/android-tv/app/build/outputs/apk/release/app-release.apk ]; then
cp apps/android-tv/app/build/outputs/apk/release/app-release.apk "/tmp/android-tv-apks/moontvplus-tv-${{ inputs.version_name }}-${{ matrix.label }}-release-signed.apk"
fi
if [ -f apps/android-tv/app/build/outputs/apk/release/app-release-unsigned.apk ]; then
cp apps/android-tv/app/build/outputs/apk/release/app-release-unsigned.apk "/tmp/android-tv-apks/moontvplus-tv-${{ inputs.version_name }}-${{ matrix.label }}-release-unsigned.apk"
fi
cp \
"apps/android-tv/app/build/outputs/apk/${{ matrix.flavor }}/${{ steps.signing.outputs.build_type }}/app-${{ matrix.flavor }}-${{ steps.signing.outputs.apk_suffix }}.apk" \
"/tmp/android-tv-apks/moontvplus-tv-${{ inputs.version_name }}-${{ matrix.label }}-${{ steps.signing.outputs.artifact_suffix }}.apk"
ls -lh /tmp/android-tv-apks
- name: Upload APK artifacts
+13 -2
View File
@@ -9,6 +9,7 @@
- `VERSION_NAME`: APK 版本名
- `VERSION_CODE`: APK 版本号,整数
- `MIN_SDK`: 最低 Android API,标准版为 `23`Android 6+),兼容版为 `21`Android 5+
- `GECKOVIEW_VERSION`: GeckoView 依赖版本,仅 GeckoView 版本使用,默认 `126.0.20240526221752`
App 启动时会自动打开:
@@ -18,10 +19,20 @@ BASE_URL 去掉末尾 / 后 + /tv
## 特性
- GitHub Actions 默认构建个版本:`android6plus` 标准版和 `android5plus` 兼容版
- GitHub Actions 默认构建个版本:`webview-android6plus``webview-android5plus``geckoview-android6plus``geckoview-android5plus`
- GitHub Actions 未配置签名 secrets 时只构建 debug APK;配置完整签名 secrets 后只构建 release APK
- `webview` 版本使用系统 Android WebView,体积小但依赖设备内置 WebView 版本
- `gecko` 版本自带 GeckoView 浏览器内核,用于旧系统 WebView 无法兼容 Next.js 页面时测试
- 锁定横屏
- 支持 Android TV Launcher
- 允许 HTTP 明文访问
- 允许 HTTPS 页面加载 HTTP 视频/图片等混合内容
- 使用 `public/logo.png` 作为图标来源
- User-Agent 追加 `MoonTVPlusAndroidTV`
- `webview` 版本 User-Agent 追加 `MoonTVPlusAndroidTV WebView`
## 本地构建
```bash
gradle assembleWebviewDebug -PBASE_URL="http://192.168.1.10:3000"
gradle assembleGeckoDebug -PBASE_URL="http://192.168.1.10:3000"
```
+26
View File
@@ -13,6 +13,7 @@ val appDisplayName = propOrEnv("APP_NAME", "APP_NAME", "MoonTVPlus TV")
val versionNameValue = propOrEnv("VERSION_NAME", "VERSION_NAME", "1.0.0")
val versionCodeValue = propOrEnv("VERSION_CODE", "VERSION_CODE", "1").toIntOrNull() ?: 1
val minSdkValue = propOrEnv("MIN_SDK", "MIN_SDK", "23").toIntOrNull() ?: 23
val geckoViewVersion = propOrEnv("GECKOVIEW_VERSION", "GECKOVIEW_VERSION", "126.0.20240526221752")
fun escapeJavaString(value: String): String = value
.replace("\\", "\\\\")
@@ -44,6 +45,27 @@ android {
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
flavorDimensions += "engine"
productFlavors {
create("webview") {
dimension = "engine"
buildConfigField("String", "ENGINE_NAME", "\"WebView\"")
resValue("string", "engine_name", "WebView")
}
create("gecko") {
dimension = "engine"
applicationIdSuffix = ".gecko"
buildConfigField("String", "ENGINE_NAME", "\"GeckoView\"")
resValue("string", "engine_name", "GeckoView")
}
}
signingConfigs {
create("release") {
val storeFilePath = System.getenv("ANDROID_KEYSTORE_PATH")
@@ -69,3 +91,7 @@ android {
}
}
}
dependencies {
add("geckoImplementation", "org.mozilla.geckoview:geckoview:$geckoViewVersion")
}
@@ -0,0 +1,95 @@
package com.moontvplus.tv;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoView;
public class MainActivity extends Activity {
private static GeckoRuntime runtime;
private GeckoSession session;
private GeckoView geckoView;
private boolean canGoBack = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
);
geckoView = new GeckoView(this);
geckoView.setFocusable(true);
geckoView.setFocusableInTouchMode(true);
geckoView.requestFocus();
setContentView(geckoView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
if (runtime == null) {
runtime = GeckoRuntime.create(this);
}
session = new GeckoSession();
session.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBackValue) {
canGoBack = canGoBackValue;
}
});
session.open(runtime);
geckoView.setSession(session);
session.loadUri(buildTvUrl(BuildConfig.BASE_URL));
}
private static String buildTvUrl(String baseUrl) {
String url = baseUrl == null ? "" : baseUrl.trim();
if (url.isEmpty()) {
url = "http://192.168.1.10:3000";
}
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
while (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
if (url.endsWith("/tv")) {
return url;
}
return url + "/tv";
}
@Override
public void onBackPressed() {
if (session != null && canGoBack) {
session.goBack();
return;
}
super.onBackPressed();
}
@Override
protected void onDestroy() {
if (session != null) {
session.close();
session = null;
}
super.onDestroy();
}
}
@@ -70,7 +70,7 @@ public class MainActivity extends Activity {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
settings.setUserAgentString(settings.getUserAgentString() + " MoonTVPlusAndroidTV");
settings.setUserAgentString(settings.getUserAgentString() + " MoonTVPlusAndroidTV WebView");
webView.setWebViewClient(new WebViewClient() {
@Override
@@ -90,8 +90,6 @@ public class MainActivity extends Activity {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// Do not bypass TLS certificate errors. HTTP is allowed by network security config;
// invalid HTTPS certificates should still be blocked for safety.
handler.cancel();
}
});
+1 -1
View File
@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=false
android.useAndroidX=true
android.nonTransitiveRClass=true
+1
View File
@@ -11,6 +11,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://maven.mozilla.org/maven2/")
}
}