- 集成 ffmpeg-kit-full-gpl 6.0.3(arm64),用 libx264 重编码修复 .h264 缺 SPS/PPS 导致的播放卡死 - 新增 VideoDownloadPlayService/VideoCacheManager,.mp4 直下直播、.h264 下载后转 mp4 缓存,转码完成后删除临时 .h264 - FFmpegKitBridge 走主线程 JNI 调用规避 IL2CPP 后台线程 FindClass 不稳定 - 视频列表按缓存状态显示 下载/播放 按钮,下载中亮色条显示百分比;播放面板原下载按钮改为保存到相册
153 lines
5.6 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|