7

我目前正在使用 FairPlay 流实现离线流。因此,我正在使用AVAssetDownloadTask.

我想向用户反馈开始下载的大小:

您确定要下载此流吗?下载需要 2.4GB,您目前还有 14GB 空间

我已经检查过类似的属性countOfBytesReceivedcountOfBytesExpectedToReceive但这些不会返回正确的值。

let headRequest = NSMutableURLRequest(URL: asset.streamURL)
headRequest.HTTPMethod = "HEAD"
let sizeTask = NSURLSession.sharedSession().dataTaskWithRequest(headRequest) { (data, response, error) in
    print("Expected size is \(response?.expectedContentLength)")
}.resume()

打印大小为 2464,最后大小为 3GB。

在下载过程中,我记录了上面的属性:

func URLSession(session: NSURLSession, assetDownloadTask: AVAssetDownloadTask, didLoadTimeRange timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) {
    print("Downloaded \( convertFileSizeToMegabyte(Float(assetDownloadTask.countOfBytesReceived)))/\(convertFileSizeToMegabyte(Float(assetDownloadTask.countOfBytesExpectedToReceive))) MB")
}

但这些保持为零:

已下载 0.0/0.0 MB

4

3 回答 3

2

HLS 流实际上是称为清单和传输流的文件的集合。清单通常包含子清单的文本列表(每个子清单对应不同的比特率),这些子清单包含包含实际电影数据的传输流列表。

在您的代码中,当您下载 HLS URL 时,您实际上只是在下载主清单,这通常是几千字节。如果要复制整个流,则需要解析所有清单,复制原始流的文件夹结构,并获取传输段(这些通常以 10 秒为单位,因此可能有数百个这些)。如果清单也使用绝对 URL 指定,您可能需要重写 URL。

要计算每个流的大小,您可以将比特率(在主清单中列出)乘以流的持续时间;对于下载目的,这可能是一个足够好的估计。

由于您在离线 FairPlay 的上下文中使用 AVAssetDownloadTask,因此这里更好的答案是实现 AVAssetDownloadDelegate。该协议中的一种方法可为您提供所需的进度:

URLSession:assetDownloadTask:didLoadTimeRange:totalTimeRangesLoaded:timeRangeExpectedToLoad:

这是WWDC 2016 Session 504,展示了该代表的实际操作。

有很多与 FairPlay 离线播放相关的细节,因此最好仔细浏览该视频。

于 2016-10-04T18:43:20.747 回答
0

我没有亲自使用过这个 API,但我至少对 HTTP Live Streaming 有点熟悉。有了这些知识,我想我知道为什么您无法获得所需的信息。

HLS 协议旨在处理实时流媒体以及固定长度资产的流媒体。它通过将媒体分割成通常约为 10 秒的块 IIRC 并在特定 URL 的播放列表文件中列出这些块的 URL 来做到这一点。

如果播放列表没有改变,那么你可以下载播放列表,计算文件数,得到第一个文件的长度,然后乘以文件数,你会得到一个粗略的近似值,你可以替换当您开始检索最后一个块时具有精确值。

但是,不能保证播放列表不会改变。 使用 HLS,播放列表可能每十秒更改一次,方法是删除(或不删除)最旧的片段并在末尾添加新片段。通过这种方式,HLS 支持了没有尽头的直播流式传输。在这种情况下,下载具有大小的概念是荒谬的。

更糟糕的是,2464 可能是播放列表文件的大小,而不是其中第一个资产的大小,也就是说,除非该子类的didReceiveResponse:方法有效,否则它不会告诉您任何内容,在这种情况下,您可能可以获得通过在Content-Length获取标题时读取每个段的长度。即使它正常工作,您可能仍然无法从该 API 获取段数(并且也不能保证所有段的长度完全相同,尽管它们应该非常接近)。

我怀疑要获得您想要的信息,即使对于非实时资产,您也可能必须获取播放列表,自己解析它,并对其中列出的每个媒体片段 URL 执行一系列 HEAD 请求。

幸运的是,HLS 规范是一个公开的标准,所以如果你想走这条路,你可以阅读 RFC 来了解播放列表文件的结构。和 AFAIK,播放列表本身没有使用任何 DRM 或任何东西加密,因此即使 API 的实际解密部分不公开(AFAIK),也应该可以这样做。

于 2016-09-20T05:05:07.023 回答
0

这是我计算最终下载大小的 C#/Xamarin 代码。它很可能是不完美的,尤其是 iOS11 支持的新编解码器,但你应该明白这一点。

private static async Task<long> GetFullVideoBitrate(string manifestUrl)
{
    string bandwidthPattern = "#EXT-X-STREAM-INF:.*(BANDWIDTH=(?<bitrate>\\d+)).*";
    string videoPattern = "^" + bandwidthPattern + "(RESOLUTION=(?<width>\\d+)x(?<height>\\d+)).*CODECS=\".*avc1.*\".*$";
    string audioPattern = "^(?!.*RESOLUTION)" + bandwidthPattern + "CODECS=\".*mp4a.*\".*$";

    HttpClient manifestClient = new HttpClient();
    Regex videoInfoRegex = new Regex(videoPattern, RegexOptions.Multiline);
    Regex audioInfoRegex = new Regex(audioPattern, RegexOptions.Multiline);
    string manifestData = await manifestClient.GetStringAsync(manifestUrl);
    MatchCollection videoMatches = videoInfoRegex.Matches(manifestData);
    MatchCollection audioMatches = audioInfoRegex.Matches(manifestData);
    List<long> videoBitrates = new List<long>();
    List<long> audioBitrates = new List<long>();

    foreach (Match match in videoMatches)
    {
        long bitrate;

        if (long.TryParse(match.Groups["bitrate"]
                               .Value,
                          out bitrate))
        {
            videoBitrates.Add(bitrate);
        }
    }

    foreach (Match match in audioMatches)
    {
        long bitrate;

        if (long.TryParse(match.Groups["bitrate"]
                               .Value,
                          out bitrate))
        {
            audioBitrates.Add(bitrate);
        }
    }

    if (videoBitrates.Any() && audioBitrates.Any())
    {
        IEnumerable<long> availableBitrate = videoBitrates.Where(b => b >= Settings.VideoQuality.ToBitRate());
        long videoBitrateSelected = availableBitrate.Any() ? availableBitrate.First() : videoBitrates.Max();
        long totalAudioBitrate = audioBitrates.Sum();

        return videoBitrateSelected + totalAudioBitrate;
    }

    return 0;
}
于 2017-10-31T15:21:30.730 回答