feat: dev-5.1 分支基线(v1.3.35 发版后代码 + 5.1 声道改造文档)

- 以 main(ccc65af) 为基线提交当前工作区代码
- 新增 5.1 声道改造文档两份
- .gitignore 补充 .vscode/、.screenshots/
This commit is contained in:
freeboss
2026-11-02 19:25:12 +08:00
parent ccc65afcbc
commit b7b5518834
1394 changed files with 13694 additions and 3710 deletions

0
mediaplayertv/.gitignore vendored Executable file → Normal file
View File

0
mediaplayertv/build.gradle Executable file → Normal file
View File

0
mediaplayertv/consumer-rules.pro Executable file → Normal file
View File

0
mediaplayertv/libs/HyMediaPlayer-v1.2.15.aar Executable file → Normal file
View File

View File

0
mediaplayertv/proguard-rules.pro vendored Executable file → Normal file
View File

0
mediaplayertv/src/main/AndroidManifest.xml Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

@@ -328,11 +328,6 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl {
}
private void initVideoView() {
// Android 4.2 兼容:ExoPlayer 2.19 官方支持 API 19+,低版本强制切系统 MediaPlayer 内核,
// 避免 PkExoMediaPlayer/ExoPlayer 类在 Dalvik 上触发 VerifyError
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
terminalType = 0;
}
if (!checkAudioCapability()){
terminalType = 0;
}
@@ -567,9 +562,7 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl {
reset();
mMediaPlayer.release();
if(mMediaPlayer != null){
// terminalType 短路:非 Exo 内核时跳过 instanceof,避免 Dalvik 解析
// PkExoMediaPlayer 引发的 ExoPlayer 类加载 VerifyError(Android 4.2)
if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){
if(mMediaPlayer instanceof PkExoMediaPlayer){
PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer;
pkExoMediaPlayer.resetListeners();
}
@@ -1498,8 +1491,7 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl {
onPauseListener = null;
onResumeListener = null;
if(mMediaPlayer != null){
// terminalType 短路:同上,避免非 Exo 内核时 Dalvik 触发 PkExoMediaPlayer 类解析
if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){
if(mMediaPlayer instanceof PkExoMediaPlayer){
PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer;
pkExoMediaPlayer.resetListeners();
}

View File

@@ -64,6 +64,14 @@ public final class ExoMediaSourceHelper {
}
public MediaSource getMediaSource(String uri, Map<String, String> headers, boolean isCache) {
// 防御性边界检查:上游偶发传入空 URL(典型如 Jetsenx SDK 返回的双清晰度 URL 都为空),
// 此时 Uri.parse("") 会得到 Uri.EMPTY,DefaultDataSource 因缺少 http/https scheme
// 会退回到 FileDataSource,再用空路径去打开本地文件,触发 FileNotFoundException: ENOENT
// 导致播放崩溃。返回 null 让调用方走 if (mMediaSource == null) return; 兜底。
if (TextUtils.isEmpty(uri)) {
android.util.Log.w("ExoMediaSourceHelper", "getMediaSource called with empty uri, return null to avoid FileDataSource ENOENT crash");
return null;
}
Uri contentUri = Uri.parse(uri);
// if ("rtmp".equals(contentUri.getScheme())) {
// return new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory())

View File

@@ -6,6 +6,7 @@ import android.media.MediaFormat;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -20,6 +21,9 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.audio.DefaultAudioSink;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@@ -32,6 +36,7 @@ import com.google.android.exoplayer2.video.VideoSize;
import com.pukka.libcore.base.BuildConfig;
import com.pukka.libcore.threading.DispatchQueue;
import com.pukka.libcore.utils.LogUtils;
import com.pukka.libcore.utils.SharedPreferenceUtils;
import com.pukka.videoplayertv.AbstractMediaPlayer;
import com.pukka.videoplayertv.IMediaDataSource;
import com.pukka.videoplayertv.ITrackInfo;
@@ -48,6 +53,13 @@ import java.util.Map;
public final class PkExoMediaPlayer extends AbstractMediaPlayer implements Player.Listener {
private final static String TAG = "PkExoMediaPlayer";
private final static String TAG2 = "PkExoMediaPlayer|播放流程";
/**
* v1.3.36: 进程级禁用 AC3 caps 注入。
* 直通初始化失败自动降级后置位,本进程内不再注入(设备级失败,重试无意义)。
*/
private static volatile boolean sAc3InjectionDisabled = false;
/** 当前播放器实例的 AudioSink 是否注入了 AC3 caps(onPlayerError 降级判定用) */
private volatile boolean mAc3Injected = false;
/**
* 视频/音频开始渲染
*/
@@ -106,9 +118,54 @@ public final class PkExoMediaPlayer extends AbstractMediaPlayer implements Playe
DefaultLoadControl mLoadControl = new DefaultLoadControl.Builder()
.setBufferDurationsMs(50000, 50000, 1500, 2000)
.build();
if (mRenderersFactory == null) {
mRenderersFactory = new DefaultRenderersFactory(mAppContext) {
@Override
protected AudioSink buildAudioSink(Context context, boolean enableFloatOutput,
boolean enableAudioTrackPlaybackParams, boolean enableOffload) {
AudioCapabilities caps = AudioCapabilities.getCapabilities(context);
boolean ac3Supported = caps.supportsEncoding(C.ENCODING_AC3);
// 5.1 开关开启且系统未禁止环绕声时,强制注入 AC3 直通能力。
// 修复:HAL 从 PCM 切回 AUTO 有延迟,系统能力查询可能返回过时结果,
// 导致 ExoPlayer 不尝试 AC3 直通。
// v1.3.36 关键修复:DefaultAudioSink.Builder(context) 传入 context 时,
// getFormatSupport() 首次调用会懒创建 AudioCapabilitiesReceiver 并用系统
// 实时 caps 覆盖注入值(反编译 2.19.1 字节码确认)→ 注入从未生效。
// 必须用无参 Builder()(context=null)才能让注入的 caps 持续有效。
// v1.3.36 降级保护:直通初始化失败后 sAc3InjectionDisabled 置位,本进程不再注入。
if (!ac3Supported && !sAc3InjectionDisabled) {
boolean surroundEnabled = SharedPreferenceUtils.getBoolean("Surround51Enabled", true);
if (surroundEnabled) {
int encodedSurround = Settings.Global.getInt(
context.getContentResolver(), "encoded_surround_output", 0);
// encoded_surround_output: 0=AUTO, 1=NEVER, 2=ALWAYS(部分固件映射异常), 3=MANUAL
if (encodedSurround != 1) {
LogUtils.d(TAG, "buildAudioSink: 系统未报告AC3,强制注入。"
+ " encodedSurround=" + encodedSurround);
caps = new AudioCapabilities(
new int[]{C.ENCODING_PCM_16BIT, C.ENCODING_AC3}, 8);
mAc3Injected = true;
}
}
} else if (ac3Supported) {
LogUtils.d(TAG, "buildAudioSink: 系统已报告AC3直通能力");
} else {
LogUtils.d(TAG, "buildAudioSink: 注入已禁用(本进程曾直通失败),走系统 caps");
}
return new DefaultAudioSink.Builder()
.setAudioCapabilities(caps)
.setEnableFloatOutput(enableFloatOutput)
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
.setOffloadMode(enableOffload
? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED
: DefaultAudioSink.OFFLOAD_MODE_DISABLED)
.build();
}
}.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
}
ExoPlayer mInternalPlayer = new ExoPlayer.Builder(
mAppContext,
mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) : mRenderersFactory,
mRenderersFactory,
new DefaultMediaSourceFactory(mAppContext),
mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
mLoadControl,
@@ -608,10 +665,64 @@ public final class PkExoMediaPlayer extends AbstractMediaPlayer implements Playe
LogUtils.d(TAG, "onPlayerError");
String errorMessage = "code:" + error.errorCode + " ||message:" + error.getMessage();
LogUtils.d(TAG2, "播放器报错 " + errorMessage);
// v1.3.36: AC3 注入会话中的音频初始化类错误 → 疑似直通初始化失败
// (平台有 raw decoder 但 policy 无 DIRECT 压缩输出时,AudioTrack(ENCODING_AC3) init 失败)。
// 此时注入的 caps 不可达,摘除注入并自动重建播放器软解降级重试(一次为限),不上抛错误。
if (mAc3Injected && !sAc3InjectionDisabled) {
int code = error.errorCode;
if (code == PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED
|| code == PlaybackException.ERROR_CODE_DECODER_INIT_FAILED
|| code == PlaybackException.ERROR_CODE_UNSPECIFIED) {
LogUtils.d(TAG, "onPlayerError: 注入会话音频初始化失败(code=" + code
+ "),自动降级为软解重试");
sAc3InjectionDisabled = true;
degradeAndRetry(error);
return;
}
}
initSeekTime();
notifyOnError(error.errorCode, errorMessage.contains("Format")?264:9999);
}
/**
* v1.3.36: 关闭 AC3 注入重建播放器,软解降级重试。
* 当前片源内闭环恢复(保留进度),仅在重试再次失败时才上抛原错误。
*/
private void degradeAndRetry(PlaybackException error) {
final long resumePos = mInternalPlayer != null
? Math.max(0, mInternalPlayer.getCurrentPosition()) : 0;
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "degradeAndRetry: 关闭AC3注入重建播放器, resumePos=" + resumePos);
try {
if (mInternalPlayer != null) {
mInternalPlayer.removeListener(PkExoMediaPlayer.this);
mInternalPlayer.release();
mInternalPlayer = null;
}
mRenderersFactory = null; // 置空以重建 AudioSink(sAc3InjectionDisabled=true,不再注入)
mAc3Injected = false;
mInternalPlayer = initPlayer();
mInternalPlayer.setWakeMode(C.WAKE_MODE_LOCAL);
setOptions();
initMediaListener();
if (mSpeedPlaybackParameters != null) {
mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
}
mIsPreparing = true;
mInternalPlayer.setMediaSource(mMediaSource);
mInternalPlayer.setPlayWhenReady(true);
mInternalPlayer.prepare();
if (resumePos > 3000) {
mInternalPlayer.seekTo(resumePos);
}
} catch (Throwable t) {
LogUtils.d(TAG, "degradeAndRetry 失败,上抛原错误:" + t);
initSeekTime();
notifyOnError(error.errorCode, 9999);
}
});
}
@Override
public void onRenderedFirstFrame() {
LogUtils.d(TAG, "onRenderedFirstFrame");

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

View File

View File

0
mediaplayertv/src/main/res/drawable/bg_control_top.xml Executable file → Normal file
View File

View File

View File

View File

View File

0
mediaplayertv/src/main/res/drawable/loading_icon.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

View File

View File

View File

View File

View File

0
mediaplayertv/src/main/res/drawable/videotip1.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

0
mediaplayertv/src/main/res/drawable/videotip2.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

View File

View File

0
mediaplayertv/src/main/res/mipmap-hdpi/dot.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 909 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

View File

View File

View File

View File

View File

0
mediaplayertv/src/main/res/values/colors.xml Executable file → Normal file
View File

0
mediaplayertv/src/main/res/values/dimens.xml Executable file → Normal file
View File

0
mediaplayertv/src/main/res/values/dimens_sw.xml Executable file → Normal file
View File

0
mediaplayertv/src/main/res/values/h_dimens.xml Executable file → Normal file
View File

0
mediaplayertv/src/main/res/values/strings.xml Executable file → Normal file
View File

0
mediaplayertv/src/main/res/values/w_dimens.xml Executable file → Normal file
View File