下载前获取AVAssetDownloadTask的大小

时间:2016-09-19 10:59:13

标签: nsurlsession nsurlsessiondatatask fairplay avassetdownloadtask

我目前正在使用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

3 个答案:

答案 0 :(得分:1)

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

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

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

这里更好的答案,因为你在离线FairPlay的上下文中使用AVAssetDownloadTask,就是实现AVAssetDownloadDelegate。该协议中的一种方法为您提供了您正在寻找的进展:

URLSession:assetDownloadTask:didLoadTimeRange:totalTimeRangesLoaded:timeRangeExpectedToLoad:

此处显示此代表的WWDC 2016 Session 504

有许多与使用FairPlay进行离线播放相关的细节,因此最好仔细阅读该视频。

答案 1 :(得分:0)

我个人没有使用过这个API,但我至少对HTTP Live Streaming很熟悉。有了这些知识,我想我知道为什么你无法得到你正在寻找的信息。

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

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

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

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

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

幸运的是,HLS规范是一个公开可用的标准,因此如果您想沿着这条路走下去,可以阅读RFC以了解播放列表文件的结构。而AFAIK,播放列表本身并未使用任何DRM或任何内容加密,因此即使API的实际解密部分不公开(AFAIK),也应该可以这样做。

答案 2 :(得分: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;
}