在下载 AVAssetDownloadTask 之前获取其大小

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


你解决了这个问题吗,@Antoine? - bnussey
还要检查:https://developer.apple.com/forums/thread/74130 - mgyky
3个回答

2
HLS流实际上是由称为清单和传输流的文件集合组成。清单通常包含子清单(每个子清单对应不同的比特率)的文本列表,而这些子清单包含包含实际电影数据的传输流列表。
在您的代码中,当您下载HLS URL时,您实际上只是下载主清单,而这通常只有几千字节。如果要复制整个流,则需要解析所有清单,复制原始流的文件夹结构,并获取传输片段(这些通常是10秒的片段,因此可以有数百个)。如果清单使用绝对URL指定,则可能需要重写URL。
要计算每个流的大小,您可以将比特率(列在主清单中)乘以流的持续时间;这可能足以估计下载目的。
在这里更好的答案是,由于您正在离线FairPlay的上下文中使用AVAssetDownloadTask,因此要实现AVAssetDownloadDelegate。该协议中的一个方法提供了您正在寻找的进度。

URLSession:assetDownloadTask:didLoadTimeRange:totalTimeRangesLoaded:timeRangeExpectedToLoad:

这里是WWDC 2016 Session 504,展示了这个代理的工作原理。

与FairPlay离线播放相关的细节非常多,因此建议仔细观看该视频。


0

我个人没有使用过这个API,但我对HTTP实时流媒体至少有一定的了解。基于这种知识,我认为我知道你为什么无法获得你要查找的信息。

HLS协议旨在处理实时流媒体以及固定长度资产的流媒体。它通过将媒体划分为通常约为十秒的块,并在特定URL的播放列表文件中列出这些块的URL来实现此目的。

如果播放列表不改变,那么您可以下载播放列表,计算文件数量,获取第一个文件的长度并将其乘以文件数量,您将得到一个粗略的近似值,当您开始检索最后一块时,您可以用精确值替换。

然而,不能保证播放列表不会更改。 使用HLS,播放列表可能每十秒钟就会发生变化,通过删除最旧的段(或不删除)并在结尾处添加新的段。这种方式支持流动直播的流媒体,根本没有结束。在这种情况下,下载具有大小的概念是毫无意义的。

让情况变得更糟的是,2464可能是播放列表文件的大小,而不是其中第一个资源的大小,也就是说,除非子类的didReceiveResponse:方法起作用,否则它告诉您什么都没有,即使如此,通过在获取时读取Content-Length头部,您可能仍然能够获得每个片段的长度。即使它正常工作,您也可能无法从此API中获取段数(而且还不能保证所有片段的长度完全相同,尽管它们应该是非常接近的)。
我怀疑为了获取所需信息,即使是对于非实时资产,您也可能需要获取播放列表,自己解析它,并对其中列出的每个媒体段URL执行一系列HEAD请求。
幸运的是,HLS规范是公开可用的标准,因此如果您想走这条路,可以阅读RFC来了解播放列表文件的结构。据我所知,播放列表本身没有使用任何DRM或其他加密技术进行加密,因此即使API的实际解密部分不是公开的(据我所知),仍然有可能这样做。

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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接