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

Swift中如何将.m3u8文件保存至本地磁盘以支持分享下载?

解决m3u8离线缓存文件的导出与分享问题

你当前使用AVAssetDownloadTask实现的离线缓存,是苹果专为HLS播放设计的私有缓存格式,存储在沙盒的隐蔽目录中,无法直接分享。要实现可分享的本地文件,需要将缓存的资产导出为标准视频格式(如MP4),或直接下载原始分片并合并。以下是两种可行方案:

方案一:导出已缓存的离线资产为可分享文件

利用AVAssetExportSession将已缓存的AVURLAsset导出到可访问的目录(如Documents),这样就能通过系统分享功能发送给其他应用。

1. 添加导出方法到DownloadManager

修改你的DownloadManager类,新增导出逻辑:

class DownloadManager: NSObject {
    static let shared = DownloadManager()
    
    // 原setupAssetDownload方法保持不变
    func setupAssetDownload(url:URL) {
        let configuration = URLSessionConfiguration.background(withIdentifier: "streamVideo")
        let downloadSession = AVAssetDownloadURLSession(configuration: configuration,
                                                        assetDownloadDelegate: self,
                                                        delegateQueue: OperationQueue.main)
        let asset = AVURLAsset(url: url)
        let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
                                                                 assetTitle: "",
                                                                 assetArtworkData: nil,
                                                                 options: nil)
        downloadTask?.resume()
    }
    
    // 新增导出方法
    func exportCachedAssetToShareableFile(completion: @escaping (URL?, Error?) -> Void) {
        guard let assetPath = UserDefaults.standard.value(forKey: "assetPath") as? String else {
            completion(nil, NSError(domain: "DownloadError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无可用离线资产"]))
            return
        }
        let baseURL = URL(fileURLWithPath: NSHomeDirectory())
        let assetURL = baseURL.appendingPathComponent(assetPath)
        let asset = AVURLAsset(url: assetURL)
        
        guard let cache = asset.assetCache, cache.isPlayableOffline else {
            completion(nil, NSError(domain: "DownloadError", code: 2, userInfo: [NSLocalizedDescriptionKey: "资产无法离线播放"]))
            return
        }
        
        // 设置导出路径(Documents目录,可自定义文件名)
        let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let outputURL = documentsDir.appendingPathComponent("exported_video.mp4")
        
        // 清理已存在的同名文件
        if FileManager.default.fileExists(atPath: outputURL.path) {
            try? FileManager.default.removeItem(at: outputURL)
        }
        
        // 创建导出会话
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
            completion(nil, NSError(domain: "ExportError", code: 3, userInfo: [NSLocalizedDescriptionKey: "无法创建导出会话"]))
            return
        }
        
        exportSession.outputURL = outputURL
        exportSession.outputFileType = .mp4
        exportSession.shouldOptimizeForNetworkUse = false
        
        // 执行导出
        exportSession.exportAsynchronously {
            DispatchQueue.main.async {
                switch exportSession.status {
                case .completed:
                    completion(outputURL, nil)
                case .failed, .cancelled:
                    completion(nil, exportSession.error ?? NSError(domain: "ExportError", code: 4, userInfo: [NSLocalizedDescriptionKey: "导出失败"]))
                default:
                    break
                }
            }
        }
    }
}

// 原AVAssetDownloadDelegate扩展保持不变
extension DownloadManager: AVAssetDownloadDelegate {
    func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
        UserDefaults.standard.set(location.relativePath, forKey: "assetPath")
    }
}

2. 调用导出并分享文件

在需要分享的地方调用导出方法,完成后用系统分享控制器发送:

DownloadManager.shared.exportCachedAssetToShareableFile { url, error in
    if let exportURL = url {
        let activityVC = UIActivityViewController(activityItems: [exportURL], applicationActivities: nil)
        // iPad需要设置popoverPresentationController
        if let popover = activityVC.popoverPresentationController {
            popover.sourceView = self.view
            popover.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
        }
        self.present(activityVC, animated: true)
    } else {
        print("导出失败:\(error?.localizedDescription ?? "未知错误")")
        // 这里可以弹出错误提示给用户
    }
}

方案二:直接下载原始m3u8分片并合并(适合需要原始文件场景)

如果需要获取原始的m3u8和TS分片,需要自行解析m3u8文件、下载所有分片并合并。但这种方法需要处理HLS加密(如果有的话),实现复杂度较高,且苹果原生的AVAssetDownloadTask已自动处理加密,因此更推荐方案一。

若要实现此方案,需:

  • 解析m3u8文件内容,提取所有TS分片的URL
  • 批量下载TS分片到本地
  • 使用AVAssetWriter或第三方工具将分片合并为标准视频文件

注意事项

  • 导出后的文件会占用额外磁盘空间,可选择在导出完成后删除原缓存文件
  • 导出时间取决于视频长度和设备性能,建议在后台执行并显示进度
  • 需处理磁盘空间不足、导出失败等异常情况

内容的提问来源于stack exchange,提问作者Dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:20:56