【问题标题】:Get the size of an AVAssetDownloadTask before downloading下载前获取 AVAssetDownloadTask 的大小
【发布时间】:2016-09-19 10:59:13
【问题描述】:

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

【问题讨论】:

标签: nsurlsession nsurlsessiondatatask fairplay avassetdownloadtask


【解决方案1】:

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

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

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

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

URLSession:assetDownloadTask:didLoadTimeRange:totalTimeRangesLoaded:timeRangeExpectedToLoad:

这里是WWDC 2016 Session 504,展示了这个委托的行动。

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

      【解决方案3】:

      这是我计算最终下载大小的 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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-05
        • 1970-01-01
        • 2018-02-19
        相关资源
        最近更新 更多