You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中无需第三方库从视频URL获取视频文件时长

不依赖第三方库获取在线MP4视频时长的C#实现

要实现不依赖第三方库获取在线视频时长,核心思路是只请求视频文件的元数据部分(而非整个文件),然后解析MP4格式的元数据结构提取时长。MP4的时长信息存储在moov原子下的mvhd(Movie Header)块中,我们可以通过HTTP的Range请求获取文件前几KB的数据来解析。

实现步骤

  1. 使用HttpClient发送Range请求,仅获取文件的前10KB数据(足够覆盖MP4的元数据区域)
  2. 解析字节流,定位moov原子
  3. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:34:52