killapp/Assets/Scripts/Video/VideoDownloadPlayService.cs
“虞渠成” 4726107e19 feat(video,android): 视频下载转码播放 + 列表下载/播放按钮
- 集成 ffmpeg-kit-full-gpl 6.0.3(arm64),用 libx264 重编码修复 .h264 缺 SPS/PPS 导致的播放卡死

- 新增 VideoDownloadPlayService/VideoCacheManager,.mp4 直下直播、.h264 下载后转 mp4 缓存,转码完成后删除临时 .h264

- FFmpegKitBridge 走主线程 JNI 调用规避 IL2CPP 后台线程 FindClass 不稳定

- 视频列表按缓存状态显示 下载/播放 按钮,下载中亮色条显示百分比;播放面板原下载按钮改为保存到相册
2026-09-08 13:31:31 +08:00

153 lines
5.6 KiB
C#

using System;
using System.IO;
using System.Threading.Tasks;
using Kill.Network;
using UnityEngine;
namespace Kill.Video
{
/// <summary>
/// 下载 .264 -> 转封装 MP4(Android MediaCodec+MediaMuxer) -> 返回本地 mp4 路径
/// 不直接控制 VideoPlayer,由调用方拿到路径后自行 Init
/// </summary>
public class VideoDownloadPlayService
{
/// <summary>
/// 完整流程:下载 → 转码
/// </summary>
/// <param name="url">视频源 url(.264 裸流)</param>
/// <param name="onDownloadProgress">下载进度回调(0~1)</param>
/// <param name="onConvertStart">下载完成,即将开始转码(可更新 UI 提示)</param>
/// <param name="onComplete">(success, mp4Path) success=false 时 mp4Path 为 null/错误信息</param>
public async Task<bool> DownloadConvertAndPlay(
string url,
Action<DownloadProgress> onDownloadProgress,
Action<string> onConvertStart,
Action<bool, string> onComplete)
{
if (string.IsNullOrEmpty(url))
{
onComplete?.Invoke(false, "url is empty");
return false;
}
string mp4Path = VideoCacheManager.GetCachedMp4Path(url);
// 1) 已缓存(mp4 直接缓存 / h264 转码后缓存),直接返回
if (File.Exists(mp4Path) && new FileInfo(mp4Path).Length > 0)
{
onComplete?.Invoke(true, mp4Path);
return true;
}
// 2a) 链接是 .mp4:直接下载即得,无需转码
if (!IsH264Url(url))
{
SafeDelete(mp4Path);
HttpResponse<string> respMp4 = null;
try
{
respMp4 = await NetworkCtrl.Instance.DownloadFile(url, mp4Path, onDownloadProgress);
}
catch (Exception e)
{
Debug.LogError("[VideoDownloadPlayService] mp4 download exception: " + e);
SafeDelete(mp4Path);
onComplete?.Invoke(false, e.Message);
return false;
}
if (respMp4 == null || !respMp4.IsSuccess || !File.Exists(mp4Path) || new FileInfo(mp4Path).Length <= 0)
{
SafeDelete(mp4Path);
onComplete?.Invoke(false, "mp4 download fail: " + (respMp4?.ErrorMessage ?? "unknown"));
return false;
}
onComplete?.Invoke(true, mp4Path);
return true;
}
// 2b) 链接是 .h264:先下载再转码
string h264Path = VideoCacheManager.GetTempH264Path(url);
SafeDelete(h264Path);
SafeDelete(mp4Path);
HttpResponse<string> resp = null;
try
{
resp = await NetworkCtrl.Instance.DownloadFile(url, h264Path, onDownloadProgress);
}
catch (Exception e)
{
Debug.LogError("[VideoDownloadPlayService] download exception: " + e);
SafeDelete(h264Path);
onComplete?.Invoke(false, e.Message);
return false;
}
if (resp == null || !resp.IsSuccess || !File.Exists(h264Path))
{
SafeDelete(h264Path);
onComplete?.Invoke(false, "download fail: " + (resp?.ErrorMessage ?? "unknown"));
return false;
}
onConvertStart?.Invoke(h264Path);
// 3) 转码 .264 -> .mp4(ffmpeg-kit,libx264 重编码)
bool convertOk = await FFmpegKitBridge.ConvertH264ToMp4Async(h264Path, mp4Path);
if (!convertOk)
{
Debug.LogError("[VideoDownloadPlayService] ffmpeg convert fail");
}
#region debug-point vf-2 (: / + mp4 )
try
{
var hi = new FileInfo(h264Path);
var mi = new FileInfo(mp4Path);
Debug.Log("[vf-2] h264Size=" + (hi.Exists ? hi.Length.ToString() : "missing")
+ " mp4Size=" + (mi.Exists ? mi.Length.ToString() : "missing")
+ " convertOk=" + convertOk);
if (mi.Exists && mi.Length > 0)
{
#if UNITY_ANDROID && !UNITY_EDITOR
FFmpegKitBridge.ProbeMp4Info(mp4Path);
#endif
}
}
catch (Exception e) { Debug.LogError("[vf-2] " + e); }
#endregion
// 转码完成,删除 .h264 临时文件
SafeDelete(h264Path);
// 失败时清理未完成 mp4
if (!convertOk || !File.Exists(mp4Path))
{
SafeDelete(mp4Path);
onComplete?.Invoke(false, "convert fail");
return false;
}
onComplete?.Invoke(true, mp4Path);
return true;
}
private static void SafeDelete(string p)
{
if (string.IsNullOrEmpty(p)) return;
try { if (File.Exists(p)) File.Delete(p); } catch { }
}
/// <summary>判断链接是否为 .h264/.264 裸流(mp4 等其他一律按可直接下载处理)</summary>
public static bool IsH264Url(string url)
{
if (string.IsNullOrEmpty(url)) return false;
var l = url.ToLowerInvariant();
return l.EndsWith(".h264") || l.EndsWith(".264", StringComparison.Ordinal);
}
}
}