在 swift 3 中播放部分下载的视频
Play partially downloaded video in swift 3
我需要播放视频的特定部分(例如,从 10 秒到 20 秒)。
所以我使用 Alamofire
和自定义 header 范围来下载这部分视频:
Alamofire.download(videoUrl,
method: .get,
headers: ["Range":String(format: "bytes=%d-%d", startByte, endByte)],
to: destination).response { (response) in
completionHandler(response.destinationURL)
}
到目前为止一切顺利,我可以看到下载的文件。
但是当我尝试播放它时,avplayer
会失败,我正在使用
videoAsset.loadValuesAsynchronously(forKeys: [ObserverContexts.urlAssetDurationKey, ObserverContexts.urlAssetPlayableKey])
异步加载特定键但无法加载持续时间状态:
let durationStatus = self.videoAsset.statusOfValue(forKey: ObserverContexts.urlAssetDurationKey, error: &durationError)
guard durationStatus == .loaded else {
Logger.log.error("durationStatus not loaded: \(self.videoId)")
self.delegate?.onLoadError(error: . durationStatusNotLoaded)
return
}
所以触发了这个错误,我无法播放视频。
我不是视频文件的高手,但我认为视频文件有一个 header,部分下载会破坏这个 header 并且播放器无法播放它。
因此,我们将不胜感激任何建议或想法(即使是最简单的)。
提前tnx。
编辑: 视频肯定有问题 header,如果我从视频开始 10 秒开始下载,视频会播放 10 秒, 但是视频结束时间或持续时间错误,我该如何解决?
这不是原始问题的答案,但我通过使用 AVFoundation 成功实现了我的目标,现在我可以下载我想要的确切部分并且可以播放了:
let downloadUrl = URL(string: videoUrl)!
let asset = AVURLAsset(url: downloadUrl)
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID:Int32(kCMPersistentTrackID_Invalid))
let audioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
let tracks = asset.tracks(withMediaType: AVMediaTypeVideo)
guard let assetVideoTrack = tracks.first else {
Logger.log.debug("video track is not available: \(videoId)")
completionHandler(nil)
return
}
let startCM = CMTime(seconds: startSecond, preferredTimescale: 1)
//let endCM = CMTime(seconds: startSecond + duration, preferredTimescale: 1)
//EDITED
let endCM = CMTime(seconds: duration, preferredTimescale: 1)
let timeRange = CMTimeRangeMake(startCM, endCM)
do {
try videoTrack.insertTimeRange(timeRange, of: assetVideoTrack, at: CMTimeMake(0, 1))
} catch let errorInsertingVideo {
Logger.log.debug("can not get video time range: \(videoId) -- \(errorInsertingVideo.localizedDescription)")
completionHandler(nil)
return
}
videoTrack.preferredTransform = assetVideoTrack.preferredTransform
guard let assetAudioTrack : AVAssetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first else {
Logger.log.debug("audio track is not available: \(videoId)")
completionHandler(nil)
return
}
do {
try audioTrack.insertTimeRange(timeRange, of: assetAudioTrack, at: CMTimeMake(0, 1))
}catch let errorInsertingAudio {
Logger.log.debug("can not get audio time range: \(videoId) -- \(errorInsertingAudio.localizedDescription)")
completionHandler(nil)
return
}
guard let destinationUrl = self.getDestinationUrl(videoId) else {
// can not access video cache dir
Logger.log.error("can not access video cache dir")
completionHandler(nil)
return
}
let exportSession : AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputURL = destinationUrl
exportSession.timeRange = timeRange
exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.exportAsynchronously(completionHandler: {
switch exportSession.status {
case .completed:
let output = exportSession.outputURL!
completionHandler(output)
case .failed:
completionHandler(nil)
break
default :
completionHandler(nil)
break
}
})
通过这个技巧,我可以从视频中间(或开始)的任何一秒开始下载恰好 10 秒(可选)的视频。
编辑:好吧这段代码可以不能下载中间部分,它只适用于你从视频的开头开始。
编辑 2:已添加修复程序。
我不认为 IOS 或 Swift 提供从中间下载图像然后播放的功能。您可以以位的形式下载视频文件内容,然后可以将它们保存为任何文件名的任何扩展名。
但是视频文件除了您下载的这些字节之外还包含大量数据,例如 header、类型、持续时间等。如果您下载的文件不包含这些数据,您的视频将无法播放。
在这种情况下,我认为(99% 确定)您将不得不使用 native 代码,而不仅仅是 Swift 将视频从特定时间解码到您想要的时间.
有很多免费的图书馆,试试吧。 ffmpeg. Check this也是一种这样的库。
祝一切顺利。
我需要播放视频的特定部分(例如,从 10 秒到 20 秒)。
所以我使用 Alamofire
和自定义 header 范围来下载这部分视频:
Alamofire.download(videoUrl,
method: .get,
headers: ["Range":String(format: "bytes=%d-%d", startByte, endByte)],
to: destination).response { (response) in
completionHandler(response.destinationURL)
}
到目前为止一切顺利,我可以看到下载的文件。
但是当我尝试播放它时,avplayer
会失败,我正在使用
videoAsset.loadValuesAsynchronously(forKeys: [ObserverContexts.urlAssetDurationKey, ObserverContexts.urlAssetPlayableKey])
异步加载特定键但无法加载持续时间状态:
let durationStatus = self.videoAsset.statusOfValue(forKey: ObserverContexts.urlAssetDurationKey, error: &durationError)
guard durationStatus == .loaded else {
Logger.log.error("durationStatus not loaded: \(self.videoId)")
self.delegate?.onLoadError(error: . durationStatusNotLoaded)
return
}
所以触发了这个错误,我无法播放视频。
我不是视频文件的高手,但我认为视频文件有一个 header,部分下载会破坏这个 header 并且播放器无法播放它。
因此,我们将不胜感激任何建议或想法(即使是最简单的)。
提前tnx。
编辑: 视频肯定有问题 header,如果我从视频开始 10 秒开始下载,视频会播放 10 秒, 但是视频结束时间或持续时间错误,我该如何解决?
这不是原始问题的答案,但我通过使用 AVFoundation 成功实现了我的目标,现在我可以下载我想要的确切部分并且可以播放了:
let downloadUrl = URL(string: videoUrl)!
let asset = AVURLAsset(url: downloadUrl)
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID:Int32(kCMPersistentTrackID_Invalid))
let audioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
let tracks = asset.tracks(withMediaType: AVMediaTypeVideo)
guard let assetVideoTrack = tracks.first else {
Logger.log.debug("video track is not available: \(videoId)")
completionHandler(nil)
return
}
let startCM = CMTime(seconds: startSecond, preferredTimescale: 1)
//let endCM = CMTime(seconds: startSecond + duration, preferredTimescale: 1)
//EDITED
let endCM = CMTime(seconds: duration, preferredTimescale: 1)
let timeRange = CMTimeRangeMake(startCM, endCM)
do {
try videoTrack.insertTimeRange(timeRange, of: assetVideoTrack, at: CMTimeMake(0, 1))
} catch let errorInsertingVideo {
Logger.log.debug("can not get video time range: \(videoId) -- \(errorInsertingVideo.localizedDescription)")
completionHandler(nil)
return
}
videoTrack.preferredTransform = assetVideoTrack.preferredTransform
guard let assetAudioTrack : AVAssetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first else {
Logger.log.debug("audio track is not available: \(videoId)")
completionHandler(nil)
return
}
do {
try audioTrack.insertTimeRange(timeRange, of: assetAudioTrack, at: CMTimeMake(0, 1))
}catch let errorInsertingAudio {
Logger.log.debug("can not get audio time range: \(videoId) -- \(errorInsertingAudio.localizedDescription)")
completionHandler(nil)
return
}
guard let destinationUrl = self.getDestinationUrl(videoId) else {
// can not access video cache dir
Logger.log.error("can not access video cache dir")
completionHandler(nil)
return
}
let exportSession : AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputURL = destinationUrl
exportSession.timeRange = timeRange
exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.exportAsynchronously(completionHandler: {
switch exportSession.status {
case .completed:
let output = exportSession.outputURL!
completionHandler(output)
case .failed:
completionHandler(nil)
break
default :
completionHandler(nil)
break
}
})
通过这个技巧,我可以从视频中间(或开始)的任何一秒开始下载恰好 10 秒(可选)的视频。
编辑:好吧这段代码可以不能下载中间部分,它只适用于你从视频的开头开始。
编辑 2:已添加修复程序。
我不认为 IOS 或 Swift 提供从中间下载图像然后播放的功能。您可以以位的形式下载视频文件内容,然后可以将它们保存为任何文件名的任何扩展名。 但是视频文件除了您下载的这些字节之外还包含大量数据,例如 header、类型、持续时间等。如果您下载的文件不包含这些数据,您的视频将无法播放。
在这种情况下,我认为(99% 确定)您将不得不使用 native 代码,而不仅仅是 Swift 将视频从特定时间解码到您想要的时间.
有很多免费的图书馆,试试吧。 ffmpeg. Check this也是一种这样的库。
祝一切顺利。