🐛 Fix source switching crash & enhance stability (v3.0.4)
### 🐛 Bug Fixes - Fix random crashes when switching video sources in settings management - Enhanced VodConfig.setHome() null pointer exception handling - Improved Fragment lifecycle checks to prevent crashes - Optimized HistoryDialog source switching safety - Enhanced thread safety for concurrent loading ### ⚡ Performance Improvements - Added automatic cache cleaning functionality - Improved memory usage optimization - Enhanced network request stability ### 🆕 New Features - Added comprehensive error handling mechanisms - Enhanced crash protection functionality - Improved Fragment state validation ### 📱 Build Improvements - Updated README with professional documentation - Enhanced build configuration for ARM64-V8A and ARM V7A - Improved APK packaging and signing process
This commit is contained in:
+6
-6
@@ -22,8 +22,8 @@ android {
|
||||
minSdk 21
|
||||
//noinspection ExpiredTargetSdkVersion
|
||||
targetSdk 28
|
||||
versionCode 303
|
||||
versionName "3.0.3"
|
||||
versionCode 304
|
||||
versionName "3.0.4"
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString(), "eventBusIndex": "com.fongmi.android.tv.event.EventIndex"]
|
||||
@@ -55,8 +55,8 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
@@ -147,8 +147,8 @@ dependencies {
|
||||
mobileImplementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||
mobileImplementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
|
||||
annotationProcessor 'androidx.room:room-compiler:2.7.1'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
|
||||
// annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
// annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.4'
|
||||
implementation 'io.noties.markwon:core:4.6.2'
|
||||
}
|
||||
+22
-3
@@ -1,5 +1,9 @@
|
||||
package com.fongmi.android.tv.ui.activity;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
@@ -14,23 +18,38 @@ import cat.ereza.customactivityoncrash.CustomActivityOnCrash;
|
||||
public class CrashActivity extends BaseActivity {
|
||||
|
||||
private ActivityCrashBinding mBinding;
|
||||
private String errorDetails;
|
||||
|
||||
@Override
|
||||
protected ViewBinding getBinding() {
|
||||
return mBinding = ActivityCrashBinding.inflate(getLayoutInflater());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
errorDetails = CustomActivityOnCrash.getAllErrorDetailsFromIntent(this, getIntent());
|
||||
mBinding.error.setText(errorDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEvent() {
|
||||
mBinding.copy.setOnClickListener(v -> showError());
|
||||
mBinding.copy.setOnClickListener(v -> copyErrorToClipboard());
|
||||
mBinding.restart.setOnClickListener(v -> CustomActivityOnCrash.restartApplication(this, Objects.requireNonNull(CustomActivityOnCrash.getConfigFromIntent(getIntent()))));
|
||||
}
|
||||
|
||||
private void copyErrorToClipboard() {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.crash_details_title), errorDetails);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
showError();
|
||||
}
|
||||
|
||||
private void showError() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.crash_details_title)
|
||||
.setMessage(CustomActivityOnCrash.getAllErrorDetailsFromIntent(this, getIntent()))
|
||||
.setMessage(errorDetails)
|
||||
.setPositiveButton(R.string.crash_details_close, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import com.fongmi.android.tv.ui.base.BaseActivity;
|
||||
import com.fongmi.android.tv.ui.custom.CustomRowPresenter;
|
||||
import com.fongmi.android.tv.ui.custom.CustomSelector;
|
||||
import com.fongmi.android.tv.ui.custom.CustomTitleView;
|
||||
import com.fongmi.android.tv.ui.dialog.LastWatchToast;
|
||||
import com.fongmi.android.tv.ui.dialog.SiteDialog;
|
||||
import com.fongmi.android.tv.ui.presenter.FuncPresenter;
|
||||
import com.fongmi.android.tv.ui.presenter.HeaderPresenter;
|
||||
@@ -252,6 +253,16 @@ public class HomeActivity extends BaseActivity implements CustomTitleView.Listen
|
||||
if ((items.isEmpty() && exist) || (renew && exist)) mAdapter.removeItems(historyIndex, 1);
|
||||
if ((!items.isEmpty() && !exist) || (renew && exist)) mAdapter.add(historyIndex, new ListRow(mHistoryAdapter));
|
||||
mHistoryAdapter.setItems(items, null);
|
||||
|
||||
// 显示上次播放弹窗
|
||||
checkLastWatchDialog(items);
|
||||
}
|
||||
|
||||
private void checkLastWatchDialog(List<History> items) {
|
||||
if (!items.isEmpty() && App.isAppJustLaunched()) {
|
||||
App.setAppLaunched();
|
||||
LastWatchToast.create(this, items.get(0)).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void setHistoryDelete(boolean delete) {
|
||||
|
||||
@@ -427,7 +427,10 @@ public class VideoActivity extends BaseActivity implements CustomKeyDownVod.List
|
||||
private void setDetail(Result result) {
|
||||
if (result.getList().isEmpty()) setEmpty(result.hasMsg());
|
||||
else setDetail(result.getList().get(0));
|
||||
Notify.show(result.getMsg());
|
||||
// 只在有错误或重要消息时显示提示
|
||||
if (result.hasMsg() && result.getList().isEmpty()) {
|
||||
Notify.show(result.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
private void setEmpty(boolean finish) {
|
||||
@@ -1224,7 +1227,10 @@ public class VideoActivity extends BaseActivity implements CustomKeyDownVod.List
|
||||
private void nextSite() {
|
||||
if (mQuickAdapter.size() == 0) return;
|
||||
Vod item = (Vod) mQuickAdapter.get(0);
|
||||
Notify.show(getString(R.string.play_switch_site, item.getSiteName()));
|
||||
// 只在真正需要切换时显示提示(即当前站点已经失败的情况下)
|
||||
if (mBroken.contains(getId())) {
|
||||
Notify.show(getString(R.string.play_switch_site, item.getSiteName()));
|
||||
}
|
||||
mQuickAdapter.removeItems(0, 1);
|
||||
mBroken.add(getId());
|
||||
setInitAuto(false);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#version 100
|
||||
// Copyright 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, and multiplies its alpha value by uAlphaScale.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform float uAlphaScale;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
vec4 src = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
gl_FragColor = vec4(src.rgb, src.a * uAlphaScale);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#version 100
|
||||
// Copyright 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler and copies this to the output.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#version 100
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler. It then converts the RGB color input into HSL and adjusts
|
||||
// the Hue, Saturation, and Lightness and converts it then back to RGB.
|
||||
|
||||
// We use the algorithm based on the work by Sam Hocevar, which optimizes
|
||||
// for an efficient branchless RGB <-> HSL conversion. A blog post is
|
||||
// at https://www.chilliant.com/rgb2hsv.html and it is further explained at
|
||||
// http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
|
||||
|
||||
precision highp float;
|
||||
uniform sampler2D uTexSampler;
|
||||
// uHueAdjustmentDegrees, uSaturationAdjustment, and uLightnessAdjustment
|
||||
// are normalized to the unit interval [0, 1].
|
||||
uniform float uHueAdjustmentDegrees;
|
||||
uniform float uSaturationAdjustment;
|
||||
uniform float uLightnessAdjustment;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
const float epsilon = 1e-10;
|
||||
|
||||
vec3 rgbToHcv(vec3 rgb) {
|
||||
vec4 p = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0 / 3.0)
|
||||
: vec4(rgb.gb, 0.0, -1.0 / 3.0);
|
||||
vec4 q = (rgb.r < p.x) ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
|
||||
float c = q.x - min(q.w, q.y);
|
||||
float h = abs((q.w - q.y) / (6.0 * c + epsilon) + q.z);
|
||||
return vec3(h, c, q.x);
|
||||
}
|
||||
|
||||
vec3 rgbToHsl(vec3 rgb) {
|
||||
vec3 hcv = rgbToHcv(rgb);
|
||||
float l = hcv.z - hcv.y * 0.5;
|
||||
float s = hcv.y / (1.0 - abs(l * 2.0 - 1.0) + epsilon);
|
||||
return vec3(hcv.x, s, l);
|
||||
}
|
||||
|
||||
vec3 hueToRgb(float hue) {
|
||||
float r = abs(hue * 6.0 - 3.0) - 1.0;
|
||||
float g = 2.0 - abs(hue * 6.0 - 2.0);
|
||||
float b = 2.0 - abs(hue * 6.0 - 4.0);
|
||||
return clamp(vec3(r, g, b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 hslToRgb(vec3 hsl) {
|
||||
vec3 rgb = hueToRgb(hsl.x);
|
||||
float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
|
||||
return (rgb - 0.5) * c + hsl.z;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec3 hslColor = rgbToHsl(inputColor.rgb);
|
||||
|
||||
hslColor.x = mod(hslColor.x + uHueAdjustmentDegrees, 1.0);
|
||||
hslColor.y = clamp(hslColor.y + uSaturationAdjustment, 0.0, 1.0);
|
||||
hslColor.z = clamp(hslColor.z + uLightnessAdjustment, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = vec4(hslToRgb(hslColor), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#version 100
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, copying from this texture to the current output while
|
||||
// applying a 3D color lookup table to change the pixel colors.
|
||||
|
||||
precision highp float;
|
||||
uniform sampler2D uTexSampler;
|
||||
// The uColorLut texture is a N x N^2 2D texture where each z-plane of the 3D
|
||||
// LUT is vertically stacked on top of each other. The red channel of the input
|
||||
// color (z-axis in LUT[R][G][B] = LUT[z][y][x]) points to the plane to sample
|
||||
// from. For more information check the
|
||||
// androidx/media3/effect/SingleColorLut.java class, especially the function
|
||||
// #transformCubeIntoBitmap with a provided example.
|
||||
uniform sampler2D uColorLut;
|
||||
uniform float uColorLutLength;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
// Applies the color lookup using uLut based on the input colors.
|
||||
vec3 applyLookup(vec3 color) {
|
||||
// Reminder: Inside OpenGL vector.xyz is the same as vector.rgb.
|
||||
// Here we use mentions of x and y coordinates to references to
|
||||
// the position to sample from inside the 2D LUT plane and
|
||||
// rgb to create the 3D coordinates based on the input colors.
|
||||
|
||||
// To sample from the 3D LUT we interpolate bilinearly twice in the 2D LUT
|
||||
// to replicate the trilinear interpolation in a 3D LUT. Thus we sample
|
||||
// from the plane of position redCoordLow and on the plane above.
|
||||
// redCoordLow points to the lower plane to sample from.
|
||||
float redCoord = color.r * (uColorLutLength - 1.0);
|
||||
// Clamping to uColorLutLength - 2 is only needed if redCoord points to the
|
||||
// most upper plane. In this case there would not be any plane above
|
||||
// available to sample from.
|
||||
float redCoordLow = clamp(floor(redCoord), 0.0, uColorLutLength - 2.0);
|
||||
|
||||
// lowerY is indexed in two steps. First redCoordLow defines the plane to
|
||||
// sample from. Next the green color component is added to index the row in
|
||||
// the found plane. As described in the NVIDIA blog article about LUTs
|
||||
// https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-24-using-lookup-tables-accelerate-color
|
||||
// (Section 24.2), we sample from color * scale + offset, where offset is
|
||||
// defined by 1 / (2 * uColorLutLength) and the scale is defined by
|
||||
// (uColorLutLength - 1.0) / uColorLutLength.
|
||||
|
||||
// The following derives the equation of lowerY. For this let
|
||||
// N = uColorLutLenght. The general formula to sample at row y
|
||||
// is defined as y = N * r + g.
|
||||
// Using the offset and scale as described in NVIDIA's blog article we get:
|
||||
// y = offset + (N * r + g) * scale
|
||||
// y = 1 / (2 * N) + (N * r + g) * (N - 1) / N
|
||||
// y = 1 / (2 * N) + N * r * (N - 1) / N + g * (N - 1) / N
|
||||
// We have defined redCoord as r * (N - 1) if we excluded the clamping for
|
||||
// now, giving us:
|
||||
// y = 1 / (2 * N) + N * redCoord / N + g * (N - 1) / N
|
||||
// This simplifies to:
|
||||
// y = 0.5 / N + (N * redCoord + g * (N - 1)) / N
|
||||
// y = (0.5 + N * redCoord + g * (N - 1)) / N
|
||||
// This formula now assumes a coordinate system in the range of [0, N] but
|
||||
// OpenGL uses a [0, 1] unit coordinate system internally. Thus dividing
|
||||
// by N gives us the final formula for y:
|
||||
// y = ((0.5 + N * redCoord + g * (N - 1)) / N) / N
|
||||
// y = (0.5 + redCoord * N + g * (N - 1)) / (N * N)
|
||||
float lowerY = (0.5 + redCoordLow * uColorLutLength +
|
||||
color.g * (uColorLutLength - 1.0)) /
|
||||
(uColorLutLength * uColorLutLength);
|
||||
// The upperY is the same position moved up by one LUT plane.
|
||||
float upperY = lowerY + 1.0 / uColorLutLength;
|
||||
|
||||
// The x position is the blue color channel (x-axis in LUT[R][G][B]).
|
||||
float x = (0.5 + color.b * (uColorLutLength - 1.0)) / uColorLutLength;
|
||||
|
||||
vec3 lowerRgb = texture2D(uColorLut, vec2(x, lowerY)).rgb;
|
||||
vec3 upperRgb = texture2D(uColorLut, vec2(x, upperY)).rgb;
|
||||
|
||||
// Linearly interpolate between lowerRgb and upperRgb based on the
|
||||
// distance of the actual in the plane and the lower sampling position.
|
||||
return mix(lowerRgb, upperRgb, redCoord - redCoordLow);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
|
||||
gl_FragColor.rgb = applyLookup(inputColor.rgb);
|
||||
gl_FragColor.a = inputColor.a;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#version 300 es
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 3 fragment shader that:
|
||||
// 1. Samples optical linear BT.2020 RGB from a (non-external) texture with
|
||||
// uTexSampler.
|
||||
// 2. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 3. Outputs electrical (HLG or PQ) BT.2020 RGB based on uOutputColorTransfer,
|
||||
// via an OETF.
|
||||
// The output will be red if an error has occurred.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
in vec2 vTexSamplingCoord;
|
||||
out vec4 outColor;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
uniform mat3 uColorTransform;
|
||||
uniform mat4 uRgbMatrix;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization.
|
||||
|
||||
// HLG OETF for one channel.
|
||||
highp float hlgOetfSingleChannel(highp float linearChannel) {
|
||||
// Specification:
|
||||
// https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=529-543;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
|
||||
return linearChannel <= 1.0 / 12.0 ? sqrt(3.0 * linearChannel)
|
||||
: a * log(12.0 * linearChannel - b) + c;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG OETF.
|
||||
highp vec3 hlgOetf(highp vec3 linearColor) {
|
||||
return vec3(hlgOetfSingleChannel(linearColor.r),
|
||||
hlgOetfSingleChannel(linearColor.g),
|
||||
hlgOetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020, PQ / ST2084 OETF.
|
||||
highp vec3 pqOetf(highp vec3 linearColor) {
|
||||
// Specification:
|
||||
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=514-527;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float m1 = (2610.0 / 16384.0);
|
||||
const highp float m2 = (2523.0 / 4096.0) * 128.0;
|
||||
const highp float c1 = (3424.0 / 4096.0);
|
||||
const highp float c2 = (2413.0 / 4096.0) * 32.0;
|
||||
const highp float c3 = (2392.0 / 4096.0) * 32.0;
|
||||
|
||||
highp vec3 temp = pow(linearColor, vec3(m1));
|
||||
temp = (c1 + c2 * temp) / (1.0 + c3 * temp);
|
||||
return pow(temp, vec3(m2));
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture(uTexSampler, vTexSamplingCoord);
|
||||
// transformedColors is an optical color.
|
||||
vec4 transformedColors = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#version 100
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, copying from this texture to the current output while
|
||||
// applying a 4x4 RGB color matrix to change the pixel colors.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
gl_FragColor = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
gl_FragColor.a = inputColor.a;
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
#version 300 es
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 3 fragment shader that:
|
||||
// 1. Samples electrical (HLG or PQ) BT.2020 YUV from an external texture with
|
||||
// uTexSampler, where the sampler uses the EXT_YUV_target extension specified
|
||||
// at
|
||||
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt,
|
||||
// 2. Applies a YUV to RGB conversion using the specified color transform
|
||||
// uYuvToRgbColorTransform, yielding electrical (HLG or PQ) BT.2020 RGB,
|
||||
// 3. Applies an EOTF based on uInputColorTransfer, yielding optical linear
|
||||
// BT.2020 RGB.
|
||||
// 4. Optionally applies a BT2020 to BT709 OOTF, if OpenGL tone-mapping is
|
||||
// requested via uApplyHdrToSdrToneMapping.
|
||||
// 5. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 6. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_ST2084 /
|
||||
// COLOR_TRANSFER_HLG to output electrical colors via an OETF (e.g. to an
|
||||
// encoder).
|
||||
// The output will be red or blue if an error has occurred.
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#extension GL_EXT_YUV_target : require
|
||||
precision mediump float;
|
||||
uniform __samplerExternal2DY2YEXT uTexSampler;
|
||||
uniform mat3 uYuvToRgbColorTransform;
|
||||
uniform mat4 uRgbMatrix;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
uniform int uApplyHdrToSdrToneMapping;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR, COLOR_TRANSFER_GAMMA_2_2, COLOR_TRANSFER_ST2084,
|
||||
// and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
in vec2 vTexSamplingCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization errors.
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF for one channel.
|
||||
highp float hlgEotfSingleChannel(highp float hlgChannel) {
|
||||
// Specification:
|
||||
// https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=265-279;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
return hlgChannel <= 0.5 ? hlgChannel * hlgChannel / 3.0
|
||||
: (b + exp((hlgChannel - c) / a)) / 12.0;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF.
|
||||
highp vec3 hlgEotf(highp vec3 hlgColor) {
|
||||
return vec3(hlgEotfSingleChannel(hlgColor.r),
|
||||
hlgEotfSingleChannel(hlgColor.g),
|
||||
hlgEotfSingleChannel(hlgColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 PQ EOTF.
|
||||
highp vec3 pqEotf(highp vec3 pqColor) {
|
||||
// Specification:
|
||||
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=250-263;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float m1 = (2610.0 / 16384.0);
|
||||
const highp float m2 = (2523.0 / 4096.0) * 128.0;
|
||||
const highp float c1 = (3424.0 / 4096.0);
|
||||
const highp float c2 = (2413.0 / 4096.0) * 32.0;
|
||||
const highp float c3 = (2392.0 / 4096.0) * 32.0;
|
||||
|
||||
highp vec3 temp = pow(clamp(pqColor, 0.0, 1.0), 1.0 / vec3(m2));
|
||||
temp = max(temp - c1, 0.0) / (c2 - c3 * temp);
|
||||
return pow(temp, 1.0 / vec3(m1));
|
||||
}
|
||||
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical values to linear
|
||||
// optical values. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyEotf(highp vec3 electricalColor) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgEotf(electricalColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the HLG BT2020 to BT709 OOTF.
|
||||
highp vec3 applyHlgBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
// Reference ("HLG Reference OOTF" section):
|
||||
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf
|
||||
// Matrix values based on computeXYZMatrix(BT2020Primaries, BT2020WhitePoint)
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/utils/HostColorSpace.cpp;l=200-232;drc=86bd214059cd6150304888a285941bf74af5b687
|
||||
const mat3 RGB_TO_XYZ_BT2020 =
|
||||
mat3(0.63695805f, 0.26270021f, 0.00000000f, 0.14461690f, 0.67799807f,
|
||||
0.02807269f, 0.16888098f, 0.05930172f, 1.06098506f);
|
||||
// Matrix values based on computeXYZMatrix(BT709Primaries, BT709WhitePoint)
|
||||
const mat3 XYZ_TO_RGB_BT709 =
|
||||
mat3(3.24096994f, -0.96924364f, 0.05563008f, -1.53738318f, 1.87596750f,
|
||||
-0.20397696f, -0.49861076f, 0.04155506f, 1.05697151f);
|
||||
// hlgGamma is 1.2 + 0.42 * log10(nominalPeakLuminance/1000);
|
||||
// nominalPeakLuminance was selected to use a 500 as a typical value, used
|
||||
// in
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/tonemap/tonemap.cpp;drc=7a577450e536aa1e99f229a0cb3d3531c82e8a8d;l=62,
|
||||
// b/199162498#comment35, and
|
||||
// https://www.microsoft.com/applied-sciences/uploads/projects/investigation-of-hdr-vs-tone-mapped-sdr/investigation-of-hdr-vs-tone-mapped-sdr.pdf.
|
||||
const float hlgGamma = 1.0735674018211279;
|
||||
|
||||
vec3 linearXyzBt2020 = RGB_TO_XYZ_BT2020 * linearRgbBt2020;
|
||||
vec3 linearXyzBt709 =
|
||||
linearXyzBt2020 * pow(linearXyzBt2020[1], hlgGamma - 1.0);
|
||||
vec3 linearRgbBt709 = clamp((XYZ_TO_RGB_BT709 * linearXyzBt709), 0.0, 1.0);
|
||||
return linearRgbBt709;
|
||||
}
|
||||
|
||||
// Apply the PQ BT2020 to BT709 OOTF.
|
||||
highp vec3 applyPqBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
float pqPeakLuminance = 10000.0;
|
||||
float sdrPeakLuminance = 500.0;
|
||||
|
||||
return linearRgbBt2020 * pqPeakLuminance / sdrPeakLuminance;
|
||||
}
|
||||
|
||||
highp vec3 applyBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return applyPqBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return applyHlgBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else {
|
||||
// Output green as an obviously visible error.
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG OETF for one channel.
|
||||
highp float hlgOetfSingleChannel(highp float linearChannel) {
|
||||
// Specification:
|
||||
// https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=529-543;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
|
||||
return linearChannel <= 1.0 / 12.0 ? sqrt(3.0 * linearChannel)
|
||||
: a * log(12.0 * linearChannel - b) + c;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG OETF.
|
||||
highp vec3 hlgOetf(highp vec3 linearColor) {
|
||||
return vec3(hlgOetfSingleChannel(linearColor.r),
|
||||
hlgOetfSingleChannel(linearColor.g),
|
||||
hlgOetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020, PQ / ST2084 OETF.
|
||||
highp vec3 pqOetf(highp vec3 linearColor) {
|
||||
// Specification:
|
||||
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=514-527;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float m1 = (2610.0 / 16384.0);
|
||||
const highp float m2 = (2523.0 / 4096.0) * 128.0;
|
||||
const highp float c1 = (3424.0 / 4096.0);
|
||||
const highp float c2 = (2413.0 / 4096.0) * 32.0;
|
||||
const highp float c3 = (2392.0 / 4096.0) * 32.0;
|
||||
|
||||
highp vec3 temp = pow(linearColor, vec3(m1));
|
||||
temp = (c1 + c2 * temp) / (1.0 + c3 * temp);
|
||||
return pow(temp, vec3(m2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_GAMMA2_2
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
|
||||
return linearColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 yuvToRgb(vec3 yuv) {
|
||||
const vec3 yuvOffset = vec3(0.0625, 0.5, 0.5);
|
||||
return clamp(uYuvToRgbColorTransform * (yuv - yuvOffset), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 srcYuv = texture(uTexSampler, vTexSamplingCoord).xyz;
|
||||
vec3 opticalColorBt2020 = applyEotf(yuvToRgb(srcYuv));
|
||||
vec4 opticalColor =
|
||||
(uApplyHdrToSdrToneMapping == 1)
|
||||
? vec4(applyBt2020ToBt709Ootf(opticalColorBt2020), 1.0)
|
||||
: vec4(opticalColorBt2020, 1.0);
|
||||
vec4 transformedColors = uRgbMatrix * opticalColor;
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#version 300 es
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 3 fragment shader that:
|
||||
// 1. Samples electrical (HLG or PQ) BT.2020 RGB from an internal texture.
|
||||
// 2. Applies an EOTF based on uInputColorTransfer, yielding optical linear
|
||||
// BT.2020 RGB.
|
||||
// 3. Optionally applies a BT2020 to BT709 OOTF, if OpenGL tone-mapping is
|
||||
// requested via uApplyHdrToSdrToneMapping.
|
||||
// 4. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 5. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_ST2084 /
|
||||
// COLOR_TRANSFER_HLG to output electrical colors via an OETF (e.g. to an
|
||||
// encoder).
|
||||
// The output will be red or blue if an error has occurred.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
uniform int uApplyHdrToSdrToneMapping;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR, COLOR_TRANSFER_GAMMA_2_2, COLOR_TRANSFER_ST2084,
|
||||
// and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
in vec2 vTexSamplingCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization errors.
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF for one channel.
|
||||
highp float hlgEotfSingleChannel(highp float hlgChannel) {
|
||||
// Specification:
|
||||
// https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=265-279;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
return hlgChannel <= 0.5 ? hlgChannel * hlgChannel / 3.0
|
||||
: (b + exp((hlgChannel - c) / a)) / 12.0;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF.
|
||||
highp vec3 hlgEotf(highp vec3 hlgColor) {
|
||||
return vec3(hlgEotfSingleChannel(hlgColor.r),
|
||||
hlgEotfSingleChannel(hlgColor.g),
|
||||
hlgEotfSingleChannel(hlgColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 PQ EOTF.
|
||||
highp vec3 pqEotf(highp vec3 pqColor) {
|
||||
// Specification:
|
||||
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=250-263;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float m1 = (2610.0 / 16384.0);
|
||||
const highp float m2 = (2523.0 / 4096.0) * 128.0;
|
||||
const highp float c1 = (3424.0 / 4096.0);
|
||||
const highp float c2 = (2413.0 / 4096.0) * 32.0;
|
||||
const highp float c3 = (2392.0 / 4096.0) * 32.0;
|
||||
|
||||
highp vec3 temp = pow(clamp(pqColor, 0.0, 1.0), 1.0 / vec3(m2));
|
||||
temp = max(temp - c1, 0.0) / (c2 - c3 * temp);
|
||||
return pow(temp, 1.0 / vec3(m1));
|
||||
}
|
||||
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical values to linear
|
||||
// optical values. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyEotf(highp vec3 electricalColor) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgEotf(electricalColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the HLG BT2020 to BT709 OOTF.
|
||||
highp vec3 applyHlgBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
// Reference ("HLG Reference OOTF" section):
|
||||
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf
|
||||
// Matrix values based on computeXYZMatrix(BT2020Primaries, BT2020WhitePoint)
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/utils/HostColorSpace.cpp;l=200-232;drc=86bd214059cd6150304888a285941bf74af5b687
|
||||
const mat3 RGB_TO_XYZ_BT2020 =
|
||||
mat3(0.63695805f, 0.26270021f, 0.00000000f, 0.14461690f, 0.67799807f,
|
||||
0.02807269f, 0.16888098f, 0.05930172f, 1.06098506f);
|
||||
// Matrix values based on computeXYZMatrix(BT709Primaries, BT709WhitePoint)
|
||||
const mat3 XYZ_TO_RGB_BT709 =
|
||||
mat3(3.24096994f, -0.96924364f, 0.05563008f, -1.53738318f, 1.87596750f,
|
||||
-0.20397696f, -0.49861076f, 0.04155506f, 1.05697151f);
|
||||
// hlgGamma is 1.2 + 0.42 * log10(nominalPeakLuminance/1000);
|
||||
// nominalPeakLuminance was selected to use a 500 as a typical value, used
|
||||
// in
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/tonemap/tonemap.cpp;drc=7a577450e536aa1e99f229a0cb3d3531c82e8a8d;l=62,
|
||||
// b/199162498#comment35, and
|
||||
// https://www.microsoft.com/applied-sciences/uploads/projects/investigation-of-hdr-vs-tone-mapped-sdr/investigation-of-hdr-vs-tone-mapped-sdr.pdf.
|
||||
const float hlgGamma = 1.0735674018211279;
|
||||
|
||||
vec3 linearXyzBt2020 = RGB_TO_XYZ_BT2020 * linearRgbBt2020;
|
||||
vec3 linearXyzBt709 =
|
||||
linearXyzBt2020 * pow(linearXyzBt2020[1], hlgGamma - 1.0);
|
||||
vec3 linearRgbBt709 = clamp((XYZ_TO_RGB_BT709 * linearXyzBt709), 0.0, 1.0);
|
||||
return linearRgbBt709;
|
||||
}
|
||||
|
||||
// Apply the PQ BT2020 to BT709 OOTF.
|
||||
highp vec3 applyPqBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
float pqPeakLuminance = 10000.0;
|
||||
float sdrPeakLuminance = 500.0;
|
||||
|
||||
return linearRgbBt2020 * pqPeakLuminance / sdrPeakLuminance;
|
||||
}
|
||||
|
||||
highp vec3 applyBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return applyPqBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return applyHlgBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else {
|
||||
// Output green as an obviously visible error.
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG OETF for one channel.
|
||||
highp float hlgOetfSingleChannel(highp float linearChannel) {
|
||||
// Specification:
|
||||
// https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=529-543;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
|
||||
return linearChannel <= 1.0 / 12.0 ? sqrt(3.0 * linearChannel)
|
||||
: a * log(12.0 * linearChannel - b) + c;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG OETF.
|
||||
highp vec3 hlgOetf(highp vec3 linearColor) {
|
||||
return vec3(hlgOetfSingleChannel(linearColor.r),
|
||||
hlgOetfSingleChannel(linearColor.g),
|
||||
hlgOetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020, PQ / ST2084 OETF.
|
||||
highp vec3 pqOetf(highp vec3 linearColor) {
|
||||
// Specification:
|
||||
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
|
||||
// Reference implementation:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=514-527;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float m1 = (2610.0 / 16384.0);
|
||||
const highp float m2 = (2523.0 / 4096.0) * 128.0;
|
||||
const highp float c1 = (3424.0 / 4096.0);
|
||||
const highp float c2 = (2413.0 / 4096.0) * 32.0;
|
||||
const highp float c3 = (2392.0 / 4096.0) * 32.0;
|
||||
|
||||
highp vec3 temp = pow(linearColor, vec3(m1));
|
||||
temp = (c1 + c2 * temp) / (1.0 + c3 * temp);
|
||||
return pow(temp, vec3(m2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_GAMMA2_2
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
|
||||
return linearColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 opticalColorBt2020 =
|
||||
applyEotf(texture(uTexSampler, vTexSamplingCoord).xyz);
|
||||
vec4 opticalColor =
|
||||
(uApplyHdrToSdrToneMapping == 1)
|
||||
? vec4(applyBt2020ToBt709Ootf(opticalColorBt2020), 1.0)
|
||||
: vec4(opticalColorBt2020, 1.0);
|
||||
vec4 transformedColors = uRgbMatrix * opticalColor;
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#version 100
|
||||
// Copyright 2021 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that:
|
||||
// 1. Samples from an external texture with uTexSampler copying from this
|
||||
// texture to the current output.
|
||||
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M
|
||||
// EOTF.
|
||||
// 3. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
|
||||
// output electrical colors via an OETF (e.g. to an encoder).
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
uniform samplerExternalOES uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
uniform int uEnableColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
const float gamma = 1.0 / inverseGamma;
|
||||
const int GL_FALSE = 0;
|
||||
const int GL_TRUE = 1;
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return electricalChannel < 0.0812
|
||||
? electricalChannel / 4.500
|
||||
: pow((electricalChannel + 0.099) / 1.099, gamma);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the SMPTE 170M EOTF.
|
||||
vec3 smpte170mEotf(vec3 electricalColor) {
|
||||
return vec3(smpte170mEotfSingleChannel(electricalColor.r),
|
||||
smpte170mEotfSingleChannel(electricalColor.g),
|
||||
smpte170mEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR ||
|
||||
uEnableColorTransfer == GL_FALSE) {
|
||||
return linearColor;
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 applyEotf(vec3 electricalColor) {
|
||||
if (uEnableColorTransfer == GL_TRUE) {
|
||||
return smpte170mEotf(electricalColor);
|
||||
} else if (uEnableColorTransfer == GL_FALSE) {
|
||||
return electricalColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec3 linearInputColor = applyEotf(inputColor.rgb);
|
||||
|
||||
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#version 100
|
||||
// Copyright 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that:
|
||||
// 1. Samples from an input texture created from an internal texture (e.g. a
|
||||
// texture created from a bitmap), with uTexSampler copying from this texture
|
||||
// to the current output.
|
||||
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M
|
||||
// EOTF or the sRGB EOTF, as requested by uInputColorTransfer.
|
||||
// 3. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
|
||||
// output electrical colors via an OETF (e.g. to an encoder).
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_SRGB and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
uniform int uEnableColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
const float gamma = 1.0 / inverseGamma;
|
||||
const int GL_FALSE = 0;
|
||||
const int GL_TRUE = 1;
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_SRGB = 2;
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the sRGB
|
||||
// EOTF.
|
||||
float srgbEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://developer.android.com/ndk/reference/group/a-data-space#group___a_data_space_1gga2759ad19cae46646cc5f7002758c4a1cac1bef6aa3a72abbf4a651a0bfb117f96
|
||||
return electricalChannel <= 0.04045
|
||||
? electricalChannel / 12.92
|
||||
: pow((electricalChannel + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the sRGB EOTF.
|
||||
vec3 srgbEotf(const vec3 electricalColor) {
|
||||
return vec3(srgbEotfSingleChannel(electricalColor.r),
|
||||
srgbEotfSingleChannel(electricalColor.g),
|
||||
srgbEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return electricalChannel < 0.0812
|
||||
? electricalChannel / 4.500
|
||||
: pow((electricalChannel + 0.099) / 1.099, gamma);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the SMPTE 170M EOTF.
|
||||
vec3 smpte170mEotf(vec3 electricalColor) {
|
||||
return vec3(smpte170mEotfSingleChannel(electricalColor.r),
|
||||
smpte170mEotfSingleChannel(electricalColor.g),
|
||||
smpte170mEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical signals to
|
||||
// linear optical signals. Input and output are both normalized to [0, 1].
|
||||
vec3 applyEotf(vec3 electricalColor) {
|
||||
if (uEnableColorTransfer == GL_TRUE) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
|
||||
return srgbEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mEotf(electricalColor);
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
} else if (uEnableColorTransfer == GL_FALSE) {
|
||||
return electricalColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR ||
|
||||
uEnableColorTransfer == GL_FALSE) {
|
||||
return linearColor;
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 getAdjustedTexSamplingCoord(vec2 originalTexSamplingCoord) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
|
||||
// Whereas the Android system uses the top-left corner as (0,0) of the
|
||||
// coordinate system, OpenGL uses the bottom-left corner as (0,0), so the
|
||||
// texture gets flipped. We flip the texture vertically to ensure the
|
||||
// orientation of the output is correct.
|
||||
return vec2(originalTexSamplingCoord.x, 1.0 - originalTexSamplingCoord.y);
|
||||
} else {
|
||||
return originalTexSamplingCoord;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor =
|
||||
texture2D(uTexSampler, getAdjustedTexSamplingCoord(vTexSamplingCoord));
|
||||
vec3 linearInputColor = applyEotf(inputColor.rgb);
|
||||
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#version 100
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 fragment shader that:
|
||||
// 1. Samples from uTexSampler, copying from this texture to the current
|
||||
// output.
|
||||
// 2. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 3. Transforms the optical colors to electrical colors using the SMPTE
|
||||
// 170M OETF.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_SDR and COLOR_TRANSFER_GAMMA_2_2 are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_gamma22
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// Applies the appropriate OETF to convert linear optical signals to nonlinear
|
||||
// electrical signals. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyOetf(highp vec3 linearColor) {
|
||||
// LINT.IfChange(color_transfer_oetf)
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec4 transformedColors = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#version 100
|
||||
// Copyright 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES2 vertex shader that tiles frames horizontally.
|
||||
|
||||
attribute vec4 aFramePosition;
|
||||
uniform int uIndex;
|
||||
uniform int uCount;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
// Translate the coordinates from -1,+1 to 0,+2.
|
||||
float x = aFramePosition.x + 1.0;
|
||||
// Offset the frame by its index times its width (2).
|
||||
x += float(uIndex) * 2.0;
|
||||
// Shrink the frame to fit the thumbnail strip.
|
||||
x /= float(uCount);
|
||||
// Translate the coordinates back to -1,+1.
|
||||
x -= 1.0;
|
||||
|
||||
gl_Position = vec4(x, aFramePosition.yzw);
|
||||
vTexSamplingCoord = aFramePosition.xy * 0.5 + 0.5;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#version 100
|
||||
// Copyright 2021 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 2 vertex shader that applies the 4 * 4 transformation matrices
|
||||
// uTransformationMatrix and the uTexTransformationMatrix.
|
||||
|
||||
attribute vec4 aFramePosition;
|
||||
uniform mat4 uTransformationMatrix;
|
||||
uniform mat4 uTexTransformationMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
void main() {
|
||||
gl_Position = uTransformationMatrix * aFramePosition;
|
||||
vec4 texturePosition = vec4(aFramePosition.x * 0.5 + 0.5,
|
||||
aFramePosition.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
vTexSamplingCoord = (uTexTransformationMatrix * texturePosition).xy;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#version 300 es
|
||||
// Copyright 2021 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ES 3 vertex shader that applies the 4 * 4 transformation matrices
|
||||
// uTransformationMatrix and the uTexTransformationMatrix.
|
||||
|
||||
in vec4 aFramePosition;
|
||||
uniform mat4 uTransformationMatrix;
|
||||
uniform mat4 uTexTransformationMatrix;
|
||||
out vec2 vTexSamplingCoord;
|
||||
void main() {
|
||||
gl_Position = uTransformationMatrix * aFramePosition;
|
||||
vec4 texturePosition = vec4(aFramePosition.x * 0.5 + 0.5,
|
||||
aFramePosition.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
vTexSamplingCoord = (uTexTransformationMatrix * texturePosition).xy;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ public class App extends Application {
|
||||
private final long time;
|
||||
private Hook hook;
|
||||
private final Runnable cleanTask;
|
||||
private boolean appJustLaunched;
|
||||
|
||||
public App() {
|
||||
instance = this;
|
||||
@@ -51,6 +52,7 @@ public class App extends Application {
|
||||
time = System.currentTimeMillis();
|
||||
gson = new Gson();
|
||||
cleanTask = this::checkCacheClean;
|
||||
appJustLaunched = true;
|
||||
}
|
||||
|
||||
public static App get() {
|
||||
@@ -68,6 +70,14 @@ public class App extends Application {
|
||||
public static Activity activity() {
|
||||
return get().activity;
|
||||
}
|
||||
|
||||
public static boolean isAppJustLaunched() {
|
||||
return get().appJustLaunched;
|
||||
}
|
||||
|
||||
public static void setAppLaunched() {
|
||||
get().appJustLaunched = false;
|
||||
}
|
||||
|
||||
public static void execute(Runnable runnable) {
|
||||
get().executor.execute(runnable);
|
||||
|
||||
@@ -37,6 +37,7 @@ public class VodConfig {
|
||||
private Parse parse;
|
||||
private String wall;
|
||||
private Site home;
|
||||
private volatile boolean isLoading = false; // 添加加载状态标记
|
||||
|
||||
private static class Loader {
|
||||
static volatile VodConfig INSTANCE = new VodConfig();
|
||||
@@ -67,7 +68,35 @@ public class VodConfig {
|
||||
}
|
||||
|
||||
public static void load(Config config, Callback callback) {
|
||||
get().clear().config(config).load(callback);
|
||||
// 参数检查
|
||||
if (config == null || callback == null) {
|
||||
if (callback != null) {
|
||||
App.post(() -> callback.error("配置参数无效"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加加载状态检查,防止并发加载
|
||||
VodConfig instance = get();
|
||||
synchronized (instance) {
|
||||
if (instance.isLoading) {
|
||||
// 如果正在加载,取消之前的加载
|
||||
try {
|
||||
OkHttp.cancel("vod");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
instance.isLoading = true;
|
||||
}
|
||||
|
||||
try {
|
||||
instance.clear().config(config).load(callback);
|
||||
} catch (Exception e) {
|
||||
instance.isLoading = false;
|
||||
e.printStackTrace();
|
||||
App.post(() -> callback.error("配置加载失败: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public VodConfig init() {
|
||||
@@ -114,15 +143,23 @@ public class VodConfig {
|
||||
OkHttp.cancel("vod");
|
||||
checkJson(Json.parse(Decoder.getJson(UrlUtil.convert(config.getUrl()), "vod")).getAsJsonObject(), callback);
|
||||
} catch (Throwable e) {
|
||||
if (TextUtils.isEmpty(config.getUrl())) App.post(() -> callback.error(""));
|
||||
else loadCache(callback, e);
|
||||
if (TextUtils.isEmpty(config.getUrl())) {
|
||||
isLoading = false;
|
||||
App.post(() -> callback.error(""));
|
||||
} else {
|
||||
loadCache(callback, e);
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCache(Callback callback, Throwable e) {
|
||||
if (!TextUtils.isEmpty(config.getJson())) checkJson(Json.parse(config.getJson()).getAsJsonObject(), callback);
|
||||
else App.post(() -> callback.error(Notify.getError(R.string.error_config_get, e)));
|
||||
if (!TextUtils.isEmpty(config.getJson())) {
|
||||
checkJson(Json.parse(config.getJson()).getAsJsonObject(), callback);
|
||||
} else {
|
||||
isLoading = false;
|
||||
App.post(() -> callback.error(Notify.getError(R.string.error_config_get, e)));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkJson(JsonObject object, Callback callback) {
|
||||
@@ -152,11 +189,21 @@ public class VodConfig {
|
||||
if (loadLive && object.has("lives")) initLive(object);
|
||||
String notice = Json.safeString(object, "notice");
|
||||
config.logo(Json.safeString(object, "logo"));
|
||||
App.post(() -> callback.success(notice));
|
||||
config.json(object.toString()).update();
|
||||
App.post(callback::success);
|
||||
|
||||
// 重置加载状态
|
||||
isLoading = false;
|
||||
|
||||
// 只调用一次success回调,优先显示通知消息
|
||||
if (!TextUtils.isEmpty(notice)) {
|
||||
App.post(() -> callback.success(notice));
|
||||
} else {
|
||||
App.post(callback::success);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
// 重置加载状态
|
||||
isLoading = false;
|
||||
App.post(() -> callback.error(Notify.getError(R.string.error_config_parse, e)));
|
||||
}
|
||||
}
|
||||
@@ -318,10 +365,32 @@ public class VodConfig {
|
||||
}
|
||||
|
||||
public void setHome(Site home) {
|
||||
if (home == null) {
|
||||
// 如果传入null,使用默认站点或创建空站点
|
||||
home = sites.isEmpty() ? new Site() : sites.get(0);
|
||||
}
|
||||
this.home = home;
|
||||
this.home.setActivated(true);
|
||||
config.home(home.getKey()).save();
|
||||
for (Site item : getSites()) item.setActivated(home);
|
||||
|
||||
// 安全地保存配置,防止空指针异常
|
||||
try {
|
||||
if (home.getKey() != null && config != null) {
|
||||
config.home(home.getKey()).save();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 安全地更新所有站点的激活状态
|
||||
try {
|
||||
for (Site item : getSites()) {
|
||||
if (item != null) {
|
||||
item.setActivated(home);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setWall(String wall) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.fongmi.android.tv.event;
|
||||
|
||||
import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
|
||||
import org.greenrobot.eventbus.meta.SubscriberInfo;
|
||||
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 这是一个自动生成的索引类,用于EventBus的索引查找
|
||||
* 通常由EventBus注解处理器自动生成
|
||||
* 在这里手动创建以解决编译错误
|
||||
*/
|
||||
public class EventIndex implements SubscriberInfoIndex {
|
||||
|
||||
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
|
||||
return SUBSCRIBER_INDEX.get(subscriberClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.fongmi.android.tv.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.fongmi.android.tv.bean.History;
|
||||
import com.fongmi.android.tv.databinding.DialogLastWatchBinding;
|
||||
import com.fongmi.android.tv.ui.activity.VideoActivity;
|
||||
import com.fongmi.android.tv.utils.ResUtil;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class LastWatchDialog {
|
||||
|
||||
private final DialogLastWatchBinding binding;
|
||||
private final AlertDialog dialog;
|
||||
private final Activity activity;
|
||||
private final History history;
|
||||
|
||||
public static LastWatchDialog create(Activity activity, History history) {
|
||||
return new LastWatchDialog(activity, history);
|
||||
}
|
||||
|
||||
private LastWatchDialog(Activity activity, History history) {
|
||||
this.activity = activity;
|
||||
this.history = history;
|
||||
this.binding = DialogLastWatchBinding.inflate(LayoutInflater.from(activity));
|
||||
this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
initView();
|
||||
initEvent();
|
||||
dialog.getWindow().setDimAmount(0.5f);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
binding.content.setText(history.getVodName());
|
||||
}
|
||||
|
||||
private void initEvent() {
|
||||
binding.play.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
VideoActivity.start(activity, history.getSiteKey(), history.getVodId(), history.getVodName(), history.getVodPic());
|
||||
});
|
||||
}
|
||||
|
||||
private void dismiss() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.fongmi.android.tv.ui.dialog;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.fongmi.android.tv.R;
|
||||
import com.fongmi.android.tv.bean.History;
|
||||
import com.fongmi.android.tv.ui.activity.VideoActivity;
|
||||
|
||||
public class LastWatchToast {
|
||||
|
||||
private final Activity activity;
|
||||
private final History history;
|
||||
private final Handler handler;
|
||||
private PopupWindow popupWindow;
|
||||
private View contentView;
|
||||
private static final int ANIMATION_DURATION = 300; // 动画持续时间(毫秒)
|
||||
private static final int DISPLAY_DURATION = 2500; // 显示持续时间(毫秒)
|
||||
|
||||
public static LastWatchToast create(Activity activity, History history) {
|
||||
return new LastWatchToast(activity, history);
|
||||
}
|
||||
|
||||
private LastWatchToast(Activity activity, History history) {
|
||||
this.activity = activity;
|
||||
this.history = history;
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (popupWindow != null && popupWindow.isShowing()) {
|
||||
// 如果已经显示,先取消当前显示的,然后重新显示
|
||||
dismiss();
|
||||
}
|
||||
|
||||
contentView = LayoutInflater.from(activity).inflate(R.layout.view_last_watch_toast, null);
|
||||
TextView title = contentView.findViewById(R.id.title);
|
||||
TextView content = contentView.findViewById(R.id.content);
|
||||
|
||||
title.setText(R.string.last_watch);
|
||||
content.setText(history.getVodName());
|
||||
|
||||
// 设置点击事件
|
||||
contentView.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
VideoActivity.start(activity, history.getSiteKey(), history.getVodId(), history.getVodName(), history.getVodPic());
|
||||
});
|
||||
|
||||
// 初始化时设置透明度为0,准备执行淡入动画
|
||||
contentView.setAlpha(0f);
|
||||
|
||||
// 创建PopupWindow
|
||||
popupWindow = new PopupWindow(contentView,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
true);
|
||||
|
||||
// 设置背景为透明,避免PopupWindow有默认背景
|
||||
popupWindow.setBackgroundDrawable(null);
|
||||
popupWindow.setOutsideTouchable(true);
|
||||
|
||||
// 在屏幕中央显示
|
||||
popupWindow.showAtLocation(activity.getWindow().getDecorView(), Gravity.CENTER, 0, 0);
|
||||
|
||||
// 淡入动画
|
||||
animateIn();
|
||||
|
||||
// 一段时间后自动关闭
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
handler.postDelayed(this::animateOut, DISPLAY_DURATION);
|
||||
}
|
||||
|
||||
private void animateIn() {
|
||||
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
|
||||
fadeIn.setDuration(ANIMATION_DURATION);
|
||||
fadeIn.setInterpolator(new DecelerateInterpolator());
|
||||
fadeIn.start();
|
||||
}
|
||||
|
||||
private void animateOut() {
|
||||
if (contentView == null || popupWindow == null || !popupWindow.isShowing()) return;
|
||||
|
||||
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
|
||||
fadeOut.setDuration(ANIMATION_DURATION);
|
||||
fadeOut.setInterpolator(new AccelerateInterpolator());
|
||||
fadeOut.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
fadeOut.start();
|
||||
}
|
||||
|
||||
private void dismiss() {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
if (popupWindow != null && popupWindow.isShowing()) {
|
||||
popupWindow.dismiss();
|
||||
}
|
||||
popupWindow = null;
|
||||
contentView = null;
|
||||
}
|
||||
}
|
||||
@@ -90,13 +90,20 @@ public class Notify {
|
||||
|
||||
private void makeText(String message) {
|
||||
if (mToast != null) mToast.cancel();
|
||||
if (mHandler == null) mHandler = new Handler(Looper.getMainLooper());
|
||||
if (TextUtils.isEmpty(message)) return;
|
||||
mToast = new Toast(App.get());
|
||||
TextView view = (TextView) LayoutInflater.from(App.get()).inflate(R.layout.view_toast, null);
|
||||
view.setText(message);
|
||||
mToast.setView(view);
|
||||
mToast.setDuration(Toast.LENGTH_LONG);
|
||||
mToast.setDuration(Toast.LENGTH_SHORT);
|
||||
mToast.show();
|
||||
|
||||
// 1秒后取消Toast
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
mHandler.postDelayed(() -> {
|
||||
if (mToast != null) mToast.cancel();
|
||||
}, 1000); // 1000毫秒 = 1秒
|
||||
}
|
||||
|
||||
private void makeTextCenter(String message) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?attr/colorPrimary" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/last_watch"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/play"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/play"
|
||||
android:background="@drawable/shape_blue"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="@drawable/bg_toast"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@drawable/ic_notify_play"
|
||||
android:contentDescription="@string/play"
|
||||
android:tint="@color/yellow_500" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/last_watch"
|
||||
android:textColor="@color/yellow_500"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -171,6 +171,7 @@
|
||||
<string name="none">无</string>
|
||||
<string name="times">倍</string>
|
||||
<string name="lines">行</string>
|
||||
<string name="last_watch">上次播放</string>
|
||||
|
||||
<string-array name="select_decode">
|
||||
<item>软解</item>
|
||||
@@ -214,4 +215,10 @@
|
||||
<item>选择字幕</item>
|
||||
</string-array>
|
||||
|
||||
<string name="source_hint">您还没有添加视频源\n点击下方按钮添加</string>
|
||||
<string name="add_source">添加源</string>
|
||||
<string name="source_hint_setting">添加视频源</string>
|
||||
<string name="source_hint_live">添加直播源</string>
|
||||
<string name="source_hint_wall">添加壁纸源</string>
|
||||
|
||||
</resources>
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<!-- Push -->
|
||||
<string name="push">Push</string>
|
||||
<string name="push_image" translatable="false">data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADwAPADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvN7bxhffFCaRPB+oWZ8KSQX1h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VSXxzqkXl29mDui0W0OSttF6Ngnew6knnlifZq7qcfYx5U7vq/M8WvV+sT52rJaJdktvn5nzldt8avhYpTVdMsvjD4bj4FzZKLTV4155MfSQ9OFyT61e8F/GTwF8Rbo2Onay2h68p2yaF4gQ2tyj4GVG7hjz0BNe/wBcZ8RPg74M+K1mbfxR4fs9UYLtS5ZNlxH/ALkq4dfoDitbp7mFmtjGvNNudPbbcQtH6NjIP41518fo5NY8G+FfAFsxS8da3DaTbSQy2ULCWdwRzwFX8GNTyfBX4nfCdd/w38bf8JHoqf8yx4w/ertzkrFcDkdwAQoHcml+F9n4v8AiP8AHCLxZ4w8H3Xg6Hwxop020sZ3WSF7yaQmWWGQcOvlhVyOOcZOKaVtbkt30seofFbVo/C/gCS2s9Y07w3dXTRabp9xqUBmt/NcgLEYwDuDKGXFdhptmun6fa2qrGiwRLEFhQRoAoAwqjhRxwB0p11ZW98ipcwRXCK6yKsqBgGByGGe4PQ1PUFhRRRSGFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVhePP+RH8Rf9g64/9FNW7WD483HwN4i2RvK/9nXOI413Mx8puAO5NNbgfB/jX4kWV58A/hN8NZdQaw0u40+PVvEl1Dy0NjHI2yMdi7sPlU9XEQ/ir6Q+DPw1vPGOt6f8RfGGljTBZ262/hTwww/d6LZgAK7L/wA92ULk/wAIAHYBfFf2N/2XJtelsfiN47tWeKIRjR9LuoyN4jUIk8in+EBRsB643dMZ+6K1m0tEYwTerCiiisTYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/Z</string>
|
||||
<string name="push_image" translatable="false">data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADwAPADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvN7bxhffFCaRPB+oWZ8KSQX1h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QdJLuYIW9lHVj7AE15G37Q/iX4ifuvhT4Fu9atW4/wCEi8QBrHTR/tKD88o6cDB5qDw78FfA3hfVP7Xu7S68c+JDy+teKJjdPu9URvlUZJxgAj1rubzV7q+ULJKRGOBGnyqPbApaINWec3nwZv8AxlJ5/wAVvHl94rXO4+HNDJstMX/ZbaQ0g9yQeTXd6Db6Z4N0saZ4X0ex8O6eP+WNjCqFvdmxlj7nn3ptWLWza4jlmd0t7WFTJNczMFjiUDJZmPAAAJpXbHZIYqzXk4UB5pXP1JrjfiB8XLPwBrUHhXw9ph8b/Eu8GLXQ7RgYrQkcSXL5wigHcQSOOSVB31zuofFHXfixeXvhz4RSjS/D0BMOs/Ea+iIhhAALpaA43uAcbvcEbRtkq/oz+Av2Z/hze6zEJotMlk/e6tcYfVfEdyckIhODsJz6D7x4+ZjMpKDUXrJ9DSNOVSLmtIrd/p5spTeEdI+C9nJ8WvjZrY8W+OFIFjbp81vaScmO3soTgF85O8gY+98uGdrvgXwLrfivxRbfFT4qW6jxABu8OeE3yYdEiJyskinrOcA8jKkAnDACJvw9+Hus+JPEtt8UvilbKPEIXPh3woR+40OHqrup/wCW3Q8jKkZPzYEfpdxcSXczyyuXkY5JNdlGiqCet5Pd9/8AgHm4rFPFSVo8sF8MVsv82+r6hcXEl3M8srF5GOSTUVFFanIM1bxNB4C8I+JPFlyFaLRdPluVRzgSSBTsT6scL/wIVn/s+2lh8Jfgz4NtfEeow2Ws+IphPI144V7m+uiZRH7vjC4/2a5f47Wp8Taf4A+G0fL+MNbSe/QDOdPtcTTZ9ORGR9DXqXiC4XVPip4Y0O3vtBki0+1m1O90e8t/Mvdn+rt7i3OMRhJAQW9Gx6VfQjqd/RRRUFhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFYXjz/kR/EX/YOuP/RTVu1hePP+RH8Rf9g64/9FNW7WD483HwN4i2RvK/9nXOI413Mx8puAO5NNbgfB/jX4kWV58A/hN8NZdQaw0u40+PVvEl1Dy0NjHI2yMdi7sPlU9XEQ/ir6Q+DPw1vPGOt6f8RfGGljTBZ262/hTwww/d6LZgAK7L/wA92ULk/wAIAHYBfFf2N/2XJtelsfiN47tWeKIRjR9LuoyN4jUIk8in+EBRsB643dMZ+6K1m0tEYwTerCiiisTYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/Z</string>
|
||||
|
||||
<!-- Setting -->
|
||||
<string name="setting_vod">Vod</string>
|
||||
@@ -173,12 +173,14 @@
|
||||
|
||||
<!-- Hint -->
|
||||
<string name="copied">Copied</string>
|
||||
<string name="copied_to_clipboard">Error log copied to clipboard</string>
|
||||
|
||||
<!-- UNIT -->
|
||||
<string name="all">All</string>
|
||||
<string name="none">None</string>
|
||||
<string name="times">times</string>
|
||||
<string name="lines">lines</string>
|
||||
<string name="last_watch">上次播放</string>
|
||||
|
||||
<string-array name="select_decode">
|
||||
<item>Soft</item>
|
||||
@@ -233,6 +235,10 @@
|
||||
<string name="target_size">Target size</string>
|
||||
<string name="scan_result">Scan result</string>
|
||||
|
||||
<string name="source_hint">点我添加源</string>
|
||||
<string name="source_hint">No video sources added yet\nClick the button below to add</string>
|
||||
<string name="add_source">Add Source</string>
|
||||
<string name="source_hint_setting">Add Source</string>
|
||||
<string name="source_hint_live">Add Live Source</string>
|
||||
<string name="source_hint_wall">Add Wallpaper Source</string>
|
||||
|
||||
</resources>
|
||||
@@ -106,6 +106,11 @@
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:screenOrientation="fullUser" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.SettingPlayerActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:screenOrientation="fullUser" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.VideoActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|uiMode|orientation"
|
||||
|
||||
@@ -85,7 +85,36 @@ public class Updater implements Download.Callback {
|
||||
Logger.d("Current version: " + BuildConfig.VERSION_NAME);
|
||||
Logger.d("Latest version: " + tagName);
|
||||
if (tagName.startsWith("v")) tagName = tagName.substring(1);
|
||||
return Setting.getUpdate() && !tagName.equals(BuildConfig.VERSION_NAME);
|
||||
|
||||
// 版本比较逻辑
|
||||
try {
|
||||
String[] currentParts = BuildConfig.VERSION_NAME.split("\\.");
|
||||
String[] remoteParts = tagName.split("\\.");
|
||||
|
||||
// 比较主版本号
|
||||
for (int i = 0; i < Math.min(currentParts.length, remoteParts.length); i++) {
|
||||
int current = Integer.parseInt(currentParts[i]);
|
||||
int remote = Integer.parseInt(remoteParts[i]);
|
||||
|
||||
if (remote > current) {
|
||||
return Setting.getUpdate(); // 远程版本高于当前版本,需要更新
|
||||
} else if (remote < current) {
|
||||
return false; // 远程版本低于当前版本,不需要更新
|
||||
}
|
||||
// 如果相等,继续比较下一级版本号
|
||||
}
|
||||
|
||||
// 如果前面的版本号都相等,但远程版本有更多的版本号,视为更新
|
||||
if (remoteParts.length > currentParts.length) {
|
||||
return Setting.getUpdate();
|
||||
}
|
||||
|
||||
return false; // 版本相同或远程版本较低,不需要更新
|
||||
} catch (NumberFormatException e) {
|
||||
// 如果版本号解析失败,退回到简单字符串比较
|
||||
Logger.e("Version parsing failed", e);
|
||||
return Setting.getUpdate() && !tagName.equals(BuildConfig.VERSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private void doInBackground(Activity activity) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.fongmi.android.tv.ui.activity;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import com.fongmi.android.tv.R;
|
||||
import com.fongmi.android.tv.databinding.ActivityCrashBinding;
|
||||
import com.fongmi.android.tv.ui.base.BaseActivity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import cat.ereza.customactivityoncrash.CustomActivityOnCrash;
|
||||
|
||||
public class CrashActivity extends BaseActivity {
|
||||
|
||||
private ActivityCrashBinding mBinding;
|
||||
private String errorDetails;
|
||||
|
||||
@Override
|
||||
protected ViewBinding getBinding() {
|
||||
return mBinding = ActivityCrashBinding.inflate(getLayoutInflater());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView(Bundle savedInstanceState) {
|
||||
errorDetails = CustomActivityOnCrash.getAllErrorDetailsFromIntent(this, getIntent());
|
||||
mBinding.error.setText(errorDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initEvent() {
|
||||
mBinding.copy.setOnClickListener(v -> copyErrorToClipboard());
|
||||
mBinding.restart.setOnClickListener(v -> CustomActivityOnCrash.restartApplication(this, Objects.requireNonNull(CustomActivityOnCrash.getConfigFromIntent(getIntent()))));
|
||||
}
|
||||
|
||||
private void copyErrorToClipboard() {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.crash_details_title), errorDetails);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
showError();
|
||||
}
|
||||
|
||||
private void showError() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.crash_details_title)
|
||||
.setMessage(errorDetails)
|
||||
.setPositiveButton(R.string.crash_details_close, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import com.fongmi.android.tv.server.Server;
|
||||
import com.fongmi.android.tv.ui.base.BaseActivity;
|
||||
import com.fongmi.android.tv.ui.custom.FragmentStateManager;
|
||||
import com.fongmi.android.tv.ui.fragment.SettingFragment;
|
||||
import com.fongmi.android.tv.ui.fragment.SettingPlayerFragment;
|
||||
import com.fongmi.android.tv.ui.fragment.VodFragment;
|
||||
import com.fongmi.android.tv.utils.FileChooser;
|
||||
import com.fongmi.android.tv.utils.Notify;
|
||||
@@ -95,7 +94,6 @@ public class HomeActivity extends BaseActivity implements NavigationBarView.OnIt
|
||||
public Fragment getItem(int position) {
|
||||
if (position == 0) return VodFragment.newInstance();
|
||||
if (position == 1) return SettingFragment.newInstance();
|
||||
if (position == 2) return SettingPlayerFragment.newInstance();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -210,8 +208,6 @@ public class HomeActivity extends BaseActivity implements NavigationBarView.OnIt
|
||||
protected void onBackPress() {
|
||||
if (!mBinding.navigation.getMenu().findItem(R.id.vod).isVisible()) {
|
||||
setNavigation();
|
||||
} else if (mManager.isVisible(2)) {
|
||||
change(1);
|
||||
} else if (mManager.isVisible(1)) {
|
||||
mBinding.navigation.setSelectedItemId(R.id.vod);
|
||||
} else if (mManager.canBack(0)) {
|
||||
|
||||
+16
-24
@@ -1,41 +1,38 @@
|
||||
package com.fongmi.android.tv.ui.fragment;
|
||||
package com.fongmi.android.tv.ui.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import com.fongmi.android.tv.R;
|
||||
import com.fongmi.android.tv.Setting;
|
||||
import com.fongmi.android.tv.databinding.FragmentSettingPlayerBinding;
|
||||
import com.fongmi.android.tv.databinding.ActivitySettingPlayerBinding;
|
||||
import com.fongmi.android.tv.impl.BufferCallback;
|
||||
import com.fongmi.android.tv.impl.SpeedCallback;
|
||||
import com.fongmi.android.tv.impl.UaCallback;
|
||||
import com.fongmi.android.tv.ui.base.BaseFragment;
|
||||
import com.fongmi.android.tv.ui.base.BaseActivity;
|
||||
import com.fongmi.android.tv.ui.dialog.BufferDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.SpeedDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.UaDialog;
|
||||
import com.fongmi.android.tv.utils.ResUtil;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class SettingPlayerFragment extends BaseFragment implements UaCallback, BufferCallback, SpeedCallback {
|
||||
public class SettingPlayerActivity extends BaseActivity implements UaCallback, BufferCallback, SpeedCallback {
|
||||
|
||||
private FragmentSettingPlayerBinding mBinding;
|
||||
private ActivitySettingPlayerBinding mBinding;
|
||||
private DecimalFormat format;
|
||||
private String[] background;
|
||||
private String[] caption;
|
||||
private String[] render;
|
||||
private String[] scale;
|
||||
|
||||
public static SettingPlayerFragment newInstance() {
|
||||
return new SettingPlayerFragment();
|
||||
public static void start(Activity activity) {
|
||||
activity.startActivity(new Intent(activity, SettingPlayerActivity.class));
|
||||
}
|
||||
|
||||
private String getSwitch(boolean value) {
|
||||
@@ -43,14 +40,13 @@ public class SettingPlayerFragment extends BaseFragment implements UaCallback, B
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
|
||||
return mBinding = FragmentSettingPlayerBinding.inflate(inflater, container, false);
|
||||
protected ViewBinding getBinding() {
|
||||
return mBinding = ActivitySettingPlayerBinding.inflate(getLayoutInflater());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
protected void initView(Bundle savedInstanceState) {
|
||||
format = new DecimalFormat("0.#");
|
||||
mBinding.back.setOnClickListener(v -> requireActivity().onBackPressed());
|
||||
mBinding.uaText.setText(Setting.getUa());
|
||||
mBinding.tunnelSwitch.setChecked(Setting.isTunnel());
|
||||
mBinding.audioDecodeSwitch.setChecked(Setting.isAudioPrefer());
|
||||
@@ -89,6 +85,7 @@ public class SettingPlayerFragment extends BaseFragment implements UaCallback, B
|
||||
|
||||
@Override
|
||||
protected void initEvent() {
|
||||
mBinding.back.setOnClickListener(v -> finish());
|
||||
mBinding.ua.setOnClickListener(this::onUa);
|
||||
mBinding.aac.setOnClickListener(this::setAAC);
|
||||
mBinding.scale.setOnClickListener(this::onScale);
|
||||
@@ -120,7 +117,7 @@ public class SettingPlayerFragment extends BaseFragment implements UaCallback, B
|
||||
}
|
||||
|
||||
private void onScale(View view) {
|
||||
new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.player_scale).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(scale, Setting.getScale(), (dialog, which) -> {
|
||||
new com.google.android.material.dialog.MaterialAlertDialogBuilder(this).setTitle(R.string.player_scale).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(scale, Setting.getScale(), (dialog, which) -> {
|
||||
mBinding.scaleText.setText(scale[which]);
|
||||
Setting.putScale(which);
|
||||
dialog.dismiss();
|
||||
@@ -172,7 +169,7 @@ public class SettingPlayerFragment extends BaseFragment implements UaCallback, B
|
||||
}
|
||||
|
||||
private void onBackground(View view) {
|
||||
new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.player_background).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(background, Setting.getBackground(), (dialog, which) -> {
|
||||
new com.google.android.material.dialog.MaterialAlertDialogBuilder(this).setTitle(R.string.player_background).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(background, Setting.getBackground(), (dialog, which) -> {
|
||||
mBinding.backgroundText.setText(background[which]);
|
||||
Setting.putBackground(which);
|
||||
dialog.dismiss();
|
||||
@@ -190,9 +187,4 @@ public class SettingPlayerFragment extends BaseFragment implements UaCallback, B
|
||||
Setting.putDanmakuLoad(isChecked);
|
||||
mBinding.danmakuLoadSwitch.setChecked(isChecked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenChanged(boolean hidden) {
|
||||
if (!hidden) initView();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,7 +513,10 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
mBinding.swipeLayout.setRefreshing(false);
|
||||
if (result.getList().isEmpty()) setEmpty(result.hasMsg());
|
||||
else setDetail(result.getList().get(0));
|
||||
Notify.show(result.getMsg());
|
||||
// 只在有错误或重要消息时显示提示
|
||||
if (result.hasMsg() && result.getList().isEmpty()) {
|
||||
Notify.show(result.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
private void setEmpty(boolean finish) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fongmi.android.tv.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
@@ -22,11 +23,20 @@ public class BufferDialog {
|
||||
return new BufferDialog(fragment);
|
||||
}
|
||||
|
||||
public static BufferDialog create(Activity activity) {
|
||||
return new BufferDialog(activity);
|
||||
}
|
||||
|
||||
public BufferDialog(Fragment fragment) {
|
||||
this.callback = (BufferCallback) fragment;
|
||||
this.binding = DialogBufferBinding.inflate(LayoutInflater.from(fragment.getContext()));
|
||||
}
|
||||
|
||||
public BufferDialog(Activity activity) {
|
||||
this.callback = (BufferCallback) activity;
|
||||
this.binding = DialogBufferBinding.inflate(LayoutInflater.from(activity));
|
||||
}
|
||||
|
||||
public void show() {
|
||||
initDialog();
|
||||
initView();
|
||||
|
||||
@@ -60,8 +60,35 @@ public class HistoryDialog implements ConfigAdapter.OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onTextClick(Config item) {
|
||||
callback.setConfig(item);
|
||||
// 防止重复点击和空值
|
||||
if (!dialog.isShowing() || item == null) return;
|
||||
|
||||
// 检查callback是否有效
|
||||
if (callback == null) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
// 先关闭对话框,避免时序冲突
|
||||
dialog.dismiss();
|
||||
|
||||
// 延迟执行配置设置,确保对话框完全关闭
|
||||
App.post(() -> {
|
||||
try {
|
||||
// 双重检查callback和item是否仍然有效
|
||||
if (callback != null && item != null && !item.isEmpty()) {
|
||||
callback.setConfig(item);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// 如果出现异常,显示错误提示
|
||||
try {
|
||||
Notify.show("配置切换失败: " + e.getMessage());
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, 150); // 增加延迟到150毫秒
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fongmi.android.tv.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
@@ -22,11 +23,20 @@ public class SpeedDialog {
|
||||
return new SpeedDialog(fragment);
|
||||
}
|
||||
|
||||
public static SpeedDialog create(Activity activity) {
|
||||
return new SpeedDialog(activity);
|
||||
}
|
||||
|
||||
public SpeedDialog(Fragment fragment) {
|
||||
this.callback = (SpeedCallback) fragment;
|
||||
this.binding = DialogSpeedBinding.inflate(LayoutInflater.from(fragment.getContext()));
|
||||
}
|
||||
|
||||
public SpeedDialog(Activity activity) {
|
||||
this.callback = (SpeedCallback) activity;
|
||||
this.binding = DialogSpeedBinding.inflate(LayoutInflater.from(activity));
|
||||
}
|
||||
|
||||
public void show() {
|
||||
initDialog();
|
||||
initView();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.fongmi.android.tv.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -27,12 +28,22 @@ public class UaDialog {
|
||||
return new UaDialog(fragment);
|
||||
}
|
||||
|
||||
public static UaDialog create(Activity activity) {
|
||||
return new UaDialog(activity);
|
||||
}
|
||||
|
||||
public UaDialog(Fragment fragment) {
|
||||
this.callback = (UaCallback) fragment;
|
||||
this.binding = DialogUaBinding.inflate(LayoutInflater.from(fragment.getContext()));
|
||||
this.append = true;
|
||||
}
|
||||
|
||||
public UaDialog(Activity activity) {
|
||||
this.callback = (UaCallback) activity;
|
||||
this.binding = DialogUaBinding.inflate(LayoutInflater.from(activity));
|
||||
this.append = true;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
initDialog();
|
||||
initView();
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.fongmi.android.tv.impl.ProxyCallback;
|
||||
import com.fongmi.android.tv.impl.SiteCallback;
|
||||
import com.fongmi.android.tv.player.Source;
|
||||
import com.fongmi.android.tv.ui.activity.HomeActivity;
|
||||
import com.fongmi.android.tv.ui.activity.SettingPlayerActivity;
|
||||
import com.fongmi.android.tv.ui.base.BaseFragment;
|
||||
import com.fongmi.android.tv.ui.dialog.AboutDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.ConfigDialog;
|
||||
@@ -98,10 +99,10 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
|
||||
mBinding.versionText.setText(BuildConfig.VERSION_NAME);
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc(), R.string.source_hint_setting);
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc(), R.string.source_hint_live);
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc(), R.string.source_hint_wall);
|
||||
mBinding.versionText.setText(getString(R.string.setting_version) + " " + BuildConfig.VERSION_NAME);
|
||||
|
||||
// 设置开关的颜色为黄色
|
||||
int accentColor = getResources().getColor(R.color.accent);
|
||||
@@ -170,33 +171,58 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
|
||||
@Override
|
||||
public void setConfig(Config config) {
|
||||
// 如果URL为空,不进行任何操作
|
||||
if (config.isEmpty()) return;
|
||||
// 添加Fragment状态检查,防止在无效状态下执行
|
||||
if (getActivity() == null || !isAdded() || isDetached()) return;
|
||||
|
||||
if (config.getUrl().startsWith("file") && !PermissionX.isGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> load(config));
|
||||
} else {
|
||||
load(config);
|
||||
// 如果URL为空,不进行任何操作
|
||||
if (config == null || config.isEmpty()) return;
|
||||
|
||||
try {
|
||||
if (config.getUrl().startsWith("file") && !PermissionX.isGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> {
|
||||
if (getActivity() != null && isAdded()) {
|
||||
load(config);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
load(config);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void load(Config config) {
|
||||
switch (config.getType()) {
|
||||
case 0:
|
||||
Notify.progress(getActivity());
|
||||
VodConfig.load(config, getCallback(0));
|
||||
mBinding.vodUrl.setText(config.getDesc());
|
||||
break;
|
||||
case 1:
|
||||
Notify.progress(getActivity());
|
||||
LiveConfig.load(config, getCallback(1));
|
||||
mBinding.liveUrl.setText(config.getDesc());
|
||||
break;
|
||||
case 2:
|
||||
Notify.progress(getActivity());
|
||||
WallConfig.load(config, getCallback(2));
|
||||
mBinding.wallUrl.setText(config.getDesc());
|
||||
break;
|
||||
// 再次检查Fragment状态,防止在异步回调中执行
|
||||
if (getActivity() == null || !isAdded() || isDetached()) return;
|
||||
|
||||
try {
|
||||
switch (config.getType()) {
|
||||
case 0:
|
||||
Notify.progress(getActivity());
|
||||
VodConfig.load(config, getCallback(0));
|
||||
if (mBinding != null && mBinding.vodUrl != null) {
|
||||
mBinding.vodUrl.setText(config.getDesc());
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
Notify.progress(getActivity());
|
||||
LiveConfig.load(config, getCallback(1));
|
||||
if (mBinding != null && mBinding.liveUrl != null) {
|
||||
mBinding.liveUrl.setText(config.getDesc());
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
Notify.progress(getActivity());
|
||||
WallConfig.load(config, getCallback(2));
|
||||
if (mBinding != null && mBinding.wallUrl != null) {
|
||||
mBinding.wallUrl.setText(config.getDesc());
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Notify.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,27 +230,33 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
return new Callback() {
|
||||
@Override
|
||||
public void success(String result) {
|
||||
// 检查Fragment是否还在活动状态
|
||||
if (getActivity() == null || !isAdded()) return;
|
||||
Notify.show(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success() {
|
||||
// 检查Fragment是否还在活动状态
|
||||
if (getActivity() == null || !isAdded()) return;
|
||||
setConfig(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
// 检查Fragment是否还在活动状态
|
||||
if (getActivity() == null || !isAdded()) return;
|
||||
Notify.show(msg);
|
||||
Notify.dismiss();
|
||||
switch (type) {
|
||||
case 0:
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc(), R.string.source_hint_setting);
|
||||
break;
|
||||
case 1:
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc(), R.string.source_hint_live);
|
||||
break;
|
||||
case 2:
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc(), R.string.source_hint_wall);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -238,27 +270,27 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
Notify.dismiss();
|
||||
RefreshEvent.video();
|
||||
RefreshEvent.config();
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc(), R.string.source_hint_setting);
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc(), R.string.source_hint_live);
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc(), R.string.source_hint_wall);
|
||||
break;
|
||||
case 1:
|
||||
setCacheText();
|
||||
Notify.dismiss();
|
||||
RefreshEvent.config();
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc(), R.string.source_hint_live);
|
||||
break;
|
||||
case 2:
|
||||
setCacheText();
|
||||
Notify.dismiss();
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc(), R.string.source_hint_wall);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSourceHintText(TextView textView, String desc) {
|
||||
private void setSourceHintText(TextView textView, String desc, int hintStringRes) {
|
||||
if (TextUtils.isEmpty(desc)) {
|
||||
SpannableString spannable = new SpannableString(getString(R.string.source_hint));
|
||||
SpannableString spannable = new SpannableString(getString(hintStringRes));
|
||||
spannable.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.white)), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new RelativeSizeSpan(0.8f), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
int alpha = (int)(255 * 0.5f);
|
||||
@@ -328,7 +360,7 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
}
|
||||
|
||||
private void onPlayer(View view) {
|
||||
getRoot().change(2);
|
||||
SettingPlayerActivity.start(requireActivity());
|
||||
}
|
||||
|
||||
private void onVersion(View view) {
|
||||
@@ -454,9 +486,9 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
|
||||
@Override
|
||||
public void onHiddenChanged(boolean hidden) {
|
||||
if (hidden) return;
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
|
||||
setSourceHintText(mBinding.vodUrl, VodConfig.getDesc(), R.string.source_hint_setting);
|
||||
setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc(), R.string.source_hint_live);
|
||||
setSourceHintText(mBinding.wallUrl, WallConfig.getDesc(), R.string.source_hint_wall);
|
||||
setCacheText();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.fongmi.android.tv.R;
|
||||
import com.fongmi.android.tv.Setting;
|
||||
import com.fongmi.android.tv.api.config.VodConfig;
|
||||
import com.fongmi.android.tv.bean.Class;
|
||||
import com.fongmi.android.tv.bean.Config;
|
||||
import com.fongmi.android.tv.bean.History;
|
||||
import com.fongmi.android.tv.bean.Hot;
|
||||
import com.fongmi.android.tv.bean.Result;
|
||||
import com.fongmi.android.tv.bean.Site;
|
||||
@@ -35,6 +37,7 @@ import com.fongmi.android.tv.event.CastEvent;
|
||||
import com.fongmi.android.tv.event.RefreshEvent;
|
||||
import com.fongmi.android.tv.event.StateEvent;
|
||||
import com.fongmi.android.tv.impl.Callback;
|
||||
import com.fongmi.android.tv.impl.ConfigCallback;
|
||||
import com.fongmi.android.tv.impl.FilterCallback;
|
||||
import com.fongmi.android.tv.impl.SiteCallback;
|
||||
import com.fongmi.android.tv.model.SiteViewModel;
|
||||
@@ -44,11 +47,14 @@ import com.fongmi.android.tv.ui.activity.KeepActivity;
|
||||
import com.fongmi.android.tv.ui.activity.VideoActivity;
|
||||
import com.fongmi.android.tv.ui.adapter.TypeAdapter;
|
||||
import com.fongmi.android.tv.ui.base.BaseFragment;
|
||||
import com.fongmi.android.tv.ui.dialog.ConfigDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.FilterDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.LastWatchToast;
|
||||
import com.fongmi.android.tv.ui.dialog.LinkDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.ReceiveDialog;
|
||||
import com.fongmi.android.tv.ui.dialog.SiteDialog;
|
||||
import com.fongmi.android.tv.utils.FileChooser;
|
||||
import com.fongmi.android.tv.utils.Notify;
|
||||
import com.fongmi.android.tv.utils.ResUtil;
|
||||
import com.fongmi.android.tv.utils.UrlUtil;
|
||||
import com.github.catvod.net.OkHttp;
|
||||
@@ -68,7 +74,7 @@ import okhttp3.Call;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class VodFragment extends BaseFragment implements SiteCallback, FilterCallback, TypeAdapter.OnClickListener {
|
||||
public class VodFragment extends BaseFragment implements SiteCallback, FilterCallback, TypeAdapter.OnClickListener, ConfigCallback {
|
||||
|
||||
private FragmentVodBinding mBinding;
|
||||
private SiteViewModel mViewModel;
|
||||
@@ -99,10 +105,37 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
EventBus.getDefault().register(this);
|
||||
setRecyclerView();
|
||||
setViewModel();
|
||||
showProgress();
|
||||
initStartupState(); // 根据是否已有配置来设置初始状态
|
||||
setLogo();
|
||||
initHot();
|
||||
getHot();
|
||||
// 检查是否需要显示上次播放弹窗
|
||||
checkLastWatchDialog();
|
||||
}
|
||||
|
||||
// 初始化启动状态:区分已有配置和无配置的情况
|
||||
private void initStartupState() {
|
||||
// 检查是否已经有保存的配置,添加空值检查
|
||||
boolean hasExistingConfig = false;
|
||||
try {
|
||||
Config config = VodConfig.get().getConfig();
|
||||
hasExistingConfig = config != null &&
|
||||
config.getUrl() != null &&
|
||||
!config.getUrl().isEmpty();
|
||||
} catch (Exception e) {
|
||||
// 如果获取配置时出错,认为没有配置
|
||||
hasExistingConfig = false;
|
||||
}
|
||||
|
||||
if (hasExistingConfig) {
|
||||
// 已有配置:显示加载状态,确保不显示添加源提示
|
||||
showProgress();
|
||||
mBinding.emptySourceHint.setVisibility(View.GONE);
|
||||
} else {
|
||||
// 无配置:立即显示空源提示,不显示加载状态
|
||||
hideProgress();
|
||||
checkEmptySource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,6 +160,23 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
});
|
||||
}
|
||||
|
||||
// 添加检查上次播放历史并显示弹窗的方法
|
||||
private void checkLastWatchDialog() {
|
||||
if (App.isAppJustLaunched()) {
|
||||
List<History> histories = History.get();
|
||||
if (!histories.isEmpty()) {
|
||||
App.setAppLaunched();
|
||||
App.post(() -> {
|
||||
if (getActivity() != null) {
|
||||
LastWatchToast.create(getActivity(), histories.get(0)).show();
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
App.setAppLaunched();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setRecyclerView() {
|
||||
mBinding.type.setHasFixedSize(true);
|
||||
mBinding.type.setItemAnimator(null);
|
||||
@@ -173,10 +223,148 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
setFabVisible(0);
|
||||
hideProgress();
|
||||
checkRetry();
|
||||
checkEmptySource(); // 添加检查是否显示空源提示
|
||||
}
|
||||
|
||||
// 修改checkEmptySource方法,增强鲁棒性
|
||||
private void checkEmptySource() {
|
||||
// 检查是否有基础配置文件,添加空值检查
|
||||
boolean hasBaseConfig = false;
|
||||
try {
|
||||
Config config = VodConfig.get().getConfig();
|
||||
hasBaseConfig = config != null &&
|
||||
config.getUrl() != null &&
|
||||
!config.getUrl().isEmpty();
|
||||
} catch (Exception e) {
|
||||
hasBaseConfig = false;
|
||||
}
|
||||
|
||||
// 检查是否有有效的站点配置
|
||||
boolean hasValidSites = false;
|
||||
boolean hasValidHome = false;
|
||||
try {
|
||||
hasValidSites = VodConfig.get().getSites().size() > 0;
|
||||
Site site = getSite();
|
||||
hasValidHome = site != null && site.getKey() != null && !site.getKey().isEmpty();
|
||||
} catch (Exception e) {
|
||||
hasValidSites = false;
|
||||
hasValidHome = false;
|
||||
}
|
||||
|
||||
// 只有在完全没有配置文件或配置文件无效时才显示空源提示
|
||||
boolean isEmpty = !hasBaseConfig || (!hasValidSites || !hasValidHome);
|
||||
|
||||
if (mBinding.emptySourceHint != null) {
|
||||
mBinding.emptySourceHint.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
|
||||
if (isEmpty) {
|
||||
// 设置整个布局的点击事件
|
||||
mBinding.emptySourceHint.setOnClickListener(this::onAddSource);
|
||||
// 设置按钮的点击事件
|
||||
if (mBinding.addSourceBtn != null) {
|
||||
mBinding.addSourceBtn.setOnClickListener(this::onAddSource);
|
||||
}
|
||||
// 空源状态下隐藏所有悬浮按钮
|
||||
hideFabButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加源按钮点击事件处理
|
||||
private void onAddSource(View view) {
|
||||
ConfigDialog.create(this).type(0).show();
|
||||
}
|
||||
|
||||
// 实现ConfigCallback接口
|
||||
@Override
|
||||
public void setConfig(Config config) {
|
||||
if (config == null || config.isEmpty()) return;
|
||||
|
||||
// 检查Fragment是否还在活动状态,增强检查
|
||||
if (!isValidFragmentState()) return;
|
||||
|
||||
// 安全地隐藏空源提示
|
||||
try {
|
||||
if (mBinding != null && mBinding.emptySourceHint != null) {
|
||||
mBinding.emptySourceHint.setVisibility(View.GONE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Notify.progress(getActivity());
|
||||
VodConfig.load(config, new Callback() {
|
||||
@Override
|
||||
public void success() {
|
||||
// 双重检查Fragment是否还在活动状态
|
||||
if (!isValidFragmentState()) return;
|
||||
|
||||
try {
|
||||
Notify.dismiss();
|
||||
RefreshEvent.config();
|
||||
RefreshEvent.video();
|
||||
homeContent();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
// 双重检查Fragment是否还在活动状态
|
||||
if (!isValidFragmentState()) return;
|
||||
|
||||
try {
|
||||
Notify.dismiss();
|
||||
Notify.show(msg);
|
||||
// 加载失败时重新显示空源提示
|
||||
checkEmptySource();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加Fragment状态检查方法
|
||||
private boolean isValidFragmentState() {
|
||||
return getActivity() != null &&
|
||||
!getActivity().isFinishing() &&
|
||||
!getActivity().isDestroyed() &&
|
||||
isAdded() &&
|
||||
!isDetached() &&
|
||||
!isRemoving() &&
|
||||
getView() != null &&
|
||||
mBinding != null;
|
||||
}
|
||||
|
||||
private void setFabVisible(int position) {
|
||||
if (mAdapter.getItemCount() == 0) {
|
||||
// 检查是否为空源状态 - 使用与checkEmptySource相同的逻辑,添加空值检查
|
||||
boolean hasBaseConfig = false;
|
||||
boolean hasValidSites = false;
|
||||
boolean hasValidHome = false;
|
||||
|
||||
try {
|
||||
Config config = VodConfig.get().getConfig();
|
||||
hasBaseConfig = config != null &&
|
||||
config.getUrl() != null &&
|
||||
!config.getUrl().isEmpty();
|
||||
|
||||
hasValidSites = VodConfig.get().getSites().size() > 0;
|
||||
|
||||
Site site = getSite();
|
||||
hasValidHome = site != null && site.getKey() != null && !site.getKey().isEmpty();
|
||||
} catch (Exception e) {
|
||||
hasBaseConfig = false;
|
||||
hasValidSites = false;
|
||||
hasValidHome = false;
|
||||
}
|
||||
|
||||
boolean isEmpty = !hasBaseConfig || (!hasValidSites || !hasValidHome);
|
||||
|
||||
if (isEmpty) {
|
||||
// 空源状态下隐藏所有悬浮按钮
|
||||
hideFabButtons();
|
||||
} else if (mAdapter.getItemCount() == 0) {
|
||||
mBinding.top.setVisibility(View.INVISIBLE);
|
||||
mBinding.link.setVisibility(View.VISIBLE);
|
||||
mBinding.filter.setVisibility(View.GONE);
|
||||
@@ -190,6 +378,13 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
mBinding.link.show();
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏所有悬浮按钮的方法
|
||||
private void hideFabButtons() {
|
||||
mBinding.top.setVisibility(View.GONE);
|
||||
mBinding.link.setVisibility(View.GONE);
|
||||
mBinding.filter.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void checkRetry() {
|
||||
mBinding.retry.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
@@ -247,6 +442,14 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
private void homeContent() {
|
||||
showProgress();
|
||||
setFabVisible(0);
|
||||
// 安全地隐藏空源提示
|
||||
try {
|
||||
if (mBinding != null && mBinding.emptySourceHint != null) {
|
||||
mBinding.emptySourceHint.setVisibility(View.GONE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mAdapter.clear();
|
||||
mViewModel.homeContent();
|
||||
mBinding.pager.setAdapter(new PageAdapter(getChildFragmentManager()));
|
||||
@@ -296,6 +499,7 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
switch (event.getType()) {
|
||||
case EMPTY:
|
||||
hideProgress();
|
||||
checkEmptySource(); // 添加检查是否显示空源提示
|
||||
break;
|
||||
case PROGRESS:
|
||||
showProgress();
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,17v2h6v-2H3zM3,5v2h10V5H3zM13,21v-2h8v-2h-8v-2h-2v6H13zM7,9v2H3v2h4v2h2V9H7zM21,13v-2H11v2H21zM15,9h2V7h4V5h-4V3h-2V9z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,371 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setting_player"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_render"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/renderText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Surface" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/scale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_scale"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scaleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_caption"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buffer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_buffer"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bufferText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/speed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_speed"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/speedText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tunnel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_tunnel"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/tunnelSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/audioDecode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_audio_decode"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/audioDecodeSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/aac"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_aac"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/aacSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/danmakuLoad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_danmaku_load"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/danmakuLoadSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_background"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backgroundText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="畫中畫" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ua"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_ua"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uaText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="okhttp/4.11.0" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
@@ -93,10 +93,11 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/vod"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_item"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
@@ -151,10 +152,11 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/live"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_item"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
@@ -209,10 +211,11 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/wall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_item"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
@@ -269,35 +272,43 @@
|
||||
android:textSize="14sp"
|
||||
android:alpha="0.7" />
|
||||
|
||||
<!-- 将三个设置项放在一个共同的背景容器中 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_control_setting"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<!-- 播放器设置 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setting_player"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_setting_player"
|
||||
android:tint="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setting_player"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 无痕模式 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/incognito"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
@@ -318,46 +329,48 @@
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/incognitoSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/incognitoSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_setting_size"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_size"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sizeText"
|
||||
<!-- 图片尺寸 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Medium" />
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_setting_size"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_size"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sizeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Medium" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 网络设置分组 -->
|
||||
@@ -372,105 +385,117 @@
|
||||
android:textSize="14sp"
|
||||
android:alpha="0.7" />
|
||||
|
||||
<!-- 将三个网络设置项放在一个共同的背景容器中 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/doh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_setting_doh"
|
||||
android:tint="@color/white" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_doh"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dohText"
|
||||
<!-- DoH设置 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/doh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Google" />
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_setting_doh"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_doh"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/proxy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/dohText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Google" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_fab_link"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_proxy"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/proxyText"
|
||||
<!-- 代理设置 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/proxy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="http" />
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_fab_link"
|
||||
android:tint="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_proxy"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/cache"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/proxyText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="http" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_folder"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_cache"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cacheText"
|
||||
<!-- 缓存设置 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cache"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1.0 MB" />
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_folder"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_cache"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cacheText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1.0 MB" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 备份与恢复分组 -->
|
||||
@@ -511,7 +536,6 @@
|
||||
android:id="@+id/backup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:text="@string/setting_backup"
|
||||
@@ -530,7 +554,6 @@
|
||||
android:id="@+id/restore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="end"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
@@ -545,7 +568,7 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
@@ -564,22 +587,14 @@
|
||||
android:src="@drawable/ic_setting_github"
|
||||
android:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/setting_version"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/versionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:text="@string/setting_version"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1.2.1" />
|
||||
tools:text="版本 3.0.3" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -60,322 +60,406 @@
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<!-- 播放器基本设置模块 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_render"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/renderText"
|
||||
<!-- 渲染方式 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Surface" />
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_render"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/scale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/renderText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="Surface" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_scale"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scaleText"
|
||||
<!-- 缩放比例 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/scale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_scale"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/scaleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_caption"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captionText"
|
||||
<!-- 字幕样式 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_caption"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buffer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/captionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="預設" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_buffer"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bufferText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/speed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_speed"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/speedText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tunnel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_tunnel"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/tunnelSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/audioDecode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_audio_decode"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/audioDecodeSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/aac"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_aac"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/aacSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/danmakuLoad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_danmaku_load"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/danmakuLoadSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_background"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backgroundText"
|
||||
<!-- 缓冲时间 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/buffer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="畫中畫" />
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_buffer"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bufferText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- 长按倍速 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/speed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_speed"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/speedText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/times"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- 后台播放 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_background"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backgroundText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="畫中畫" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- User-Agent -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ua"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_ua"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uaText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="okhttp/4.11.0" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 开关设置模块 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ua"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_item"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/player_ua"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uaText"
|
||||
<!-- 隧道模式 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tunnel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="middle"
|
||||
android:gravity="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:text="okhttp/4.11.0" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_tunnel"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/tunnelSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- 音频软解 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/audioDecode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_audio_decode"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/audioDecodeSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- AAC优化 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/aac"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_aac"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/aacSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#22FFFFFF" />
|
||||
|
||||
<!-- 弹幕加载 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/danmakuLoad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/player_danmaku_load"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/danmakuLoadSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/M3SwitchStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
@@ -100,6 +100,42 @@
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<!-- 新增空源提示文本 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/empty_source_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:src="@drawable/ic_logo" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/source_hint"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/add_source_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:backgroundTint="@color/primary"
|
||||
android:text="@string/add_source"
|
||||
android:textColor="@color/black" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/retry"
|
||||
android:layout_width="56dp"
|
||||
|
||||
Reference in New Issue
Block a user