如何在C#中无需第三方库从视频URL获取视频文件时长
不依赖第三方库获取在线MP4视频时长的C#实现
要实现不依赖第三方库获取在线视频时长,核心思路是只请求视频文件的元数据部分(而非整个文件),然后解析MP4格式的元数据结构提取时长。MP4的时长信息存储在moov原子下的mvhd(Movie Header)块中,我们可以通过HTTP的Range请求获取文件前几KB的数据来解析。
实现步骤
- 使用
HttpClient发送Range请求,仅获取文件的前10KB数据(足够覆盖MP4的元数据区域) - 解析字节流,定位
moov原子 - 在
moov原子中找到mvhd块,提取时长和时间刻度,计算最终时长
完整代码
using System; using System.Net.Http; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { string videoUrl = "https://example.com/video.mp4"; try { TimeSpan duration = await GetMp4VideoDurationAsync(videoUrl); Console.WriteLine($"视频时长:{duration.ToString(@"hh\:mm\:ss")}"); } catch (Exception ex) { Console.WriteLine($"获取时长失败:{ex.Message}"); } } static async Task<TimeSpan> GetMp4VideoDurationAsync(string videoUrl) { using (HttpClient client = new HttpClient()) { // 发送Range请求,仅获取前10KB数据 client.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(0, 10240); HttpResponseMessage response = await client.GetAsync(videoUrl); if (response.StatusCode == System.Net.HttpStatusCode.PartialContent) { byte[] data = await response.Content.ReadAsByteArrayAsync(); return ParseMp4Duration(data); } else { throw new InvalidOperationException("服务器不支持Range请求,无法获取部分数据"); } } } static TimeSpan ParseMp4Duration(byte[] mp4Data) { int offset = 0; // 遍历MP4的原子结构,寻找moov原子 while (offset + 8 <= mp4Data.Length) { // 读取原子大小(4字节大端) uint size = BitConverter.ToUInt32(new byte[] { mp4Data[offset + 3], mp4Data[offset + 2], mp4Data[offset + 1], mp4Data[offset] }, 0); // 读取原子类型(4字节) string type = System.Text.Encoding.ASCII.GetString(mp4Data, offset + 4, 4); if (type == "moov") { // 进入moov原子内部,寻找mvhd int moovOffset = offset + 8; while (moovOffset + 8 <= offset + size) { uint mvhdSize = BitConverter.ToUInt32(new byte[] { mp4Data[moovOffset + 3], mp4Data[moovOffset + 2], mp4Data[moovOffset + 1], mp4Data[moovOffset] }, 0); string mvhdType = System.Text.Encoding.ASCII.GetString(mp4Data, moovOffset + 4, 4); if (mvhdType == "mvhd") { // mvhd结构中,版本号1字节,标志3字节,创建时间4/8字节,修改时间4/8字节,时间刻度4字节,时长4/8字节 int version = mp4Data[moovOffset + 8]; int timeScaleOffset = moovOffset + 16; // 版本+标志+创建时间(8字节)+修改时间(8字节)=16字节 int durationOffset = timeScaleOffset + 4; uint timeScale = BitConverter.ToUInt32(new byte[] { mp4Data[timeScaleOffset + 3], mp4Data[timeScaleOffset + 2], mp4Data[timeScaleOffset + 1], mp4Data[timeScaleOffset] }, 0); ulong duration = 0; if (version == 1) { // 64位时长 duration = BitConverter.ToUInt64(new byte[] { mp4Data[durationOffset +7], mp4Data[durationOffset +6], mp4Data[durationOffset +5], mp4Data[durationOffset +4], mp4Data[durationOffset +3], mp4Data[durationOffset +2], mp4Data[durationOffset +1], mp4Data[durationOffset] }, 0); } else { // 32位时长 duration = BitConverter.ToUInt32(new byte[] { mp4Data[durationOffset +3], mp4Data[durationOffset +2], mp4Data[durationOffset +1], mp4Data[durationOffset] }, 0); } // 时长 = 总时长值 / 时间刻度(单位:秒) double totalSeconds = (double)duration / timeScale; return TimeSpan.FromSeconds(totalSeconds); } moovOffset += (int)mvhdSize; } throw new InvalidOperationException("在moov原子中未找到mvhd块"); } offset += (int)size; } throw new InvalidOperationException("未找到moov原子,可能不是MP4文件"); } }
关键说明
- Range请求:通过
Range: bytes=0-10239请求文件前10KB,避免下载整个视频,大幅提升效率 - MP4结构解析:MP4由多个"原子"组成,
moov是存储元数据的核心原子,mvhd包含视频的时间刻度和总时长值 - 字节序处理:MP4使用大端字节序,所以需要反转字节数组后再用
BitConverter解析 - 兼容性:代码兼容32位和64位的
mvhd版本(MP4的版本字段区分)
注意事项
- 该方案仅支持MP4格式的视频,如果需要支持其他格式(如FLV、MKV),需要对应解析各自的元数据结构
- 部分服务器可能不支持Range请求,此时需要降级处理(但大部分CDN和静态文件服务器都支持)
内容的提问来源于stack exchange,提问作者Ravikiran Kulat
相关产品推荐
相关产品推荐

